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

Diagram with SockJS Service

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



Logos of projects


The Spring Approach




STOMP

STOMP Frame


STOMP Frame Content


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


SEND frame



Example

Client Subscribes To Receive Messages


SUBSCRIBE frame

Example

Client Receives Message


MESSAGE frame

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");
  }

}


Architecture diagram


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;
  }

}

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");
  }

}

Architecture diagram


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);
  }

}

Architecture diagram



Managing Inactive Queues

(with Full-Featured Broker)

Stock Portfolio App



https://github.com/rstoyanchev/

spring-websocket-portfolio