http module

要使用Node.js的HTTP服务器和客户端,您需要使用require('node:http')引入模块。

Node.js中的HTTP接口被设计成支持协议中许多传统上难以使用的特性,特别是大型、可能是分块编码的消息。这个接口小心翼翼地不会缓冲整个请求或响应,所以用户可以流式传输数据。

HTTP消息头由一个对象表示,格式如下:

{ 'content-length': '123',
  'content-type': 'text/plain',
  'connection': 'keep-alive',
  'host': 'example.com',
  'accept': '*/*' }

键名(Keys)会被转换成小写,而值(Values)不会被修改。

为了支持任何可能的HTTP应用程序,Node.js的HTTP API 抽象级别非常低(底层)。它仅负责流的处理和消息解析。它将消息解析为头部和正文,但不解析实际的头部或正文内容。

有关如何处理重复头部的详细信息,请参阅message.headers

原始头部(按接收顺序)会保留在rawHeaders属性中,它是一个数组,格式如下:

[ 'ConTent-Length', '123456',
  'content-LENGTH', '123',
  'content-type', 'text/plain',
  'CONNECTION', 'keep-alive',
  'Host', 'example.com',
  'accepT', '*/*' ]

例如,前面的消息头对象可能具有如上所示的rawHeaders列表。

Class: http.Agent

一个代理(Agent)负责管理HTTP客户端的连接持久性和重用。它维护了一个队列,用于存储特定主机和端口的待处理请求,在队列为空之前,会重复使用单个套接字连接,之后套接字要么被销毁,要么被放入池中,以便再次用于相同主机和端口的请求。套接字是销毁还是放入池中取决于keepAlive选项。

放入池中的连接启用了TCP Keep-Alive,但服务器仍然可以关闭空闲连接,这种情况下,它们将从池中删除,并且在针对相同主机和端口的新HTTP请求时将创建新连接。服务器也可能拒绝允许在同一连接上进行多个请求,这种情况下,每个请求都必须重新建立连接,不能被池化。代理仍然会向该服务器发出请求,但每个请求都将在一个新的连接上进行。

当连接由客户端或服务器关闭时,它将从池中移除。池中的任何未使用的套接字将被取消引用(unref),以防止在没有未完成请求时使Node.js进程保持运行。详情请参阅socket.unref()

最佳实践是在代理实例不再使用时调用destroy()方法,因为未使用的套接字会消耗操作系统资源。

可以通过以下方式之一将套接字从代理中移除:当套接字触发’close’事件或’agentRemove’事件时,它将从代理中移除。如果希望将一个HTTP请求保持长时间打开但不将其保留在代理中,可以执行类似以下示例的操作:

http.get(options, (res) => {
  // 处理响应
}).on('socket', (socket) => {
  socket.emit('agentRemove');
});

也可以为单个请求使用代理。通过在http.get()http.request()函数中提供{ agent: false }选项,可以使用一次性使用的默认选项的代理进行客户端连接。

// 使用agent:false创建一个仅用于此请求的新代理
http.get({
  hostname: 'localhost',
  port: 80,
  path: '/',
  agent: false,
}, (res) => {
  // 处理响应
});

new Agent([options])

