写点什么

深入理解 nodejs 的 HTTP 处理流程

发布于: 2021 年 02 月 03 日

简介

我们已经知道如何使用 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 的博客

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


发布于: 2021 年 02 月 03 日阅读数: 873
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
深入理解nodejs的HTTP处理流程