iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
JavaScript

Learn HTTP With JS(2)系列 第 11

Node.js http.Agent:TCP 連線池管理教學

  • 分享至 

  • xImage
  •  

前言

我們有了以下知識

終於可以進到 Node.js http 模組了!

本文測試、引用的 Node.js 原始碼為 v24.x

Why http.Agent ?

如果沒有 http.Agent 的話

import http from "http";

// server
const server = http.createServer((req, res) => {
  console.log("req.headers", req.headers);
  res.end();
});
server.listen(5000);

// client
const noop = () => {};
const request = http.request({
  host: "localhost",
  port: 5000,
  path: "/",
  agent: false, // ✅ 不使用 `http.Agent`
});
request.end();
request.on("response", (res) => {
  console.log("res.headers", res.headers);
  res.resume();
  res.on("end", noop);
});

從 log 的 Connection: close 可以得出結論:每次 HTTP round trip 結束,都會關閉 TCP 連線

req.headers { host: 'localhost:5000', connection: 'close' }
res.headers {
  date: 'Fri, 06 Feb 2026 01:33:20 GMT',
  connection: 'close',
  'content-length': '0'
}

從 TCP 的視角來看,每次都需要
"三次交握開啟連線" + "四次交握關閉連線",效能上會比較差

/wireshark-tcp-3+4

http.Agent 為此而生,它幫使用者管理

  • TCP socket 連線池
  • concurrent 連線上限

new http.Agent(options)

https://nodejs.org/docs/latest-v24.x/api/http.html#new-agentoptions

option description
keepAlive Keep sockets around even when there are no outstanding requests, so they can be used for future requests without having to reestablish a TCP connection.
keepAliveMsecs net.createServerkeepAliveInitialDelay
agentKeepAliveTimeoutBuffer 假設 server 設定 keep-alive: timeout=3http.Agent 設定 agentKeepAliveTimeoutBuffer = 1000,那 http.Agent 會在 3000 - 1000 = 2 秒後,將這個連線視為過期,為了避免 client 還想傳送資料,但 server 已經要關閉這條連線
maxSockets 每個 origin 最多可以有幾個 concurrent TCP socket,參考 options.maxSockets 圖解,(origin 是 agent.getName([options]) 的回傳值)
maxTotalSockets 最多可以有幾個 concurrent TCP socket
maxFreeSockets Only works when keepAlive = true
scheduling 要如何從 freeSockets 陣列中選擇 - fifo (First In First Out) - lifo (Last In First Out)
timeout socket.timeout
proxyEnv v24.5.0 加入的,目前還在 Stability: 1.1 - Active development,細節在未來(9/1 的文章)介紹
defaultPort Default port to use when the port is not specified in requests
protocol The protocol to use for the agent

methods

method description
createConnection(options[, callback]) net.createConnection(),❌ 正常不會碰到,有客製化行為才需要 override
keepSocketAlive(socket) ❌ 正常不會碰到,有客製化行為才需要 override
reuseSocket(socket, request) ❌ 正常不會碰到,有客製化行為才需要 override
destroy() 銷毀整個 http.Agent
getName([options]) ❌ 正常不會碰到,用來當作連線池的 group key,詳細請參考 Read-Only properties

properties

這三個是在 new http.Agent(options) 設定的,故不多贅述

Read-Only properties

這三個則是由 http.Agent 控制的

{
  'example.com:80:': [Socket, Socket],
  'www.google.com:80:': [Socket, Socket]
}
{
  'example.com:80:': [ClientRequest, ClientRequest],
  'www.google.com:80:': [ClientRequest, ClientRequest]
}
  • socketshttp.Agent 使用中的 sockets
{
  'example.com:80:': [Socket, Socket],
  'www.google.com:80:': [Socket, Socket]
}

這邊的 example.com:80:www.google.com:80: 就是 agent.getName([options]) 回傳的 group key

options.maxSockets 圖解

http-agent-max-sockets.svg

ClientRequestnet.Socket 連結的橋樑

當你用 http.request 發起請求時,背後會優先從 http.Agent 的連線池(freeSockets)挑選一個已連線的 net.Socket 關聯到這個 ClientRequest。若 freeSockets 為空,就會建立一個新的 TCP 連線

詳細的實作可以看 lib/_http_agent.jsAgent.prototype.addRequest

我們寫個 PoC 來測試

import http from "http";
import assert from "assert";
import { nextTick } from "process";

// HTTP server
const httpServer = http.createServer((req, res) => res.end());
httpServer.listen(5000);

// HTTP client
const agent = new http.Agent({ keepAlive: true });
// ✅ 剛開始沒有建立任何 TCP 連線
assert(Object.keys(agent.freeSockets).length === 0);
const clientRequest1 = http.request({ host: "localhost", port: 5000, agent });
clientRequest1.on("socket", () => console.log(clientRequest1.reusedSocket)); // ❌ false
clientRequest1.end();
clientRequest1.on("close", () =>
  nextTick(() => {
    // ✅ 使用 nextTick,確保 localhost:5000 的 TCP socket 已經回收到 freeSockets
    assert(Object.keys(agent.freeSockets).length === 1);
    const clientRequest2 = http.request({
      host: "localhost",
      port: 5000,
      agent,
    });
    clientRequest2.on("socket", () => console.log(clientRequest2.reusedSocket)); // ✅ true
    clientRequest2.end();
  }),
);

小結

Without http.Agent (agent: false) With http.Agent (keepAlive: true)
Connection New TCP connection per request Reuse socket from freeSockets
Header Connection: close Connection: keep-alive
Handshake 3-way + 4-way handshake x N 3-way handshake x 1, then reuse
Property State Description
sockets In use Socket currently handling a request
freeSockets Idle Kept alive for reuse (keepAlive: true)
requests Queued Waiting for a free socket (over maxSockets / maxTotalSockets)

上一篇
深入 net.Socket:4-way handshake、socket.end vs destroy 差異
下一篇
Node.js http 模組 Class 介紹:writeHead、flushHeaders 全解析
系列文
Learn HTTP With JS(2)21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言