Creating a WebSocket server

For this task, we will use the non-core websocket module to create a pure WebSocket server that will receive and respond to WebSocket requests from the browser.

Getting ready

We'll create a new folder for our project which will hold two files: server.js and client.html. server.js. They provide the server-side websocket functionality and serve up the client.html file. For the server-side WebSocket functionality, we also need to install the websocket module:

npm install websocket

Note

For more information on the websocket module, see https://www.github.com/Worlize/WebSocket-Node.

How to do it...

A WebSocket is an HTTP upgrade. As such, WebSocket servers run on top of HTTP servers. So we'll require the http and websocket servers, plus we'll also load our client.html file (which we'll be creating soon) and the url module:

var http = require('http'),
var WSServer = require('websocket').server;
var url = require('url'),
var clientHtml = require('fs').readFileSync('client.html'),

Now let's create the HTTP server, and supply it to a new WebSocket server:

var plainHttpServer = http.createServer(function (request, response) {
    response.writeHead(200, {'Content-type' : 'text/html'});
    response.end(clientHtml);
  }).listen(8080);

var webSocketServer = new WSServer({httpServer: plainHttpServer});

var accept = [ 'localhost', '127.0.0.1' ];

Note

We bind our HTTP server to port 8080 because binding to ports lower than 1000 takes root access. This means our script would have to be executed with root privileges which is a bad idea. SeeChapter 10, Taking It Live, for more information and how to bind to the HTTP port (80) safely.

We've also created a new array, called accept. We use this inside the WebSocket server to restrict which originating sites can connect. For our example, we only allow connections from localhost or 127.0.0.1. If we were hosting live we would include any domains pointing to our server in the accept array.

Now that we have our webSocketServer instance, we can listen to its request event and respond accordingly:

webSocketServer.on('request', function (request) {
  request.origin = request.origin || '*'; //no origin? Then use * as wildcard.
  if (accept.indexOf(url.parse(request.origin).hostname) === -1) {
    request.reject();
    console.log('disallowed ' + request.origin);
    return;
  }

  var websocket = request.accept(null, request.origin);

  websocket.on('message', function (msg) {
    console.log('Recieved "' + msg.utf8Data + '" from ' + request.origin);
    if (msg.utf8Data === 'Hello') {
      websocket.send('WebSockets!'),
    }
  });

  websocket.on('close', function (code, desc) {
   console.log('Disconnect: ' + code + ' - ' + desc);
  });

});

In our request event callback, we conditionally accept the request, then listen for the message and close events, responding with WebSockets! if the message from the client is Hello.

Now for the client, we'll place the following HTML structure:

<html>
<head>
</head>
<body>
<input id=msg><button id=send>Send</button>
<div id=output></div>

<script>
//client side JavaScript will go here
</script>

</body>
</html>

And the content of our script tags should look as follows:

<script>
(function () {
  var ws = new WebSocket("ws://localhost:8080"),
    output = document.getElementById('output'),
    send = document.getElementById('send'),

  function logStr(eventStr, msg) {
    return '<div>' + eventStr + ': ' + msg + '</div>';
  }  

  send.addEventListener('click', function () 
      var msg = document.getElementById('msg').value;
      ws.send(msg);
      output.innerHTML += logStr('Sent', msg);
  });

  ws.onmessage = function (e) {
    output.innerHTML += logStr('Recieved', e.data);
  };

  ws.onclose = function (e) {
    output.innerHTML += logStr('Disconnected', e.code + '-' + e.type);
  };

  ws.onerror = function (e) {
    output.innerHTML += logStr('Error', e.data);
  };  

}());

</script>

If we initialize our server with node server.js, then direct our (WebSocket-compliant) browser to http://localhost:8080, type Hello in the textbox, and click on Send. The terminal console will output:

Recieved "Hello" from http://localhost:8080

And our browser will show that Hello was sent and WebSockets! was received, as shown in the following screenshot:

How to do it...

We could use our textbox to send any string we like to our server, but only Hello will gain a response.

How it works...

