深入理解流式传输数据之HTTP(二)

介绍

我们将发现如何使用 Node 从文件或其他来源中提取数据,然后使用 Node 进行读取、写入和操作,就像使用 Node 一样容易。最终,我们将学习如何使用 Node 开发具有快速 I/O 接口的网络服务器,支持高并发应用程序,同时在成千上万的客户端之间共享实时数据。

创建一个 HTTP 服务器

HTTP 是建立在请求/响应模型之上的无状态数据传输协议:客户端向服务器发出请求,服务器然后返回响应。由于促进这种快速模式的网络通信是 Node 设计的出色之处,Node 作为一个用于创建服务器的工具包获得了早期广泛的关注,尽管它当然也可以用于做更多的事情。在本书中,我们将创建许多 HTTP 服务器的实现,以及其他协议服务器,并将在更深入的上下文中讨论最佳实践,这些上下文是特定的业务案例。预期你已经有一些类似的经验。出于这两个原因,我们将快速地从一般概述中进入一些更专业的用途。

在最简单的情况下,HTTP 服务器会响应连接尝试,并在数据到达和发送时进行管理。通常使用http模块的createServer方法创建一个 Node 服务器:

vbscript 复制代码
const http = require('http');
let server = http.createServer((request, response) => {
   response.writeHead(200, { 
      'Content-Type': 'text/plain'
   });
   response.write("PONG");
   response.end();
}).listen(8080);

server.on("request", (request, response) => {
   request.setEncoding("utf8");
   request.on("readable", () => console.log(request.read()));
   request.on("end", () => console.log("DONE"));
});

http.createServer返回的对象是http.Server的一个实例,它扩展了EventEmitter,在网络事件发生时广播,比如客户端连接或请求。前面的代码是编写 Node 服务器的常见方式。然而,值得指出的是,直接实例化http.Server类有时是区分不同服务器/客户端交互的一种有用方式。我们将在接下来的示例中使用这种格式。

在这里,我们创建一个基本的服务器,它只是在连接建立时报告,并在连接终止时报告:

javascript 复制代码
const http = require('http');
const server = new http.Server();
server.on('connection', socket => {
   let now = new Date();
   console.log(`Client arrived: ${now}`);
   socket.on('end', () => console.log(`client left: ${new Date()}`));
});
// Connections get 2 seconds before being terminated
server.setTimeout(2000, socket => socket.end());
server.listen(8080);

在构建多用户系统时,特别是经过身份验证的多用户系统,服务器-客户端事务的这一点是客户端验证和跟踪代码的绝佳位置,包括设置或读取 cookie 和其他会话变量,或向在并发实时应用程序中共同工作的其他客户端广播客户端到达事件。

通过添加一个请求的监听器,我们可以得到更常见的请求/响应模式,作为一个Readable流进行处理。当客户端 POST 一些数据时,我们可以像下面这样捕获这些数据:

vbscript 复制代码
server.on('request', (request, response) => {
   request.setEncoding('utf8');
   request.on('readable', () => {
      let data = request.read();
      data && response.end(data);
   });
});

尝试使用curl向这个服务器发送一些数据:

arduino 复制代码
curl http://localhost:8080 -d "Here is some data"
// Here is some data

通过使用连接事件,我们可以很好地将我们的连接处理代码分开,将其分组到清晰定义的功能域中,正确地描述为响应特定事件执行的功能域。在上面的示例中,我们看到了如何设置一个定时器,在两秒后启动服务器连接。

如果只是想设置在套接字被假定超时之前的不活动毫秒数,只需使用server.timeout = (Integer)num_milliseconds。要禁用套接字超时,请传递一个值0(零)。

现在让我们看看 Node 的 HTTP 模块如何用于进入更有趣的网络交互。

发出 HTTP 请求

网络应用程序通常需要进行外部 HTTP 调用。HTTP 服务器也经常被要求为向其发出请求的客户端执行 HTTP 服务。Node 提供了一个简单的接口来进行外部 HTTP 调用。

