Allow use of @SendToUser even w/o authenticated user
Before this change, subscribing to a user destination and use of
@SendToUser annotation required an authenticated user.
This change makes it possible to subscribe to a user destination from
WebSocket sessions without an authenticated user. In such cases the
destination is associated with one session only rather than with a
user (and all their sessions).
It is then also possible to send a message to a user destination
via "/user/{sessionId}/.." rather than "/user/{user}/...".
That means @SendToUser works relying on the session id of the input
message, effectively sending a reply to destination private to the
session.
A key use case for this is handling an exception with an
@MessageExceptionHandler method and sending a reply with @SendToUser.
Issue: SPR-11309
@ -145,10 +145,18 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
@@ -145,10 +145,18 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
@ -168,13 +176,11 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
@@ -168,13 +176,11 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
@ -107,14 +107,15 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@@ -107,14 +107,15 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@ -134,17 +135,13 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@@ -134,17 +135,13 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@ -153,11 +150,12 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@@ -153,11 +150,12 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@ -186,14 +184,16 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@@ -186,14 +184,16 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
@ -171,12 +171,16 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
@@ -171,12 +171,16 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
@ -186,4 +186,13 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
@@ -186,4 +186,13 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
@ -301,6 +301,7 @@ public class StompSubProtocolHandler implements SubProtocolHandler, ApplicationE
@@ -301,6 +301,7 @@ public class StompSubProtocolHandler implements SubProtocolHandler, ApplicationE
@ -118,10 +119,10 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@@ -118,10 +119,10 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@ -140,8 +141,8 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@@ -140,8 +141,8 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@ -149,7 +150,7 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@@ -149,7 +150,7 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@ -157,6 +158,29 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@@ -157,6 +158,29 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
assertTrue(payload.endsWith("\"Got error: Bad input\"\0"));
}
finally{
session.close();
}
}
@IntegrationTestController
staticclassSimpleController{
@ -174,10 +198,10 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@@ -174,10 +198,10 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
@ -38116,35 +38116,113 @@ be plugged in (see examples in <<websocket-server-deployment>>).
@@ -38116,35 +38116,113 @@ be plugged in (see examples in <<websocket-server-deployment>>).
==== User Destinations
An application can send messages targeting a specific user.
In order for a connected user to receive messages, they must be authenticated
so that their session is associated with a concrete user name.
See the previous section on information about authentication.
Spring's STOMP support recognizes destinations prefixed with `/user/`.
For example, a client can subscribe to destination `/user/position-updates`.
Spring's STOMP support recognizes destinations prefixed with `"/user/"`.
For example, a client might subscribe to the destination `"/user/position-updates"`.
This destination will be handled by the `UserDestinationMessageHandler` and
transformed into a destination unique to the user's session,
e.g. `/user/position-updates-123`. This provides the convenience of subscribing
to a generically named destination, while also ensuring that it doesn't "collide"
with any other user that also subscribes to `/user/position-updates`
in order to receive stock position updates unique to them.
On the sending side, messages can be sent to a destination such as
`/user/{username}/position-updates`, which in turn will be translated
by the `UserDestinationMessageHandler` into the same unique destination
belonging to the specified user name.
This allows any component within the application to send messages to a specific
user without necessarily knowing anything more than their name and a generic
destination.
When this is used with an external message broker, check the broker documentation
on how to manage inactive queues, so that when the user session is over, all
unique user queues are removed. For example, RabbitMQ creates auto-delete queues
when destinations like `/exchange/amq.direct/position-updates` are used.
transformed into a destination unique to the user session,
e.g. `"/user/position-updates-user123"`. This provides the convenience of subscribing
to a generically named destination while at the same time ensuring no collisions
with other users subscribing to the same destination so that each user can receive
unique stock position updates.
On the sending side messages can be sent to a destination such as
`"/user/{username}/position-updates"`, which in turn will be translated
by the `UserDestinationMessageHandler` into one or more destinations, one for each
session associated with the user. This allows any component within the application to
send messages targeting a specific user without necessarily knowing anything more
than their name and the generic destination. This is also supported through an
annotation as well as a messaging template.
For example message-handling method can send messages to the user associated with
the message being handled through the `@SendToUser` annotation:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@Controller
public class MyController {
@MessageMapping("/trade")
@SendToUser("/queue/position-updates")
public TradeResult executeTrade(Trade trade, Principal principal) {
// ...
return tradeResult;
}
}
----
If the user has more than one sessions, by default all of the sessions subscribed
to the given destination are targeted. However sometimes, it may be necessary to
target only the session that sent the message being handled. This can be done by
setting the `broadcast` attribute to false, for example: