HTTP is a data transfer protocol built upon a request/response model. Normally, a client makes a request to a server, receives a response, makes another request, and so on. HTTP is stateless, which simply means that each request or response maintains no information on previous requests or responses. Facilitating this sort of rapid-pattern network communication is the sort of I/O that Node is designed to excel at. While Node represents a much more interesting technology stack overall, it does help engineers in creating networked protocol servers. In this section, we will move through a general overview of how to set up a basic HTTP server and then into a few more specialized uses of the protocol.
Hello world
An HTTP server responds to connection attempts and manages data as it arrives and as it is sent along. A Node server is typically created using the createServer method of the HTTP module:
var http = require('http');
var server = http.createServer(function(request, response) {
console.log('Got Request Headers: ');
console.log(request.headers);
response.writeHead(200, {
'Content-Type': 'text/plain'
});
response.write('PONG');
response.end();
}).listen(8080);
The object returned by http.createServer is an instance of http.Server, which extends EventEmitter and broadcasts network events as they occur, such as a client connection or request. Most server implementations using Node use this method of instantiation. However, listening for event broadcasts by an http.Server instance can be a more useful, even natural, way to organize server/client interactions within a Node program.
Here, we create a basic server that simply reports when a connection is made and when it is terminated:
var http = require('http');
var server = new http.Server();
server.on("connection", function(socket) {
console.log("Client arrived: " + new Date());
socket.on("end", function() {
console.log("Client left: " + new Date());
});
})
server.listen(8080);
When building multiuser systems, especially authenticated multiuser systems, this point in the server-client transaction is an excellent place for client validation and a tracking code. Cookies can be set and read, along with other session variables. A client arrival event can be broadcast to other concurrent clients interacting within real-time applications.
By adding a listener for requests, we arrive at the more common request/response pattern, handled as a Readable stream. When a client posts data, we can catch that data, as shown here:
Using connection events, we can nicely separate our connection-handling code, grouping it into clearly defined functional domains, which are correctly described as executing in response to particular events.
For example, we can set timers on server connections. Here, we can terminate client connections that fail to send new data within a roughly 2-second window:
HTTP servers are often called upon to perform HTTP services for clients making requests. Most commonly, this sort of proxying was done on behalf of web applications running in browsers with restrictions on cross-domain requests. Node provides an easy interface to make external HTTP calls.
For example, the following code will fetch the front page of google.com:
Here, we simply dump a Readable stream to the terminal, but this stream could easily be piped to a Writable stream, perhaps bound to a file handle. Note that you must always signify that you're done with a request using the request.end method.
Tip
A popular Node module to manage HTTP requests is Mikeal Rogers' request:
Let's now look at a few more advanced implementations of HTTP servers, where we perform general network services for clients.
Proxying and tunneling
Sometimes, it is useful to provide a means for one server to function as a proxy, or broker, for other servers. This would allow one server to distribute requests to other servers, for example. Another use would be to provide access to a secured server to users who are unable to connect to that server directly—this is often seen in countries that place restrictions on Internet access. It is also common to have one server answering for more than one URL using a proxy; that one server can forward requests to the right recipient.
Because Node has consistent network interfaces implemented as evented streams, we can build a simple HTTP proxy in just a few lines of code. For example, the following program will set up an HTTP server on port 8080, which will respond to any request by fetching the front page of Google and piping that back to the client:
var http = require('http');
var server = new http.Server();
server.on("request", function(request, socket) {
http.request({
host: 'www.google.com',
method: 'GET',
path: "/",
port: 80
}, function(response) {
response.pipe(socket);
}).end();
});
server.listen(8080);
Once this server receives the client socket, it is free to push content from any readable stream back to the client. Here, the result of the GET of www.google.com is so streamed. One can easily see how an external content server managing a caching layer for your application might become a proxy endpoint.
Using similar ideas, we can create a tunneling service using Node's native CONNECT support:
var http = require('http');
var net = require('net');
var url = require('url');
var proxy = new http.Server();
proxy.on('connect', function(request, clientSocket, head) {
var reqData = url.parse('http://' + request.url);
var remoteSocket = net.connect(reqData.port, reqData.hostname, function() {
clientSocket.write('HTTP/1.1 200 \r\n\r\n');
remoteSocket.write(head);
// The bi-directional tunnel
remoteSocket.pipe(clientSocket);
clientSocket.pipe(remoteSocket);
});
}).listen(8080, function() {
We've set up a proxy server that responds to clients requesting an HTTP CONNECT method [on("connect")], which contains the request object, the network socket-binding client and server, and the 'head' (the first packet) of the tunneling stream. When a CONNECT request is received from a client, we parse out request.url, fetch the requested host information, and open the requested network socket. By piping remote data to the client and client data to the remote connection, a bidirectional data tunnel is established. Now we need only make the CONNECT request to our proxy, as follows:
Once a status 200 confirmation of our CONNECT request is received, we can push request packets down this tunnel, catching responses and dumping those to stdout:
Web applications have grown in size, importance, and complexity. The security of web applications has, therefore, become an important topic. For one reason or another, early web applications were allowed to venture into the experimental world of client-side business logic, unsecured password transmission, and open web services while shielded by only a diaphanous curtain. This is becoming harder to find among users interested in the security of their information.
As Node is regularly deployed as a web server, it is imperative that the community begins to accept responsibility for securing these servers. HTTPS is a secure transmission protocol—essentially, encrypted HTTP formed by layering the HTTP protocol on top of the SSL/TLS protocol. Let's learn how to secure our Node deployments.
Creating a self-signed certificate for development
In order to support SSL connections, a server will need a properly signed certificate. While developing, it is much easier to simply create a self-signed certificate, allowing us to use Node's HTTPS module.
These are the steps needed to create a certificate for development. Remember that this process does not create a real certificate, and the generated certificate is not secure—it simply allows us to develop within an HTTPS environment from a terminal:
These keys can now be used to develop HTTPS servers. The contents of these files need simply be passed along as options to a Node server running on the (default) SSL port 443:
var https = require('https');
var fs = require('fs');
https.createServer({
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
}, function(req,res) {
...
}).listen(443)
Note
Free low-assurance SSL certificates are available from http://www.startssl.com/ for cases where self-signed certificates are not ideal during development.
Installing a real SSL certificate
In order to move a secure application out of a development environment and into an Internet-exposed environment, a real certificate will need to be purchased. The prices of these certificates have been dropping year by year, and it should be easy to find providers of reasonably priced certificates with a high enough level of security. Some providers even offer free personal-use certificates.
Setting up a professional certificate simply requires changing the HTTPS options we introduced previously. Different providers will have different processes and filenames. Typically, you will need to download or, otherwise, receive a private #key file from your provider, your signed domain certificate #crt file, and a general bundle #ca describing certificate chains:
var options = {
key : fs.readFileSync('mysite.key'),
cert : fs.readFileSync('mysite.com.crt'),
ca : [ fs.readFileSync('gd_bundle.crt') ]
};
It is important to note that the #ca parameter must be sent as an array even if the bundle of certificates has been concatenated into one file.
Here are the key takeaways of this:
HTTP sockets are abstracted into evented streams. This is true for all network interfaces provided by Node. These streams can easily be connected to one another.
Because stream activity is evented, those events can be recorded. Very precise logging information on the behavior of a system can be recorded either in event handlers or by piping streams through a PassThrough Stream parameter that might listen for and record events.
Node excels as an I/O service. Node servers can act as dispatchers solely interested in brokering communication between a client and any number of remote services or even specialized processes running on a local OS.
Now that you know how to set up an HTTP server and work with the protocol within Node, go ahead and experiment. Create a small application on your local machine that allows users to read a Twitter feed or connect to a public data API. Get used to authenticating remote services over the wire and interacting with them either through their API or by otherwise acting as a proxy for their data. Get used to composing network applications by integrating remote network services using Node as a broker.
Running your own servers in production can be expensive and time consuming, especially if you aren't familiar with systems administration. For this reason, a large number of cloud-hosting companies have sprung up and many are designed specifically for the Node developer.
Let's take a look at a few of them. By way of comparison, the same Node application will be deployed on each—an editable JSON document stored in MongoDB bound to a simple browser-based User Interface (UI). You are encouraged to try these services out in order, which is not necessary though.