Reusing Your RESTful Code In WebSockets

ayasin
7 min readMar 1, 2015

--

There are many times when being able to reuse code from your RESTful calls would be really nice in your WebSockets without lots of refactoring. In this post I’ll describe doing this with FRHTTP and socket.io. If your application is already using Express, Koa, or Hapi, don’t worry. FRHTTP and socket.io work great with all of them.

We’re going to write a fairly simple chat client (in fact very similar to the one found in the socket.io sample app). We’ll be adding 1 extra requirement, the ability to post chats to the group from a REST endpoint. Why would we want this? Imagine a situation where we have network admins in a chat room. We might want to allow systems to post status to the chat room without needing to write a full blown socket.io client.

Here are the requirements for our simple chat system:

  1. The ability to join chats
  2. The ability to notify everyone of any chat messages posted
  3. The ability to post message to the chat stream from a REST endpoint

A Simple Web Form

Let’s start by creating a simple page that can post to our server. Create a file called rest_index.html and copy the following code block into it. Alternatively you can get the final code from GitHub.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Send To Chat</title>
</head>
<body>
<p>
<b>Send a chat</b>
</p>
<form action="/chat/publish" type="GET">
<label>Sender </label><input type="text" name="source">
<br><br>
<label>Message </label><br>
<textarea name="msg"></textarea>
<br><br>
<button type="submit">Send</button>
</form>
</body>
</html>

Not pretty, but functional for our test purposes. This will display a form for us to enter our user (source) and a message. When we hit send, it will do a GET to “/chat/publish”. From a “REST correctness” standpoint, this should probably be a POST, but for ease of calling it from anywhere, we’ve made it a GET.

A WebSocket Page

Now that we have a simple form to send a message to our server, let’s create a version which uses WebSockets. For our WebSockets we’ll be using the socket.io library. Create a file called socket_index.html then copy the following into it:

