深刻理解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的博客

歡迎關注個人公衆號:「程序那些事」最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

相關文章
相關標籤/搜索