|

Node.js and Websockets: A Modular Approach

codeIn our last article we covered how you can connect two Node.js processes using streams. In this article we will explore that same technique, but this time we’ll connect a browser to a Node.js websocket server and implement a simple ping service as an example. Read more about websocket servers in the Node Cookbook.

Install some modules

You can find the source files for the examples in this blog post here: https://github.com/pgte/websockets-modular-approach-article-code.

First, set up a project folder where you will place a package.json file that will contain the project meta-data, including the dependencies (package.json):

{
  "name": "websockets-demo",
  "version": "0.0.1",
  "dependencies": {
    "browserify": "*",
    "shoe": "*",
    "duplex-emitter": "*",
    "reconnect": "*",
    "ecstatic": "*"
  }
}

Now you will need to run the following command to download and install these packages in your local folder:

  
$ npm install

The websocket API

Sockjs is a websocket emulation that offers an API similar to websockets, but it works for older browsers. If presented with a modern browser, Sockjs will use websockets. If not, it falls back to a number of techniques to simulate full-duplex communication.

First, we’ll need to create a websocket server using shoe. Shoe is a small module that wraps the Sockjs API and turns it into a Node.js streaming API. Let’s then create a file named server.js that does that:

var duplexEmitter = require('duplex-emitter');
var shoe = require('shoe');

var sock = shoe(function(stream) {
  var client = duplexEmitter(stream);

  client.on('ping', function(ts) {
    console.log('got client ping', ts);
    client.emit('pong', ts);
  });
});

Notice that here we’re using the duplex-emitter module, which turns a duplex stream into a remote event emitter.

The server is a ping server and simply replies to ping events sent from the client. Notice that the ping event is an arbitrary event type name — you could extend this and use whatever event type you need in your application.

Once the server gets a ping event containing the timestamp as the first and sole argument, it replies to the client with that same timestamp.

Static file server

Now, we need to create a static file server for serving an HTML file and the JavaScript code to the browser. For that, we append these lines to the server.js file:

tail of server.js:

var ecstatic = require('ecstatic')(__dirname + '/browser');
var server = require('http').createServer(ecstatic);

server.listen(8080, function() {
  console.log('Server listening');
});

sock.install(server, '/websocket');

Here we’re creating an HTTP server and telling it that it serves static files from the browser directory. Then, we’re telling the server to listen to the TCP port 8080. Finally, we install the websocket server inside the /websocket URL path.

Here is the full server code (server.js):

var duplexEmitter = require('duplex-emitter');
var shoe = require('shoe');

var sock = shoe(function(stream) {
  var client = duplexEmitter(stream);

  client.on('ping', function(ts) {
    console.log('got client ping', ts);
    client.emit('pong', ts);
  });
});

var ecstatic = require('ecstatic')(__dirname + '/browser');
var server = require('http').createServer(ecstatic);

server.listen(8080, function() {
  console.log('Server listening');
});

sock.install(server, '/websocket');

Browser code

Let’s now create a simple browser script that connects to the server and repeatedly sends it ping events (browser/websocket.js):

var duplexEmitter = require('duplex-emitter');
var reconnect = require('reconnect');

var stream = reconnect(function(stream) {
  console.log('connected');

  var server = duplexEmitter(stream);

  var interval = setInterval(function() {
    server.emit('ping', Date.now());
  }, 1000);

  server.on('pong', function(timestamp) {
    console.log('got pong from server. ping time is ' + 
     (Date.now() - timestamp) + ' ms');
  });

  stream.once('end', function() {
    clearInterval(interval);
  });
}).connect('/websocket');

This browser script uses reconnect to connect to the server URL /websocket. Once we get a websocket connection, represented in the stream argument, we use duplex-emitter to transform that stream in to a remote event emitter.

After this, we set up an interval that repeatedly sends ping events to the server containing the timestamp as the first and sole argument.

When the server sends back pong events as a response, we set up a pong event listener. These responses will contain the timestamp that we passed in, which we then use to measure the ping round-trip time and print this to the console.

Once the stream ends — in our case the server is inaccessible — it emits the end event, which we listen to in order to stop sending ping messages by clearing the interval we had initially setup.

Browserify

You may have noticed that we’re using require in our browser code. Browser JavaScript doesn’t come with a module system, much less a synchronous one! We can do this because we will be resolving these dependencies in a compile step, which produces a single bundled file that we serve in one time to the browser.