例如,以下代码将获取www.example.org的 HTML 首页:

lua 复制代码
const http = require('http');
http.request({ 
   host: 'www.example.org',
   method: 'GET',
   path: "/"
}, function(response) {
   response.setEncoding("utf8");
   response.on("readable", () => console.log(response.read()));
}).end();

正如我们所看到的,我们正在使用一个Readable流,可以写入文件。

管理 HTTP 请求的一个流行的 Node 模块是 Mikeal Roger 的 request:github.com/request/request

因为通常使用HTTP.requestGET外部页面,Node 提供了一个快捷方式:

javascript 复制代码
http.get("http://www.example.org/", response => {
  console.log(`Status: ${response.statusCode}`);
}).on('error', err => {
  console.log("Error: " + err.message);
});

现在让我们看一些更高级的 HTTP 服务器实现,其中我们为客户端执行一般的网络服务。

代理和隧道

有时,为一个服务器提供作为代理或经纪人的功能对其他服务器很有用。这将允许一个服务器将负载分发给其他服务器,例如。另一个用途是为无法直接连接到该服务器的用户提供对安全服务器的访问。一个服务器为多个 URL 提供答复是很常见的------使用代理,一个服务器可以将请求转发给正确的接收者。

由于 Node 在其网络接口中具有一致的流接口,我们可以用几行代码构建一个简单的 HTTP 代理。例如,以下程序将在端口8080上设置一个 HTTP 服务器,该服务器将通过获取网站的首页并将该页面传送回客户端来响应任何请求:

vbscript 复制代码
const http = require('http');
const server = new http.Server();

server.on("request", (request, socket) => {
   console.log(request.url);
   http.request({ 
      host: 'www.example.org',
      method: 'GET',
      path: "/",
      port: 80
   }, response => response.pipe(socket))
   .end();
});

server.listen(8080, () => console.log('Proxy server listening on localhost:8080'));

继续启动这个服务器,并连接到它。一旦这个服务器接收到客户端套接字,它就可以自由地从任何可读流中向客户端推送内容,这里,www.example.orgGET结果被流式传输。一个外部内容服务器管理应用程序的缓存层可能成为代理端点的例子。

使用类似的想法,我们可以使用 Node 的原生CONNECT支持创建一个隧道服务。隧道涉及使用代理服务器作为客户端的中间人与远程服务器进行通信。一旦我们的代理服务器连接到远程服务器,它就能在该服务器和客户端之间来回传递消息。当客户端和远程服务器之间无法直接建立连接或不希望建立连接时,这是有利的。

首先,我们将设置一个代理服务器来响应HTTP CONNECT请求,然后向该服务器发出CONNECT请求。代理接收我们客户端的Request对象,客户端的套接字本身,以及隧道流的头部(第一个数据包):

ini 复制代码
const http = require('http');
const net = require('net');
const url = require('url');
const proxy = new http.Server();

proxy.on('connect', (request, clientSocket, head) => {
  let reqData = url.parse(`http://${request.url}`);
  let remoteSocket = net.connect(reqData.port, reqData.hostname, () => {
    clientSocket.write('HTTP/1.1 200 \r\n\r\n');
    remoteSocket.write(head);
    remoteSocket.pipe(clientSocket);
    clientSocket.pipe(remoteSocket);
   });
}).listen(8080);

let request = http.request({
  port: 8080,
  hostname: 'localhost',
  method: 'CONNECT',
  path: 'www.example.org:80'
});
request.end();

request.on('connect', (res, socket, head) => {
  socket.setEncoding("utf8");
  socket.write('GET / HTTP/1.1\r\nHost: www.example.org:80\r\nConnection: close\r\n\r\n');
  socket.on('readable', () => {
      console.log(socket.read());
   });
  socket.on('end', () => {
    proxy.close();
  });
});

