"The basic issue is there isn't enough information in an incoming websocket message for the container to know where to route it if there are multiple methods where it may land."
Danny Coward, JSR-356 lead
In response to question on user mailing list
| z, ? | toggle help (this) |
| space, → | next slide |
| shift-space, ← | previous slide |
| d | toggle debug mode |
| ## <ret> | go to slide # |
| c, t | table of contents (vi) |
| f | toggle footer |
| r | reload slides |
| n | toggle notes |
| p | run preshow |
Callable and DeferredResultHttpServletRequest.upgrade
import org.springframework.web.socket.*;
public class MyHandler extends TextWebSocketHandlerAdapter {
@Override
public void handleTextMessage(WebSocketSession session,
TextMessage message) throws Exception {
session.sendMessage(message);
}
}
@Configuration
public class WsConfig implements WebSocketConfigurer {
}
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
}
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(new MyHandler(), "/echo");
}
}
var ws = new WebSocket("wss://localhost:8080/myapp/echo");
ws.onopen = function () {
console.log("opened");
};
ws.onmessage = function (event) {
console.log('message: ' + event.data);
};
ws.onclose = function (event) {
console.log('closed:' + event.code);
};
HandshakeInterceptorfalse from beforeHandshakeHttpSessionHandshakeInterceptor
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(echoHandler(), "/echo");
}
}
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(echoHandler(), "/echo")
.addInterceptors(new MyHandshakeInterceptor());
}
}
PerConnectionWebSocketHandlerWebSocketHandler type to constructor
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Bean
public WebSocketHandler snakeHandler() {
return new PerConnectionWebSocketHandler(
SnakeWebSocketHandler.class);
}
}
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Bean
public WebSocketHandler snakeHandler() {
return new PerConnectionWebSocketHandler(
SnakeWebSocketHandler.class);
}
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(snakeHandler(), "/snake");
}
}
DispatcherServletRequestUpgradeStrategyspring-websocket GET /echo
GET /echo/info
POST /echo/<server>/<session>/<transport>
"/echo/**"
"/echo/{server}/{session}/xhr-polling"
TransportHandlerWebSocketHandler
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/echo");
}
}
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/echo").withSockJS();
}
}
import org.springframework.web.socket.*;
public class MyHandler extends TextWebSocketHandlerAdapter {
@Override
public void handleTextMessage(WebSocketSession session,
TextMessage message) throws Exception {
session.sendMessage(message);
}
}
SockJsService sends periodic heartbeats
"The basic issue is there isn't enough information in an incoming websocket message for the container to know where to route it if there are multiple methods where it may land."
Danny Coward, JSR-356 lead
In response to question on user mailing list
"The subprotocol attribute of the handshake might serve as a useful place to attach this kind of message description/meta data, or some JSR356 specific headers in the handshake. Of course, the other issue is that the other end is often javascript, which would need to participate in some way in such a scheme."
"These are all things we'll likely look at in the next version."
From next reply on same thread

spring-messaging moduleMessage, MessageChannel, MessageHandler, etc
@MessageMapping, @SubscribeEvent, etc

SENDSUBSCRIBEUNSUBSCRIBEMESSAGEERRORRECEIPTACKNACK"Destination" Header"/queue/a", "/topic/a")


@Configuration
@EnableWebSocketMessageBroker
public class Config
implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry r){
}
@Override
public void configureMessageBroker(MessageBrokerConfigurer c){
}
}
@Configuration
@EnableWebSocketMessageBroker
public class Config
implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry r){
r.addEndpoint("/stomp");
}
@Override
public void configureMessageBroker(MessageBrokerConfigurer c){
}
}
@Configuration
@EnableWebSocketMessageBroker
public class Config
implements WebSocketMessageBrokerConfigurer{
@Override
public void registerStompEndpoints(StompEndpointRegistry r){
r.addEndpoint("/stomp");
}
@Override
public void configureMessageBroker(MessageBrokerConfigurer c){
c.enableSimpleBroker("/topic/");
}
}
@Configuration
@EnableWebSocketMessageBroker
public class Config
implements WebSocketMessageBrokerConfigurer{
@Override
public void registerStompEndpoints(StompEndpointRegistry r){
r.addEndpoint("/stomp");
}
@Override
public void configureMessageBroker(MessageBrokerConfigurer c){
c.enableSimpleBroker("/topic/");
c.setApplicationDestinationPrefixes("/app");
}
}