We only have one script source file that requires the necessary dependencies, and we simply need to point browserify to one script, and it will figure out the dependencies and bundle them together, making it all work.

From the command line, you enter the following command:

$ node_modules/.bin/browserify browser/websocket.js -o browser/bundle.js

This tells browserify to pack the browser/websocket.js script, and all of the necessary dependencies, and output the result to browser/bundle.js.

Make them talk!

Now we just need to get this browser script into the browser. For this, we’ll create this index HTML file (browser/index.html):

<script type="text/javascript" src="bundle.js"></script>

Now you can start the server by launching it from the command line:

$ node server.js
Server listening

Point your browser to http://localhost:8080, and open the browser console. You should see, on each second that passes, the following printed out in the console:

got pong from server. ping time is 5 ms
got pong from server. ping time is 2 ms
got pong from server. ping time is 3 ms

Summary

Using a modular approach and a set of chosen modules, you can use this technique to make your Node.js server have an interactive talk with a browser script, and integrate your Node.js websocket service over a protocol that sends remote events.

Safari Books Online has the content you need

Check out these Node.js books available from Safari Books Online:

In just 24 sessions of one hour or less, Sams Teach Yourself Node.js in 24 Hours will help you master the Node.js platform and use it to build server-side applications with extraordinary speed and scalability. Using this text’s straightforward, step-by-step approach, you’ll move from basic installation, configuration, and programming all the way through real-time messaging between browser and server, testing and deployment. Every lesson and case-study application builds on what you’ve already learned, giving you a rock-solid foundation for real-world success!
Node.js is a powerful and popular new framework for writing scalable network programs using JavaScript. Professional Node.js begins with an overview of Node.js and then quickly dives into the code, core concepts, and APIs. In-depth coverage pares down the essentials to cover debugging, unit testing, and flow control so that you can start building and testing your own modules right away.
This book shows you how to transfer your JavaScript skills to server side programming. With simple examples and supporting code, Node Cookbook talks you through various server side scenarios often saving you time, effort, and trouble by demonstrating best practices and showing you how to avoid security faux pas.
Node Web Development gives you an excellent starting point straight into the heart of developing server side web applications with node. You will learn, through practical examples, how to use the HTTP Server and Client objects, the Connect and Express application frameworks, the algorithms for asynchronous execution, and use both SQL and MongoDB databases.

About the author

Pedro Teixeira is a geek, programmer, freelancer, and entrepreneur. Author of some Node.js modules, the Node Tuts screencast show, some books about Node.js and overall fervent proclaimer of the Node.js creed. He is also the Co-founder and Partner of The Node Firm, and the organizer of the Lisbon JavaScript Conference.

About Safari Books Online

Safari Books Online is an online learning library that provides access to thousands of technical, engineering, business, and digital media books and training videos. Get the latest information on topics like Windows 8, Android Development, iOS Development, Cloud Computing, HTML5, and so much more – sometimes even before the book is published or on bookshelves. Learn something new today with a free subscription to Safari Books Online.
|

3 Responses to Node.js and Websockets: A Modular Approach

  1. dan says:

    this is the best article I have ever read about web sockets from the following reasons:
    1. code samples on github, easy to clone and try it
    2. great explanation of almost every module used (maybe reconnect was a bit vague)
    3. an example that can be used as a starting point of a real project.
    4. used very powerful modules that are lacking good documentation and real world usage.

    What would be nice is showing how to do that with more common packages (like socket.io) and showing what are the limitation of socket.io that sockjs/shoe overcome.

    I assume the answer would be ‘support for streaming’. it would be great to see a few lines of example of how to use streaming with shoe.

    Thanks Pedro for a fantastic article!

    • Pedro Teixeira says:

      Hi Dan,

      Thanks so much for the kind words!
      Sockjs is more comparable to Engine.io, Socket.io is huge in comparison.
      Comparing to Engine.io, Sockjs provides a simpler API that is easily wrapped by Shoe. Shoe provides a streaming API that lets you mix and match streams on both ends.
      This makes the architecture more flexible than if I had used Engine.io:
      In some projects I can use the streaming API, while in others I may use duplex-emitter, dnode or any other protocol that accepts a duplex stream as transport.

      • dan says:

        nice. thanks for explaining that.
        it will be fantastic to have a blog post about scenarios and examples for using streaming API, duplex-eitter and dnode.

        thanks