以下是有关options对象和http.Agent的配置选项的详细说明:

  • keepAlive<boolean>):保持套接字在没有未完成请求时保持连接,以便它们可以用于将来的请求而无需重新建立TCP连接。默认值为false

  • keepAliveMsecs<number>):当使用keepAlive选项时,指定TCP Keep-Alive 数据包的初始延迟。如果keepAlive选项为false或未定义,则忽略此选项。默认值为1000毫秒。

  • maxSockets<number>):允许每个主机的最大套接字数量。如果同一主机打开多个并发连接,每个请求将使用新的套接字,直到达到maxSockets值。如果主机尝试打开的连接数超过了maxSockets,附加的请求将进入待处理请求队列,并在现有连接终止时进入活动连接状态。这确保了在任何时候,从给定主机最多只有maxSockets个活动连接。默认值为Infinity

  • maxTotalSockets<number>):总共允许的所有主机的最大套接字数量。每个请求将使用新的套接字,直到达到最大值。默认值为Infinity

  • maxFreeSockets<number>):保留在空闲状态的每个主机的最大套接字数。仅在keepAlive设置为true时相关。默认值为256

  • scheduling<string>):在选择下一个可用套接字时应用的调度策略。可以是’fifo’或’lifo’。这两种调度策略的主要区别在于,’lifo’选择最近使用的套接字,而’fifo’选择最近未使用的套接字。在每秒请求率较低的情况下,’lifo’调度将降低选择可能由于空闲而被服务器关闭的套接字的风险。在每秒请求率较高的情况下,‘fifo’调度将最大化打开套接字的数量,而’lifo’调度将尽可能降低打开套接字的数量。默认值为’lifo’。

  • timeout<number>):套接字超时时间(以毫秒为单位)。这将设置套接字创建时的超时时间。

  • options中的socket.connect()也受支持。

默认的http.globalAgent用于http.request(),并将所有这些值设置为它们各自的默认值。

要配置它们中的任何一个,必须创建一个自定义的http.Agent实例。

const http = require('node:http');
const keepAliveAgent = new http.Agent({ keepAlive: true });
options.agent = keepAliveAgent;
http.request(options, onResponseCallback);

agent.createConnection(options[, callback])

 `options`<Object>):包含连接详细信息的选项。查看`net.createConnection()`以获取选项的格式。
 `callback`<Function>):接收创建的套接字的回调函数。
 返回值:`<stream.Duplex>`,生成可用于HTTP请求的套接字/流

默认情况下,此函数与net.createConnection()相同。但是,自定义代理可以覆盖此方法,以实现更大的灵活性。

套接字/流可以通过两种方式提供:通过从此函数返回套接字/流,或通过将套接字/流传递给回调函数。

除非用户指定了除<net.Socket>之外的套接字类型,否则此方法保证返回<net.Socket>类的实例,它是<stream.Duplex>的子类。

回调函数的签名为(err, stream),其中err表示可能的错误,stream表示创建的套接字/流。

以下是有关agent.keepSocketAlive(socket)agent.reuseSocket(socket, request)方法的说明:

agent.keepSocketAlive(socket)

  • socket<stream.Duplex>

当套接字从请求中分离并可能被代理保持时调用此方法。默认行为是执行以下操作:

  • socket.setKeepAlive(true, this.keepAliveMsecs):启用套接字的TCP Keep-Alive功能,并使用代理的keepAliveMsecs值设置初始延迟。
  • socket.unref():取消对套接字的引用,允许应用程序退出而不等待套接字关闭。
  • return true;

特定代理子类可以覆盖此方法。如果此方法返回假值,则套接字将被销毁,而不是保持以供下一次请求使用。

socket参数可以是<net.Socket>的实例,也可以是<stream.Duplex>的子类。

agent.reuseSocket(socket, request)

  • socket<stream.Duplex>):当套接字由于保持活动选项而被附加到请求后调用此方法。

  • request<http.ClientRequest>):表示HTTP客户端请求的对象。

默认行为是执行以下操作:

  • socket.ref():增加对套接字的引用计数,以确保它保持活动状态,因为它将被附加到新的请求中。

特定代理子类可以覆盖此方法。

socket参数可以是<net.Socket>的实例,也可以是<stream.Duplex>的子类。

agent.destroy

销毁代理当前正在使用的套接字。

通常情况下,这并不是必需的。但是,如果使用启用了keepAlive的代理,则最好在不再需要时明确关闭代理。否则,套接字可能在服务器终止它们之前保持打开状态相当长的时间。

agent.freeSockets <Object>

当启用keepAlive时,该对象包含当前等待代理使用的套接字数组。请勿修改。

