簡介
我們已經知道如何使用nodejs搭建一個HTTP服務,今天我們會詳細的介紹nodejs中的HTTP處理流程,從而對nodejs的HTTP進行深入的理解。
使用nodejs建立HTTP服務
使用nodejs建立HTTP服務很簡單,nodejs提供了專門的HTTP模組,我們可以使用其中的createServer方法來輕鬆建立HTTP服務:
const http = require('http');
const server = http.createServer((request, response) => {
// magic happens here!
});
首先createServer方法傳入的是一個callback函式,這個callback函式將會在每次服務端接收到客戶端的請求時呼叫。所以這個callback函式,也叫做 request handler.
再看看createServer的返回值,createServer返回的是一個EventEmitter物件。
之前我們也介紹過了EventEmitter,它可以傳送和接收事件,所以我們可以使用on來監聽客戶端的事件。
上面的程式碼相當於:
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和埠,以最終啟動服務。
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屬性。
怎麼從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的部落格
歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!