一旦我们向运行在端口 8080 上的本地隧道服务器发出请求,它将建立与目的地的远程套接字连接,并保持这个远程套接字和(本地)客户端套接字之间的"桥梁"。远程连接当然只看到我们的隧道服务器,这样客户端可以以某种匿名的方式连接到远程服务(这并不总是一种不正当的做法!)。

HTTPS、TLS(SSL)和保护您的服务器

Web 应用程序的安全性近年来已成为一个重要的讨论话题。传统应用程序通常受益于主要部署基础的主要服务器和应用程序堆栈中设计成熟的安全模型。出于某种原因,Web 应用程序被允许进入客户端业务逻辑的实验世界,并由一层薄薄的帷幕保护着开放的 Web 服务。

由于 Node 经常部署为 Web 服务器,社区有责任开始确保这些服务器的安全。HTTPS 是一种安全的传输协议------本质上是通过在 SSL/TLS 协议之上叠加 HTTP 协议而形成的加密 HTTP。

为开发创建自签名证书

为了支持 SSL 连接,服务器将需要一个正确签名的证书。在开发过程中,简单创建一个自签名证书会更容易,这将允许您使用 Node 的 HTTPS 模块。

这些是创建开发证书所需的步骤。我们创建的证书不会展示身份,就像第三方的证书那样,但这是我们使用 HTTPS 加密所需要的。从终端:

vbscript 复制代码
openssl genrsa -out server-key.pem 2048
 openssl req -new -key server-key.pem -out server-csr.pem
 openssl x509 -req -in server-csr.pem -signkey server-key.pem -out server-cert.pem

这些密钥现在可以用于开发 HTTPS 服务器。这些文件的内容只需作为选项传递给 Node 服务器即可:

javascript 复制代码
const https = require('https');
const fs = require('fs');
https.createServer({
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem')
}, (req, res) => {
  ...
}).listen(443);

在开发过程中,可以从www.startssl.com/获得免费的低保障 SSL 证书,这是自签名证书不理想的情况。此外,www.letsencrypt.org已经开始了一个激动人心的倡议,为所有人提供免费证书(更安全的网络)。

安装真正的 SSL 证书

为了将安全应用程序从开发环境移出并放入暴露在互联网环境中,需要购买真正的证书。这些证书的价格一年比一年都在下降,应该很容易找到价格合理且安全级别足够高的证书提供商。一些提供商甚至提供免费的个人使用证书。

设置专业证书只需要更改我们之前介绍的 HTTPS 选项。不同的提供商将有不同的流程和文件名。通常,您需要从提供商那里下载或以其他方式接收private .key文件,已签名的域证书.crt文件,以及描述证书链的捆绑文件:

css 复制代码
let options = {
  key: fs.readFileSync("mysite.key"),
  cert: fs.readFileSync("mysite.com.crt"),
  ca: [ fs.readFileSync("gd_bundle.crt") ]
};

重要的是要注意,ca参数必须作为数组发送,即使证书的捆绑已经连接成一个文件。

请求对象

HTTP 请求和响应消息是相似的,包括以下内容:

  • 状态行,对于请求来说,类似于 GET/index.html HTTP/1.1,对于响应来说,类似于 HTTP/1.1 200 OK
  • 零个或多个头部,对于请求可能包括Accept-Charset: UTF-8 或 From: [email protected],对于响应可能类似于Content-Type: text/html 和 Content-Length: 1024
  • 消息正文,对于响应可能是一个 HTML 页面,对于POST请求可能是一些表单数据

我们已经看到了 Node 中 HTTP 服务器接口预期暴露一个请求处理程序,以及这个处理程序将被传递一些形式的请求和响应对象,每个对象都实现了可读或可写流。

我们将在本章后面更深入地讨论POST数据和Header数据的处理。在此之前,让我们先了解如何解析请求中包含的一些更直接的信息。

URL 模块