freeSockets列表中的套接字将在“timeout”上自动销毁并从数组中删除。

agent.getName([options])

  • options(<Object>):一组选项,提供有关名称生成的信息。
  • host(<string>):要发出请求的服务器的域名或IP地址。
  • port(<number>):远程服务器的端口。
  • localAddress(<string>):在发出请求时用于网络连接的本地接口绑定。
  • family(<integer>):如果不等于未定义,则必须为4或6。
  • 返回值:<string>,获取一组请求选项的唯一名称,以确定是否可以重用连接。对于HTTP代理,这将返回host:port:localAddress或host:port:localAddress:family。对于HTTPS代理,名称包括CA、证书、密码和其他确定套接字可重用性的HTTPS/TLS特定选项。

agent.maxFreeSockets <number

默认设置为256。对于启用了keepAlive的代理,这会设置在空闲状态下保持打开的最大套接字数量。

agent.maxSockets <number

默认设置为Infinity。这确定代理可以针对每个来源(origin)同时打开多少个并发套接字。来源是agent.getName()的返回值。

agent.maxTotalSockets <number

默认设置为Infinity。这确定代理可以同时打开多少个并发套接字。与maxSockets不同,此参数适用于所有来源(origins)。

agent.requests <Object>

这是一个包含尚未分配到套接字的请求队列的对象。请不要修改它。

agent.sockets <Object

这是一个包含代理当前正在使用的套接字数组的对象。请不要修改它。



Class: http.ClientRequest

  • Extends: <http.OutgoingMessage>

这个对象是在内部创建的,并从http.request()中返回。它表示一个正在进行中的请求,其标头已经排队。标头仍然可以使用setHeader(name, value)getHeader(name)removeHeader(name) API进行更改。实际的标头将随着第一个数据块的发送或调用request.end()时一起发送。

要获取响应,请为请求对象添加’response’事件的侦听器。当接收到响应标头时,将从请求对象上发出’response’事件。‘response’事件执行时会传递一个http.IncomingMessage类的实例作为参数。

在’response’事件期间,可以向响应对象添加侦听器,特别是用于监听’data’事件。

如果没有添加’response’处理程序,响应将完全被丢弃。但是,如果添加了’response’事件处理程序,则必须消耗响应对象中的数据,可以通过在每次触发’readable’事件时调用response.read(),或者通过添加’data’处理程序,或者调用.resume()方法。直到数据被消耗,’end’事件才会触发。此外,在数据被读取之前,它将占用可能导致’进程内存不足’错误的内存。

为了向后兼容,只有在已注册’error’侦听器的情况下,res才会发出’error’。

设置Content-Length标头以限制响应正文的大小。如果将response.strictContentLength设置为true,并且与Content-Length标头值不匹配,将导致抛出错误,错误代码为: ‘ERR_HTTP_CONTENT_LENGTH_MISMATCH’。

Content-Length的值应该以字节为单位,而不是字符。请使用Buffer.byteLength()来确定正文的字节长度。

Event:‘close’

这表示请求已完成,或者其底层连接在响应完成之前被提前终止。

Event:‘connect’

  • response(<http.IncomingMessage>):表示服务器对使用CONNECT方法的请求的HTTP响应对象。
  • socket(<stream.Duplex>):表示与服务器建立的连接的套接字/流对象。
  • head(<Buffer>):包含与连接相关的头信息的缓冲区。

当服务器使用CONNECT方法响应请求时,会触发connect事件。如果没有监听此事件,那么接收到CONNECT方法的客户端将会关闭它们的连接。

此事件保证会传递一个<net.Socket>类的实例,它是<stream.Duplex>的子类,除非用户指定了除<net.Socket>之外的套接字类型。

下面是一个示例,演示了如何监听connect事件的客户端和服务器对:

const http = require('node:http');
const net = require('node:net');
const { URL } = require('node:url');

