Creating an HTTP server
HTTP is a stateless data transfer protocol built upon a request/response model: clients make requests to servers, which then return a response. As facilitating this sort of rapid-pattern network communication is the sort of I/O Node was designed to excel at, Node gained early widespread attention as a toolkit for creating servers—though it can certainly be used to do much, much more. Throughout this book, we will be creating many implementations of HTTP servers, as well as other protocol servers, and will be discussing best practices in more depth, contextualized within specific business cases. It is expected that you have already had some experience doing the same. For both of these reasons, we will quickly move through a general overview into some more specialized uses.
At its simplest, 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:
const http = require('http');
let server = http.createServer((request, response) => {
response.writeHead(200, {
'Content-Type': 'text/plain'
});
response.write("PONG");
response.end();
}).listen(8080);
server.on("request", (request, response) => {
request.setEncoding("utf8");
request.on("readable", () => console.log(request.read()));
request.on("end", () => console.log("DONE"));
});
The object returned by http.createServer is an instance of http.Server, which extends EventEmitter, broadcasting network events as they occur, such as a client connection or request. The code prior is a common way to write Node servers. However, it is worth pointing out that directly instantiating the http.Server class is sometimes a useful way to distinguish distinct server/client interactions. We will use that format for the following examples.
Here, we create a basic server that simply reports when a connection is made, and when it is terminated:
const http = require('http');
const server = new http.Server();
server.on('connection', socket => {
let now = new Date();
console.log(`Client arrived: ${now}`);
socket.on('end', () => console.log(`client left: ${new Date()}`));
});
// Connections get 2 seconds before being terminated
server.setTimeout(2000, socket => socket.end());
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 tracking code, including setting or reading of cookies and other session variables, or the broadcasting of a client arrival event to other clients working together in a concurrent real-time application.
By adding a listener for requests, we arrive at the more common request/response pattern, handled as a Readable stream. When a client POSTs some data, we can catch that data like the following:
server.on('request', (request, response) => {
request.setEncoding('utf8');
request.on('readable', () => {
let data = request.read();
data && response.end(data);
});
});
Try sending some data to this server using curl:
curl http://localhost:8080 -d "Here is some data"
// Here is some data
By using connection events, we can nicely separate our connection handling code, grouping it into clearly defined functional domains correctly described as executing in response to particular events. In the example above we saw how to set a timer that kicks server connections after two seconds.
If one simply wants to set the number of milliseconds of inactivity before a socket is presumed to have timed out, simply use server.timeout = (Integer)num_milliseconds. To disable socket timeouts, pass a value of 0 (zero).
Let's now take a look at how Node's HTTP module can be used to enter into more interesting network interactions.