SockJS - Websockets continued

- 9 mins

Who should read this?

This is a follow up blog post after Websockets - All you need to know. If you have not checked it out, I recommend going through that first unless you have your basics clear and just want to refresh SockJS.

The goal of SockJS is to let applications use a WebSocket API but fall back to non-WebSocket alternatives when necessary at runtime, i.e. without the need to change application code.

This blog uses an example from callicoder.com to show SockJS in action. The link to the example can be found at the bottom of this post.

What you should expect?

  1. How does SockJS work?
  2. IE8 & 9 Case
  3. Authentication
  4. User destinations
  5. Performance
  6. Points to remember
  7. Spring specifics
  8. Further reading

How does SockJS work?

Step 1: GET /info - to obtain basic information from the server

This is a request for information that can influence the client’s choice of transports. After that it must decide what transport to use. If possible WebSocket is used. If not, in most browsers there is at least one HTTP streaming option and if not then HTTP (long) polling is used.

Markdown Image

Step 1 /info message sent by client

All transport requests have the following URL structure:

http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}

server-id - useful for routing requests in a cluster but not used otherwise
session-id - correlates HTTP requests belonging to a SockJS session
transport - indicates the transport type, e.g. websocket, xhr-streaming, etc.

The session between the client and the server is always initialized by the client. The client chooses server_id, which should be a three digit number: 000 to 999. It can be provided by the user or it can be generated randomly. The main reason for this parameter is to make it easier to configure load balancer - and enable sticky sessions based on first part of the url.

Second parameter session_id must be a random string, unique for every session. It is undefined what happens when two clients share the same session_id. It is client’s responsibility to choose an identifier with enough entropy.

Markdown Image

Server response showing the server_id and the session_id

SockJS adds minimal message framing. For example the server sends open frame initially. Messages are sent as a [“message1”,”message2”] (JSON-encoded array). Heartbeat frame is sent if no messages flow for 25 seconds by default, and close frame to close the session.

Note

Step 2: Framing accepted by client

  1. Open Frame (o) - Every time a new session is established, the server must immediately send the open frame. This is required, as some protocols (mostly polling) can’t distinguish between a properly established connection and a broken one - we must convince the client that it is indeed a valid url and it can be expecting further messages in the future on that url.
  2. Heartbeat frame (h) - Most loadbalancers have arbitrary timeouts on connections. In order to keep connections from breaking, the server must send a heartbeat frame every now and then. The typical delay is 25 seconds and should be configurable.
  3. Array (a) - of json-encoded messages. For example: a[“message”].
  4. Close frame (c) - This frame is send to the browser every time the client asks for data on closed connection. This may happen multiple times. Close frame contains a code and a string explaining a reason of closure, like: c[3000,”Go away!”]

Markdown Image

Showcasing various frames exchanged between client and server

Step 3: Framing accepted by server

SockJS server does not have any framing defined. All incoming data is treated as incoming messages, either single json-encoded messages or an array of json-encoded messages, depending on transport.

Markdown Image

Summary of the entire process. Notice blue comments scribbled in the image?

IE8 & 9 Case

The SockJS client supports Ajax/XHR streaming in IE 8 and 9 via Microsoft’s XDomainRequest. That works across domains but does not support sending cookies. Cookies are very often essential for Java applications. However since the SockJS client can be used with many server types (not just Java ones), it needs to know whether cookies matter. The very first /info request from the SockJS client is a request for information that can influence the client’s choice of transports. One of those details is whether the server application relies on cookies?

Authentication

Every STOMP over WebSocket messaging session begins with an HTTP request.

Web applications already have authentication and authorization in place to secure HTTP requests. Therefore for a WebSocket handshake, or for SockJS HTTP transport requests, typically there will already be an authenticated user.

In short there is nothing special a typical web application needs to do above and beyond what it already does for security

Note that the STOMP protocol does have a login and passcode headers on the CONNECT frame. Those were originally designed for and are still needed, for example, for STOMP over TCP.

Token authentication (JWT)

JWT can be used as the authentication mechanism in Web applications including STOMP over WebSocket interactions by maintaining identity through a cookie-based Websocket/SOCKJS session. However, not all web applications see it fit to maintain cookie-based sessions on the server. Also, for mobile applications where its common to use headers for authentication instead of cookie-based sessions.

The WebSocket protocol RFC 6455 “doesn’t prescribe any particular way that servers can authenticate clients during the WebSocket handshake.”

Therefore applications that wish to avoid the use of cookies may not have any good alternatives for authentication at the HTTP authentication level. Instead of using cookies you may prefer to authenticate with headers at the STOMP messaging protocol level at CONNECT message.

User destinations

For sending messages to a perticular user you will have to go through the documentation of your specific STOMP Client as WebSocket/STOMP being protocols just mention how messages are sent and received. And they do not talk about any specific use cases.

Performance

In a messaging application messages are passed through channels for asynchronous executions backed by thread pools. Configuring such an application requires good knowledge of the channels and the flow of messages.

Check out Spring specific flow of messages here

However, here are some recommendations:

  1. If the handling of messages is mainly CPU bound then the number of threads in the inbound channel should remain close to the number of processors.
  2. If the work they do is more IO bound and requires blocking or waiting on a database or other external system then the thread pool size will need to be increased.
  3. On the outboud channel it is all about sending messages to WebSocket clients. If clients are on a fast network then the number of threads should remain close to the number of available processors. If they are slow or on low bandwidth they will take longer to consume messages and put a burden on the thread pool. Therefore increasing the thread pool size will be necessary.

Points to remember

Spring specifics

  byte[] nonce = new byte[16];
  random.nextBytes(nonce);
  this.key = ByteString.of(nonce).base64();

Further reading

  1. https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html
  2. https://tools.ietf.org/html/rfc6455
  3. https://stomp.github.io/stomp-specification-1.2.html#Abstract
  4. https://www.callicoder.com/spring-boot-websocket-chat-example/

Pranjal Gore

Pranjal Gore

Software Developer | Aspiring Blogger

comments powered by Disqus
rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora