WebSockets with Spring 4
Sergi Almar
@sergialmar
Who I am
•

CTO @ Voz.io

•

Spring Certified Trainer

•

javaHispano Core Member

•

Spring I/O conference organiser
Collaborative Apps
Multiplayer Games
Multimedia Chat
Social Feeds
Sports Updates
Financial Tickets
Clickstream Data
Online Education
Location-based Apps
Real-Time Data on the Web
•

Polling

•

Long Polling / Comet

•

Flash
Problem

Applications need two-way communication
Too many connections and overhead with ajax / comet
WebSockets

two-way communication done right
WebSocket Protocol / RFC 6455
•

Real-time full duplex communication over TCP

•

Uses port 80 / 443 (URL scheme: ws:// and wss://)

•

Small overhead for text messages (frames)
•

•

0x00 for frame start, 0xFF for frame end (vs HTTP 1Kb)

Ping / pong frames for staying alive
WebSocket Handshake
GET /mychat HTTP/1.1!
Host: server.example.com!
Upgrade: websocket!
Connection: Upgrade!
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==!
Sec-WebSocket-Protocol: chat!
Sec-WebSocket-Version: 13!
Origin: http://example.com!

client sends a WebSocket handshake request
HTTP/1.1 101 Switching Protocols!
Upgrade: websocket!
Connection: Upgrade!
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=!
Sec-WebSocket-Protocol: chat!

server response
JS WebSocket API
var ws = new WebSocket("ws://www.java2days.com/ws");!

!
// When the connection is open, send some data to the server!
ws.onopen = function () {!
ws.send('Here I am!');!
};!

!
// Log messages from the server!
ws.onmessage = function (event) {!
console.log('message: ' + event.data);!
};!

!
ws.onclose = function (event) {!
console.log('closed:' + event.code);!
};!
Java WebSocket Implementations
•

Multiple implementations before the standard

•

JSR-356 (May 2013)
•

Reference implementation Tyrus (bundled with
Glassfish 4)

•

Rewrite across containers (tomcat 8.0.0-RC5, Jetty
9.1…)
WebSockets.springify()
Spring WebSockets
•

WebSockets are now supported in Spring 4

•

Fallback options with SockJS

•

STOMP over WebSocket

•

Foundation for messaging architecture
WebSocket Handlers
public class EchoHandler extends TextWebSocketHandlerAdapter {!

!
@Override!
public void handleTextMessage(WebSocketSession session, !
! ! ! ! ! ! ! ! ! ! TextMessage message) throws Exception {!
session.sendMessage(message);!
}!
!
}
WebSocket Config
@Configuration!
@EnableWebSocket!
public class WsConfig implements WebSocketConfigurer {!

!
!

!

@Override!
public void registerWebSocketHandlers(!
! ! ! ! ! ! ! ! ! WebSocketHandlerRegistry registry) {!

!
registry.addHandler(new EchoHandler(), "/echo");!
}!
}!
Per-Session Handler
Previous example showed how to configure a global handler

•

•

but you may want to have a stateful per-session handler

@Configuration!
@EnableWebSocket!
public class WsConfig implements WebSocketConfigurer {!

!

!

@Bean!
DI
public WebSocketHandler echoHandler() {!
return new PerConnectionWebSocketHandler(EchoHandler.class);!
}!
@Override!
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {!
registry.addHandler(echoHandler(), "/echo");!
}!

}
Can anyone join the party?

http://caniuse.com/#feat=websockets / DEC 2013
SockJS

dude, where are my socks?
SockJS
•

Coherent, cross-browser, Javascript API for full duplex
communication.

•

Close to HTML5 WebSockets API

•

Client and server side implementation (ruby, node…
and also in spring-websockets)
SocksJS API
var sock = new SockJS('http://www.java2days.com/ws');!

!
sock.onopen = function() {!
sock.send('Here I am');
};!

!

!
sock.onmessage = function(event) {!
console.log('message', e.data);!
};!

!
sock.onclose = function() {!
console.log('close');!
};!
SockJS URLs
•

Base URL: /base_url

•

Info test: /base_url/info

•

Session URL: /base_url/server/session
Enabling SocksJS
@Configuration!
@EnableWebSocket!
public class WsConfig implements WebSocketConfigurer {!

!
!

!

@Override!
public void registerWebSocketHandlers(!
! ! ! ! ! ! ! ! ! WebSocketHandlerRegistry registry) {!

!
registry.addHandler(new EchoHandler(), “/echo”).withSockJS();!
}!
}!

MessageHandler doesn’t change
(SocketJsService delivers the message to the handler regardless of the protocol)
Problem
•

WebSockets are too low level and different from
HTTP / REST

•

We need asynchronous, event-driven, reactive
programming style

•

Mmmm, that sounds familiar: JMS, AMQP… 

but we still want to stick to the Spring MVC
programming model
Here’s were we are…
…but we want something like this
Solution
•

Stomp over WebSocket

•

Some Spring Integration types have been promoted to
the core
•
•

•

Message, MessageChannel, MessageHandler…
@MessageMapping, @SubscribeEvent…

New spring-messaging module
STOMP
STOMP
•

Simple interoperable protocol for asynchronous messaging

•

Supported by Apache ActiveMQ, RabbitMQ, HornetQ…

•

Frames modelled on HTTP

var socket = new SockJS('/myapp/echo');!
var client = Stomp.over(socket);

COMMAND!
header1:value1!
header2:value2!
!
Body^@!
Client Frames
•

SEND a message

•

SUBSCRIBE / UNSUBSCRIBE from destination

•

ACK / NACK the reception of a message (optional by
default)
Server Frames
•

Convey a MESSAGE from subscription to the client

•

Send RECEIPT when server has successfully
processed a client frame

•

Send an ERROR if something goes wrong
Configuration
!
@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/");!
Subscriptions
c.setApplicationDestinationPrefixes("/app");!
processed by spring
}!

!
}!

simple broker
Sending Messages
stomp.js
stompClient.send("/app/trade", {}, JSON.stringify(trade));

prefix
SEND!
destination:/app/trade!
content-type:application/json!
content-length:47!

!
{"action":"Sell","ticker":"DELL","shares":"10"}!

supports ant-style patterns
@MessageMapping("/trade")!
public void executeTrade(Trade trade, Principal principal) {!
! trade.setUsername(principal.getName());!
! this.tradeService.executeTrade(trade);!
}
Handler Methods
•

Flexible handler method signatures
•

@PathVariable, @Header/@Headers, @Payload, Message, Principal

•

Message converters
@Controller!
public class ChatController {!

!
@MessageMapping("/message")!
public void sendMessage(String message, Principal principal) {!
// ...!
}!
}!
Handler Methods
•

Can also return a value
!
!
!

Return wrapped in a Message and sent to /topic/message
@MessageMapping("/message")!
public String sendMessage(String message) {!
return message.toUpperCase();!
}

!
•

Or define the destination with @SendTo
@MessageMapping("/message")!
@SendTo("/topic/spring-room")!
public String sendMessage(String message) {!
return message.toUpperCase();!
}!
Intercepting Subscriptions
stompClient.subscribe("/app/positions", function(message) {!
...!
});!
SUBSCRIBE!
id:sub-0!
destination:/app/positions!
@Controller!
public class PortfolioController {!

!
@SubscribeEvent("/positions")!
public List<Position> getPositions(Principal p) {!
Portfolio portfolio = ...!
return portfolio.getPositions();!
}!
}

sent directly to the client, not going
though the message broker
User destinations
•

User specific queues can be used
•
•

•

