HTTP 事务的剖析
本指南的目的是让你充分了解 Node.JS HTTP 处理的过程, 我们假设你在一般意义上知道 HTTP 请求的工作方式, 无论语言或编程环境如何, 我们还假设你对 Node.JS EventEmitters 和 Streams 有点熟悉, 如果你对它们不太熟悉, 那么值得快速阅读每个 API 文档.
创建服务器
任何节点 web 服务器应用程序在某些时候都必须创建 Web 服务器对象, 这是通过使用 createServer 完成的.
- const http = require('http');
- const server = http.createServer((request, response) => {
- // magic happens here!
- });
传递给 createServer 的函数对于针对该服务器发出的每个 HTTP 请求都会调用一次, 因此它被称为请求处理程序, 实际上, createServer 返回的 Server 对象是一个 EventEmitter, 我们这里只是创建 server 对象的简写, 然后稍后添加监听器.
- const server = http.createServer();
- server.on('request', (request, response) => {
- // the same kind of magic happens here!
- });
当 HTTP 请求命中服务器时, node 使用一些方便的对象调用请求处理函数来处理事务, request 和 response, 我们很快就会讲到.
为了实际处理请求, 需要在 server 对象上调用 listen 方法, 在大多数情况下, 你需要传递给 listen 的是你希望服务器监听的端口号, 还有一些其他选项, 请参阅 API 参考.
方法, URL 和 Headers
处理请求时, 你可能要做的第一件事就是查看方法和 URL, 以便采取适当的措施, Node 通过将方便的属性放在 request 对象上来使这相对轻松.
const { method, url } = request;
注意: request 对象是 IncomingMessage 的一个实例.
这里的 method 将始终是普通的 HTTP 方法 / 动作, url 是没有服务器, 协议或端口的完整 URL, 对于典型的 URL, 这意味着包括第三个正斜杠后的所有内容.
Headers 也不远, 它们在自己的 request 对象中, 被称为 headers.
- const {
- headers
- } = request;
- const userAgent = headers['user-agent'];
这里需要注意的是, 无论客户端实际发送它们的方式如何, 所有 headers 都仅以小写字母表示, 这简化了为任何目的解析 headers 的任务.
如果重复某些 headers, 则它们的值将被覆盖或以逗号分隔的字符串连接在一起, 具体取决于 header, 在某些情况下, 这可能会有问题, 因此 rawHeaders 也可用.
请求体
收到 POST 或 PUT 请求时, 请求体可能对你的应用程序很重要, 获取 body 数据比访问请求 headers 更复杂一点, 传递给处理程序的 request 对象实现了 ReadableStream 接口, 就像任何其他流一样, 可以在其他地方监听或传输此流, 我们可以通过监听流的'data'和'end'事件来直接从流中获取数据.
每个'data'事件中发出的块是一个 Buffer, 如果你知道它将是字符串数据, 那么最好的方法是在数组中收集数据, 然后在'end', 连接并对其进行字符串化.
- 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
- });
注意: 这看起来有点单调乏味, 而且在很多情况下确实如此, 幸运的是, 在 https://www.npmjs.com/ 上有像 https://www.npmjs.com/package/concat-stream 和 https://www.npmjs.com/package/body 这样的模块可以帮助隐藏一些逻辑, 在走这条路之前, 要很好地了解正在发生的事情, 这就是为什么你在这里!
关于错误的简单介绍
由于 request 对象是一个 ReadableStream, 它也是一个 EventEmitter, 发生错误时的行为与此类似.
request 流中的错误通过在流上发出'error'事件来呈现, 如果你没有该事件的侦听器, 则会抛出错误, 这可能会导致 Node.JS 程序崩溃. 因此, 你应该在请求流上添加'error'侦听器, 即使你只是记录它并继续前进 (虽然最好发送某种 HTTP 错误响应, 稍后会详细介绍).
- request.on('error', (err) => {
- // This prints the error message and stack trace to `stderr`.
- console.error(err.stack);
- });
还有其他方法可以处理这些错误, 例如其他抽象和工具, 但始终要注意错误可能并且确实会发生, 并且你将不得不处理它们.
到目前为止我们已经得到了什么
此时, 我们已经介绍了如何创建服务器, 并从请求中获取方法, URL,headers 和 body, 当我们将它们放在一起时, 它可能看起来像这样:
- 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();
- // At this point, we have the headers, method, url and body, and can now
- // do whatever we need to in order to respond to this request.
- });
- }).listen(8080); // Activates this server, listening on port 8080.
如果我们运行此示例, 我们将能够接收请求, 但不会响应它们, 实际上, 如果你在 Web 浏览器中请求此示例, 则你的请求将超时, 因为没有任何内容被发送回客户端.
到目前为止, 我们还没有涉及响应对象, 它是 ServerResponse 的一个实例, 它是一个 WritableStream, 它包含许多用于将数据发送回客户端的有用方法, 接下来我们将介绍.
HTTP 状态码
如果不设置它, 响应中的 HTTP 状态码始终为 200, 当然, 并非每个 HTTP 响应都保证这一点, 并且在某些时候你肯定希望发送不同的状态码, 为此, 你可以设置 statusCode 属性.
response.statusCode = 404; // Tell the client that the resource wasn't found.
还有其他一些快捷方式, 我们很快就会看到.
设置响应 Headers
Headers 是通过一个名为 setHeader 的方便方法设置的.
- response.setHeader('Content-Type', 'application/json');
- response.setHeader('X-Powered-By', 'bacon');
在响应上设置 headers 时, 大小写对其名称不敏感, 如果重复设置标题, 则设置的最后一个值是发送的值.
显式发送 Header 数据
我们已经讨论过的设置 headers 和状态码的方法假设你正在使用 "隐式 headers", 这意味着在开始发送 body 数据之前, 你需要依赖 node 在正确的时间为你发送 headers.
如果需要, 可以将 headers 显式写入响应流, 为此, 有一个名为 writeHead 的方法, 它将状态码和 headers 写入流.
- response.writeHead(200, {
- 'Content-Type': 'application/json',
- 'X-Powered-By': 'bacon'
- });
一旦设置了 headers(隐式或显式), 你就可以开始发送响应数据了.
发送响应体
由于 response 对象是 WritableStream, 因此将响应体写入客户端只需使用常用的流方法即可.
- 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>');
注意: 在开始向 body 写入数据块之前设置状态和 headers 很重要, 这是有道理的, 因为 headers 在 HTTP 响应中位于 body 之前.
关于错误的另一件事
response 流也可以发出'error'事件, 在某些时候你也必须处理它, 所有关于 request 流错误的建议仍然适用于此处.
把它放在一起
现在我们已经了解了如何进行 HTTP 响应, 让我们把它们放在一起, 在前面的示例的基础上, 我们将创建一个服务器, 用于发回用户发送给我们的所有数据, 我们将使用 JSON.stringify 将该数据格式化为 JSON.
- 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);
Echo 服务器示例
让我们简化前面的示例来进行一个简单的 echo 服务器, 它只是在响应中发送请求中收到的任何数据, 我们需要做的就是从请求流中获取数据并将该数据写入响应流, 类似于我们之前所做的.
- const http = require('http');
- http.createServer((request, response) => {
- let body = [];
- request.on('data', (chunk) => {
- body.push(chunk);
- }).on('end', () => {
- body = Buffer.concat(body).toString();
- response.end(body);
- });
- }).listen(8080);
现在让我们调整一下, 我们只想在以下条件下发送 echo:
请求方法是 POST.
URL 是 / echo.
在任何其他情况下, 我们只想响应 404.
- const http = require('http');
- http.createServer((request, response) => {
- if (request.method === 'POST' && request.url === '/echo') {
- let body = [];
- request.on('data', (chunk) => {
- body.push(chunk);
- }).on('end', () => {
- body = Buffer.concat(body).toString();
- response.end(body);
- });
- } else {
- response.statusCode = 404;
- response.end();
- }
- }).listen(8080);
注意: 通过这种方式检查 URL, 我们正在做一种 "路由" 的形式, 其他形式的路由可以像 switch 语句一样简单, 也可以像 https://www.npmjs.com/package/express 这样的整个框架一样复杂, 如果你正在寻找可以进行路由的东西, 请尝试使用 https://www.npmjs.com/package/router .
现在让我们来简化一下吧, 请记住, request 对象是 ReadableStream,response 对象是 WritableStream, 这意味着我们可以使用 pipe 将数据从一个引导到另一个, 这正是我们想要的 echo 服务器!
- const http = require('http');
- http.createServer((request, response) => {
- if (request.method === 'POST' && request.url === '/echo') {
- request.pipe(response);
- } else {
- response.statusCode = 404;
- response.end();
- }
- }).listen(8080);
我们还没有完成, 正如本指南中多次提到的, 错误可以而且确实会发生, 我们需要处理它们.
为了处理请求流上的错误, 我们将错误记录到 stderr 并发送 400 状态码以指示 Bad Request, 但是, 在实际应用程序中, 我们需要检查错误以确定正确的状态码和消息是什么, 与通常的错误一样, 你应该查阅错误文档.
在响应中, 我们只是将错误记录到 stderr.
- const http = require('http');
- http.createServer((request, response) => {
- request.on('error', (err) => {
- console.error(err);
- response.statusCode = 400;
- response.end();
- });
- response.on('error', (err) => {
- console.error(err);
- });
- if (request.method === 'POST' && request.url === '/echo') {
- request.pipe(response);
- } else {
- response.statusCode = 404;
- response.end();
- }
- }).listen(8080);
我们现在已经介绍了处理 HTTP 请求的大部分基础知识, 此时, 你应该能够:
使用请求处理程序函数实例化 HTTP 服务器, 并让它侦听端口.
从 request 对象中获取 headers,URL, 方法和 body 数据.
根据 request 对象中的 URL 和 / 或其他数据做出路由决策.
通过 response 对象发送 headers,HTTP 状态码和 body 数据.
从 request 对象和 response 对象管道数据.
处理 request 和 response 流中的流错误.
从这些基础知识中, 可以构建用于许多典型用例的 Node.JS HTTP 服务器, 这些 API 提供了许多其他功能, 因此请务必阅读有关 EventEmitters,Streams 和 HTTP 的 API 文档.
上一篇: Node.JS 中的定时器
来源: https://segmentfault.com/a/1190000017473229