NodeJS之HTTP模块

2019/8/27 NodeJS

🌙 NodeJS之HTTP模块学习笔记

HTTP模块

🌙 1.简介

NodeJS 中的 HTTP 接口旨在支持传统上难以使用的协议的许多特性。 特别是,大块的、可能块编码的消息。 接口永远不会缓冲整个请求或响应,所以用户能够流式传输数据。--- 摘自官网http(HTTP) (opens new window)

🌙 1.使用方式

要使用 HTTP 服务器和客户端,必须 require('http')

const http = require('http');
// 不需要实例化,直接使用
http.createServer(function(req, res){...})
1
2
3

🌙 2.从module.exports说起

查看http模块源码:

module.exports = {
  METHODS: methods.slice().sort(), // HTTP方法
  STATUS_CODES, // 标准 HTTP 响应状态码
  Agent: httpAgent.Agent, // 管理 HTTP 客户端的连接持久性和重用
  ClientRequest, // 请求类
  IncomingMessage, // 访问响应状态、消息头、以及数据
  OutgoingMessage,
  Server, // 服务
  ServerResponse, // 响应类
  createServer, // 创建服务 new Server
  get, // 创建get请求api
  request // 创建各种http请求api
};
1
2
3
4
5
6
7
8
9
10
11
12
13

这里可以看出,http模块默认将各种方法导出,它没有定义一个统一的构造方法,可以直接通过http.createServer这种方式调用。

🌙 3.http.METHODS

返回类型:string[]

解析器支持的 HTTP 方法列表,依赖于_http_common模块。

const { methods } = require('_http_common');
module.exports = {
    ...
  METHODS: methods.slice().sort(),
    ...
}
1
2
3
4
5
6

这里http.METHODS_http_common模块中的methods做了一个浅拷贝并排序。

🌙 4.http.STATUS_CODES

返回类型:Object

所有标准 HTTP 响应状态码的集合,以及每个状态码的简短描述。

