WebSockets

Dr. Greg Bernstein

April 22nd, 2020

WebSockets

References

Software

Client Side (DOM API):

Server Side:

What Is It?

From RFC6455

The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code.

Two-Way Communications?

Don’t we have this already

Doesn’t HTTP offer two-way communications?

It sure does! Clients make requests to Servers and the Servers respond to those requests

Server Initiated?

What if a server wants to initiate the communications?

  • Can a server make an HTTP request to a client? NO
  • But how can we have “server push” type of services?
  • I’ve got a bunch running in my browser right now! See Push Notifications

Server Push Notifications

From Push Notifications

FireFox subscribing to push notification:

Controlling Notifications in Browser

Chrome Settings Example

How does this work?

From Push Notifications

  • “Websites can install a Service Worker, a background web page with a limited set of functionality, that can subscribe to the push service”

  • To avoid tracking and spam an intermediate server run by Mozilla or Google gets between your browser and the site to limit messages and to limit information about you passing to the end server.

  • This is not “real time” two way communications. Nor is it intended for transferring lots of data.

What is WebSockets? Part II

From Dr. B:

WebSockets enables soft real-time two-way communication “directly” between a client and a remote host where either side can send a message whenever it likes. The messages are entirely determined by the application.

What is WebSockets? Part III

From Dr. B:

  • Soft Real-Time: In the sense of best effort packet deliver. There are no performance guarantees.

  • Message Content: Format and content entirely determined by client and server. Text and Binary formats are supported.

  • Not request/response: client or server just send messages. No behavior beyond this is required. This is NOT HTTP!

The WebSocket Protocol

Two Way Communications Challenge

  • HTTP was a request/response protocol with specific formats for the two message types. A number of methods (GET, POST, …), and a ton of different headers

  • WebSockets is going to supply us with soft real-time, two-way, text or binary communications via messages. This sounds daunting.

  • To understand what WebSockets provides let compare the HTTP and Websockets protocol stacks.

Protocols Stacks Compared

These don’t look very different

TCP Supports Two Way Communications

  • TCP is a two-way protocol. It is HTTP that is request/response oriented. TCP has to be two-way to provide reliable delivery.

  • TCP provides a byte-stream, i.e., it doesn’t care whether the data is binary or text.

WebSocket Adds

  • Message Framing: TCP is stream oriented, WebSockets understands messages not just bytes.

  • An HTTP to WebSockets *handshake: How can we (client/server) establish a WebSocket connection? There is a procedure to “upgrade” from HTTP to WebSockets known as the opening Handshake

  • There are also special URIs for websockets, e.g., ws://localhost:8999/myws

WS Framing in TCP I

From RFC6455

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

WS Framing in TCP II

The opcode tells us the frame type (from RFC6455)

*  %x0 denotes a continuation frame

*  %x1 denotes a text frame

*  %x2 denotes a binary frame

*  %x3-7 are reserved for further non-control frames

*  %x8 denotes a connection close

*  %x9 denotes a ping

*  %xA denotes a pong

*  %xB-F are reserved for further control frames

WebSocket Clients

WebSocket DOM API

WebSocket Creation, Destruction, Send

  • Creation myWS = new WebSocket(url[, protocols]);

  • Destruction myWS.close()

  • Send a message myWS.send(data)

WebSocket Events

  • close – Fired when a connection with a WebSocket is closed.

  • error – Fired when a connection with a WebSocket has been closed because of an error.

  • message – Fired when data is received through a WebSocket.

  • open – Fired when a connection with a WebSocket is opened.

Example: Time Server Client 1

  • We will create a WebSocket browser client that creates a WebSocket to a “time server”

  • The time server sends the current time at the server every 5 seconds via a WebSocket message (text).

  • We provide a user interface that provides control over WebSocket creation and reports status and the latest messages.

Example: Time Server Client 2

file: public/TimeSocketClient.html

Prior to Opening WS

Example: Time Server Client 3

Opening and closing of WebSocket

