Dr. Greg Bernstein
April 22nd, 2020
Wikipedia WebSockets. Good place to start for overview.
The WebSocket Protocol – RFC6455. The actual standard. Fairly readable.
Web Push Notifications are not WebSockets.
Client Side (DOM API):
Server Side:
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.
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
What if a server wants to initiate the communications?
From Push Notifications
FireFox subscribing to push notification:
Chrome Settings Example
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.
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.
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!
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.
These don’t look very different
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.
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
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 ... |
+---------------------------------------------------------------+
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
Creation myWS = new WebSocket(url[, protocols]);
Destruction myWS.close()
Send a message myWS.send(data)
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.
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.
file: public/TimeSocketClient.html
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();
}
}
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);
Express.js is an HTTP(S) server framework not a WebSocket Server
For Node.js WS: a Node.js WebSocket library is very, very popular. Also see WS on GitHub
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.
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.
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);
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);
});
});
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);
});
});
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}.`);
});
Example server debug output when another client attaches