Deeply understand the HTTP processing flow of nodejs

Time:2021-7-25

brief introduction

We already know how to use nodejs to build an HTTP service. Today, we will introduce the HTTP processing flow in nodejs in detail, so as to have an in-depth understanding of nodejs http.

Creating HTTP services using nodejs

It is easy to create HTTP services using nodejs. Nodejs provides a special HTTP module. We can use the createserver method to easily create HTTP services:

const http = require('http');

const server = http.createServer((request, response) => {
  // magic happens here!
});

First, the createserver method passes in a callback function, which will be called every time the server receives a request from the client. So this callback function is also called request handler

Look at the return value of createserver. Createserver returns an EventEmitter object.

We also introduced EventEmitter before. It can send and receive events, so we can use on to listen to events on the client.

The above code is equivalent to:

const server = http.createServer();
server.on('request', (request, response) => {
  // the same kind of magic happens here!
});

When the request event is sent, the subsequent handler method will be triggered and the request and response parameters will be passed in. We can write business logic in this handler.

Of course, in order for the HTTP server to run normally, we also need to add the listen method to bind the IP and port to finally start the service.

const hostname = '127.0.0.1'
const port = 3000

server.listen(port, hostname, () => {
  console.log(`please visit http://${hostname}:${port}/`)
})

Deconstruction request

The above request parameter is actually an http.incomingmessage object. Let’s see the definition of this object:

    class IncomingMessage extends stream.Readable {
        constructor(socket: Socket);

        aborted: boolean;
        httpVersion: string;
        httpVersionMajor: number;
        httpVersionMinor: number;
        complete: boolean;
        /**
         * @deprecate Use `socket` instead.
         */
        connection: Socket;
        socket: Socket;
        headers: IncomingHttpHeaders;
        rawHeaders: string[];
        trailers: NodeJS.Dict<string>;
        rawTrailers: string[];
        setTimeout(msecs: number, callback?: () => void): this;
        /**
         * Only valid for request obtained from http.Server.
         */
        method?: string;
        /**
         * Only valid for request obtained from http.Server.
         */
        url?: string;
        /**
         * Only valid for response obtained from http.ClientRequest.
         */
        statusCode?: number;
        /**
         * Only valid for response obtained from http.ClientRequest.
         */
        statusMessage?: string;
        destroy(error?: Error): void;
    }

Usually, we need to use the method, URL and headers attributes in the request.

How do I get these properties from the request? Yes, we can use the deconstruction assignment in ES6:

const { method, url } = request;

const { headers } = request;
const userAgent = headers['user-agent'];

The header of request is an incominghttpheaders, which inherits from nodejs.dict.

Processing request body

From the source code, we can see that request is a stream object. For a stream object, if we want to obtain its request body, it is not as simple as obtaining static method and URL.

We handle the body by listening to the data and end events of the request.

let body = [];
request.on('data', (chunk) => {
  body.push(chunk);
}).on('end', () => {
  body = Buffer.concat(body).toString();
  // at this point, `body` has the entire request body stored in it as a string
});

Because each data event, the received chunk is actually a buffer object. We save these buffer objects, and finally use buffer.concat to merge them to get the final result.

It seems a little complicated to directly use nodejs to handle the body. Fortunately, most nodejs web frameworks, such as koa and express, simplify the handling of the body.

Handling exceptions

Exception handling is implemented by listening to the error event of request.

If you do not capture the error processing event in the program, error will throw and terminate your nodejs program, so we must capture this error event.

request.on('error', (err) => {
  // This prints the error message and stack trace to `stderr`.
  console.error(err.stack);
});

Deconstruction response

Response is an http.serverresponse class:

    class ServerResponse extends OutgoingMessage {
        statusCode: number;
        statusMessage: string;

        constructor(req: IncomingMessage);

        assignSocket(socket: Socket): void;
        detachSocket(socket: Socket): void;
        // https://github.com/nodejs/node/blob/master/test/parallel/test-http-write-callbacks.js#L53
        // no args in writeContinue callback
        writeContinue(callback?: () => void): void;
        writeHead(statusCode: number, reasonPhrase?: string, headers?: OutgoingHttpHeaders): this;
        writeHead(statusCode: number, headers?: OutgoingHttpHeaders): this;
        writeProcessing(): void;
    }

For response, we mainly focus on statuscode:

response.statusCode = 404; 

Response Headers:

Response provides the setheader method to set the corresponding header value.

response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'bacon');

There is also a more direct way to write head and status code at the same time:

response.writeHead(200, {
  'Content-Type': 'application/json',
  'X-Powered-By': 'bacon'
});

Finally, we need to write the response body. Because the response is a writablestream, we can write multiple times and end with the end method:

response.write('<html>');
response.write('<body>');
response.write('<h1>Hello, World!</h1>');
response.write('</body>');
response.write('</html>');
response.end();

Or we can replace it with an end:

response.end('<html><body><h1>Hello, World!</h1></body></html>');

To sum up, our code is as follows:

const http = require('http');

http.createServer((request, response) => {
  const { headers, method, url } = request;
  let body = [];
  request.on('error', (err) => {
    console.error(err);
  }).on('data', (chunk) => {
    body.push(chunk);
  }).on('end', () => {
    body = Buffer.concat(body).toString();
    // BEGINNING OF NEW STUFF

    response.on('error', (err) => {
      console.error(err);
    });

    response.statusCode = 200;
    response.setHeader('Content-Type', 'application/json');
    // Note: the 2 lines above could be replaced with this next one:
    // response.writeHead(200, {'Content-Type': 'application/json'})

    const responseBody = { headers, method, url, body };

    response.write(JSON.stringify(responseBody));
    response.end();
    // Note: the 2 lines above could be replaced with this next one:
    // response.end(JSON.stringify(responseBody))

    // END OF NEW STUFF
  });
}).listen(8080);

Author: what about the flybean program

Link to this article:http://www.flydean.com/nodejs-http-in-depth/

Source of this article: flybean’s blog

Welcome to my official account: the most popular interpretation of “those things”, the most profound dry cargo, the most concise tutorial, and many small tricks you don’t know, etc. you’ll find them!