@Controller
public class GreetingController {
@MessageMapping("/greetings")
public void handle(String greeting) {
// ...
}
}
@MessageMapping@PathVariable arguments
@Header/@Headers, @Payload, Message, Principal
@MessageMapping"/topic"
@SendTo
@Controller
public class GreetingController {
@MessageMapping("/greetings")
public String greet(String greeting) {
return "[" + getTimestamp() + "]: " + greeting;
}
}
@Controller
public class GreetingController {
// A message is broadcast to "/topic/greetings"
@MessageMapping("/greetings")
public String greet(String greeting) {
return "[" + getTimestamp() + "]: " + greeting;
}
}
@SendTo
@Controller
public class GreetingController {
@MessageMapping("/greetings")
@SendTo("/topic/wishes")
public String greet(String greeting) {
return "[" + getTimestamp() + "]: " + greeting;
}
}
SimpMessagingTemplate
@Controller
public class GreetingController {
@Autowired
private SimpMessagingTemplate template;
@RequestMapping(value="/greetings", method=POST)
public void greet(String greeting) {
String text = "[" + getTimeStamp() + "]:" + greeting;
this.template.convertAndSend("/topic/wishes", text);
}
}
@Controller
public class PortfolioController {
@SubscribeEvent("/positions")
public List<Position> getPositions(Principal p) {
Portfolio portfolio = ...
return portfolio.getPositions();
}
}
@Configuration
@EnableWebSocketMessageBroker
public class Config
implements WebSocketMessageBrokerConfigurer{
@Override
public void configureMessageBroker(MessageBrokerConfigurer c){
}
}
@Configuration
@EnableWebSocketMessageBroker
public class Config
implements WebSocketMessageBrokerConfigurer{
@Override
public void configureMessageBroker(MessageBrokerConfigurer c){
c.enableStompBrokerRelay("/queue/", "/topic/");
}
}
@Configuration
@EnableWebSocketMessageBroker
public class Config
implements WebSocketMessageBrokerConfigurer{
@Override
public void configureMessageBroker(MessageBrokerConfigurer c){
c.enableStompBrokerRelay("/queue/", "/topic/");
c.setApplicationDestinationPrefixes("/app");
}
}

CONNECT frame has authentication headers"/user/**"UserDestinationHandler"/user/..." destinations"/user/queue/abc" (without collision)"/user/{username}/queue/abc""/user/queue/..."
var socket = new SockJS('/myapp/portfolio');
var client = Stomp.over(socket);
client.connect('', '', function(frame) {
client.subscribe("/user/queue/trade-confirm",function(msg){
// ...
});
client.subscribe("/user/queue/errors",function(msg){
// ...
});
}
@Controller
public class GreetingController {
@MessageMapping("/greetings")
public String greet(String greeting) {
return "[" + getTimestamp() + "]: " + greeting;
}
}
@Controller
public class GreetingController {
@MessageMapping("/greetings")
@SendToUser
public String greet(String greeting) {
return "[" + getTimestamp() + "]: " + greeting;
}
}
@Controller
public class GreetingController {
// Message sent to "/user/{username}/queue/greetings"
@MessageMapping("/greetings")
@SendToUser
public String greet(String greeting) {
return "[" + getTimestamp() + "]: " + greeting;
}
}
@Controller
public class GreetingController {
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleException(IllegalStateException ex) {
return ex.getMessage();
}
}
SimpMessagingTemplate@Service
public class TradeService {
@Autowired
private SimpMessagingTemplate template;
public void executeTrade(Trade trade) {
String user = trade.getUser();
String dest = "/queue/trade-confirm";
TradeResult result = ...
this.template.convertAndSendToUser(user, dest, result);
}
}

"/exchange/amq.direct/a"