I would like to understand how convertAndSendToUser works in Spring SockJS+Websocket framework.
In client, we would connect as
stompClient.connect(login, password, callback())
which will result in connect request with "Stomp credentials" of login and password, that can be seen e.g. if we handle SessionConnectEvent http://www.sergialmar.com/2014/03/detect-websocket-connects-and-disconnects-in-spring-4/
But it remains unclear to me whether this will be the "user" meant in server-side send operation to a queue:
simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
The closest I can get is to read this thread Sending message to specific user on Spring Websocket, answer by Thanh Nguyen Van, but it is still unclear.
Basically what I need to do, is to subscribe some clients to same topic, but on server, send them different data. Client may supply user identifier.
We know we can send messages to the client from a stomp server using the topic prefixes that he is subscribed to e.g. /topic/hello. We also know we can send messages to a specific user because spring provides the convertAndSendToUser(username, destination, message) API. It accepts a String username which means if we somehow have a unique username for every connection, we should be able to send messages to specific users subscribed to a topic.
What's less understood is, where does this username come from ?
This username is part of a java.security.Principal interface. Each StompHeaderAccessor or WebSocketSession object has instance of this principal and you can get the user name from it. However, as per my experiments, it is not generated automatically. It has to be generated manually by the server for every session.
To use this interface first you need to implement it.
class StompPrincipal implements Principal {
String name
StompPrincipal(String name) {
this.name = name
}
#Override
String getName() {
return name
}
}
Then you can generate a unique StompPrincipal for every connection by overriding the DefaultHandshakeHandler. You can use any logic to generate the username. Here is one potential logic which uses UUID :
class CustomHandshakeHandler extends DefaultHandshakeHandler {
// Custom class for storing principal
#Override
protected Principal determineUser(
ServerHttpRequest request,
WebSocketHandler wsHandler,
Map<String, Object> attributes
) {
// Generate principal with UUID as name
return new StompPrincipal(UUID.randomUUID().toString())
}
}
Lastly, you need to configure your websockets to use your custom handshake handler.
#Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry
.addEndpoint("/stomp") // Set websocket endpoint to connect to
.setHandshakeHandler(new CustomHandshakeHandler()) // Set custom handshake handler
.withSockJS() // Add Sock JS support
}
That's It. Now your server is configured to generate a unique principal name for every connection. It will pass that principal as part of StompHeaderAccessor objects that you can access through connection event listeners, MessageMapping functions etc...
From event listeners :
#EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) {
// Get Accessor
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage())
}
From Message Mapped APIs
#MessageMapping('/hello')
protected void hello(SimpMessageHeaderAccessor sha, Map message) {
// sha available in params
}
One last note about using convertAndSendToUser(...). When sending messages to a user, you will use something like this
convertAndSendToUser(sha.session.principal.name, '/topic/hello', message)
However, for subscribing the client, you will use
client.subscribe('/user/topic/hello', callback)
If you subscribe the client to /topic/hello you will only receive broadcasted messages.
I did not do any specific configuration and I can just do this:
#MessageMapping('/hello')
protected void hello(Principal principal, Map message) {
String username = principal.getName();
}
Similar to Wenneguen I was able to do just by injecting Principal in the MessageMapping method
public void processMessageFromClient(#Payload String message, Principal principal) {
the principal.getName() implementation is from org.springframework.security.authentication.UsernamePasswordAuthenticationToken
class
Related
I am new to Spring boot websocket and messaging semantics. Currently i am able to send private messages using the below code.
String queueName = "/user/" + username + "/queue/wishes";
simpMessagingTemplate.convertAndSend(queueName, message);
When trying to use convertAndSendToUser I am not getting any error but the message is not getting sent. I knew that with sendToUser there should be a slight change in how the destination name should be formed but I am not getting it right.
String queueName = "/user/queue/wishes";
simpMessagingTemplate.convertAndSendToUser(username, queueName, message);
Below is my subscription code.
stompClient.subscribe('/user/queue/wishes', function(message) {
alert(message);
})
I had a similar problem, if your username is actually a sessionId, then try to use one of the overloaded methods that accept headers (so said in SimpMessageSendingOperations javadoc):
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(username);
headerAccessor.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(username, "/queue/wishes", message, headerAccessor.getMessageHeaders());
my example
It is not getting sent because your "destination" i.e "queuename" is incorrect.
As you can see here SimpMessagingTemplate.java#L230 this is the method that gets invoked as a part of the chain on invocations of template.convertAndSendToUser().
This method already prefixes /user to the final destination. So instead you should do something like this:
simpMessagingTemplate.convertAndSendToUser(username,"/queue/wishes", message);
Now with the correct destination it should get sent to user.
Finally figured out the problem. I did a silly mistake. I was filling the User DestinationPrefix as part of WebSocket config. but didn't set it up for the injected bean SimpMessaging template.
You must registry the prefix of SimpleBroker:
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic", "/queue", "/user");
registry.setUserDestinationPrefix("/user");
}
Although the UserDestinationPrefix has been set for default value "/user/", you must to add the SimpleBroker prefix.
I'm using Jooby's MVC routes for an API. I have also set up a websocket, to which a few clients connect. What I'm trying to do is send a message to all connected websocket clients whenever a specific http request is received in the server. This is how my route method looks like:
#Path("/player")
#Produces("application/json")
public class PlayerRoute {
#POST
public Result newPlayer(Request req, #Body Player player) {
//do some process here
//this is what I'm trying to achieve..
allWebsocketSessions.foreach(session ->
session.send("a new player has been created")
);
return Results.ok();
}
}
I've read jooby's documentation but can't figure out how to do it.
Thanks in advance.
It seems for "session you can call "set" and "get" methods only. Method "send" you can call for "response".
I have implemented the following websocket endpoint
#MessageMapping("/socket/{myId}/")
#SendTo("/queue/myqueue")
public MyObject getObject(#DestinationVariable String myId) throws Exception {
return new MyObject("MyId:" + myId);
}
Now how can I send message to that endpoint from one of my service.java class?
There will be front-end client as well, which will read the message from websocket once the service.java class's method send some message to websocket endpoint. I am a little confused that how can I do that?
Any help would be appreciated
When using a raw websocket(without STOMP), the message sent lacks of information to make Spring route it to a specific message handler method (we don't have any messaging protocol), so instead of annotating your controller, you'll have to implement a WebSocketHandler by extending TextWebSocketHandler
public void handleTextMessage(WebSocketSession session, TextMessage message){
}
Checkout an example here spring boot websocket without STOMP and SockJs
You should take a look at SimpMessagingTemplate.
For example, if you want to send a message for a specific user from your service class:
#Autowired
private SimpMessagingTemplate messagingTemplate;
public void sendMessage(User user, String message) {
Objects.requireNonNull(user);
Objects.requireNonNull(message);
messagingTemplate.convertAndSendToUser(user.getUsername(), "/queue/myqueue", message);
}
I have a Spring webservice #Controller class with a #MessageMapping annotated method as follows:
#MessageMapping("/trade")
public void executeTrade(MarketOrderRequest trade, Principal principal) {
trade.setUserID(principal.getName());
logger.debug("Trade: " + trade);
this.tradeService.executeTrade(trade);
}
I am sending a JSON string message built using the same MarketOrderRequest POJO as is accepted by the server method. With some Key:Value pairs which are set null (but are still present).
The WebSocketConfig class has configured the following endpoints:
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
When i try to send a message to this messagemapping using this code:
MarketOrderRequest request = new MarketOrderRequest();
//{set request variables..}
StompHeaders someHeaders = new StompHeaders();
someHeaders.putAll(sessionHeaders);
someHeaders.setDestination("/app/trade");
session.send(someHeaders, request);
With headers:
{Cookie=[JSESSIONID=8421F536B639126F84F12E655375D790; Path=/spring-websocket-portfolio/; HttpOnly], version=[1.2], heart-beat=[0,0], user-name=[fabrice], destination=[/app/trade]}
The server then prints that a method cannot be found for the request:
Searching methods to handle SEND /app/trade session=397da625042343b4bac1c913b6d8ec22 application/json;charset=UTF-8
payload={"uuid":null,"symbol":"EUR/USD","price":1.10182,"side":"1","qty":50000,"quoteID"...(truncated)
WebSocketAnnotationMethodMessageHandler[DEBUG] - No matching methods.
The server code is lifted from this project and altered slightly to suit my needs: link
I have added some role-based web socket security in an AbstractSecurityWebSocketMessageBrokerConfigurer implementation class as follows:
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated()
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
.simpDestMatchers("/app/**").hasAnyRole("roleA", "roleB", "roleC")
//{some more subscribe dest matchers by role which are working}
}
would this possibly effect the WebSocketAnnotationMethodMessageHandler's attempts to map the request? It is pretty much the only change I have made to the config. My subscribe mappings are working perfectly.
To me it seems that there is a problem finding the method due to either the JSON or Principal parameters. I am sending the correct object type so is this possibly a problem with the User principal? Thanks
There was an error in my WebSocketConfig class.
The #componentscan annotation had the wrong package name. I updated the name to the correct value ( the name of my base package eg "com.my.project" ). Now during deployment in the logs, I can see the controller resources being mapped to the methods in my class.
Eg log output for one method:
Mapped "{[/order],messageType=[MESSAGE]}" onto public void com.my.project.web.PortfolioController.executeOrder(tradeObjects.OrderRequest,java.security.Principal)
I have to develop a Java web service that get a request and sends immediately an acknowledgment (synchronous), so that far, it is simple.
Next, the web service has to do multiple checks on the request, then send a response according to that (synchronous too, because i don't have a callback endpoint from the client).
The problem is that i can send the ackowledgment, and i launch the multiple checks in another thread, but when the checks are done, the client already recieved his response, and i can't send another one.
Here's what i did for now:
#WebService
public class Configuration {
#Resource WebServiceContext context;
#WebMethod
public ReqAckType configure(#XmlElement(required = true) #WebParam(name = "reqType")
ReqType req) {
ReqAckType ack = new ReqAckType();
ack.setReceptionTime(Calendar.getInstance());
ChecksScheduler cs = ChecksScheduler.getInstance();
Checks checks = cs.schedule(req);
ack.setInternalId(checks.getId());
return ack;
}
}
If anyone can help me figure out how to send two separate message (ack and response), knowing that i have to send them separately and the checks take too much time (it's because of that, that i have to send and ack), i would be thankful.
I am using Oracle Fusion Middleware (Weblogic, JDeveloper, ..)