Image for post
Image for post
Photo by Neven Krcmarek on Unsplash

WebSockets With Spring, Part 1: HTTP and WebSocket

Aliaksandr Liakh
Nov 6, 2020 · 15 min read

Introduction

The HTTP protocol is a request-response protocol. That means that only a client can send HTTP requests to a server. A server can only service HTTP requests by sending back HTTP responses, but a server can not send unrequested HTTP responses to a client.

HTTP-based mechanisms

Because HTTP was not designed to support server-initiated messages, several mechanisms to achieve this have been developed, each with different benefits and drawbacks.

HTTP polling

During the polling mechanism, a client sends periodic requests to a server, and the server responds immediately. If there is new data, the server returns it, otherwise the server returns an empty response. After receiving the response, the client waits for a while before sending another request.

HTTP polling
HTTP polling

HTTP long polling

During the long polling mechanism, a client sends a request to a server and starts waiting for a response. The server does not respond until new data arrives or a timeout occurs. When new data becomes available, the server sends a response to the client. After receiving the response, the client immediately sends another request.

HTTP long polling
HTTP long polling

HTTP streaming

During the streaming mechanism, a client sends a request to a server and keeps it open indefinitely. The server does not respond until new data arrives. When new data becomes available, the server sends it back to the client as a part of the response. The data sent by the server does not close the request.

HTTP streaming
HTTP streaming

Server-Sent Events

Server-Sent Events (SSE) is a standardized streaming mechanism that has the network protocol and the EventSource API for browsers. SSE defines a uni-directional UTF-8 encoded events stream from a server to a browser. Events have mandatory values and can have optional types and unique identifiers. In case of failure, SSE supports automatic client reconnection from the last received event.

GET /sse HTTP/1.1 
Host: server.com
Accept: text/event-stream
HTTP/1.1 200 OK 
Connection: keep-alive
Content-Type: text/event-stream
Transfer-Encoding: chunked
retry: 1000data: A text messagedata: {"message": "a JSON message"} event: text
data: A message of type 'text'
id: 1
event: text
data: A message of type 'text' with a unique identifier
:ping

WebSocket

Prerequisites

WebSocket is designed to overcome the limitations of HTTP-based mechanisms (polling, long polling, streaming) in full-duplex communication between browsers and servers:

  • HTTP has request and response headers, WebSocket messages can have a format suitable for specific applications (unnecessary metadata are not transmitted over the network)
  • HTTP is a half-duplex protocol, WebSocket is a full-duplex protocol (low-latency messages can be transmitted at the same time in both directions)

Design

WebSocket is a protocol that allows simultaneous bi-directional transmission of text and binary messages between clients (mostly browsers) and servers over a single TCP connection. WebSocket can communicate over TCP on port 80 (“ws” scheme) or over TLS/TCP on port 443 (“wss” scheme).

WebSocket
WebSocket
  • WebSocket shares the same 80 and 443 ports as HTTP and HTTPS
  • WebSocket supports HTTP network intermediaries (proxies, firewalls, routers, etc.)
  • conversion between IP addresses used in TCP to URLs used on the Web
  • message protocol on top of byte stream protocol
  • closing handshake

The WebSocket protocol

The WebSocket network protocol consists of two components:

  1. the binary message framing for sending text and binary messages

Opening handshake

Before starting the exchange of messages, the client and server negotiate the parameters of the establishing connection. WebSocket reuses the existing HTTP Upgrade mechanism with custom Sec-WebSocket-* headers to perform the connection negotiation.