每当向 HTTP 服务器发出请求时,请求对象将包含 URL 属性,标识目标资源。这可以通过request.url访问。Node 的 URL 模块用于将典型的 URL 字符串分解为其组成部分。请参考以下图示:

我们看到url.parse方法是如何分解字符串的,每个部分的含义应该是清楚的。也许很明显,如果query字段本身被解析为键/值对会更有用。这可以通过将true作为parse方法的第二个参数来实现,这将把上面给出的查询字段值更改为更有用的键/值映射:

css 复制代码
query: { filter: 'sports', maxresults: '20' }

这在解析 GET 请求时特别有用。url.parse还有一个与这两个 URL 之间的差异有关的最后一个参数:

  • http://www.example.org
  • //www.example.org

这里的第二个 URL 是 HTTP 协议的一个(相对较少知道的)设计特性的一个例子:协议相对 URL(技术上是网络路径引用),而不是更常见的绝对 URL。

要了解更多关于如何使用网络路径引用来平滑资源协议解析的信息,请访问:tools.ietf.org/html/rfc3986#section-4.2

正在讨论的问题是:url.parse将以斜杠开头的字符串视为路径,而不是主机。例如,url.parse("//www.example.org")将在主机和路径字段中设置以下值:

yaml 复制代码
host: null,
 path: '//www.example.org'

我们实际上想要的是相反的:

yaml 复制代码
host: 'www.example.org',
 path: null

为了解决这个问题,将true作为url.parse的第三个参数传递,这表明斜杠表示主机,而不是路径:

csharp 复制代码
url.parse("//www.example.org", null, true);

也有可能开发人员想要创建一个 URL,比如通过http.request进行请求时。所述 URL 的各个部分可能分布在各种数据结构和变量中,并且需要被组装。您可以通过将从url.parse返回的对象传递给url.format方法来实现这一点。

以下代码将创建 URL 字符串http://www.example.org

php 复制代码
url.format({
  protocol: 'http:',
  host: 'www.example.org'
});

同样,您还可以使用url.resolve方法来生成 URL 字符串,以满足需要连接基本 URL 和路径的常见情况:

perl 复制代码
url.resolve("http://example.org/a/b", "c/d"); //'http://example.org/a/c/d'
url.resolve("http://example.org/a/b", "/c/d"); 
//'http://example.org/c/d'
url.resolve("http://example.org", "http://google.com"); //'http://google.com/'

Querystring 模块

正如我们在URL模块中看到的,查询字符串通常需要被解析为键/值对的映射。Querystring模块将分解现有的查询字符串为其部分,或者从键/值对的映射中组装查询字符串。

例如,querystring.parse("foo=bar&bingo=bango")将返回:

css 复制代码
{
  foo: 'bar',
  bingo: 'bango'
}

如果我们的查询字符串没有使用正常的"&"分隔符和"="赋值字符格式化,Querystring模块提供了可定制的解析。

Querystring的第二个参数可以是自定义的分隔符字符串,第三个参数可以是自定义的赋值字符串。例如,以下将返回与先前给出的具有自定义格式的查询字符串相同的映射:

javascript 复制代码
let qs = require("querystring");
console.log(qs.parse("foo:bar^bingo:bango", "^", ":"));
// { foo: 'bar', bingo: 'bango' }

您可以使用Querystring.stringify方法组成查询字符串:

less 复制代码
console.log(qs.stringify({ foo: 'bar', bingo: 'bango' }));
// foo=bar&bingo=bango

与解析一样,stringify还接受自定义的分隔符和赋值参数:

less 复制代码
console.log(qs.stringify({ foo: 'bar', bingo: 'bango' }, "^", ":"));
// foo:bar^bingo:bango

查询字符串通常与GET请求相关联,在?字符后面看到。正如我们之前看到的,在这些情况下,使用url模块自动解析这些字符串是最直接的解决方案。然而,以这种方式格式化的字符串也会在处理POST数据时出现,在这些情况下,Querystring模块是真正有用的。我们将很快讨论这种用法,但首先,关于 HTTP 头部的一些内容。