// Create an HTTP tunneling proxy
const proxy = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('okay');
});
proxy.on('connect', (req, clientSocket, head) => {
  // Connect to an origin server
  const { port, hostname } = new URL(`http://${req.url}`);
  const serverSocket = net.connect(port || 80, hostname, () => {
    clientSocket.write('HTTP/1.1 200 Connection Established\r\n' +
                    'Proxy-agent: Node.js-Proxy\r\n' +
                    '\r\n');
    serverSocket.write(head);
    serverSocket.pipe(clientSocket);
    clientSocket.pipe(serverSocket);
  });
});

// Now that proxy is running
proxy.listen(1337, '127.0.0.1', () => {

  // Make a request to a tunneling proxy
  const options = {
    port: 1337,
    host: '127.0.0.1',
    method: 'CONNECT',
    path: 'www.google.com:80',
  };

  const req = http.request(options);
  req.end();

  req.on('connect', (res, socket, head) => {
    console.log('got connected!');

    // Make a request over an HTTP tunnel
    socket.write('GET / HTTP/1.1\r\n' +
                 'Host: www.google.com:80\r\n' +
                 'Connection: close\r\n' +
                 '\r\n');
    socket.on('data', (chunk) => {
      console.log(chunk.toString());
    });
    socket.on('end', () => {
      proxy.close();
    });
  });
});

Event:‘continue’

当服务器发送一个'100 Continue’的HTTP响应时,通常是因为请求包含了’Expect: 100-continue’,会触发continue事件。这是一个指示客户端应该发送请求正文的指令。

Event:‘finish’

当请求已发送时,会触发 ‘finish’ 事件。更具体地说,这个事件在响应头和正文的最后一部分已交给操作系统以在网络上传输时触发。这并不意味着服务器已经收到任何内容。

Event:‘information’

  • info <Object>
    • httpVersion <string>
    • httpVersionMajor <integer>
    • httpVersionMinor <integer>
    • statusCode <integer>
    • statusMessage <string>
    • headers <Object>
    • rawHeaders <string[]>

当服务器发送1xx中间响应(不包括101 Upgrade)时,将触发information事件。此事件的侦听器将收到一个包含HTTP版本、状态码、状态消息、键值标头对象以及原始标头名称和它们相应值的数组的对象。

const http = require('node:http');

const options = {
  host: '127.0.0.1',
  port: 8080,
  path: '/length_request',
};

// Make a request
const req = http.request(options);
req.end();

req.on('information', (info) => {
  console.log(`Got information prior to main response: ${info.statusCode}`);
});

由于101 Upgrade状态与传统的HTTP请求/响应链不同,如WebSocket、原地TLS升级或HTTP 2.0等,因此不会触发此事件。要接收101 Upgrade通知,请改为监听’upgrade’事件。

Event:‘response’

当接收到对该请求的响应时,将触发response事件。此事件仅触发一次。

Event:‘socket’

这个事件保证会传递一个<net.Socket>类的实例,它是<stream.Duplex>的子类,除非用户指定了除<net.Socket>之外的套接字类型。

Event:’timeout’

当底层套接字因长时间不活动而超时时,会触发timeout事件。这只是通知套接字已经空闲了。请求必须手动销毁。

另请参阅:request.setTimeout()。

Event:‘upgrade’

  • response <http.IncomingMessage>
  • socket <stream.Duplex>
  • head <Buffer>

每当服务器通过升级响应请求时,都会触发upgrade事件。如果没有监听此事件,而响应的状态码为101 Switching Protocols,那么接收到升级标头的客户端将会关闭它们的连接。

此事件保证会传递一个<net.Socket>类的实例,它是<stream.Duplex>的子类,除非用户指定了除<net.Socket>之外的套接字类型。

下面是一个客户端和服务器示例,演示了如何监听’upgrade’事件:

const http = require('node:http');