const STATUS_CODES = {
  100: 'Continue',
  101: 'Switching Protocols',
  102: 'Processing',                 // RFC 2518, obsoleted by RFC 4918
  103: 'Early Hints',
  200: 'OK',
  201: 'Created',
  202: 'Accepted',
  203: 'Non-Authoritative Information',
  204: 'No Content',
  205: 'Reset Content',
  206: 'Partial Content',
  207: 'Multi-Status',               // RFC 4918
  208: 'Already Reported',
  226: 'IM Used',
  300: 'Multiple Choices',           // RFC 7231
  301: 'Moved Permanently',
  302: 'Found',
  303: 'See Other',
  304: 'Not Modified',
  305: 'Use Proxy',
  307: 'Temporary Redirect',
  308: 'Permanent Redirect',         // RFC 7238
  400: 'Bad Request',
  401: 'Unauthorized',
  402: 'Payment Required',
  403: 'Forbidden',
  404: 'Not Found',
  405: 'Method Not Allowed',
  406: 'Not Acceptable',
  407: 'Proxy Authentication Required',
  408: 'Request Timeout',
  409: 'Conflict',
  410: 'Gone',
  411: 'Length Required',
  412: 'Precondition Failed',
  413: 'Payload Too Large',
  414: 'URI Too Long',
  415: 'Unsupported Media Type',
  416: 'Range Not Satisfiable',
  417: 'Expectation Failed',
  418: 'I\'m a Teapot',              // RFC 7168
  421: 'Misdirected Request',
  422: 'Unprocessable Entity',       // RFC 4918
  423: 'Locked',                     // RFC 4918
  424: 'Failed Dependency',          // RFC 4918
  425: 'Unordered Collection',       // RFC 4918
  426: 'Upgrade Required',           // RFC 2817
  428: 'Precondition Required',      // RFC 6585
  429: 'Too Many Requests',          // RFC 6585
  431: 'Request Header Fields Too Large', // RFC 6585
  451: 'Unavailable For Legal Reasons',
  500: 'Internal Server Error',
  501: 'Not Implemented',
  502: 'Bad Gateway',
  503: 'Service Unavailable',
  504: 'Gateway Timeout',
  505: 'HTTP Version Not Supported',
  506: 'Variant Also Negotiates',    // RFC 2295
  507: 'Insufficient Storage',       // RFC 4918
  508: 'Loop Detected',
  509: 'Bandwidth Limit Exceeded',
  510: 'Not Extended',               // RFC 2774
  511: 'Network Authentication Required' // RFC 6585
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

🌙 5.http.createServer方法

源码解读:

const {
    ...
  Server,
 	...
} = require('_http_server');

function createServer(opts, requestListener) {
  return new Server(opts, requestListener);
}
1
2
3
4
5
6
7
8
9

Server通过http.createServer来实例化,也就是说,我们也可以通过以下方式创建服务器:

const http = require('http');
// 实例化
const server = new http.Server(function(req, res){...})
1
2
3

🌙 6.http.Agent类

简介:Agent 负责管理 HTTP 客户端的连接持久性和重用。当不再使用时最好 destroy() (opens new window) Agent 实例,因为未使用的Socket会消耗操作系统资源。参见官网http.Agent 类 (opens new window)

🌙 6.1new Agent([options])构造方法

options Object (opens new window) 要在代理上设置的可配置选项集:

  • keepAlive boolean默认值: false, true-开启长连接;

    不要与 Connection 请求头的 keep-alive 值混淆。 Connection: keep-alive 请求头始终在使用代理时发送,除非明确指定 Connection 请求头、或者 keepAlivemaxSockets 选项分别设置为 falseInfinity,在这种情况下将会使用 Connection: close

NodeJS如何实现真正的长连接? (opens new window)

解决使用 KeepAlive Agent 遇到的 ECONNRESET (opens new window)

要配置其中任何一个,则必须创建自定义的 http.Agent (opens new window) 实例:

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

🌙 6.2 agent.createConnection(options[, callback])

生成用于 HTTP 请求的Socket或流。默认情况下,此函数与 net.createConnection() (opens new window) 相同。

🌙 6.3 agent.keepSocketAlive(socket)

socket stream.Duplex (opens new window)

socket 与请求分离并且可以由 Agent 保留时调用。 默认行为是:

socket.setKeepAlive(true, this.keepAliveMsecs);
socket.unref();
return true;
1
2
3

此方法可以由特定的 Agent 子类重写。

🌙 6.4 agent.reuseSocket(socket, request)

由于 keep-alive 选项而在持久化后将 socket 附加到 request 时调用。 默认行为是:

socket.ref();
1

此方法可以由特定的 Agent 子类重写。

🌙 6.5 agent.destroy()

销毁代理当前使用的所有Socket

通常没有必要这样做。 但是,如果使用启用了 keepAlive 的代理,则最好在代理不再使用时显式关闭代理。 否则,在服务器终止Socket之前,Socket可能会挂起很长时间。

🌙 6.6 agent.freeSockets

Object (opens new window)

一个对象,其中包含当启用 keepAlive 时代理正在等待使用的Socket数组。 不要修改。

freeSockets 列表中的 socket 会在 `'timeout' 时自动被销毁并从数组中删除。

🌙 6.7 agent.getName(options)

获取一组请求选项的唯一名称,以判定一个连接是否可以被重用。 对于 HTTP 代理,这返回 host:port:localAddresshost:port:localAddress:family。 对于 HTTPS 代理,该名称包括 CA、证书、密码、以及其他可判定Socket可重用性的 HTTPS/TLS 特有的选项。

🌙 6.8 agent.maxFreeSockets

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

🌙 6.9 agent.requests

一个对象,包含尚未分配给Socket的请求队列。 不要修改。

🌙 6.10 agent.sockets

  • Object](http://nodejs.cn/s/jzn6Ao)

一个对象,包含尚未分配给Socket的请求队列。 不要修改。

🌙 7.http.ClientRequest类

继承自: Stream (opens new window)

源码解读:

function request(url, options, cb) {
  return new ClientRequest(url, options, cb);
}
1
2
3

此对象由 http.request() (opens new window) 内部创建并返回。 它代表正在进行中的请求,其请求头已进入队列。 请求头仍然可以使用 setHeader(name, value) (opens new window)getHeader(name) (opens new window)removeHeader(name) (opens new window) API 进行改变。 实际的请求头将会与第一个数据块一起发送,或者当调用 request.end() (opens new window) 时发送。

要获得响应,则为请求对象添加 'response' (opens new window) 事件监听器。 当接收到响应头时,会从请求对象中触发 'response' (opens new window) 事件。 'response' (opens new window) 事件执行时具有一个参数,该参数是 http.IncomingMessage (opens new window) 的实例。

'response' (opens new window) 事件期间,可以添加监听器到响应对象,比如监听 'data' 事件。

如果没有添加 'response' (opens new window) 事件处理函数,则响应将会被完全地丢弃。 如果添加了 'response' (opens new window) 事件处理函数,则必须消费完响应对象中的数据,每当有 'readable' 事件时调用 response.read()、或添加 'data' 事件处理函数、或通过调用 .resume() 方法。 在消费完数据之前,不会触发 'end' 事件。 此外,在读取数据之前,它将会占用内存,这最终可能导致进程内存不足的错误。

request 对象不同,如果响应过早地关闭,则 response 对象不会触发 'error' 事件而是触发 'aborted' 事件。

Node.js 不会检查 Content-Length 和已传输的请求体的长度是否相等。

🌙 7.1 http.ClientRequest事件监听

🌙 7.1.1 'abort' 事件

当请求被客户端中止时触发。 此事件仅在第一次调用 abort() 时触发。

🌙 7.1.2 'connect' 事件

每次服务器使用 CONNECT 方法响应请求时都会触发。 如果未监听此事件,则接收 CONNECT 方法的客户端将关闭其连接。

此事件保证传入 net.Socket (opens new window) 类(stream.Duplex (opens new window) 的子类)的实例,除非用户指定了 net.Socket (opens new window) 以外的Socket类型。

客户端和服务器对演示了如何监听 '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('响应内容');
});
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: 'nodejs.cn:80'
  };

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

  req.on('connect', (res, socket, head) => {
    console.log('已连接');

    // 通过 HTTP 隧道发出请求。
    socket.write('GET / HTTP/1.1\r\n' +
                 'Host: nodejs.cn:80\r\n' +
                 'Connection: close\r\n' +
                 '\r\n');
    socket.on('data', (chunk) => {
      console.log(chunk.toString());
    });
    socket.on('end', () => {
      proxy.close();
    });
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

🌙 7.1.3 'continue' 事件

当服务器发送 100 Continue HTTP 响应时触发,通常是因为请求包含 Expect: 100-continue。 这是客户端应发送请求主体的指令。

🌙 7.1.4 'information' 事件

info Object (opens new window)

服务器发送 1xx 中间响应(不包括 101 Upgrade)时触发。 此事件的监听器将会接收一个对象,该对象包含 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(`获得主响应之前的信息: ${info.statusCode}`);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

101 Upgrade 状态不会触发此事件,因为它们与传统的 HTTP 请求/响应链断开,例如 Web Socket、现场 TLS 升级、或 HTTP 2.0。 要收到 101 Upgrade 的通知,请改为监听 'upgrade' (opens new window) 事件。

🌙 7.1.5 'response' 事件

当收到此请求的响应时触发。 此事件仅触发一次。

🌙 7.1.6 'socket' 事件

此事件保证传入 net.Socket (opens new window) 类(stream.Duplex (opens new window) 的子类)的实例,除非用户指定了 net.Socket (opens new window) 以外的Socket类型。

🌙 7.1.7 'timeout' 事件

当底层Socket因不活动而超时时触发。 这只会通知Socket已空闲。 必须手动中止请求。

另请参见:request.setTimeout() (opens new window)

🌙 7.1.8 'upgrade' 事件

每次服务器响应升级请求时发出。 如果未监听此事件且响应状态码为 101 Switching Protocols,则接收升级响应头的客户端将关闭其连接。

此事件保证传入net.Socket (opens new window)类(stream.Duplex (opens new window)的子类)的实例,除非用户指定了net.Socket (opens new window)以外的Socket类型。

客户端服务器对,演示如何监听 'upgrade' 事件。

const http = require('http');

// 创建 HTTP 服务器。
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('响应内容');
});
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('接收到响应');
    socket.end();
    process.exit(0);
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

🌙 7.2 request.aborted

boolean

如果请求已中止,则 request.aborted 属性将会为 true

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

  • data: string | Buffer
  • encoding: string
  • callback: function
  • 返回:this --- request,即 可以链式调用

🌙 7.4 request.destroy([error])

  • error: Error
  • 返回: this
🌙 7.5 request.destroyed

boolean

request.destroyed调用之后变为true

🌙 7.6 request.flushHeaders()

刷新请求头。

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

这通常是期望的(它节省了 TCP 往返),但是可能很晚才发送第一个数据。 request.flushHeaders() 绕过优化并启动请求。

🌙 7.7 request.getHeader(name)

  • name: string

  • 返回:any

读取请求中的一个请求头。 该名称不区分大小写。 返回值的类型取决于提供给 request.setHeader() (opens new window) 的参数。

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' 的类型为字符串数组。
1
2
3
4
5
6
7
8
9

🌙 7.8 request.maxHeadersCount

限制最大响应头数。 如果设置为 0,则不会应用任何限制。

🌙 7.9 request.path

🌙 7.10 request.removeHeader(name)

移除已定义到请求头对象中的请求头。

request.removeHeader('Content-Type');
1

🌙 7.11 request.setHeader(name, value)

  • name: string
  • value: any

为请求头对象设置单个请求头的值。

如果此请求头已存在于待发送的请求头中,则其值将被替换。 这里可以使用字符串数组来发送具有相同名称的多个请求头。 非字符串值将被原样保存。 因此 request.getHeader() (opens new window) 可能会返回非字符串值。 但是非字符串值将转换为字符串以进行网络传输。

request.setHeader('Content-Type', 'application/json');
// 或:
request.setHeader('Cookie', ['type=ninja', 'language=javascript']);
1
2
3

🌙 7.12 request.setNoDelay([noDelay])

一旦将Socket分配给此请求并且连接了Socket,就会调用 socket.setNoDelay() (opens new window)

🌙 7.13 request.setSocketKeepAlive([enable][, initialDelay])

一旦将Socket分配给此请求并连接了Socket,就会调用 socket.setKeepAlive() (opens new window)

🌙 7.14 request.setTimeout(timeout[, callback])

一旦将Socket分配给此请求并且连接了Socket,就会调用 socket.setTimeout() (opens new window)

🌙 7.15 request.socket

指向底层Socket。 通常用户无需访问此属性。 特别是,由于协议解析器附加到Socket的方式,Socket将不会触发 'readable' 事件。 也可以通过 request.connection 访问 socket

const http = require('http');
const options = {
  host: 'nodejs.cn',
};
const req = http.get(options);
req.end();
req.once('response', (res) => {
  const ip = req.socket.localAddress;
  const port = req.socket.localPort;
  console.log(`您的 IP 地址是 ${ip},源端口是 ${port}`);
  // 使用响应对象。
});
1
2
3
4
5
6
7
8
9
10
11
12

🌙 7.16 request.writableFinished

如果在触发 'finish' (opens new window) 事件之前,所有数据都已刷新到底层系统,则为 true

🌙 7.17 request.write(chunk[, encoding][, callback])

发送一个请求主体的数据块。 通过多次调用此方法,可以将请求主体发送到服务器。 在这种情况下,建议在创建请求时使用 ['Transfer-Encoding', 'chunked'] 请求头行。

encoding 参数是可选的,仅当 chunk 是字符串时才适用。 默认为 'utf8'

callback 参数是可选的,当刷新此数据块时调用,但仅当数据块非空时才会调用。

如果将整个数据成功刷新到内核缓冲区,则返回 true。 如果全部或部分数据在用户内存中排队,则返回 false。 当缓冲区再次空闲时,则触发 'drain' 事件。

当使用空字符串或 buffer 调用 write 函数时,则什么也不做且等待更多输入。

🌙 7.17 request.reusedSocket

  • boolean

判断request是否是通过一个被拒绝的socket发送。

当通过启用保持keep-alive 的代理发送请求时,底层的socket可能會被拒絕。但是,如果服务器在不幸的时间关闭连接,则客户端可能会遇到“ ECONNRESET”错误。

const http = require('http');

// Server has a 5 seconds keep-alive timeout by default
http
  .createServer((req, res) => {
    res.write('hello\n');
    res.end();
  })
  .listen(3000);

setInterval(() => {
  // Adapting a keep-alive agent
  http.get('http://localhost:3000', { agent }, (res) => {
    res.on('data', (data) = {
      // Do nothing
    });
  });
}, 5000); // Sending request on 5s interval so it's easy to hit idle timeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

通过判断req.reusedSocket的值,我们可以再次重新启用连接,从而避免这个错误:

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) = {
      // Check if retry is needed
      if (req.reusedSocket && err.code === 'ECONNRESET') {
        retriableRequest();
      }
    });
}

retriableRequest();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

🌙 8.http.ServerResponse 类

继承自: Stream (opens new window)

源码解读:

const {
    ...
  ServerResponse
    ...
} = require('_http_server');

module.exports = {
    ...
  ServerResponse,
    ...
};
1
2
3
4
5
6
7
8
9
10
11

http.ServerResponse 类直接将_http_server中的ServerResponse引入并导出。

此对象由 HTTP 服务器在内部创建,而不是由用户创建。 它作为第二个参数传给 'request' (opens new window) 事件。—— 官网 (opens new window)

🌙 8.1 http.ServerResponse 事件监听

🌙 8.1.1 'close' 事件

表明底层的连接已被终止。

res.on('close', function() {})
1

🌙 8.1.2 'finish' 事件

响应发送后触发。 更具体地说,当响应头和主体的最后一段已经切换到操作系统以通过网络传输时,触发该事件。 这并不意味着客户端已收到任何信息。

res.on('finish', function() {})
1

🌙 8.2 response.cork()

继承自stream.Writable 类,同 writable.cork() (opens new window)

writable.cork() 方法强制把所有写入的数据都缓冲到内存中。 当调用 stream.uncork() (opens new window)stream.end() (opens new window) 方法时,缓冲的数据才会被输出。

writable.cork() 的主要目的是为了适应将几个数据快速连续地写入流的情况。 writable.cork() 不会立即将它们转发到底层的目标,而是缓冲所有数据块,直到调用 writable.uncork(),这会将它们全部传给 writable._writev()(如果存在)。 这可以防止出现行头阻塞的情况,在这种情况下,正在等待第一个数据块被处理的同时对数据进行缓冲。 但是,使用 writable.cork() 而不实现 writable._writev() 可能会对吞吐量产生不利影响。

🌙 8.3 response.end([data[, encoding]][, callback])

此方法向服务器发出信号,表明已发送所有响应头和主体,该服务器应该视为此消息已完成。 必须在每个响应上调用此 response.end() 方法。

如果指定了 data,则相当于调用 response.write(data, encoding) (opens new window) 之后再调用 response.end(callback)

如果指定了 callback,则当响应流完成时将调用它。

🌙 8.4 response.flushHeaders()

刷新响应头。 另可参见:request.flushHeaders() (opens new window)

🌙 8.5 response.getHeader(name)

读出已排队但未发送到客户端的响应头。 该名称不区分大小写。 返回值的类型取决于提供给 response.setHeader() (opens new window) 的参数。

response.setHeader('Content-Type', 'text/html');
response.setHeader('Content-Length', Buffer.byteLength(body));
response.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']);
const contentType = response.getHeader('content-type');
// contentType 是 'text/html'。
const contentLength = response.getHeader('Content-Length');
// contentLength 的类型为数值。
const setCookie = response.getHeader('set-cookie');
// setCookie 的类型为字符串数组。
1
2
3
4
5
6
7
8
9

🌙 8.6 response.getHeaderNames()

返回一个数组,其中包含当前传出的响应头的唯一名称。 所有响应头名称都是小写的。

response.setHeader('Foo', 'bar');
response.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']);

const headerNames = response.getHeaderNames();
// headerNames === ['foo', 'set-cookie']
1
2
3
4
5

🌙 8.7 response.getHeaders()

返回当前传出的响应头的浅拷贝。 由于使用浅拷贝,因此可以更改数组的值而无需额外调用各种与响应头相关的 http 模块方法。 返回对象的键是响应头名称,值是各自的响应头值。 所有响应头名称都是小写的。

response.getHeaders() 方法返回的对象不是从 JavaScript Object 原型继承的。 这意味着典型的 Object 方法,如 obj.toString()obj.hasOwnProperty() 等都没有定义并且不起作用。

response.setHeader('Foo', 'bar');
response.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']);

const headers = response.getHeaders();
// headers === { foo: 'bar', 'set-cookie': ['foo=bar', 'bar=baz'] }
1
2
3
4
5

源码解读:

// Returns a shallow copy of the current outgoing headers.
OutgoingMessage.prototype.getHeaders = function getHeaders() {
  const headers = this[kOutHeaders];
  const ret = Object.create(null); // 对象不是从 JavaScript `Object` 原型继承的,生成一个纯粹的 {}
  if (headers) {
    const keys = Object.keys(headers);
    for (var i = 0; i < keys.length; ++i) {
      const key = keys[i];
      const val = headers[key][1];
      ret[key] = val;
    }
  }
  return ret;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以看出responseOutgoingMessage的实例化对象。

...
new http.Server(function (req, res) {
    console.log(req.statusCode);
    console.log(res.getHeaders());
    console.log(res instanceof http.OutgoingMessage); // true
    ...
}.listen(7777);
1
2
3
4
5
6
7

🌙 8.8 response.hasHeader(name)

如果当前在传出的响应头中设置了由 name 标识的响应头,则返回 true。 响应头名称匹配不区分大小写。

const hasContentType = response.hasHeader('content-type');
1

🌙 8.9 response.removeHeader(name)

移除排队等待中的隐式发送的响应头。

response.removeHeader('Content-Encoding');
1

🌙 8.10 response.setHeader(name, value)

为隐式响应头设置单个响应头的值。 如果此响应头已存在于待发送的响应头中,则其值将被替换。 在这里可以使用字符串数组来发送具有相同名称的多个响应头。 非字符串值将被原样保存。 因此 response.getHeader() (opens new window) 可能返回非字符串值。 但是非字符串值将转换为字符串以进行网络传输。

response.setHeader('Content-Type', 'text/html');
1

或:

response.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']);
1

尝试设置包含无效字符的响应头字段名称或值将导致抛出 TypeError (opens new window)

当使用 response.setHeader() (opens new window) 设置响应头时,它们将与传给 response.writeHead() (opens new window) 的任何响应头合并,其中 response.writeHead() (opens new window) 的响应头优先

// 返回 content-type = text/plain
const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.setHeader('X-Foo', 'bar');
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('ok');
});
1
2
3
4
5
6
7

如果调用了 response.writeHead() (opens new window) 方法并且尚未调用此方法,则它将直接将提供的响应头值写入网络通道而不在内部进行缓存,并且响应头上的 response.getHeader() (opens new window) 将不会产生预期的结果。 如果需要渐进的响应头填充以及将来可能的检索和修改,则使用 response.setHeader() (opens new window) 而不是 response.writeHead() (opens new window)

🌙 8.11 response.setTimeout(msecs[, callback])

将Socket的超时值设置为 msecs。 如果提供了回调,则会将其作为监听器添加到响应对象上的 'timeout' 事件中。

如果没有 'timeout' 监听器添加到请求、响应、或服务器,则Socket在超时时将被销毁。 如果有回调处理函数分配给请求、响应、或服务器的 'timeout' 事件,则必须显式处理超时的Socket

🌙 8.12 response.socket

stream.Duplex (opens new window)

指向底层的socket。 通常用户不需要访问此属性。 特别是,由于协议解析器附加到socket的方式,socket将不会触发 'readable' 事件。 在调用 response.end() 之后,此属性将为空。 也可以通过 response.connection 访问 socket

const http = require('http');
const server = http.createServer((req, res) => {
  const ip = res.socket.remoteAddress;
  const port = res.socket.remotePort;
  res.end(`您的 IP 地址是 ${ip},您的源端口是 ${port}`);
}).listen(3000);
1
2
3
4
5
6

此属性保证是 net.Socket (opens new window) 类(stream.Duplex (opens new window) 的子类)的实例,除非用户指定了 net.Socket (opens new window) 以外的socket类型。

🌙 8.13 response.statusCode

当使用隐式的响应头时(没有显式地调用 response.writeHead() (opens new window)),此属性控制在刷新响应头时将发送到客户端的状态码。

response.statusCode = 404;
1

响应头发送到客户端后,此属性表示已发送的状态码。

🌙 8.14 response.statusMessage

当使用隐式的响应头时(没有显式地调用 response.writeHead() (opens new window)),此属性控制在刷新响应头时将发送到客户端的状态消息。 如果保留为 undefined,则将使用状态码的标准消息。

response.statusMessage = 'Not found';
1

响应头发送到客户端后,此属性表示已发送的状态消息。

🌙 8.15response.write(chunk[, encoding][, callback])

chunk 可以是字符串或 buffer。 如果 chunk 是一个字符串,则第二个参数指定如何将其编码为字节流。 当刷新此数据块时将调用 callback

第一次调用 response.write() (opens new window) 时,它会将缓冲的响应头信息和主体的第一个数据块发送给客户端。 第二次调用 response.write() (opens new window) 时,Node.js 假定数据将被流式传输,并分别发送新数据。 也就是说,响应被缓冲到主体的第一个数据块。

如果将整个数据成功刷新到内核缓冲区,则返回 true。 如果全部或部分数据在用户内存中排队,则返回 false。 当缓冲区再次空闲时,则触发 'drain' 事件。

🌙 8.16 response.writeHead(statusCode[, statusMessage][, headers])

向请求发送响应头。 状态码是一个 3 位的 HTTP 状态码,如 404。 最后一个参数 headers 是响应头。 可以可选地将用户可读的 statusMessage 作为第二个参数。

返回对 ServerResponse 的引用,以便可以链式调用。

const body = 'hello world';
response
  .writeHead(200, {
    'Content-Length': Buffer.byteLength(body),
    'Content-Type': 'text/plain'
  })
  .end(body);
1
2
3
4
5
6
7

此方法只能在消息上调用一次,并且必须在调用 response.end() (opens new window) 之前调用。

当使用 response.setHeader() (opens new window) 设置响应头时,则与传给 response.writeHead() (opens new window) 的任何响应头合并,且 response.writeHead() (opens new window) 的优先。

如果调用此方法并且尚未调用 response.setHeader() (opens new window),则直接将提供的响应头值写入网络通道而不在内部进行缓存,响应头上的 response.getHeader() (opens new window) 将不会产生预期的结果。 如果需要渐进的响应头填充以及将来可能的检索和修改,则改用 response.setHeader() (opens new window)

// 返回 content-type = text/plain
const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.setHeader('X-Foo', 'bar');
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('ok');
});
1
2
3
4
5
6
7

Content-Length 以字节而非字符为单位。 使用 Buffer.byteLength() 来判断主体的长度(以字节为单位)。 Node.js 不检查 Content-Length 和已传输的主体的长度是否相等。

尝试设置包含无效字符的响应头字段名称或值将导致抛出 TypeError (opens new window)

🌙 9.http.get(options[, callback])http.get(url[, options][, callback])

这个方法与 http.request() (opens new window) 的唯一区别是它将方法设置为 GET 并自动调用 req.end()

http.get('http://nodejs.cn/index.json', (res) => {
  const { statusCode } = res;
  const contentType = res.headers['content-type'];

  let error;
  if (statusCode !== 200) {
    error = new Error('请求失败\n' +
                      `状态码: ${statusCode}`);
  } else if (!/^application\/json/.test(contentType)) {
    error = new Error('无效的 content-type.\n' +
                      `期望的是 application/json 但接收到的是 ${contentType}`);
  }
  if (error) {
    console.error(error.message);
    // 消费响应数据来释放内存。
    res.resume();
    return;
  }

  res.setEncoding('utf8');
  let rawData = '';
  res.on('data', (chunk) => { rawData += chunk; });
  res.on('end', () => {
    try {
      const parsedData = JSON.parse(rawData);
      console.log(parsedData);
    } catch (e) {
      console.error(e.message);
    }
  });
}).on('error', (e) => {
  console.error(`出现错误: ${e.message}`);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

🌙 10. http.request(options[, callback])http.request(url[, options][, callback])

const postData = querystring.stringify({
  'msg': '你好世界'
});

const options = {
  hostname: 'nodejs.cn',
  port: 80,
  path: '/upload',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(postData)
  }
};

const req = http.request(options, (res) => {
  console.log(`状态码: ${res.statusCode}`);
  console.log(`响应头: ${JSON.stringify(res.headers)}`);
  res.setEncoding('utf8');
  res.on('data', (chunk) = {
    console.log(`响应主体: ${chunk}`);
  });
  res.on('end', () => {
    console.log('响应中已无数据');
  });
});

req.on('error', (e) => {
  console.error(`请求遇到问题: ${e.message}`);
});

// 将数据写入请求主体。
req.write(postData);
// 必须始终调用 req.end() 来表示请求的结束
req.end();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

🌙 11. 小结

  • 导入http模块

    const http = require('http');
    
    1
  • 创建服务并监听端口

    http.createServer((res, req) => {
        // res: http.ServerResponse类
        // req: http.ClientRequest类
        response.setHeader('Content-Type', 'text/html');
        // 优先于setHeader
        res.writeHead(200, { 'Content-type': 'text/html;charset=UTF-8' }); 
        // 必须调用end()方法
        res.end()
    }).listen(3000);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 创建get请求

    http.get('http://nodejs.cn/index.json', (res) => {
        // do what you want
    }
    
    1
    2
    3

    http.get是创建http请求GET方法快捷方式。

  • 创建post请求

    http.request({method: 'POST'}, (res) => {})
    
    1

    http.request是创建http请求万能方法。

本文学习笔记大部分是查看官网API,部分是查看nodejs源码,只供参考,不喜勿喷 (#^.^#)。