GET /socket HTTP/1.1
Host: server.com
Connection: Upgrade
Upgrade: websocket
Origin: http://example.com
Sec-WebSocket-Version: 8, 13
Sec-WebSocket-Key: 7c0RT+Z1px24ypyYfnPNbw==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp, v12.stomp
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
HTTP/1.1 101 Switching Protocols 
Connection: Upgrade
Upgrade: websocket
Access-Control-Allow-Origin: http://example.com
Sec-WebSocket-Accept: O1a/o0MeFzoDgn+kCKR91UkYDO4=
Sec-WebSocket-Protocol: v12.stomp
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15
  • the server confirms the protocol upgrade with 101 Switching Protocols response line and the same Connection and Upgrade headers
  • the server confirms that the client from this origin is allowed to access the resource via the Access-Control-Allow-Origin header
  • the server confirms the protocol by returning the Sec-WebSocket-Accept header
Base64
.getEncoder()
.encodeToString(
MessageDigest
.getInstance("SHA-1")
.digest((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
.getBytes(StandardCharsets.UTF_8)));
  • the server select one of the subprotocols via the Sec-WebSocket-Protocol header (if the server does not support any subprotocol, then the connection is canceled)
  • the server confirms one or more extensions via the Sec-WebSocket-Extensions header (if the server does not support some extensions, then the connection proceeds without them)

Message framing

WebSocket uses a binary message framing: the sender splits each application message into one or more frames, transports them across the network to the destination, reassembles them, and notifies the receiver once the entire message has been received.

  1. reserve (3 bits) — the reserve flags for extensions
  2. operation code (4 bits) — the type of frame: data frames (text or binary) or control frames (connection close, ping/pong for connection liveness checks)
  3. mask (1 bit) — the flag that indicates whether the payload data is masked (all frames sent from client to server are masked)
  4. payload length (7 bits, or 7+16 bits, or 7+64 bits) — the variable-length payload length (if 0–125, then that is the payload length; if 126, then the following 2 bytes represent the payload length; if 127, then the following 8 bytes represent the payload length)
  5. masking key (0 or 4 bytes) — the masking key contains a 32-bit value used to XOR the payload data
  6. payload data (n bytes) — the payload data contains extension data (if extensions are used) concatenated with application data

Closing handshake

Either party can initiate a closing handshake by sending a closing frame. On receiving such a frame, the other party sends a closing frame in response, if it has not already sent one. After sending the closing frame, a party does not send any further data. After receiving a closing frame, a party discards any further data received. Once a party has both sent and received a closing frame, that endpoint closes the WebSocket connection.

The WebSocket API

The WebSocket API is the interface that a browser must implement to communicate with servers using the WebSocket protocol.

if (window.WebSocket) {
// WebSocket is supported
} else {
// WebSocket is not supported
}
const ws = new WebSocket('ws://server.com/socket'); 
ws.binaryType = "blob";
ws.onopen = function () {
// send binary messages
ws.send(new Blob([new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x21]).buffer]));
// send text messages
ws.send("Hello!");
}
ws.onclose = function () {
// handle disconnect
}
ws.onmessage = function(msg) {
if (msg.data instanceof Blob) {
// receive binary messages
} else {
// receive text messages
}
}
ws.onerror = function (error) {
// handle errors
}

Examples

Introduction

The Spring Framework provides support for WebSocket clients and servers in the spring-websocket module.

  • the server sends periodic messages to the client
  • the server receives messages from a client, logs them, and sends them back to the client
  • the client sends aperiodic messages to the server
  • the client receives messages from a server and logs them

Java Spring server

Java Spring server consists of two parts: Spring WebSocket events handler and Spring WebSocket configuration.

