📜 ⬆️ ⬇️

Spring Websocket + SockJs. How it works?

Good day, dear habravchane. In this article I want to continue the story of the device Spring Websocket , having considered the server implementation of Spring Websocket + SockJs.

SockJs is a JavaScript library that provides a two-way inter-domain communication channel between the client and the server. In other words, SockJs simulates the WebSocket API. Under the hood, SockJS first tries to use the native implementation of the WebSocket API. If this fails, different browser-specific transport protocols are used and represent them through abstractions like WebSocket. Today we will talk about the port of this library to the world of Spring Frameworks.

Using Websocket and SockJs is available in Spring since version 4.0.
Currently, depending on the browser, SockJs can use the following transport protocols:
BrowserWebsocketsStreamingPolling
IE 6, 7nonojsonp-polling
IE 8, 9 (cookies = no)noxdr-streaming *xdr-polling *
IE 8, 9 (cookies = yes)noiframe-htmlfileiframe-xhr-polling
IE 10rfc6455xhr-streamingxhr-polling
Chrome 6-13hixie-76xhr-streamingxhr-polling
Chrome 14+hybi-10 / rfc6455xhr-streamingxhr-polling
Firefox <10no **xhr-streamingxhr-polling
Firefox 10+hybi-10 / rfc6455xhr-streamingxhr-polling
Safari 5.xhixie-76xhr-streamingxhr-polling
Safari 6+rfc6455xhr-streamingxhr-polling
Opera 10.70+no **iframe-eventsourceiframe-xhr-polling
Opera 12.10+rfc6455xhr-streamingxhr-polling
Konquerornonojsonp-polling

* IE 8+ supports XDomainRequest, which is essentially a modified AJAX / XHR capable of sending cross-domain requests. But does not send cookies.
** Firefox 4.0 and Opera 11.00 were shipped with the hixie-76 Websocket protocol disabled. Which can be activated in the settings.

Sometimes it is necessary to download html from a file. However, in this case, the Origin header is not committed, which can make the use of cross-domain queries unavailable. To resolve this situation, the following protocols are used:
BrowserWebsocketsStreamingPolling
IE 8, 9same as aboveiframe-htmlfileiframe-xhr-polling
Othersame as aboveiframe-eventsourceiframe-xhr-polling

Consider the basic protocols in a little more detail.
')

Websocket


WebSocket provides two-way communication between the client and the server using a single TCP connection.

XhrPolling (long)


This interaction protocol is characterized by the situation when, when sending a request to the server, the connection is not closed until the message appears on the server. When a message appears, data is sent to the client and a new connection is created.

JsonpPolling (long)


JSONP ("JSON with Padding").
Similar to the previous protocol, but used for cross-domain interaction. When sending data, the server encodes the data in JSON and wraps it in a function call, the name of which is received from the callback parameter.

XhrStreaming


This protocol is based on the possibility of obtaining part of the data until full download.

Partial load example
<script> var xhr = new XMLHttpRequest(); xhr.open('GET', '/stream'); xhr.seenBytes = 0; xhr.onreadystatechange = function() { if(xhr.readyState == 3) { var newData = xhr.response.substr(xhr.seenBytes); //   xhr.seenBytes = xhr.responseText.length; } }; xhr.send(); </script> 


Event source


As an implementation of this protocol on the client side, an EventSource object is used. This object is designed to send text messages using Http. The main advantage of this approach is the automatic reconnection and the presence of message identifiers for the resumption of data flow.

IFrame


The idea of ​​using an IFrame is to enable the page to be processed sequentially as data is loaded from the server. The interaction scheme is quite simple - a hidden IFrame is created, a request is sent to the server, which returns the header of the document and keeps the connection. Every time a new data appears, the server frames it with a script tag and sends it to an IFrame. IFrame receiving a new script block will begin its execution.

Htmlfile


This approach is used in IE and consists in wrapping an IFrame into an ActiveX object. And the main advantage of using is hiding actions in the IFrame from the user.

SockJs structure


Hierarchy of transport handlers



Session hierarchy



Creating a configuration class


To be able to use SockJs in a Spring application, simply call the .withSockJS () method when registering handlers (WebSocketHandler).

 @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new EchoWebSocketHandler(), "/init").withSockJS(); } } 

The implementation of the withSockJS () method can be seen in the AbstractWebSocketHandlerRegistration class. The main objective of this method is to create a SockJsServiceRegistration factory, from which the main Http processing class for SockJsService requests is created. After creating an instance of SockJsService, this service is bound to WebSocketHandler and converted to HandlerMapping. The adapter in this case is the class SockJsHttpRequestHandler.

When creating an instance of SockJsService, the task scheduler (TaskScheduler) is passed to it, which will later be used to send Heartbeat messages.

Jackson2SockJsMessageCodec is used as the default message conversion codec.

To connect SockJs on the client side, you need to add a javascript library, and create a SockJS object, while changing the protocol of our endpoint from ws (wss) to http (https)

 <!DOCTYPE html> <html lang="en" ng-app="testSockJs"> <head> <meta charset="utf-8"> <title>Test SockJs</title> </head> <body> <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"> </script> <script> var ws = new SockJS("http://localhost:8080/init"); ws.onmessage = function(data){ console.log(data); } </script> </body> </html> 


Description of the interaction algorithm


Work begins with a client request / info, in response to which the server returns an object of the form
 {"entropy":293909549,"origins":["*:*"],"cookie_needed":true,"websocket":true} 

which indicates the available url for processing client requests. Whether cookies are necessary and is it possible to use webSocket. Based on this data, the client library selects the transport protocol.

All client requests are
 http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport} 

{server-id} is a random parameter from 000 to 999, the only purpose of which is to simplify balancing on the server side.
{session-id} -maps HTTP requests that belong to the SockJS session.
{transport} - indicates the transport protocol "websocket", "xhr-streaming", etc.

To maintain compatibility with Websocket, Api SockJs uses a custom messaging protocol:
o - (open frame) is sent each time a new session is opened.
c - (close frame) is sent when the client requests that the connection be closed.
h - (heartbeat frame) check the availability of the connection.
a - (data frame) An array of json messages. For example: a ["message"].

Fallback example


Consider an example when we do not have the ability to process Websocket on the server; it is quite simple to do this by setting the webSocketEnabled variable to false in the SockJsServiceRegistration class

 @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new EchoWebSocketHandler(), "/init").setAllowedOrigins("*").withSockJS().setWebSocketEnabled(false); } } 

The client will check the possibility of opening a socket by calling / info. Having received a negative response, two channels will be used for exchanging messages: one for receiving messages - usually the streaming protocol, and one for sending messages to the server (http requests). These communication channels will be connected to one sessionId transmitted in the URL.

When sending a message from a client, the request goes to the DispatcherServlet, from where it is redirected to our SockJsHttpRequestHandler adapter. This class converts the request and forwards it to the SockJsService, which delegates the function of accepting a message to a user session of SockJsSession. And since our session is tied to the WebSocketHandler handler, we receive the sent message in our handler.

To send a message to the client, we still use the WebSocketSession. The fact is that SockJsSession is an extension of WebSocketSession. And specific implementations of SockJsSession are tied to the transport protocol. Therefore, on the server side, when calling session.sendMessage (new TextMessage ("some message")); the message is converted to a specific protocol type and a formatted message is sent to the client.

That is, in fact, all the magic of the possibility of a fallback when using SockJs.

Used sources:
Websocket sockjs
SockJs protocol

Source: https://habr.com/ru/post/343908/


All Articles