/user/** kind of paths
queues with unique id will be created

Useful to send user related information or errors

client.subscribe("/user/queue/private-messages", function(msg) {!
// ...!
});!

!
client.subscribe("/user/queue/errors", function(msg) {!
// ...!
});!
Sending to user
@Controller!
public class ChatController {!
Will be sent to /user/{username}/queue/message
!
@MessageMapping("/message")!
@SendToUser!
public String sendMessage(String message) {!
return message.toUpperCase();!
}!
!
@MessageExceptionHandler!
@SendToUser("/queue/errors")!
public String handleException(IllegalStateException ex) {!
return ex.getMessage();!
}!
!
}!
SimpMessagingTemplate
•

Sending messages without handler methods

template.convertAndSend("/topic/chat", message;!
template.convertAndSendToUser(user, dest, message;
Using a Message Broker
•

Previous configuration used Spring’s simple broker
•
•

•

Not suitable for clustering
Subset of STOMP

A message broker can be plugged-in instead
Message Broker Config
@Configuration!
@EnableWebSocketMessageBroker!
public class Config implements WebSocketMessageBrokerConfigurer{!
!
@Override!
public void configureMessageBroker(MessageBrokerConfigurer c){!
c.enableStompBrokerRelay("/queue/", "/topic/");!
c.setApplicationDestinationPrefixes("/app");!
}!
}
Thanks!
@sergialmar

WebSockets with Spring 4

  • 1.
    WebSockets with Spring4 Sergi Almar @sergialmar
  • 2.
    Who I am • CTO@ Voz.io • Spring Certified Trainer • javaHispano Core Member • Spring I/O conference organiser
  • 3.
    Collaborative Apps Multiplayer Games MultimediaChat Social Feeds Sports Updates Financial Tickets Clickstream Data Online Education Location-based Apps
  • 4.
    Real-Time Data onthe Web • Polling • Long Polling / Comet • Flash
  • 5.
    Problem Applications need two-waycommunication Too many connections and overhead with ajax / comet
  • 6.
  • 7.
    WebSocket Protocol /RFC 6455 • Real-time full duplex communication over TCP • Uses port 80 / 443 (URL scheme: ws:// and wss://) • Small overhead for text messages (frames) • • 0x00 for frame start, 0xFF for frame end (vs HTTP 1Kb) Ping / pong frames for staying alive
  • 8.
    WebSocket Handshake GET /mychatHTTP/1.1! Host: server.example.com! Upgrade: websocket! Connection: Upgrade! Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==! Sec-WebSocket-Protocol: chat! Sec-WebSocket-Version: 13! Origin: http://example.com! client sends a WebSocket handshake request HTTP/1.1 101 Switching Protocols! Upgrade: websocket! Connection: Upgrade! Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=! Sec-WebSocket-Protocol: chat! server response
  • 9.
    JS WebSocket API varws = new WebSocket("ws://www.java2days.com/ws");! ! // When the connection is open, send some data to the server! ws.onopen = function () {! ws.send('Here I am!');! };! ! // Log messages from the server! ws.onmessage = function (event) {! console.log('message: ' + event.data);! };! ! ws.onclose = function (event) {! console.log('closed:' + event.code);! };!
  • 10.
    Java WebSocket Implementations • Multipleimplementations before the standard • JSR-356 (May 2013) • Reference implementation Tyrus (bundled with Glassfish 4) • Rewrite across containers (tomcat 8.0.0-RC5, Jetty 9.1…)
  • 11.
  • 12.
    Spring WebSockets • WebSockets arenow supported in Spring 4 • Fallback options with SockJS • STOMP over WebSocket • Foundation for messaging architecture
  • 13.
    WebSocket Handlers public classEchoHandler extends TextWebSocketHandlerAdapter {! ! @Override! public void handleTextMessage(WebSocketSession session, ! ! ! ! ! ! ! ! ! ! ! TextMessage message) throws Exception {! session.sendMessage(message);! }! ! }
  • 14.
    WebSocket Config @Configuration! @EnableWebSocket! public classWsConfig implements WebSocketConfigurer {! ! ! ! @Override! public void registerWebSocketHandlers(! ! ! ! ! ! ! ! ! ! WebSocketHandlerRegistry registry) {! ! registry.addHandler(new EchoHandler(), "/echo");! }! }!
  • 15.
    Per-Session Handler Previous exampleshowed how to configure a global handler • • but you may want to have a stateful per-session handler @Configuration! @EnableWebSocket! public class WsConfig implements WebSocketConfigurer {! ! ! @Bean! DI public WebSocketHandler echoHandler() {! return new PerConnectionWebSocketHandler(EchoHandler.class);! }! @Override! public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {! registry.addHandler(echoHandler(), "/echo");! }! }
  • 16.
    Can anyone jointhe party? http://caniuse.com/#feat=websockets / DEC 2013
  • 17.
  • 18.
    SockJS • Coherent, cross-browser, JavascriptAPI for full duplex communication. • Close to HTML5 WebSockets API • Client and server side implementation (ruby, node… and also in spring-websockets)
  • 19.
    SocksJS API var sock= new SockJS('http://www.java2days.com/ws');! ! sock.onopen = function() {! sock.send('Here I am'); };! ! ! sock.onmessage = function(event) {! console.log('message', e.data);! };! ! sock.onclose = function() {! console.log('close');! };!
  • 20.
    SockJS URLs • Base URL:/base_url • Info test: /base_url/info • Session URL: /base_url/server/session
  • 21.
    Enabling SocksJS @Configuration! @EnableWebSocket! public classWsConfig implements WebSocketConfigurer {! ! ! ! @Override! public void registerWebSocketHandlers(! ! ! ! ! ! ! ! ! ! WebSocketHandlerRegistry registry) {! ! registry.addHandler(new EchoHandler(), “/echo”).withSockJS();! }! }! MessageHandler doesn’t change (SocketJsService delivers the message to the handler regardless of the protocol)
  • 22.
    Problem • WebSockets are toolow level and different from HTTP / REST • We need asynchronous, event-driven, reactive programming style • Mmmm, that sounds familiar: JMS, AMQP… 
 but we still want to stick to the Spring MVC programming model
  • 23.
  • 24.
    …but we wantsomething like this
  • 25.
    Solution • Stomp over WebSocket • SomeSpring Integration types have been promoted to the core • • • Message, MessageChannel, MessageHandler… @MessageMapping, @SubscribeEvent… New spring-messaging module
  • 26.
  • 27.
    STOMP • Simple interoperable protocolfor asynchronous messaging • Supported by Apache ActiveMQ, RabbitMQ, HornetQ… • Frames modelled on HTTP var socket = new SockJS('/myapp/echo');! var client = Stomp.over(socket); COMMAND! header1:value1! header2:value2! ! Body^@!
  • 28.
    Client Frames • SEND amessage • SUBSCRIBE / UNSUBSCRIBE from destination • ACK / NACK the reception of a message (optional by default)
  • 29.
    Server Frames • Convey aMESSAGE from subscription to the client • Send RECEIPT when server has successfully processed a client frame • Send an ERROR if something goes wrong
  • 30.
    Configuration ! @Configuration! @EnableWebSocketMessageBroker! public class Configimplements WebSocketMessageBrokerConfigurer {! ! @Override! public void registerStompEndpoints(StompEndpointRegistry r){! r.addEndpoint("/stomp");! }! ! @Override! public void configureMessageBroker(MessageBrokerConfigurer c){! c.enableSimpleBroker("/topic/");! Subscriptions c.setApplicationDestinationPrefixes("/app");! processed by spring }! ! }! simple broker
  • 31.
    Sending Messages stomp.js stompClient.send("/app/trade", {},JSON.stringify(trade)); prefix SEND! destination:/app/trade! content-type:application/json! content-length:47! ! {"action":"Sell","ticker":"DELL","shares":"10"}! supports ant-style patterns @MessageMapping("/trade")! public void executeTrade(Trade trade, Principal principal) {! ! trade.setUsername(principal.getName());! ! this.tradeService.executeTrade(trade);! }
  • 32.
    Handler Methods • Flexible handlermethod signatures • @PathVariable, @Header/@Headers, @Payload, Message, Principal • Message converters @Controller! public class ChatController {! ! @MessageMapping("/message")! public void sendMessage(String message, Principal principal) {! // ...! }! }!
  • 33.
    Handler Methods • Can alsoreturn a value ! ! ! Return wrapped in a Message and sent to /topic/message @MessageMapping("/message")! public String sendMessage(String message) {! return message.toUpperCase();! } ! • Or define the destination with @SendTo @MessageMapping("/message")! @SendTo("/topic/spring-room")! public String sendMessage(String message) {! return message.toUpperCase();! }!
  • 34.
    Intercepting Subscriptions stompClient.subscribe("/app/positions", function(message){! ...! });! SUBSCRIBE! id:sub-0! destination:/app/positions! @Controller! public class PortfolioController {! ! @SubscribeEvent("/positions")! public List<Position> getPositions(Principal p) {! Portfolio portfolio = ...! return portfolio.getPositions();! }! } sent directly to the client, not going though the message broker
  • 35.
    User destinations • User specificqueues can be used • • • /user/** kind of paths queues with unique id will be created Useful to send user related information or errors client.subscribe("/user/queue/private-messages", function(msg) {! // ...! });! ! client.subscribe("/user/queue/errors", function(msg) {! // ...! });!
  • 36.
    Sending to user @Controller! publicclass ChatController {! Will be sent to /user/{username}/queue/message ! @MessageMapping("/message")! @SendToUser! public String sendMessage(String message) {! return message.toUpperCase();! }! ! @MessageExceptionHandler! @SendToUser("/queue/errors")! public String handleException(IllegalStateException ex) {! return ex.getMessage();! }! ! }!
  • 37.
    SimpMessagingTemplate • Sending messages withouthandler methods template.convertAndSend("/topic/chat", message;! template.convertAndSendToUser(user, dest, message;
  • 38.
    Using a MessageBroker • Previous configuration used Spring’s simple broker • • • Not suitable for clustering Subset of STOMP A message broker can be plugged-in instead
  • 39.
    Message Broker Config @Configuration! @EnableWebSocketMessageBroker! publicclass Config implements WebSocketMessageBrokerConfigurer{! ! @Override! public void configureMessageBroker(MessageBrokerConfigurer c){! c.enableStompBrokerRelay("/queue/", "/topic/");! c.setApplicationDestinationPrefixes("/app");! }! }
  • 40.