Framework & Platform/Spring
spring - springx2013-websocket
linuxism
2014. 3. 29. 20:30
WebSocket Apps
Spring Framework 4 (part 1)
Rossen Stoyanchev
Part 1:
Comprehensive Intro to WebSocket
in Spring Framework 4.0
Part 2:
Building WebSocket Applications
The WebSocket Protocol
How Did We Get Here?
How WebSocket Works
Practical Considerations
Java WebSocket API
Relation To the Servlet API
Server Availability
Spring Framework 4.0
Example
import org.springframework.web.socket.*;
public class MyHandler extends TextWebSocketHandlerAdapter {
@Override
public void handleTextMessage(WebSocketSession session,
TextMessage message) throws Exception {
session.sendMessage(message);
}
}
Basic Config
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(new MyHandler(), "/echo");
}
}
Browser client
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
Add Interceptor
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(echoHandler(), "/echo")
.addInterceptors(new MyHandshakeInterceptor());
}
}
Per-Session Handler
Configure Per-Session Handler
@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");
}
}
Deployment Notes
SockJS
SockJS Protocol
SockJS URL Scheme
GET /echo
GET /echo/info
POST /echo/<server>/<session>/<transport>
SockJsService
Configure SockJS
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/echo").withSockJS();
}
}
WebSocket Handler
(same as before)
import org.springframework.web.socket.*;
public class MyHandler extends TextWebSocketHandlerAdapter {
@Override
public void handleTextMessage(WebSocketSession session,
TextMessage message) throws Exception {
session.sendMessage(message);
}
}
Server Versions
Detecting Client Disconnects
WebSocket/SockJS Sample
https://github.com/rstoyanchev/
spring-websocket-test
Questions?
WebSocket Apps
Spring Framework 4 (part 2)
Rossen Stoyanchev
Non-Trivial Applications
Using a WebSocket API is a bit like
writing a custom Servlet application
Except WebSocket is even lower level
You'll likely have 1 WebSocket handler
for the whole application
Annotations can't help much either...
...not without making assumptions about
what's in a message!
"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
The Sub-protocol Attribute
Furthermore..
Message Brokers an Option?
We Need a Bit of Both Worlds
Many Approaches Exist
The Spring Approach
STOMP
STOMP Frame
Client-to-Server Commands
Server-to-Client Commands
"Destination"
Header
Keep in mind:
A server cannot send unsolicited messages
client must subscribe first
Example
Client Sends Message
Example
Client Subscribes To Receive Messages
Example
Client Receives Message
STOMP vs plain WebSocket
STOMP over WebSocket
Spring Framework 4.0
Basic 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/");
c.setApplicationDestinationPrefixes("/app");
}
}
Handle a Message
@Controller
public class GreetingController {
@MessageMapping("/greetings")
public void handle(String greeting) {
// ...
}
}
@MessageMapping
Return Values
Send via Return Value
@Controller
public class GreetingController {
// A message is broadcast to "/topic/greetings"
@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;
}
}
Send to Different Destination
with @SendTo
@Controller
public class GreetingController {
@MessageMapping("/greetings")
@SendTo("/topic/wishes")
public String greet(String greeting) {
return "[" + getTimestamp() + "]: " + greeting;
}
}
Send via 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);
}
}
Handle Subscription
(Request-Reply Pattern)
@Controller
public class PortfolioController {
@SubscribeEvent("/positions")
public List<Position> getPositions(Principal p) {
Portfolio portfolio = ...
return portfolio.getPositions();
}
}
Plug in Message Broker
Steps to Use Message Broker
Enable "Broker Relay"
@Configuration
@EnableWebSocketMessageBroker
public class Config
implements WebSocketMessageBrokerConfigurer{
@Override
public void configureMessageBroker(MessageBrokerConfigurer c){
c.enableStompBrokerRelay("/queue/", "/topic/");
c.setApplicationDestinationPrefixes("/app");
}
}
Authentication
Destination "/user/**"
UserDestinationHandler
Client Subscribes
To "/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){
// ...
});
}
Send Reply To User
@Controller
public class GreetingController {
// Message sent to "/user/{username}/queue/greetings"
@MessageMapping("/greetings")
@SendToUser
public String greet(String greeting) {
return "[" + getTimestamp() + "]: " + greeting;
}
}
Send Error To User
@Controller
public class GreetingController {
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleException(IllegalStateException ex) {
return ex.getMessage();
}
}
Send Message To User
via 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);
}
}