源代码: lib/http.js
要使用 HTTP 服务器和客户端,则必须 require('http')
。
Node.js 中的 HTTP 接口旨在支持该协议的许多传统上难以使用的功能。 特别是大的,可能是块编码的消息。 接口从不缓冲整个请求或响应,因此用户能够流式传输数据。
HTTP 消息头由类似如下的对象表示:
{ 'content-length': '123',
'content-type': 'text/plain',
'connection': 'keep-alive',
'host': 'example.com',
'accept': '*/*' }
键是小写的。 值不会被修改。
为了支持所有可能的 HTTP 应用程序,Node.js HTTP API 是非常低层的。 它只进行流处理和消息解析。 它将消息解析为标头和正文,但不解析实际的标头或正文。
有关如何处理重复标头的详细信息,请参阅 message.headers
。
接收到的原始标头保留在 rawHeaders
属性中,其是 [key, value, key2, value2, ...]
数组。
例如,上面的消息头对象有类似如下的 rawHeaders
列表:
[ 'ConTent-Length', '123456',
'content-LENGTH', '123',
'content-type', 'text/plain',
'CONNECTION', 'keep-alive',
'Host', 'example.com',
'accepT', '*/*' ]
http.Agent
类#Agent
负责管理 HTTP 客户端连接的持久性和重用。
它维护一个给定主机和端口的待处理请求队列,为每个请求重用单个套接字连接,直到队列为空,此时套接字要么被销毁,要么放入池中,在那里它会被再次用于请求到相同的主机和端口。
是销毁还是池化取决于 keepAlive
选项。
池化的连接会为其启用 TCP Keep-Alive,但服务器可能仍会关闭空闲连接,在这种情况下,它们将从池中删除,并在为该主机和端口发出新的 HTTP 请求时建立新连接。
服务器也可能拒绝允许通过同一个连接的多个请求,在这种情况下,必须为每个请求重新建立连接,并且不能池化。
Agent
仍将向该服务器发出请求,但每个请求都将通过新连接发生。
当客户端或服务器关闭连接时,它会从池中删除。
池中任何未使用的套接字都将被取消引用,以免在没有未完成请求时保持 Node.js 进程运行。
(见 socket.unref()
)。
一个很好的做法是,当不再使用时则 destroy()
Agent
实例,因为未使用的套接字会消耗操作系统资源。
当套接字触发 'close'
事件或 'agentRemove'
事件时,则套接字将从代理中删除。
当打算让 HTTP 请求长时间打开而不将其保留在代理中时,可以执行类似以下的操作:
http.get(options, (res) => {
// 做些事情
}).on('socket', (socket) => {
socket.emit('agentRemove');
});
代理也可用于单个请求。
通过提供 {agent: false}
作为 http.get()
或 http.request()
函数的选项,则单次使用的具有默认选项的 Agent
将用于客户端连接。
agent:false
:
http.get({
hostname: 'localhost',
port: 80,
path: '/',
agent: false // 仅为这个请求创建新代理
}, (res) => {
// 使用响应做些事情
});
new Agent([options])
#options
<Object> 要在代理上设置的可配置选项集。
可以有以下字段:
keepAlive
<boolean> 即使没有未完成的请求,也要保留套接字,这样它们就可以用于未来的请求,而无需重新建立 TCP 连接。
不要与 Connection
标头的 keep-alive
值混淆。
使用代理时总是发送 Connection: keep-alive
标头,除非显式指定了 Connection
标头或当 keepAlive
和 maxSockets
选项分别设置为 false
和 Infinity
,在这种情况下将使用 Connection: close
。 默认值: false
。keepAliveMsecs
<number> 使用 keepAlive
选项时,指定 TCP Keep-Alive 数据包的初始延迟。
当 keepAlive
选项为 false
或 undefined
时则忽略。 默认值: 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> 套接字超时(以毫秒为单位)。
这将在创建套接字时设置超时。socket.connect()
中的 options
也受支持。
http.request()
使用的默认 http.globalAgent
将所有这些值设置为各自的默认值。
要配置其中任何一个,则必须创建自定义的 http.Agent
实例。
const http = require('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> 接收创建的套接字的回调函数生成用于 HTTP 请求的套接字/流。
默认情况下,此函数与 net.createConnection()
相同。
但是,如果需要更大的灵活性,自定义代理可能会覆盖此方法。
可以通过以下两种方式之一提供套接字/流:通过从此函数返回套接字/流,或将套接字/流传给 callback
。
此方法保证返回 <net.Socket> 类(<stream.Duplex> 的子类)的实例,除非用户指定 <net.Socket> 以外的套接字类型。
callback
的参数为 (err, stream)
。
agent.keepSocketAlive(socket)
#socket
<stream.Duplex>当 socket
从请求中分离并且可以由 Agent
持久化时调用。
默认行为是:
socket.setKeepAlive(true, this.keepAliveMsecs);
socket.unref();
return true;
此方法可以被特定的 Agent
子类覆盖。
如果此方法返回假值,则套接字将被销毁,而不是将其持久化以供下一个请求使用。
socket
参数可以是 <net.Socket>(<stream.Duplex> 的子类)的实例。
agent.reuseSocket(socket, request)
#socket
<stream.Duplex>request
<http.ClientRequest>当 socket
由于保持活动选项而持久化后附加到 request
时调用。
默认行为是:
socket.ref();
此方法可以被特定的 Agent
子类覆盖。
socket
参数可以是 <net.Socket>(<stream.Duplex> 的子类)的实例。
agent.destroy()
#销毁代理当前正在使用的所有套接字。
通常没有必要这样做。
但是,如果使用启用了 keepAlive
的代理,则最好在不再需要代理时显式关闭该代理。
否则,套接字可能会在服务器终止它们之前保持打开很长时间。
agent.freeSockets
#当启用 keepAlive
时,包含当前等待代理使用的套接字数组的对象。
不要修改。
freeSockets
列表中的套接字将被自动销毁并从 'timeout'
上的数组中删除。
agent.getName(options)
#获取一组请求选项的唯一名称,以确定是否可以重用连接。
对于 HTTP 代理,则这将返回 host:port:localAddress
或 host:port:localAddress:family
。
对于 HTTPS 代理,则名称包括 CA、证书、密码和其他确定套接字可重用性的 HTTPS/TLS 特定选项。
agent.maxFreeSockets
#默认设置为 256。
对于启用了 keepAlive
的代理,这将设置在空闲状态下将保持打开的最大套接字数量。
agent.maxSockets
#默认设置为 Infinity
。
确定代理可以为每个来源打开多少个并发套接字。
来源是 agent.getName()
的返回值。
agent.maxTotalSockets
#默认设置为 Infinity
。
确定代理可以打开多少个并发套接字。
与 maxSockets
不同,此参数适用于所有来源。
agent.requests
#包含尚未分配给套接字的请求队列的对象。 不要修改。
agent.sockets
#包含代理当前正在使用的套接字数组的对象。 不要修改。
http.ClientRequest
类#此对象从 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'
。
Node.js 不会检查内容长度和已经传输的正文的长度是否相等。
'abort'
事件#'close'
event 。当请求被客户端中止时触发。
此事件仅在第一次调用 abort()
时触发。
'connect'
事件#response
<http.IncomingMessage>socket
<stream.Duplex>head
<Buffer>每次服务器使用 CONNECT
方法响应请求时触发。
如果未监听此事件,则接收 CONNECT
方法的客户端将关闭其连接。
除非用户指定 <net.Socket> 以外的套接字类型,否则此事件保证传入 <net.Socket> 类(<stream.Duplex> 的子类)的实例。
演示如何监听 'connect'
事件的客户端和服务器对:
const http = require('http');
const net = require('net');
const { URL } = require('url');
// 创建 HTTP 隧道代理
const proxy = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('okay');
});
proxy.on('connect', (req, clientSocket, head) => {
// 连接到源服务器
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);
});
});
// 现在代理正在运行
proxy.listen(1337, '127.0.0.1', () => {
// 向隧道代理发出请求
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!');
// 通过 HTTP 隧道发出请求
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();
});
});
});
'continue'
事件#当服务器发送 '100 Continue' HTTP 响应时触发,通常是因为请求包含 'Expect: 100-continue'。 这是客户端应该发送请求正文的指令。
'information'
事件#info
<Object>
当服务器发送 1xx 中间响应(不包括 101 升级)时触发。 此事件的监听器将接收一个对象,其中包含 HTTP 版本、状态码、状态消息、键值标头对象和带有原始标头名称及其各自值的数组。
const http = require('http');
const options = {
host: '127.0.0.1',
port: 8080,
path: '/length_request'
};
// 发出请求
const req = http.request(options);
req.end();
req.on('information', (info) => {
console.log(`Got information prior to main response: ${info.statusCode}`);
});
101 升级状态不会触发此事件,因为它们脱离了传统的 HTTP 请求/响应链,例如 Web 套接字、就地 TLS 升级或 HTTP 2.0。 要收到 101 升级通知,请改为监听 'upgrade'
事件。
'response'
事件#response
<http.IncomingMessage>当接收到对此请求的响应时触发。 此事件仅触发一次。
'socket'
事件#socket
<stream.Duplex>除非用户指定 <net.Socket> 以外的套接字类型,否则此事件保证传入 <net.Socket> 类(<stream.Duplex> 的子类)的实例。
'timeout'
事件#当底层套接字因不活动而超时时触发。 这仅通知套接字已空闲。 必须手动销毁请求。
另见: request.setTimeout()
。
'upgrade'
事件#response
<http.IncomingMessage>socket
<stream.Duplex>head
<Buffer>每次服务器响应升级请求时触发。 如果未监听此事件且响应状态码为 101 Switching Protocols,则接收升级标头的客户端将关闭其连接。
除非用户指定 <net.Socket> 以外的套接字类型,否则此事件保证传入 <net.Socket> 类(<stream.Duplex> 的子类)的实例。
演示如何监听 'upgrade'
事件的客户端服务器对。
const http = require('http');
// 创建 HTTP 服务器
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); // 回声
});
// 现在该服务器正在运行
server.listen(1337, '127.0.0.1', () => {
// 发出请求
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.abort()
#request.destroy()
。将请求标记为中止。 调用它会导致响应中的剩余数据被丢弃并销毁套接字。
request.aborted
#request.destroyed
。如果请求已中止,则 request.aborted
属性将为 true
。
request.connection
#request.socket
.参见 request.socket
。
request.end([data[, encoding]][, callback])
#data
<string> | <Buffer>encoding
<string>callback
<Function>完成发送请求。
如果正文的任何部分未发送,则会将它们刷新到流中。
如果请求被分块,则将发送终止的 '0\r\n\r\n'
。
如果指定了 data
,则相当于调用 request.write(data, encoding)
后跟 request.end(callback)
。
如果指定了 callback
,则将在请求流完成时调用。
request.destroy([error])
#销毁请求。
可选地触发 'error'
事件,并发出 'close'
事件。
调用它会导致响应中的剩余数据被丢弃并销毁套接字。
有关详细信息,请参阅 writable.destroy()
。
request.destroyed
#在调用 request.destroy()
之后是 true
。
有关详细信息,请参阅 writable.destroyed
。
request.finished
#request.writableEnded
.如果 request.end()
已被调用,则 request.finished
属性将为 true
。
如果请求是通过 http.get()
发起的,则会自动调用 request.end()
。
request.flushHeaders()
#刷新请求头。
出于效率原因,Node.js 通常会缓冲请求头,直到调用 request.end()
或写入第一块请求数据。
然后尝试将请求头和数据打包到单个 TCP 数据包中。
这通常是需要的(节省了 TCP 往返),但是当第一个数据直到可能很晚才发送时才需要。
request.flushHeaders()
绕过优化并启动请求。
request.getHeader(name)
#读取请求的标头。
该名称不区分大小写。
返回值的类型取决于提供给 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' 是 'text/html'
const contentLength = request.getHeader('Content-Length');
// 'contentLength' 是数字类型
const cookie = request.getHeader('Cookie');
// 'cookie' 是 string[] 类型
request.getRawHeaderNames()
#返回包含当前传出原始标头的唯一名称的数组。 标头名称返回并设置了它们的确切大小写。
request.setHeader('Foo', 'bar');
request.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']);
const headerNames = request.getRawHeaderNames();
// headerNames === ['Foo', 'Set-Cookie']
request.maxHeadersCount
#2000
限制最大响应头计数。 如果设置为 0,则不会应用任何限制。
request.path
#request.method
#request.host
#request.protocol
#request.removeHeader(name)
#name
<string>删除已定义到标头对象中的标头。
request.removeHeader('Content-Type');
request.reusedSocket
#当通过启用保持活动的代理发送请求时,可能会重用底层套接字。 但是如果服务器在不幸的时间关闭连接,客户端可能会遇到 'ECONNRESET' 错误。
const http = require('http');
// 服务器默认有 5 秒保持活动超时
http
.createServer((req, res) => {
res.write('hello\n');
res.end();
})
.listen(3000);
setInterval(() => {
// 调整保持活动代理
http.get('http://localhost:3000', { agent }, (res) => {
res.on('data', (data) => {
// 什么都不做
});
});
}, 5000); // 以 5 秒的间隔发送请求,因此很容易达到空闲超时
通过标记请求是否重用套接字,可以基于它进行自动错误重试。
const http = require('http');
const agent = new http.Agent({ keepAlive: true });
function retriableRequest() {
const req = http
.get('http://localhost:3000', { agent }, (res) => {
// ...
})
.on('error', (err) => {
// 检查是否需要重试
if (req.reusedSocket && err.code === 'ECONNRESET') {
retriableRequest();
}
});
}
retriableRequest();
request.setHeader(name, value)
#