simonewebdesign

101 Web Socket Protocol Handshake: a pragmatic guide

So you want to set up a web socket server, but you don’t know how. Maybe you just landed here after a bunch of Google searches. Well, keep reading: you’re in the right place.

The Web Socket in a nutshell

The new protocol is revolutionizing the whole Internet.

The Web Socket protocol is the new alternative to the very popular HTTP protocol. The logic of the latter is well known: the client requests, the server responds. Request, response. Stop. This is what we call a simplex connection. It worked for decades, but has severe limitations therefore it’s not ideal for rich web applications, which would work better under a persistent connection. We need to serve content in real time; that’s why the new protocol is born.

The WebSocket is awesome, especially if your goal is to build a chat, a VoIP server, or even a RTS game! Sounds good, huh? Let’s see how it works.

Roll your own Web Socket server and client

This guide is focused on the official W3C’s WebSocket API specification for the client part; we’ll use a Node.js WebSocket implementation as the server. If you’re curious on how Node.js works, just check it out: there are tons of resources out there.

Okay, let’s get started with the client: nothing fussy, It’s just a simple HTML page. Call it index.html.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>WebSocket Client</title>
</head>
<body>
  <h1>WebSocket Client</h1>
  <p>Open the JavaScript console to see the response!</p>
  <form>
    <label for="message">Send a message</label>
    <input id="message" name="message" type="text">
    <button id="send" name="send">Send</button>
  </form>
  <script src="ws_client.js"></script>
</body>
</html>

You may have noticed the script tag:

<script src="ws_client.js"></script>

Of course we need a bit of JavaScript in order to make it work, but don’t worry! Initializing a web socket server is just as simple as the following line of code. Create the file ws_client.js and write:

var ws = new WebSocket("ws://localhost:1337");

If you’re already familiar with object-oriented programming, you should know we have been built a new instance of the WebSocket object, that’s built-in in all modern browsers. The ws://localhost:1337 part just tells the WebSocket API to initialize a new connection on localhost:1337 using the ws protocol.

Now let’s code some event handlers. I won’t elaborate, they’re self-descriptive:

ws.onopen = function(ev) {
  console.log('Connection opened.');
}
ws.onmessage = function(ev) {
  console.log('Response from server: ' + ev.data);
}
ws.onclose = function(ev) {
  console.log('Connection closed.');
}
ws.onerror = function(ev) {
  console.log('An error occurred. Sorry for that.');
}

Since we want to send a message to the server, we should use the WebSocket.send() function; but it doesn’t have a callback return value, so I’ll define a new function, sendMessage(), that does exactly the same plus logging the message on the console.

WebSocket.prototype.sendMessage = function(msg) {
  this.send(msg);
  console.log('Message sent: ' + msg);
}

Finally I get the button to work by attaching an event handler to it:

var sendBtn = document.getElementById('send');
sendBtn.addEventListener('click', function(ev) {
  var message = document.getElementById('message').value;
  ws.sendMessage(message);
  ev.preventDefault();
});

Basically, when we click on the button we send a message to the server. That’s it. You may wonder what does ev.preventDefault() do; it just makes sure that when we click the Send button, the form doesn’t get submitted.


At this stage you should have downloaded and installed Node.js: the procedure is slightly different for every operating system; just make sure to download the right installer.

Node comes with npm, the official package manager, that makes the WebSocket-Node package installation ridiculously easy:

$ npm install websocket

This shell command should install the WS implementation for Node.js on your machine. Now open the Node installation folder (if you don’t know where it is and you’re using a UNIX-based operating system, try which node) and create the file that will host our server:

$ cd ~/local/node
$ touch ws_server.js

Since this file will be executed by Node itself, it must start with the following:

#!/usr/bin/env node

Require the WS server and the HTTP protocol APIs:

var WebSocketServer = require('websocket').server;
var http = require('http');

Then you can define the HTTP server itself:

var server = http.createServer(function(request, response) {
  console.log('Received request from ' + request.url);
  response.writeHead(404);
  response.end();
});

The server must be listening on a port. I’ve chosen the 1337 port, but it can be almost anything.

server.listen(1337, function() {
  console.log('Server is listening on port 1337.');
});

Our WebSocket server is based on the HTTP server we have just created above, with a bit of security rules:

wsServer = new WebSocketServer({
  httpServer: server,
  autoAcceptConnections: false // because security matters
});

The autoAcceptConnections parameter is absolutely necessary here, because we don’t want to allow connections from any source domain! That’s why I wrote a custom function that accepts or discards an incoming remote connection: it accepts only requests from 127.0.0.1 or http://localhost, but you can of course allow any origin you want.

function isAllowedOrigin(origin) {
  valid_origins = ['http://localhost', '127.0.0.1'];
  if (valid_origins.indexOf(origin) != -1) { // is in array
    console.log('Connection accepted from origin ' + origin);
    return true;
  }
  return false;
}

This way we can easily filter connections based on the origin:

wsServer.on('request', function(request) {
  var connection = isAllowedOrigin(request.origin) ?
    request.accept() :
    request.reject();
}

How can we check if a client sends us a message? We can do it by using the message event:

connection.on('message', function(message) {
  console.log('Received Message: ' + message.utf8Data);
}

The message must be in UTF-8 format; we must inspect the message.type attribute. If the type is correct, we can process the request and define the response accordingly. Here’s my demonstrative implementation:

if (message.type === 'utf8') {

  var response = '';

  switch (message.utf8Data) {
    case 'hi':
      response = 'Hey there';
      break;
    case 'hello':
      response = 'Heya!';
      break;
    case 'xyzzy':
      response = 'Nothing happens.';
      break;
    case 'desu':
      response = 'Keep typing, man. Keep typing.';
      break;
    default:
      response = "Hello. Uh... what am I supposed to do with '" +
      message.utf8Data + "'?";
  }
  connection.sendUTF(response);
}

It’s always a good idea to log when the connection starts and gets closed:

wsServer.on('connection', function(webSocketConnection) {
  console.log('Connection started.');
});

connection.on('close', function(reasonCode, description) {
  console.log(connection.remoteAddress + ' has been disconnected.');
});

And that’s all. We now have a basic WebSocket server implementation and we’re ready for the handshake.

Establish a connection between server and client

We can now test our brand new WebSocket server. Open the command prompt and start the server with:

$ node ws_server.js
Server is listening on port 1337.

Finally open the client (index.html) with your favourite browser and try to send a message.

When you send a web socket request as a client, you’ll send an HTTP header like this one:

GET / HTTP/1.1
Host: localhost:1337
User-Agent: Mozilla/5.0 [...]
Upgrade: websocket
...

And the server will respond something like:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
...

This means you just established a full-duplex connection between the client and the server. If everything went as expected, the console should say something like:

Connection accepted from origin: http://localhost

And from this point every message will be logged on the console. Of course you shouldn’t close the console or the server will go down. If you want to close the connection you can both press Ctrl + C or close the browser’s window.


I hope you enjoyed following this guide. You can read the full source code of this tutorial on GitHub, or download it directly. The demo also comes with a file for testing your actual browser support for the WebSocket API.

Last update:

View this page on GitHub