public class ServerWebSocketHandler extends TextWebSocketHandler implements SubProtocolCapable {   private final Set<WebSocketSession> sessions = new CopyOnWriteArraySet<>();   @Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("Server connection opened");
sessions.add(session);
TextMessage message = new TextMessage("one-time message from server");
logger.info("Server sends: {}", message);
session.sendMessage(message);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
logger.info("Server connection closed: {}", status);
sessions.remove(session);
}
@Scheduled(fixedRate = 10000)
void sendPeriodicMessages() throws IOException {
for (WebSocketSession session : sessions) {
if (session.isOpen()) {
String broadcast = "server periodic message " + LocalTime.now();
logger.info("Server sends: {}", broadcast);
session.sendMessage(new TextMessage(broadcast));
}
}
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String request = message.getPayload();
logger.info("Server received: {}", request);
String response = String.format("response from server to '%s'", HtmlUtils.htmlEscape(request));
logger.info("Server sends: {}", response);
session.sendMessage(new TextMessage(response));
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
logger.info("Server transport error: {}", exception.getMessage());
}
@Override
public List<String> getSubProtocols() {
return Collections.singletonList("subprotocol.demo.websocket");
}
}
@Configuration
@EnableWebSocket
public class ServerWebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler(), "/websocket");
}
@Bean
public WebSocketHandler webSocketHandler() {
return new ServerWebSocketHandler();
}
}
@SpringBootApplication
@EnableScheduling
public class ServerWebSocketApplicaion {
public static void main(String[] args) {
SpringApplication.run(ServerWebSocketApplicaion.class, args);
}
}

JavaScript browser client

The JavaScript browser client uses the standardized WebSocket browser object. It is important, that the client uses the “ws” scheme to specify the server URL.

let webSocket = null;// 'Connect' button click handler
function connect() {
webSocket = new WebSocket('ws://localhost:8080/websocket',
'subprotocol.demo.websocket');
webSocket.onopen = function () {
log('Client connection opened');
console.log('Subprotocol: ' + webSocket.protocol);
console.log('Extensions: ' + webSocket.extensions);
};
webSocket.onmessage = function (event) {
log('Client received: ' + event.data);
};
webSocket.onerror = function (event) {
log('Client error: ' + event);
};
webSocket.onclose = function (event) {
log('Client connection closed: ' + event.code);
};
}
// 'Disconnect' button click handler
function disconnect() {
if (webSocket != null) {
webSocket.close();
webSocket = null;
}
}
// 'Send' button click handler
function send() {
const message = $("#request").val();
log('Client sends: ' + message);
webSocket.send(message);
}
WebSocket in a browser
WebSocket in a browser

Java Spring client

Java Spring client consists of two parts: Spring WebSocket events handler and Spring WebSocket configuration.

public class ClientWebSocketHandler extends TextWebSocketHandler {   @Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("Client connection opened");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
logger.info("Client connection closed: {}", status);
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
logger.info("Client received: {}", message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
logger.info("Client transport error: {}", exception.getMessage());
}
}
  • the implemented WebSocketHandler class — to handle WebSocket events during communication
@Configuration
public class ClientWebSocketConfig {
@Bean
public WebSocketConnectionManager webSocketConnectionManager() {
WebSocketConnectionManager manager = new WebSocketConnectionManager(
webSocketClient(),
webSocketHandler(),
"ws://localhost:8080/websocket"
);
manager.setAutoStartup(true);
return manager;
}
@Bean
public WebSocketClient webSocketClient() {
return new StandardWebSocketClient();
}
@Bean
public WebSocketHandler webSocketHandler() {
return new ClientWebSocketHandler();
}
}
@SpringBootApplication
public class ClientWebSocketApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ClientWebSocketApplication.class)
.web(WebApplicationType.NONE)
.run(args);
}
}

Conclusion

WebSocket is another communication technology for the Web designed to solve a specific range of problems where the capabilities of HTTP-based solutions are limited. But like any other technology, WebSockets is not a “silver bullet” and it has its advantages and drawbacks.

  • high-frequency messages with small payloads are used
  • the messaging communication model is used — when messages are sent by either party independently of each other
  • in enterprise applications when browsers and networks infrastructure is under control
  • it is possible to benefit from idempotency, safety, cacheability HTTP requests
  • the request-response communication model is used — when requests are always acknowledged by responses
  • it is expensive to modify the existing hardware and software infrastructure to support WebSocket

The Startup

Medium's largest active publication, followed by +752K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store