处理头

向 Node 服务器发出的每个 HTTP 请求可能包含有用的头信息,客户端通常希望从服务器接收类似的包信息。Node 提供了简单的接口来读取和写入头信息。我们将简要介绍这些简单的接口,澄清一些细节。最后,我们将讨论如何在 Node 中实现更高级的头使用,研究 Node 服务器可能需要适应的一些常见网络责任。

典型的请求头将如下所示:

头是简单的键/值对。请求键始终小写。在设置响应键时,可以使用任何大小写格式。

读取头很简单。通过检查request.header对象来读取头信息,这是头键/值对的一对一映射。要从前面的示例中获取accept 头,只需读取request.headers.accept

通过设置 HTTP 服务器的maxHeadersCount属性,可以限制传入头的数量。

如果希望以编程方式读取头,Node 提供了response.getHeader方法,接受头键作为其第一个参数。

当写入头时,请求头是简单的键/值对,我们需要更具表现力的接口。由于响应通常必须发送状态码,Node 提供了一种简单的方法来准备响应状态行和头组的一条命令:

css 复制代码
response.writeHead(200, {
  'Content-Length': 4096,
  'Content-Type': 'text/plain'
});

要单独设置头,可以使用response.setHeader,传递两个参数:头键,然后是头值。

要使用相同名称设置多个头,可以将数组传递给response.setHeader

vbscript 复制代码
response.setHeader("Set-Cookie", ["session:12345", "language=en"]);

有时,在排队 后可能需要删除响应头。这可以通过使用response.removeHeader来实现,将要删除的头名称作为参数传递。

必须在写入响应之前写入头。在发送响应后写入头是错误的。

使用 cookies

HTTP 协议是无状态的。任何给定的请求都没有关于先前请求的信息。对于服务器来说,这意味着确定两个请求是否来自同一个浏览器是不可能的。为了解决这个问题,发明了 cookie。cookie 主要用于在客户端(通常是浏览器)和服务器之间共享状态,存在于浏览器中的小型文本文件。

Cookie 是不安全的。Cookie 信息在服务器和客户端之间以纯文本形式流动。中间存在任意数量的篡改点。例如,浏览器允许轻松访问它们。这是一个好主意,因为没有人希望他们的浏览器或本地机器上的信息被隐藏,超出他们的控制。

尽管如此,cookie 也被广泛用于维护状态信息,或者维护状态信息的指针,特别是在用户会话或其他身份验证方案的情况下。

假设您对 cookie 的一般功能很熟悉。在这里,我们将讨论 Node HTTP 服务器如何获取、解析和设置 cookie。我们将使用一个回显发送 cookie 值的服务器的示例。如果没有 cookie 存在,服务器将创建该 cookie,并指示客户端再次请求它。

考虑以下代码:

ini 复制代码
const http = require('http');
const url = require('url');
http.createServer((request, response) => {
  let cookies = request.headers.cookie;
  if(!cookies) {
    let cookieName = "session";
    let cookieValue = "123456";
    let numberOfDays = 4;
    let expiryDate = new Date();
    expiryDate.setDate(expiryDate.getDate() + numberOfDays);

    let cookieText = `${cookieName}=${cookieValue};expires=${expiryDate.toUTCString()};`;
    response.setHeader('Set-Cookie', cookieText);
    response.writeHead(302, {'Location': '/'});
    return response.end();
  }

  cookies.split(';').forEach(cookie => {
    let m = cookie.match(/(.*?)=(.*)$/);
    cookies[m[1].trim()] = (m[2] || '').trim();
  });

  response.end(`Cookie set: ${cookies.toString()}`);
}).listen(8080);

首先,我们创建一个检查请求头中的 cookie 的服务器:

vbscript 复制代码
let server = http.createServer((request, response) => {
  let cookies = request.headers.cookie;
  ...

请注意,cookie 存储为request.headerscookie属性。如果该域不存在 cookie,我们将需要创建一个,给它命名为session,值为123456

javascript 复制代码
if (!cookies) {
  ...
  let cookieText = `${cookieName}=${cookieValue};expires=${expiryDate.toUTCString()};`;
  response.setHeader('Set-Cookie', cookieText);
  response.writeHead(302, {
    'Location': '/'
  });
  return response.end();
}

如果我们第一次设置了这个 cookie,客户端被指示再次向同一服务器发出请求,使用 302 Found 重定向,指示客户端再次调用我们的服务器位置。由于现在为该域设置了一个 cookie,随后的请求将包含我们的 cookie,我们将处理它:

ini 复制代码
cookies.split(';').forEach(cookie => {
 let m = cookie.match(/(.*?)=(.*)$/);
 cookies[m[1].trim()] = (m[2] || '').trim();
});
response.end(`Cookie set: ${cookies.toString()}`);

现在,如果你访问localhost:8080,你应该看到类似于这样的显示:

ini 复制代码
Cookie set: AuthSession=c3Bhc3F1YWxpOjU5QzkzRjQ3OosrEJ30gDa0KcTBhRk-YGGXSZnT; io=QuzEHrr5tIZdH3LjAAAC

理解内容类型

客户端通常会传递一个请求头,指示预期的响应 MIME(多用途互联网邮件扩展)类型。客户端还会指示请求体的 MIME 类型。服务器将类似地提供有关响应体的 MIME 类型的头信息。例如,HTML 的 MIME 类型是 text/html。

正如我们所见,HTTP 响应有责任设置描述其包含的实体的头。同样,GET请求通常会指示资源类型,MIME 类型,它期望作为响应。这样的请求头可能看起来像这样:

vbnet 复制代码
Accept: text/html

接收这样的指令的服务器有责任准备一个符合发送的 MIME 类型的实体主体,如果能够这样做,它应该返回类似的响应头:

css 复制代码
Content-Type: text/html; charset=utf-8

因为请求还标识了所需的特定资源(例如/files/index.html),服务器必须确保返回给客户端的请求资源实际上是正确的 MIME 类型。虽然看起来很明显,由扩展名html标识的资源实际上是 MIME 类型 text/html,但这并不确定------文件系统不会阻止将图像文件命名为html扩展名。解析扩展名是一种不完美的确定文件类型的方法。我们需要做更多的工作。

UNIX 的file程序能够确定系统文件的 MIME 类型。例如,可以通过运行以下命令来确定没有扩展名的文件(例如resource)的 MIME 类型:

css 复制代码
file --brief --mime resource

我们传递参数指示file输出资源的 MIME 类型,并且输出应该是简要的(只有 MIME 类型,没有其他信息)。这个命令可能返回类似于text/plain; charset=us-ascii的内容。在这里,我们有一个解决问题的工具。

有关文件实用程序的更多信息,请参阅:man7.org/linux/man-pages/man1/file.1.html

回想一下,Node 能够生成子进程,我们有一个解决方案来准确确定系统文件的 MIME 类型的问题。我们可以使用 Node 的child_process模块的 Node 命令exec方法来确定文件的 MIME 类型,就像这样:

javascript 复制代码
let exec = require('child_process').exec;
exec("file --brief --mime resource", (err, mime) => {
  console.log(mime);
});

这种技术在从外部位置流入的文件进行验证时也很有用。遵循"永远不要相信客户端"的原则,检查文件发布到 Node 服务器的Content-type头是否与本地文件系统中存在的接收文件的实际 MIME 类型匹配,这总是一个好主意。

处理 favicon 请求

当通过浏览器访问 URL 时,通常会注意到浏览器标签中或浏览器地址栏中有一个小图标。这个图标是一个名为favicon.ico的图像,它在每个请求中都会被获取。因此,一个 HTTP GET 请求通常会结合两个请求------一个用于获取 favicon,另一个用于获取请求的资源。

Node 开发人员经常对这种重复的请求感到惊讶。任何一个 HTTP 服务器的实现都必须处理 favicon 请求。为此,服务器必须检查请求类型并相应地处理它。以下示例演示了一种这样做的方法:

ini 复制代码
const http = require('http');
http.createServer((request, response) => { 
  if(request.url === '/favicon.ico') {
    response.writeHead(200, {
      'Content-Type': 'image/x-icon'
    });
    return response.end();
  }
  response.writeHead(200, {
    'Content-Type': 'text/plain'
  });
  response.write('Some requested resource');
  response.end();

}).listen(8080);

这段代码将简单地发送一个空的图像流用于 favicon。如果有一个要发送的 favicon,你可以简单地通过响应流推送这些数据,就像我们之前讨论过的那样。

处理 POST 数据

在网络应用程序中使用的最常见的REST方法之一是 POST。根据REST规范,POST不是幂等的,与大多数其他众所周知的方法(GETPUTDELETE等)相反。这是为了指出POST数据的处理往往会对应用程序的状态产生重大影响,因此应该小心处理。

我们现在将讨论处理最常见类型的通过表单提交的POST数据。更复杂的POST类型------多部分上传------将在第四章中讨论,使用 Node 访问文件系统

让我们创建一个服务器,该服务器将向客户端返回一个表单,并回显客户端使用该表单提交的任何数据。我们需要首先检查请求的URL,确定这是一个表单请求还是表单提交,在第一种情况下返回表单的HTML,在第二种情况下解析提交的数据:

ini 复制代码
const http = require('http');
const qs = require('querystring');

http.createServer((request, response) => {
   let body = "";
   if(request.url === "/") {
      response.writeHead(200, {
         "Content-Type": "text/html"
      });
      return response.end(
         '<form action="/submit" method="post">\
         <input type="text" name="sometext">\
         <input type="submit" value="Send some text">\
         </form>'
      );
   }
}).listen(8080);

请注意,我们响应的表单只有一个名为sometext的字段。这个表单应该以sometext=entered_text的形式将数据 POST 到路径/submit。为了捕获这些数据,添加以下条件:

ini 复制代码
if(request.url === "/submit") {
   request.on('readable', () => {
      let data = request.read();
      data && (body += data);
   });
   request.on('end', () => {
      let fields = qs.parse(body);
      response.end(`Thanks for sending: ${fields.sometext}`);
   });
}

一旦我们的POST流结束,我们使用Querystring.parse解析主体,从中得到一个键/值映射,我们可以从中取出名称为sometext的表单元素的值,并向客户端响应我们已经收到他们的数据。

总结

Node 的设计者成功地创建了一个简单、可预测且方便的解决方案,解决了在不同来源和目标之间实现高效 I/O 的挑战性设计问题,同时保持了易于管理的代码。它的抽象流接口促进了一致的可读和可写接口的实例化,以及将这个接口扩展到 HTTP 请求和响应、文件系统、子进程和其他数据通道,使得使用 Node 进行流编程成为一种愉快的体验。

相关推荐
小鱼计算机2 小时前
【6】深入学习http模块(万字)-Nodejs开发入门
前端·javascript·http·node.js·http请求
橘右溪5 小时前
Node.js中URL模块详解
node.js
橘右溪5 小时前
Node.js net模块详解
node.js
Mintopia6 小时前
Node.js 中的this
前端·javascript·node.js
玲小珑6 小时前
LLMOps开发(四) Tool + Agent
langchain·node.js·ai编程
橘右溪7 小时前
Node.js cluster模块详解
node.js
·薯条大王7 小时前
Node.js介绍
服务器·node.js
橘右溪8 小时前
Node.js中Stream模块详解
node.js
行星0088 小时前
Mac M1管理多个Node.js版本
macos·node.js
橘右溪8 小时前
Node.js种cluster模块详解
node.js