// Create an HTTP server
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('okay');
});
server.on('upgrade', (req, socket, head) => {
  socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
               'Upgrade: WebSocket\r\n' +
               'Connection: Upgrade\r\n' +
               '\r\n');

  socket.pipe(socket); // echo back
});

// Now that server is running
server.listen(1337, '127.0.0.1', () => {

  // make a request
  const options = {
    port: 1337,
    host: '127.0.0.1',
    headers: {
      'Connection': 'Upgrade',
      'Upgrade': 'websocket',
    },
  };

  const req = http.request(options);
  req.end();

  req.on('upgrade', (res, socket, upgradeHead) => {
    console.log('got upgraded!');
    socket.end();
    process.exit(0);
  });
});

request.cork()

See writable.cork().

request.end([data[, encoding]][, callback])

  • data <string> | <Buffer> | <Uint8Array>
  • encoding <string>
  • callback <Function>
  • Returns: <this>

完成发送请求。如果请求正文的任何部分尚未发送,它将会将它们刷新到流中。如果请求是分块的,这将发送终止符 ‘0\r\n\r\n’。

如果指定了数据(data),这等同于调用 request.write(data, encoding),然后再调用 request.end(callback)

如果指定了回调(callback),它将在请求流完成时调用。

request.destroy([error])

  • error Optional, an error to emit with ’error’ event.
  • Returns:

销毁请求。可以选择发出一个’error’事件,并发出一个’close’事件。调用此方法将导致响应中剩余的数据被丢弃,并且套接字被销毁。

有关详细信息,请参阅writable.destroy()。

request.destroyed <boolean>

在调用了request.destroy()之后为true。

有关详细信息,请参阅writable.destroyed。

request.flushHeaders()

request.flushHeaders() 用于刷新请求标头。

出于效率原因,Node.js通常会将请求标头缓冲,直到调用 request.end() 或写入请求数据的第一个块。然后,它尝试将请求标头和数据打包到单个TCP数据包中。

通常情况下这是可取的(它节省了一个TCP往返),但在第一个数据可能直到很久以后才发送时,这并不是理想的情况。request.flushHeaders() 可以绕过这个优化,立即启动请求。

request.getHeader(name)

  • name <string>
  • Returns <any>

request.getHeader() 用于读取请求中的一个标头。标头名称是不区分大小写的。返回值的类型取决于传递给 request.setHeader() 的参数。

request.setHeader('content-type', 'text/html');
request.setHeader('Content-Length', Buffer.byteLength(body));
request.setHeader('Cookie', ['type=ninja', 'language=javascript']);
const contentType = request.getHeader('Content-Type');
// 'contentType' is 'text/html'
const contentLength = request.getHeader('Content-Length');
// 'contentLength' is of type number
const cookie = request.getHeader('Cookie');
// 'cookie' is of type string[]

request.getHeaderNames()

  • Returns <string>

request.getHeader() 方法返回一个包含当前传出标头的唯一名称的数组。所有标头名称都是小写的。

request.setHeader('Foo', 'bar');
request.setHeader('Cookie', ['foo=bar', 'bar=baz']);

const headerNames = request.getHeaderNames();
// headerNames === ['foo', 'cookie']

request.getHeaders()

  • Returns <Object>

request.getHeaders() 方法返回当前传出标头的浅拷贝。由于使用了浅拷贝,数组值可以在不需要额外调用各种与标头相关的 http 模块方法的情况下进行修改。返回对象的键是标头名称,值是相应的标头值。所有标头名称都是小写的。

request.getHeaders() 方法返回的对象不会原型继承自 JavaScript 对象。这意味着典型的对象方法,如 obj.toString()、obj.hasOwnProperty() 和其他方法都未定义,不会起作用。

request.setHeader('Foo', 'bar');
request.setHeader('Cookie', ['foo=bar', 'bar=baz']);

const headers = request.getHeaders();
// headers === { foo: 'bar', 'cookie': ['foo=bar', 'bar=baz'] }