I have added custom token based authentication for my spring-web app and extending the same for spring websocket as shown below
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String jwtToken = accessor.getFirstNativeHeader("Auth-Token");
if (!StringUtils.isEmpty(jwtToken)) {
Authentication auth = tokenService.retrieveUserAuthToken(jwtToken);
SecurityContextHolder.getContext().setAuthentication(auth);
accessor.setUser(auth);
//for Auth-Token '12345token' the user name is 'user1' as auth.getName() returns 'user1'
}
}
return message;
}
});
}
}
The client side code to connect to the socket is
var socket = new SockJS('http://localhost:8080/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({'Auth-Token': '12345token'}, function (frame) {
stompClient.subscribe('/user/queue/greetings', function (greeting) {
alert(greeting.body);
});
});
And from my controller I am sending message as
messagingTemplate.convertAndSendToUser("user1", "/queue/greetings", "Hi User1");
For the auth token 12345token the user name is user1. But when I send a message to user1, its not received at the client end. Is there anything I am missing with this?
In your Websocket controller you should do something like this :
#Controller
public class GreetingController {
#Autowired
private SimpMessagingTemplate messagingTemplate;
#MessageMapping("/hello")
public void greeting(Principal principal, HelloMessage message) throws Exception {
Greeting greeting = new Greeting();
greeting.setContent("Hello!");
messagingTemplate.convertAndSendToUser(message.getToUser(), "/queue/reply", greeting);
}
}
On the client side, your user should subscribe to topic /user/queue/reply.
You must also add some destination prefixes :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue" ,"/user");
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user");
}
/*...*/
}
When your server receive a message on the /app/hello queue, it should send a message to the user in your dto. User must be equal to the user's principal.
I think the only problem in your code is that your "/user" is not in your destination prefixes. Your greetings messages are blocked because you sent them in a queue that begin with /user and this prefixe is not registered.
You can check the sources at git repo :
https://github.com/simvetanylen/test-spring-websocket
Hope it works!
In my previous project I sent messages to one specific user; in detail I wrote the following:
CLIENT SIDE:
function stompConnect(notificationTmpl)
{
var socket = new SockJS('/comm-svr');
stompClient = Stomp.over(socket);
var theUserId
stompClient.connect({userId:theUserId}, function (frame) {
debug('Connected: ' + frame);
stompClient.subscribe('/topic/connect/'+theUserId, function (data) {
//Handle data
}
});
}
SERVER SIDE
Spring websocket listener:
#Component
public class WebSocketSessionListener
{
private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionListener.class.getName());
private List<String> connectedClientId = new ArrayList<String>();
#EventListener
public void connectionEstablished(SessionConnectedEvent sce)
{
MessageHeaders msgHeaders = sce.getMessage().getHeaders();
Principal princ = (Principal) msgHeaders.get("simpUser");
StompHeaderAccessor sha = StompHeaderAccessor.wrap(sce.getMessage());
List<String> nativeHeaders = sha.getNativeHeader("userId");
if( nativeHeaders != null )
{
String userId = nativeHeaders.get(0);
connectedClientId.add(userId);
if( logger.isDebugEnabled() )
{
logger.debug("Connessione websocket stabilita. ID Utente "+userId);
}
}
else
{
String userId = princ.getName();
connectedClientId.add(userId);
if( logger.isDebugEnabled() )
{
logger.debug("Connessione websocket stabilita. ID Utente "+userId);
}
}
}
#EventListener
public void webSockectDisconnect(SessionDisconnectEvent sde)
{
MessageHeaders msgHeaders = sde.getMessage().getHeaders();
Principal princ = (Principal) msgHeaders.get("simpUser");
StompHeaderAccessor sha = StompHeaderAccessor.wrap(sde.getMessage());
List<String> nativeHeaders = sha.getNativeHeader("userId");
if( nativeHeaders != null )
{
String userId = nativeHeaders.get(0);
connectedClientId.remove(userId);
if( logger.isDebugEnabled() )
{
logger.debug("Connessione websocket stabilita. ID Utente "+userId);
}
}
else
{
String userId = princ.getName();
connectedClientId.remove(userId);
if( logger.isDebugEnabled() )
{
logger.debug("Connessione websocket stabilita. ID Utente "+userId);
}
}
}
public List<String> getConnectedClientId()
{
return connectedClientId;
}
public void setConnectedClientId(List<String> connectedClientId)
{
this.connectedClientId = connectedClientId;
}
}
Spring websocket message sender:
#Autowired
private SimpMessagingTemplate msgTmp;
private void propagateDvcMsg( WebDeviceStatusInfo device )
{
String msg = "";
String userId =((Principal)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getName()
msgTmp.convertAndSend("/topic/connect"+userId, msg);
}
I hope it's useful
Related
I'm experiencing this difficulty and I still don't understand what's happening, I have a scenario where an api uses a feing client to obtain a user's profile from an external service, until this moment everything was going well, but when I solved it add the fallback, spring starts to inject the SecurityContextHolder.getContext() always with a null value, and with that the interceptor cannot get the token of the user who made the primary call of the application.
appliction.properties
application.authserver.url=http://localhost:8088/auth
feign.circuitbreaker.enabled=true
Interceptor Class
#Component
#Slf4j
public class FeingClientConfig {
#Value("${app.name}")
private String appName;
#Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
if (SecurityContextHolder.getContext().getAuthentication() != null) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof Jwt) {
var jwt = (Jwt) principal;
log.info("-------------------- service integration -----------------");
log.info(String.format(" Endpoint: %s", template.method() + " " + template.feignTarget().url() + template.url()));
log.info(String.format(" User: %s", jwt.getClaim("user_name").toString()));
log.info(String.format(" Bearer token: %s", jwt.getTokenValue().substring(0, 30) + "....."));
log.info("----------------------------------------------------------");
template.header("Authorization", String.format("Bearer %s", jwt.getTokenValue()));
template.header("x-app-name", appName);
}
}
}
};
}
}
Client
#FeignClient(url = "${application.authserver.url}", name = "authClient",
fallbackFactory = AuthClientFallBackFactory.class)
public interface AuthClient {
#GetMapping("/user/profile")
public UserProfile getProfile();
}
Fallback
#Slf4j
#Component
public class AuthClientFallBackFactory implements FallbackFactory<AuthClient> {
#Override
public AuthClient create(Throwable cause) {
return new AuthClient() {
#Override
public UserProfile getProfile() {
log.error("*** Fallback: error on get profile.", cause);
return new UserProfile();
}
};
}
}
If the feign.circuitbreaker.enabled property was set to false, this ignores the use of the fallback, but the SecurityContextHolder.getContext() object is inject correctly
What am I doing wrong?
Using in this project:
JDK 11
Spring Boot [2.4.2]
This question already has an answer here:
Spring websocket end-poind and send message
(1 answer)
Closed 7 months ago.
I'm trying to build a Spring STOMP websocket + ActiveMQ service. I have set the websocket and the ActiveMQ queue.
ActiveMQ queue works just fine but I'm not able to make my websocket endpoint send messages to the clients connected to the topic.
Websocket client seems to connect just fine also. The thing is that when the controller receives information it is not caught on the client.
--WebsocketConfig.java--
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").withSockJS();
}
}
--WebsocketController.java--
#Controller
public class WebsocketController {
#Autowired
private ItemService itemService;
#JmsListener(destination = "items-queue")
#MessageMapping("/websocket")
#SendTo("/topic/items")
public String itemsWebsocket(Iterable<Item> items) {
System.out.println("Websocket controller reached");
for (Item item : items) System.out.println(item.getName());
return "hi from websocket";
}
}
--app.js--
let stompClient = null;
function connect() {
let socket = new SockJS('/websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/items', function (items) {
appendItems(items);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
console.log("Disconnected");
}
function appendItems(items) {
console.log(items);
const itemListContainer = document.getElementById("item-list");
itemListContainer.innerText = "";
Array.from(items).forEach( item => {
const itemContainer = document.createElement("div");
itemContainer.innerText = item.name;
itemListContainer.append(itemContainer);
});
}
connect();
I just found out that if I apply the changes from here it does the thing.
I'm trying to implement Oauth2 login with Dagger2. Once the access_token gets expired, I have successfully generated new access_token through the refresh_token, but the Authenticator goes on infinite loop once refresh_token is also expired.
This is my Network module, where I defined, Authenticator and Interceptor in OkHttp Client
#Module
public class NetworkModule
{
#Provides
#Singleton
OkHttpClient provideOkHttpClient(TokenAuthenticator tokenAuthenticator, TokenInceptor tokenInceptor, SharedManager sharedManager)
{
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// adding socket time for read/write/reconnect
httpClient.connectTimeout(30, TimeUnit.SECONDS);
httpClient.writeTimeout(30, TimeUnit.SECONDS);
httpClient.readTimeout(30, TimeUnit.SECONDS);
// setting the accept type of the request to application/json
httpClient.addNetworkInterceptor(new Interceptor()
{
#Override
public Response intercept(Chain chain) throws IOException {
Request.Builder requestBuilder = chain.request().newBuilder();
requestBuilder.header("Accept", "application/json");
return chain.proceed(requestBuilder.build());
}
});
httpClient.addInterceptor(logging).addInterceptor(tokenInceptor);
httpClient.authenticator(tokenAuthenticator);
return httpClient.build();
}
}
#Provides
Retrofit provideRetrofit(OkHttpClient okHttpClient){
return new Retrofit.Builder()
.baseUrl(ApiConstants.API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
#Provides
#Singleton
ApiService provideApiService(Retrofit retrofit, TokenService apiServiceHolder)
{
ApiService apiService = retrofit.create(ApiService.class);
apiServiceHolder.setApiService(apiService);
return apiService;
}
#Provides
#Singleton
public SharedPreferences providePreferences(Application application)
{
return application.getSharedPreferences(Constants.APP_PREFERENCES, Context.MODE_PRIVATE);
}
#Provides
#Singleton
public SharedManager provideSharedManager(SharedPreferences sharedPreferences)
{
return new SharedManager(sharedPreferences);
}
#Provides
#Singleton
public TokenAuthenticator tokenAuthenticator(TokenService tokenService, SharedManager sharedManager)
{
return new TokenAuthenticator(tokenService, sharedManager);
}
#Provides
#Singleton
public TokenInceptor tokenInceptor(SharedManager sharedManager)
{
return new TokenInceptor(sharedManager);
}
#Provides
#Singleton
public TokenService apiServiceHolder()
{
return new TokenService();
}
}
Here's the Interceptor
#Singleton
public class TokenInceptor implements Interceptor
{
SharedManager sharedManager;
#Inject
public TokenInceptor(SharedManager sharedManager)
{
this.sharedManager = sharedManager;
}
#Override
public Response intercept(Chain chain) throws IOException
{
Request request = chain.request();
// we don't need header in login/register so, we remove the header from these api request endpoints
if(request.url().encodedPath().contains("/token/client") && request.method().equalsIgnoreCase("POST"))
{
return chain.proceed(request);
}
// then we add the authenticator to other api requests
HttpUrl url = request.url();
Request.Builder urlBuilder = request.newBuilder().addHeader(ApiConstants.AUTHORIZATION, sharedManager.getBearer()).url(url);
Request apiRequest = urlBuilder.build();
return chain.proceed(apiRequest);
}
}
Here's the Authenticator
#Singleton
public class TokenAuthenticator implements Authenticator
{
private SharedManager sharedManager;
private TokenService tokenService;
#Inject
public TokenAuthenticator(#NonNull TokenService apiServiceHolder, SharedManager sharedManager)
{
this.tokenService = apiServiceHolder;
this.sharedManager = sharedManager;
}
#Nullable
#Override
public Request authenticate(Route route, Response response) throws IOException
{
if(!response.request().header(ApiConstants.AUTHORIZATION).equals(sharedManager.getBearer()))
{
return null;
}
retrofit2.Response<TokenResponse> tokenResponse = tokenService.getApiService().refreshToken(sharedManager.getRefresh()).execute();
TokenResponse responseData = tokenResponse.body();
if(tokenResponse.isSuccessful() && responseData!= null)
{
TokenResponse responseRequest = (TokenResponse) tokenResponse.body();
String new_token = responseRequest.getAccess();
sharedManager.saveAccessToken(new_token);
return response.request().newBuilder().header(ApiConstants.AUTHORIZATION,sharedManager.getBearer()).build();
}
else
{
// As per my assumption, the refresh token might expire here
Log.e("refresh_token","expired");
}
return null;
}
}
Here's the TokenService class
public class TokenService
{
ApiService apiService = null;
#Nullable
public ApiService getApiService() {
return apiService;
}
public void setApiService(ApiService apiService) {
this.apiService = apiService;
}
}
Here's SharedManager class
public class SharedManager
{
private SharedPreferences sharedPreferences;
#Inject
public SharedManager(SharedPreferences sharedPreferences)
{this.sharedPreferences = sharedPreferences;};
public void saveAccessToken(String token)
{
sharedPreferences.edit().putString(ApiConstants.ACCESS_TOKEN, token).commit();
}
public void saveRefreshToken(String token)
{
sharedPreferences.edit().putString(ApiConstants.REFRESH, token).commit();
}
public String getAccessToken()
{
return sharedPreferences.getString(ApiConstants.ACCESS_TOKEN, "");
}
public String getRefresh()
{
return sharedPreferences.getString(ApiConstants.REFRESH, "");
}
public String getBearer()
{
return "Bearer "+getAccessToken();
}
public void clearAll()
{
sharedPreferences.edit().clear().commit();
}
}
Here's ApiService interface
public interface ApiService
{
// client login
#POST("token/client")
#FormUrlEncoded
Call<ResponseBody> loginUser(#Field("email") String email,
#Field("password") String password);
// method for refresh token
#POST("token/refresh")
#FormUrlEncoded
Call<TokenResponse> refreshToken(#Field("refresh") String refresh);
// get agent
#GET("agent")
Call<ResponseBody> getAgentTour();
}
Can anyone trace out the faults in the code here ? The code structure changed while posting in stack.
A standard refresh token grant message will return an error code of invalid_grant when the refresh token finally expires.
{
"error": "invalid_grant",
"error_description": "An optional description message that varies between vendors"
}
At this point you should do two things:
For any in flight API calls, throw an exception with an error code such as 'login_required', that your error handling code can silently ignore
Then perform a login redirect to start a new user session
SAMPLE CODE OF MINE
A something to compare against, I have an AppAuth code sample that you can run and which allows simulation of token expiry events:
Android Code to Handle Invalid Grant
Android Code Sample Blog Post
Of course you would need to translate this behaviour to your own Dagger based coding preferences ...
I am trying to run a websocket example having java code in one port(8080) and java script code in another port(8080)
problem : when i deploy client and server on same port it works properly but when client app.js is on different port than server port it throws error
Access to XMLHttpRequest at
'http://localhost:8091/websocket-example/info?t=1601539296700' from
origin 'http://localhost:8080' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
java configuration file
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/websocket-example")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
contoller file
#Controller
public class ChattingController {
#MessageMapping("/user")
#SendTo("/topic/user")
public UserResponse getUser(User user) {
return new UserResponse("Hi " + user.getName());
}
}
app.js
var stompClient = null;
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#userinfo").html("");
}
function connect() {
var socket = new SockJS('http://localhost:8091/websocket-example');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/user', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.send("/app/user", {}, JSON.stringify({'name': $("#name").val()}));
}
function showGreeting(message) {
$("#userinfo").append("<tr><td>" + message + "</td></tr>");
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendName(); });
});
springsecurity file
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer, WebMvcRegistrations {
private static final String EVERYTHING = "/**";
#Bean
public SuperInterceptor superInterceptor() {
return new SuperInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> paths = new ArrayList<String>();
paths.add("/login");
paths.add("/user/register");
registry.addInterceptor(superInterceptor()).addPathPatterns(EVERYTHING).excludePathPatterns(paths);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable();
httpSecurity.cors();
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
}
ps: i am new to websocket sorry if this is a naive issue
If you configure it with Spring Security then try this, it might helps you. Add it in the WebSecurityConfig file of Spring Security.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
}
OR
If you are just configure the Spring MVC then try this:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST","PUT", "DELETE");
}
};
}
I have a REST service which receives some data and check the data through an ansynchronous IBM MQ request.
REST controller:
#RestController
#RequestMapping("/request")
public class RequestController {
#RequestMapping(method = RequestMethod.POST)
public Response postRequest(#RequestBody Request request) {
String data = request.getData();
jmsSender.send(data);
// Now I need the response from MQ
// String mqResponse = ...
if (mqIsValid(mqResponse)) {
return createValidResponse();
}
return createNotValidResponse();
}
}
MQ sender:
#Service
public class JmsSender {
public void send(String data) {
jmsTemplate.convertAndSend("QUEUE.TO.MQ", data);
}
}
MQ receiver:
#Component
public class JmsReceiver {
#JmsListener(destination = "QUEUE.FROM.MQ, containerFactory = "DefaultJmsListenerContainerFactory")
public void receiveMessage(String message) {
// How to pass the message to the controller?
}
}
How can I wait for the right data from MQ to create the correct response in the controller?
Is it possible to use a BlockingQueue for this like described here? In my case I have to distinguish the data. I can't just take the first data from the blocking queue.
If for example there are two REST requests simultaneously (with data: abc and xyz). How can I make sure to response the right answer not just the first answer I get from MQ?
I also can't change the MQ interface.
Try using a CountDownLatch like below.
#RestController
#RequestMapping("/request")
public class RequestController {
#RequestMapping(method = RequestMethod.POST)
public Response postRequest(#RequestBody Request request) {
final CountDownLatch jmsLatch = new CountDownLatch (1);
String data = request.getData();
jmsSender.send(data, jmsLatch);
try {
latch.await(); // wait untill latch counted down to 0
} catch (InterruptedException e) {
return createNotValidResponse();
}
return createValidResponse();
}
}
Modify the send method to get the CountDownLatch from controller.
#Service
public class JmsSender {
public void send(String data, final CountDownLatch jmsLatch) {
jmsLatch.await();
jmsTemplate.convertAndSend("QUEUE.TO.MQ", data);
}
}
Modify the receive method to get the same CountDownLatch from controller.
#Component
public class JmsReceiver {
#JmsListener(destination = "QUEUE.FROM.MQ", containerFactory = "DefaultJmsListenerContainerFactory")
public void receiveMessage(String message, final CountDownLatch jmsLatch) {
// Pass the message to the controller
jmsLatch.countDown();
}
}
The trick here is you have to spread the same CountDownLatch instance from controller to the sender and receiver class and call the countDown method after you receive the message.
Since I couldn't find any suitable solution for me, I've created a simple waiting mechanism to get the data.
MqReceiver:
#Component
public class JmsReceiver {
private final Lock lock;
private final Condition containsKey;
private final Map<String, String> responses;
public JmsReceiver() {
this.lock = new ReentrantLock();
this.containsKey = lock.newCondition();
this.responses = new HashMap<>();
}
#JmsListener(destination = "QUEUE.FROM.MQ", containerFactory = "DefaultJmsListenerContainerFactory")
public void receiveMessage(String message) {
put(getKeyFromMessage(message), message);
}
public String get(String key) throws InterruptedException {
lock.lock();
try {
while (!responses.containsKey(key)) {
containsKey.await();
}
return responses.get(key);
} finally {
lock.unlock();
}
}
public void put(String key, String messagee) {
lock.lock();
try {
responses.put(key, messagee);
containsKey.signalAll();
} finally {
lock.unlock();
}
}
}
This can be used in the controller:
#RestController
#RequestMapping("/request")
public class RequestController {
#RequestMapping(method = RequestMethod.POST)
public Response postRequest(#RequestBody Request request) {
String data = request.getData();
jmsSender.send(data);
String key = getKeyFromData(data);
// waits until MQ sends the data
String mqResponse = jmsReceiver.get(key);
if (mqIsValid(mqResponse)) {
return createValidResponse();
}
return createNotValidResponse();
}
}
Solution for scenario sync-async implementing request-reply pattern with jms(activemq)
The idea of this example is working in two different services in a different jvm. The solution is tested concurrent with several instances services:
Service 1 (M1) - Rest api synchronous and in some point start an
async flow using activemq to call the second Service M2 implementing Integration Pattern Request-Reply. You don't need to stop or wait any thread, the jms pattern implements the ack Session.AUTO_ACKNOWLEDGE.
#PostMapping
public AnyDto sendMessage(final AnyDto anyDto) {
return routeService.send(anyDto);
}
public void flowOrchestation (final anyDto data) throws JMSException {
final ObjectMessage objectMessage = composeTemplateMessage(data);
final AnyDto responseDto = jmsMessagingTemplate.convertSendAndReceive(new ActiveMQQueue("queue.request"),
objectMessage, AnyDto.class);
}
private ObjectMessage composeTemplateMessage(final AnyDto data) throws JMSException {
jmsTemplate.setReceiveTimeout(10000L);
jmsMessagingTemplate.setJmsTemplate(jmsTemplate);
Session session = jmsMessagingTemplate.getConnectionFactory().createConnection()
.createSession(false, Session.AUTO_ACKNOWLEDGE);
final ObjectMessage objectMessage = session.createObjectMessage(data);
objectMessage.setJMSCorrelationID(UUID.randomUUID().toString());
objectMessage.setJMSReplyTo(new ActiveMQQueue("queue.response"));
objectMessage.setJMSExpiration(0);
objectMessage.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
return objectMessage;
}
Timeout and expiration can be modified depending your requeriments. 0 expiration means no time to expire.
Service 2 (M2): Just receive the message and make the response to JmsReplyTo setted on M1.
#Component
public class Consumer implements SessionAwareMessageListener<Message> {
#Override
#JmsListener(destination = "${queue.request}")
public void onMessage(Message message, Session session) throws JMSException {
AnyDto anyDto = (AnyDto) ((ActiveMQObjectMessage) message).getObject();
//do some stuff
final ObjectMessage responseMessage = new ActiveMQObjectMessage();
responseMessage.setJMSCorrelationID(message.getJMSCorrelationID());
responseMessage.setObject(dtoModified);
final MessageProducer producer = session.createProducer(message.getJMSReplyTo());
producer.send(responseMessage);
}}