In server.js, when we require the websocket module's server method, we load a constructor function into WSServer, (which is why we capitalized the first letter). We initialize WSServer using new and pass in our plainHttpServer which transforms it into a WebSocket-enabled server.

The HTTP server will still serve normal HTTP requests, but when it receives a WebSocket connection handshake the webSocketServer kicks into action to establish a persistent connection to the client.

As soon as the client.html file is loaded in the browser (served by the HTTP server in server.js) and the inline script is executed, the WebSocket upgrade request is made to the server.

When the server receives this WebSocket upgrade request, webSocketServer emits a request event, which we scrutinize with our accept array before deciding whether we will respond.

Our accept array holds a whitelist of hosts we'll allow to interface with our WebSocket server. We gain some security by only allowing known sources to use our WebSocket server.

Inside the webSocketServer request event, request.origin is parsed with url.parse to retrieve the host name section of the origin URL. If the host name isn't found in our accept whitelist we call request.reject.

If our originating host passes, we create a websocket variable from request.accept. The first parameter of request.accept allows us to define a custom sub-protocol. We could create an array of websockets using multiple request.accepts with different sub-protocols which represent different behaviors. When initiating our client, we would pass an additional argument containing that sub-protocol (for example, new WebSocket("ws://localhost:8080", 'myCustomProtocol')). However, we pass in null, such functionality isn't required for our purposes. The second parameter allows us to inform request.accept of the host we wish to allow (there is also a third that can be used for passing cookies).

For each message received from the client, WebSocket emits a message event. This is where we log the received data to the console and check whether the incoming message is Hello. If it is, we use the WebSocket.send method to respond to the client with WebSockets!.

Finally, we listen for the close event, to inform console that the connection has been terminated.

There's more...

WebSockets have so much potential for efficient, low latency real-time web apps, but compatibility can be a challenge. Let's look at some other uses for WebSockets, plus a tip on getting WebSockets to work in Firefox.

Supporting older Firefox browsers

Firefox versions 6 to 11 do support WebSockets. However, they use a vendor prefix so our client.html will not work on these Firefox versions.

To fix this, we simply prepend the following to the script in our client.html file:

window.WebSocket = window.WebSocket || window.MozWebSocket;

If the WebSocket API does not exist, we try MozWebSocket.

Creating a node-based WebSocket client

The websocket module also allows us to create a WebSocket client. We may wish to interface Node with a pre-existing WebSocket server, which is primarily for browser clients (if not, we are better off creating a simple TCP server. See Chapter 8, Integrating Network Paradigms).

So let's implement the same functionality in client.html using Node. We'll create a new file in the same directory, calling it client.js:

var WSClient = require('websocket').client;

new WSClient()
  
  .on('connect', function (connection) {
    var msg = 'Hello';

    connection.send(msg);
    console.log('Sent: ' + msg);

    connection.on('message', function (msg) {
      console.log("Received: " + msg.utf8Data);
    }).on('close', function (code, desc) {
      console.log('Disconnected: ' + code + ' - ' + desc);
    }).on('error', function (error) {
      console.log("Error: " + error.toString());
    });


  })
  .on('connectFailed', function (error) {
    console.log('Connect Error: ' + error.toString());
  })
  .connect('ws://localhost:8080/', null, 'http://localhost:8080'),

For brevity, we've simply hardcoded our msg variable, though we could have used process.stdin or process.argv to input a custom message. We initialize a new client with the websocket module's client method. Then we immediately begin to listen for the connect and connectFailed events.

After the two on methods, we chain the connect method. The first parameter is our WebSocket server, the second is the protocol (remember, we have a null protocol for request.accept in our recipe), and the third defines the request.origin value.

Origin protection is designed to prevent an attack that only works from browsers. So although we can manufacture origins outside of the browser, it doesn't pose the same threat. The biggest threat is from a JavaScript injection attack into a high traffic site, that could cause a large amount of unauthorized connections from an unintended origin resulting in Denial of Service. See Chapter 7, Implementing Security, Encryption, and Authentication.

See also

  • Seamless fallbacking with socket.io discussed in this chapter
  • Serving static files Chapter 1, Making a Web Server
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.10.116