<!doctype html>
<html lang="en">
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 1px solid black; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
Name: <input id="s" autocomplete="off" style="width: auto" value="Socket User"/><br>
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<script>
var socket = io();
$('form').submit(function(){
socket.emit('chat message', {msg: $('#m').val(), source: $('#s').val()});
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
</script>
</body>
</html>

This page is almost identical to the sample chat application available in the socket.io repo on GitHub. The differences are that we’ve added 1 extra input (the source), and changed the data returned to include the value of that input in a field called source.

Very briefly, this page has a form with a send button. When the form is submitted, we grab the value from the source and message inputs and publish it to the socket on the ‘chat message’ channel.

Setting Up The Basic Server

To begin we’ll need a package.json. Here are it’s contents:

{
"name": "socket_io_sample",
"version": "1.0.0",
"private": "true",
"description": "A simple socket.io server using frhttp",
"main": "server.js",
"scripts": {
"test": "jasmine-node spec/"
},
"keywords": [
"socket.io",
"FRP",
"frhttp"
],
"author": "you@email.com",
"license": "MIT",
"dependencies": {
"frhttp": "^0.1.4",
"socket.io": "^1.3.4"
}
}

Once you have this, run ‘npm install’. This will install frhttp and socket.io.

Next we’ll create a server.js file for our Node code. In this file add the following:

var server = require('frhttp').createServer(),
http = require('http'),
httpServer = http.createServer(server.runRoute),
io = require('socket.io')(httpServer),
socketIOWriter = function (data) {
io.emit(data.target, data.message);
};
server.GET('/socket').onValue(function (route) {
route.render([], function (writer) {
writer.writeFile('text/html', __dirname + '/socket_index.html');
});
});
server.GET('/rest').onValue(function (route) {
route.render([], function (writer) {
writer.writeFile('text/html', __dirname + '/rest_index.html');
});
});
httpServer.listen(3000);

This file does the work of setting up our server and serving our static files. In addition it creates a function that will write a message to all connected socket.io clients. If you’re using ExpressJS, Hapi, or Koa you can replace your final fn(req, res, next) function with server.runRoute. For more details you can read this blog post.

Setting Up The GET Endpoint

Next we’ll set up the GET Endpoint. Before the last line (htttpServer.listen) add the following:

(I’ve used a gist here because medium’s code blocks fall apart pretty quickly when you want to talk about code by line number).

FRHTTP is a functional reactive (data driven) framework. What this means is that each route consists of a group of functions. When the data these functions need is ready, they are executed. There are also a few advanced options you can use to control how and how often these functions are executed before a response is sent to the caller. I won’t be going into detail about writing routes in this post(you can read about it in detail here), but the data driven aspect is important because this is what we’ll be leveraging to make this route usable from the WebSocket as well as from the normal GET call.

Because FRHTTP prefers pure functions (functions that only operate on their inputs, produce outputs that are repeatable given the same inputs, and that don’t change the state of the system) we “inject” the socket.io writer function into the route on line 3. Once we inject something, any function can request it as a parameter by name.

In this route we’ve registered 3 functions to possibly be executed before we render a response.

On line 4 we registered a built in function to read the query string and create a JavaScript object from it.

On line 5 we register a function called “initially”. This function takes our GET fields and converts them into a standard set of parameters. This “normalization” step is critical for reuse as we’ll see later.

Line 10 is a “side effect” function (a function that doesn’t follow all the rules of a pure function) which calls the function that writes the message to all the clients.

Setting Up Socket.IO

Here’s where things get interesting, we’re going to use our GET route to publish messages to all the connected clients when we receive a message from a socket.io client.

To do this, we’ll add the following code right after the last block we added.

io.on('connection', function (socket) {
socket.on('chat message', function (data) {
server.runRouteWithRender(null, 'GET', '/chat/publish', data);
});
});

So what’s going on here? FRHTTP has 2 ways to run a route. The first is for basic HTTP routes, called runRoute. The second, runRouteWithRender allows the caller to specify a custom response object (to maybe render to an email, or a file, etc) and optionally a custom render function (since the normal render may not be appropriate with the custom response object). This allows you to run the data through all the functions it needs to run through then do something completely different with the output. The signature for the runRouteWithRender is as follows:

runRouteWithRender(responseObject, method, path, injected, params, fn)

Passing null for the responseObject tells the system we don’t need to render anything, just run the route. Injected allows us to push data into the route. Params and fn are for overriding the renderer (since we don’t want to render, we can leave these blank as well).

Recall that FRHTTP is data driven. What we inject determines what functions will be run. We aren’t providing a request object, the functions defined at lines 4 and 5 don’t have the data they require, and therefore won’t run. Since we’re providing a msg and a source (injected via data) the first (and only) function that can run is “side-effect publish message”. This runs and writes our message to the socket.io clients.

Putting It Together

Now that we have all the parts, we can run the server via ‘node server.js’. Once the server is running you can open 2 browser windows (in different browsers if you want), navigate one to localhost:3000/rest_index.html and the other to localhost:3000/socket_index.html. Type a message into the rest window and see it appear in the socket window. You can also type a message in the socket window and see it appear.

Recap

We took advantage of the fact that FRHTTP is data driven to jump into the middle of a route allowing us to reuse the same code to power our socket.io side. This allowed us to reduce our entire socket.io message handler to 1 line of code.

The route we’re demonstrating is necessarily simple for instructional purposes but it’s easy to expand this to very complex scenarios where being able to jump into the middle of a chain is really useful for code reuse and testing (imagine if we did several DB actions or something of that sort).

Learn More

All the sources for the example used in this post are available on GitHub. You can install FRHTTP via npm (npm install frhttp), find the code here and you can read about using it here.

You can read about socket.io here.

About Me

I’m Amir Yasin; a polyglot developer deeply interested in high performance, scalability, software architecture and generally solving hard problems. You can follow me on Medium where I blog about software engineering, follow me on Twitter where I occasionally say interesting things, or check out my contributions to the FOSS community on GitHub.

If you enjoyed this post, I’d really appreciate a recommend (just click the heart below).

--

--