Category: nodeJS

HTTP server applications with Node.js

In this tutorial we’ll learn about HTTP server applications and HTTP sniffing by David Herron, a software engineer in Silicon Valley, who has worked on various enterprise web application projects.

Launching a server with Node.js

Many scripts that you’ll run are server processes. Before you get started, you need to launch a simple HTTP server with Node.js. Borrow the simple server script on the Node.js home page (http://nodejs.org), and create a file named app.js containing the following:

const http = require('http');

http.createServer(function (req, res) {

  res.writeHead(200, {'Content-Type': 'text/plain'});

  res.end('Hello, World!\n');

}).listen(8124, '127.0.0.1');

console.log('Server running at http://127.0.0.1:8124');

Run it as follows:

$ node app.js

Server running at http://127.0.0.1:8124

This is the simplest of web servers you can build with Node.js. Now, visit http://127.0.0.1:8124 in your browser to see the Hello, World! message:

HTTP server applications

The HTTP server object is the foundation of all Node.js web applications. The object itself is very close to the HTTP protocol, and its use requires knowledge of that protocol. In most cases, you’ll be able to use an application framework such as Express that hides the HTTP protocol details, allowing you to focus on business logic.

The http.createServer function will create an http.Server object because it is an EventEmitter; this can be written in another way to make that fact explicit:

const http = require('http');

const server = http.createServer();

server.on('request',  (req, res) => {

  res.writeHead(200, {'Content-Type': 'text/plain'});

  res.end('Hello, World!\n');

});

server.listen(8124, '127.0.0.1');

console.log('Server running at http://127.0.0.1:8124');

The request event takes a function, which receives request and response objects. The request object has data from the web browser, while the response object is used to gather the data to be sent in the response. The listen function causes the server to start listening and arranging to dispatch an event for every request arriving from a web browser.

Creating a Node.Js Server

Now, here is something more interesting with different actions based on the URL. Create a new file, named server.js, containing the following code:

const http = require('http');

const util = require('util');

const url  = require('url');

const os   = require('os');

 const server = http.createServer();

server.on('request', (req, res) => {

    var requrl = url.parse(req.url, true);

    if (requrl.pathname === '/') {

        res.writeHead(200, {'Content-Type': 'text/html'});

        res.end(

`<html><head><title>Hello, world!</title></head>

<body><h1>Hello, world!</h1>

<p><a href='/osinfo'>OS Info</a></p>

</body></html>`);

    } else if (requrl.pathname === "/osinfo") {

        res.writeHead(200, {'Content-Type': 'text/html'});

        res.end(

`<html><head><title>Operating System Info</title></head>

<body><h1>Operating System Info</h1>

<table>

<tr><th>TMP Dir</th><td>${os.tmpdir()}</td></tr>

<tr><th>Host Name</th><td>${os.hostname()}</td></tr>

<tr><th>OS Type</th><td>${os.type()} ${os.platform()} ${os.arch()}

${os.release()}</td></tr>

<tr><th>Uptime</th><td>${os.uptime()} ${util.inspect(os.loadavg())}</td></tr> <tr><th>Memory</th><td>total: ${os.totalmem()} free: ${os.freemem()}</td></tr>

<tr><th>CPU's</th><td><pre>${util.inspect(os.cpus())}</pre></td></tr>

<tr><th>Network</th><td><pre>${util.inspect(os.networkInterfaces())}</pre></td></tr>

</table>

</body></html>`);

    } else {

        res.writeHead(404, {'Content-Type': 'text/plain'});

        res.end("bad URL "+ req.url);

    } });

 server.listen(8124); console.log('listening to http://localhost:8124');

To run it, type the following command:

$ node server.js

listening to http://localhost:8124

This application is meant to be similar to PHP’s sysinfo function. Node’s os module is consulted to provide information about the server. This example can easily be extended to gather other pieces of data about the server:

A central part of any web application is the method of routing requests to request handlers. The request object has several pieces of data attached to it, two of which are used for routing requests—the request.url and request.method fields.

In server.js, you can consult the request.url data to determine which page to show, after parsing (using url.parse) to ease the digestion process. In this case, you can do a simple comparison of the pathname to determine which handler method to use.

Some web applications care about the HTTP verb (GET, DELETE, POST, and so on) used and must consult the request.method field of the request object. For example, POST is frequently used for FORM submissions.

The pathname portion of the request URL is used to dispatch the request to the correct handler. While this routing method, based on simple string comparison, will work for a small application, it’ll quickly become unwieldy. Larger applications will use pattern matching to use part of the request URL to select the request handler function and other parts to extract request data out of the URL.

A search for a URL match in the npm repository turns up several promising packages that could be used to implement request matching and routing. A framework like Express has this capability already baked in and tested.

If the request URL is not recognized, the server sends back an error page using a 404 result code. The result code informs the browser about the status of the request, where a 200 code means everything is fine, and a 404 code means that the requested page doesn’t exist. There are, of course, many other HTTP response codes, each with their own meaning.

HTTP Sniffer – listening to the HTTP conversation

The events emitted by the HTTPServer object can be used for additional purposes beyond the immediate task of delivering a web application. The following code demonstrates a useful module that listens to all the HTTPServer events. It could be a useful debugging tool, which also demonstrates how HTTPServer objects operate.

Node.js’s HTTPServer object is an EventEmitter and the HTTP Sniffer simply listens to every server event, printing out information pertinent to each event. What you’re about to do is:

  1. Create a module, httpsniffer that prints information about HTTP requests.
  2. Add that module to the jsscript you just created.
  3. Rerun that server to view a trace of HTTP activity.

Create a file named httpsniffer.js containing the following code:

const util = require('util');

const url  = require('url');



const timestamp = () => { return new Date().toISOString(); }



exports.sniffOn = function(server) {

  server.on('request', (req, res) => {

    console.log(`${timestamp()} e_request`);

    console.log(`${timestamp()} ${reqToString(req)}`);

  });

  server.on('close', errno => { console.log(`${timestamp()} e_close

  ${errno}`); });

  server.on('checkContinue', (req, res) => {

    console.log(`${timestamp()} e_checkContinue`);

    console.log(`${timestamp()} ${reqToString(req)}`);

    res.writeContinue();

  });

  server.on('upgrade', (req, socket, head) => {

    console.log(`${timestamp()} e_upgrade`);

    console.log(`${timestamp()} ${reqToString(req)}`);

  });

  server.on('clientError', () => { console.log(`${timestamp()}

  e_clientError`); });

};



const reqToString = exports.reqToString = (req) => {

  var ret=`req ${req.method} ${req.httpVersion} ${req.url}` +'\n';

  ret += JSON.stringify(url.parse(req.url, true)) +'\n';

  var keys = Object.keys(req.headers);

  for (var i = 0, l = keys.length; i < l; i++) {

    var key = keys[i];

    ret += `${i} ${key}: ${req.headers[key]}` +'\n';

  }

  if (req.trailers) ret += util.inspect(req.trailers) +'\n';

  return ret;

};

Wow! That was a lot of code! However, the key to it is the sniffOn function. When given an HTTP Server object, it uses the .on function to attach listener functions that print data about each emitted event. It gives a fairly detailed trace of HTTP traffic on an application.

In order to use it, simply insert this code just before the listen function in server.js:

require('./httpsniffer').sniffOn(server);

server.listen(8124);

console.log('listening to http://localhost:8124');

With this in place, run the server you launched earlier. You can visit http://localhost:8124/ in your browser and see the following console output:

$ node server.js

listening to http://localhost:8124

2017-12-03T19:21:33.162Z request

2017-12-03T19:21:33.162Z request GET 1.1 /

{"protocol":null,"slashes":null,"auth":null,"host":null,"port":null,"hostname":null,"hash":null,"search":"","query":{},"pathname":"/","path":"/","href":"/"}

0 host: localhost:8124

1 upgrade-insecure-requests: 1

2 accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

3 user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5

4 accept-language: en-us

5 accept-encoding: gzip, deflate

6 connection: keep-alive

{}



2017-12-03T19:21:42.154Z request

2017-12-03T19:21:42.154Z request GET 1.1 /osinfo

{"protocol":null,"slashes":null,"auth":null,"host":null,"port":null,"hostname":null,"hash":null,"search":"","query":{},"pathname":"/osinfo","path":"/osinfo","href":"/osinfo"}

0 host: localhost:8124

1 connection: keep-alive

2 upgrade-insecure-requests: 1

3 accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

4 user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5

5 referer: http://localhost:8124/

6 accept-language: en-us

7 accept-encoding: gzip, deflate

{}

You now have a tool for snooping on HTTPServer events. This simple technique prints a detailed log of the event data. The pattern can be used for any EventEmitter object. You can use this technique as a way to inspect the actual behavior of EventEmitter objects in your program.

If you found this article helpful, you can explore David Herron’s Node.js Web Development – Fourth Edition to create real-time applications using Node.js 10, Docker, MySQL, MongoDB, and Socket.IO. With this practical guide, you can go beyond the developer’s laptop to cover live deployment, including HTTPS and hardened security.