"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 DeferredResult
HttpServletRequest.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);
};
HandshakeInterceptor
false
from beforeHandshake
HttpSessionHandshakeInterceptor
@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());
}
}
PerConnectionWebSocketHandler
WebSocketHandler
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");
}
}
DispatcherServlet
RequestUpgradeStrategy
spring-websocket
GET /echo
GET /echo/info
POST /echo/<server>/<session>/<transport>
"/echo/**"
"/echo/{server}/{session}/xhr-polling"
TransportHandler
WebSocketHandler
@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
SEND
SUBSCRIBE
UNSUBSCRIBE
MESSAGE
ERROR
RECEIPT
ACK
NACK
"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"