1. 程式人生 > >深入理解nodejs的HTTP處理流程

深入理解nodejs的HTTP處理流程

[toc] # 簡介 我們已經知道如何使用nodejs搭建一個HTTP服務,今天我們會詳細的介紹nodejs中的HTTP處理流程,從而對nodejs的HTTP進行深入的理解。 # 使用nodejs建立HTTP服務 使用nodejs建立HTTP服務很簡單,nodejs提供了專門的HTTP模組,我們可以使用其中的createServer方法來輕鬆建立HTTP服務: ~~~js const http = require('http'); const server = http.createServer((request, response) => { // magic happens here! }); ~~~ 首先createServer方法傳入的是一個callback函式,這個callback函式將會在每次服務端接收到客戶端的請求時呼叫。所以這個callback函式,也叫做 request handler. 再看看createServer的返回值,createServer返回的是一個EventEmitter物件。 之前我們也介紹過了EventEmitter,它可以傳送和接收事件,所以我們可以使用on來監聽客戶端的事件。 上面的程式碼相當於: ~~~js const server = http.createServer(); server.on('request', (request, response) => { // the same kind of magic happens here! }); ~~~ 當傳送request事件的時候,就會觸發後面的handler method,並傳入request和response引數。我們可以在這個handler中編寫業務邏輯。 當然,為了讓http server正常執行,我們還需要加上listen方法,來繫結ip和埠,以最終啟動服務。 ~~~js const hostname = '127.0.0.1' const port = 3000 server.listen(port, hostname, () => { console.log(`please visit http://${hostname}:${port}/`) }) ~~~ # 解構request 上面的request引數實際上是一個http.IncomingMessage物件,我們看下這個物件的定義: ~~~js 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; 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屬性。 怎麼從request中拿到這些屬性呢?對的,我們可以使用ES6中解構賦值: ~~~js 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。 ~~~js 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事件。 ~~~js request.on('error', (err) => { // This prints the error message and stack trace to `stderr`. console.error(err.stack); }); ~~~ # 解構response response是一個http.ServerResponse類: ~~~js 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: ~~~js response.statusCode = 404; ~~~ Response Headers: response提供了setHeader方法來設定相應的header值。 ~~~js response.setHeader('Content-Type', 'application/json'); response.setHeader('X-Powered-By', 'bacon'); ~~~ 還有一個更加直接的同時寫入head和status code: ~~~js response.writeHead(200, { 'Content-Type': 'application/json', 'X-Powered-By': 'bacon' }); ~~~ 最後,我們需要寫入response body,因為response是一個WritableStream,所以我們可以多次寫入,最後以end方法結束: ~~~js response.write(''); response.write(''); response.write('

Hello, World!

'); response.write(''); response.write(''); response.end(); ~~~ 或者我們可以用一個end來替換: ~~~js response.end('

Hello, World!

'); ~~~ 綜上,我們的程式碼是這樣的: ~~~js 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/](http://www.flydean.com/nodejs-http-in-depth/) > > 本文來源:flydean的部落格 > > 歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來