深刻理解nodejs的HTTP处理流程

简介

咱们已经知道如何使用nodejs搭建一个HTTP服务,今天咱们会详细的介绍nodejs中的HTTP处理流程,从而对nodejs的HTTP进行深刻的理解。html

使用nodejs建立HTTP服务

使用nodejs建立HTTP服务很简单,nodejs提供了专门的HTTP模块,咱们可使用其中的createServer方法来轻松建立HTTP服务:node

const http = require('http');

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

首先createServer方法传入的是一个callback函数,这个callback函数将会在每次服务端接收到客户端的请求时调用。因此这个callback函数,也叫作 request handler.git

再看看createServer的返回值,createServer返回的是一个EventEmitter对象。github

以前咱们也介绍过了EventEmitter,它能够发送和接收事件,因此咱们可使用on来监听客户端的事件。web

上面的代码至关于:express

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

当发送request事件的时候,就会触发后面的handler method,并传入request和response参数。咱们能够在这个handler中编写业务逻辑。json

固然,为了让http server正常运行,咱们还须要加上listen方法,来绑定ip和端口,以最终启动服务。app

const hostname = '127.0.0.1'
const port = 3000

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

解构request

上面的request参数其实是一个http.IncomingMessage对象,咱们看下这个对象的定义:框架

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;
    }

一般咱们须要用到request中的method,url和headers属性。koa

怎么从request中拿到这些属性呢?对的,咱们可使用ES6中解构赋值:

const { method, url } = request;

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

其中request的headers是一个IncomingHttpHeaders,它继承自NodeJS.Dict。

处理Request Body

从源码能够看出request是一个Stream对象,对于stream对象来讲,咱们若是想要获取其请求body的话,就不像获取静态的method和url那么简单了。

咱们经过监听Request的data和end事件来处理body。

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
});

由于每次data事件,接收到的chunk其实是一个Buffer对象。咱们将这些buffer对象保存起来,最后使用Buffer.concat来对其进行合并,最终获得最后的结果。

直接使用nodejs来处理body看起来有点复杂,幸运的是大部分的nodejs web框架,好比koa和express都简化了body的处理。

处理异常

异常处理是经过监听request的error事件来实现的。

若是你在程序中并无捕获error的处理事件,那么error将会抛出并终止你的nodejs程序,因此咱们必定要捕获这个error事件。

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

解构response

response是一个http.ServerResponse类:

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;
    }

对于response来讲,咱们主要关注的是statusCode:

response.statusCode = 404;

Response Headers:

response提供了setHeader方法来设置相应的header值。

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

还有一个更加直接的同时写入head和status code:

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

最后,咱们须要写入response body,由于response是一个WritableStream,因此咱们能够屡次写入,最后以end方法结束:

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

或者咱们能够用一个end来替换:

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

综上,咱们的代码是这样的:

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);

本文做者:flydean程序那些事

本文连接:http://www.flydean.com/nodejs-http-in-depth/

本文来源:flydean的博客

欢迎关注个人公众号:「程序那些事」最通俗的解读,最深入的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

相关文章
相关标签/搜索