let openB = document.getElementById("OpenB");
let closeB = document.getElementById("CloseB");
let statusP = document.getElementById("Status");
let messageP = document.getElementById("Message");
let ws = null;

function openSocket() {
   ws = new WebSocket("ws://localhost:2112");
   ws.addEventListener("open", sockOpen);
   ws.addEventListener("message", sockMessage);
   ws.addEventListener("close", sockClose);
}

function closeSocket() {
   if (ws) {
         ws.close();
   }
}

Example: Time Server Client 4

WebSocket event handling

function sockOpen() {
   statusP.innerHTML = "Open"
}

function sockClose() {
   statusP.innerHTML = "Close"
}

function sockMessage(msg) {
   console.log(msg);
   messageP.innerHTML = msg.data;
   messageP.classList.toggle("highlight");
}

openB.addEventListener("click", openSocket);
closeB.addEventListener("click", closeSocket);

Example: Time Server Client 4

Running

WebSocket Servers

Server Support

HTTP and WebSocket Servers

  • An HTTP(S) Server receives upgrade header to let it know that a WebSocket is desired. We use Node.js’s HTTP server.

  • Based on this and other criteria (such as security/authorization) we will continue or stop the WebSocket creation. We use a WebSocket Server to create an individual WebSocket. This comes from the WS library.

  • For each client gets its own WebSocket which we may want to track in some way. This comes from the WS library.

Keeping things separate

  • Express.js can create a Node.js HTTP server for us. We’ve used this all throughout this class.

  • WS can create a Node.js HTTP server for us, run with an HTTP server we give it, or run separately from the HTTP server (we have to handle upgrades).

  • We will keep our Express.js app, our HTTP server, and our WSS separate for clarity and this would be needed for authentication of WS clients.

Time Server Part 1

From file timeServer.js: Setting up Express app and HTTP server

const express = require("express");
const http = require("http");
const WebSocket = require("ws");

const app = express(); // Use Express as our framework

// BEGIN: HTTP request processing portion of server
app.use(express.static("public")); // To deliver App

app.get("/", function(req, res) {
    res.redirect("/TimeSocketClient.html");
});
// END: HTTP request processing portion of server

// Create HTTP server with Node.js with our express app
// listening for requests
const server = http.createServer(app);

Time Server Part 2

From file timeServer.js: Create a WebSocker server and listen for upgrade events from the HTTP server

const wss = new WebSocket.Server({ clientTracking: true, noServer: true });

server.on("upgrade", function(request, socket, head) {
    console.log("Heard a WS upgrade...");
    // can control who can have a WebSocket here
    // The WebSocket Server creates the WebSocket here
    wss.handleUpgrade(request, socket, head, function(ws) {
        wss.emit("connection", ws, request);
    });
});

Time Server Part 3

From file timeServer.js: set up a new WebSocket

let timerMap = new Map(); // associate timers with websockets

/*  When the connection is finally established
    we receive a WebSocket (ws) to communicate with
    a particular client. */
wss.on("connection", function(ws, req) {
    console.log("Server heard something about WS.");
    console.log(
        `Request IP: ${req.connection.remoteAddress}, ${req.connection.remotePort}`
    );
    ws.send("Hi from Time Server!"); // can send messages
    
    let timer = setInterval(function(){
        let dt = new Date();
        ws.send(dt.toString()); // sending messages
        console.log(`There are ${wss.clients.size} clients, sent time ${dt}`);
    }, 5000);
    timerMap.set(ws, timer);
    
    ws.on("message", function(message) { // register message handler
        console.log(`Received message ${message}`);
        // Send a reply if desired
        ws.send("Got your message");
    });

    ws.on("close", function(code) { // register close handler
        console.log(`socket closed`);
        clearInterval(timerMap.get(ws));
        timerMap.delete(ws);
    });
});

Time Server Part 4

From file timeServer.js: start the HTTP server

let port = 2112;
let host = "localhost";
server.listen(port, host, function() {
    console.log(`Time Server started at ${host}, port ${port}.`);
});

Time Server Part 5

Example server debug output when another client attaches

// reveal.js plugins