You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js中如何测量HTTP请求详细响应时间?求非request替代方案

需求:测量HTTP请求的详细响应时间

我正在开发一个向不同Web服务发送HTTP请求的解决方案,需要测量详细的响应时间(DNS查询、TCP连接、TLS握手、首字节到达、内容传输、总耗时)。目前使用node:https模块实现了需求,但希望找到非已废弃的request库替代方案,尝试用axios未成功实现该需求。

当前node:https实现代码如下:

import https from "node:https";

const NS_PER_SEC = 1e9;
const MS_PER_NS = 1e6;
const timings = {
  // 使用process.hrtime()避免时钟漂移
  startAt: process.hrtime(),
  dnsLookupAt: undefined,
  tcpConnectionAt: undefined,
  tlsHandshakeAt: undefined,
  firstByteAt: undefined,
  endAt: undefined,
};

let responseBody = "";

const req = https.request(
  {
    hostname: SERVICE_URL,
    port: 443,
    path: "/",
    method: "GET",
    timeout: 5000,

    headers: {
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
      Accept: "*/*",
      "Accept-Language": "en-US,en;q=0.9",
      "Accept-Encoding": "gzip, deflate, br",
    },
  },
  onAnyMessage
);

async function onAnyMessage(res) {
  res.once("readable", () => {
    timings.firstByteAt = process.hrtime();
  });
  res.on("data", (chunk) => {
    responseBody += chunk;
  });
  res.on("end", () => {
    timings.endAt = process.hrtime();
    console.log(responseBody);
    console.log(getTimings(timings));
  });
}

function getTimings(eventTimes) {
  return {
    // 使用IP地址时无DNS查询
    dnsLookup:
      eventTimes.dnsLookupAt !== undefined
        ? getHrTimeDurationInMs(eventTimes.startAt, eventTimes.dnsLookupAt)
        : undefined,
    tcpConnection: getHrTimeDurationInMs(
      eventTimes.dnsLookupAt || eventTimes.startAt,
      eventTimes.tcpConnectionAt
    ),
    // 非HTTPS请求无TLS握手
    tlsHandshake:
      eventTimes.tlsHandshakeAt !== undefined
        ? getHrTimeDurationInMs(
            eventTimes.tcpConnectionAt,
            eventTimes.tlsHandshakeAt
          )
        : undefined,
    firstByte: getHrTimeDurationInMs(
      eventTimes.tlsHandshakeAt || eventTimes.tcpConnectionAt,
      eventTimes.firstByteAt
    ),
    contentTransfer: getHrTimeDurationInMs(
      eventTimes.firstByteAt,
      eventTimes.endAt
    ),
    total: getHrTimeDurationInMs(eventTimes.startAt, eventTimes.endAt),
  };
}

/**
 * 计算process.hrtime()的毫秒差
 * @function getHrTimeDurationInMs
 * @param {Array} startTime - [秒, 纳秒]
 * @param {Array} endTime - [秒, 纳秒]
 * @return {Number} 毫秒级时长
 */
function getHrTimeDurationInMs(startTime, endTime) {
  const secondDiff = endTime[0] - startTime[0];
  const nanoSecondDiff = endTime[1] - startTime[1];
  const diffInNanoSecond = secondDiff * NS_PER_SEC + nanoSecondDiff;

  return diffInNanoSecond / MS_PER_NS;
}

req.on("socket", (socket) => {
  socket.on("lookup", () => {
    timings.dnsLookupAt = process.hrtime();
  });
  socket.on("connect", () => {
    timings.tcpConnectionAt = process.hrtime();
  });
  socket.on("secureConnect", () => {
    timings.tlsHandshakeAt = process.hrtime();
  });
});

req.on("error", (err) => {
  console.log(err);
});

req.end();

替代方案推荐

1. 使用undici(Node.js官方推荐的HTTP客户端)

undici内置支持请求时序测量,无需手动监听socket事件,API更简洁:

import { request } from 'undici';

async function fetchWithTimings(url) {
  const startTime = process.hrtime();
  
  const { body, timingEvents } = await request(url, {
    headers: {
      "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
      Accept: "*/*",
      "Accept-Language": "en-US,en;q=0.9",
      "Accept-Encoding": "gzip, deflate, br",
    },
    timeout: 5000
  });

  // 读取响应体
  const responseBody = await body.text();
  
  // 计算各阶段耗时(timingEvents包含各事件的时间戳,单位毫秒)
  const timings = {
    dnsLookup: timingEvents.lookupEnd - timingEvents.lookupStart,
    tcpConnection: timingEvents.connectEnd - timingEvents.connectStart,
    tlsHandshake: timingEvents.secureConnectEnd - timingEvents.secureConnectStart,
    firstByte: timingEvents.responseStart - timingEvents.secureConnectEnd,
    contentTransfer: timingEvents.responseEnd - timingEvents.responseStart,
    total: process.hrtime(startTime)[0] * 1000 + process.hrtime(startTime)[1] / 1e6
  };

  console.log(responseBody);
  console.log(timings);
}

// 调用示例
fetchWithTimings('https://' + SERVICE_URL);

2. 使用got库

got提供了timings选项,可直接获取各阶段耗时:

import got from 'got';

async function fetchWithTimings(url) {
  const response = await got(url, {
    headers: {
      "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
      Accept: "*/*",
      "Accept-Language": "en-US,en;q=0.9",
      "Accept-Encoding": "gzip, deflate, br",
    },
    timeout: { request: 5000 },
    responseType: 'text'
  });

  // 提取timings数据
  const timings = {
    dnsLookup: response.timings.lookup,
    tcpConnection: response.timings.connect,
    tlsHandshake: response.timings.tls,
    firstByte: response.timings.response - (response.timings.tls || response.timings.connect),
    contentTransfer: response.timings.end - response.timings.response,
    total: response.timings.total
  };

  console.log(response.body);
  console.log(timings);
}

// 调用示例
fetchWithTimings('https://' + SERVICE_URL);

3. 修复axios实现

axios可以通过监听http模块的socket事件来实现时序测量,以下是可行方案:

import axios from 'axios';

async function fetchWithTimings(url) {
  const timings = {
    startAt: process.hrtime(),
    dnsLookupAt: undefined,
    tcpConnectionAt: undefined,
    tlsHandshakeAt: undefined,
    firstByteAt: undefined,
    endAt: undefined
  };

  const response = await axios.get(url, {
    headers: {
      "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
      Accept: "*/*",
      "Accept-Language": "en-US,en;q=0.9",
      "Accept-Encoding": "gzip, deflate, br",
    },
    timeout: 5000,
    // 拦截请求,监听socket事件
    transformRequest: [function (data) {
      this.adapter = async (config) => {
        return new Promise((resolve, reject) => {
          const promise = axios.defaults.adapter(config);
          promise.then(res => {
            timings.endAt = process.hrtime();
            resolve(res);
          }).catch(reject);

          // 获取socket并监听事件
          config.transport.on('socket', (socket) => {
            socket.on('lookup', () => timings.dnsLookupAt = process.hrtime());
            socket.on('connect', () => timings.tcpConnectionAt = process.hrtime());
            socket.on('secureConnect', () => timings.tlsHandshakeAt = process.hrtime());
          });

          // 监听响应开始(首字节)
          config.transport.on('response', (res) => {
            res.once('readable', () => timings.firstByteAt = process.hrtime());
          });
        });
      };
      return data;
    }]
  });

  // 复用原有的getTimings函数计算耗时
  const timingResults = getTimings(timings);
  console.log(response.data);
  console.log(timingResults);
}

// 复用原有的getTimings和getHrTimeDurationInMs函数
function getTimings(eventTimes) {
  return {
    dnsLookup: eventTimes.dnsLookupAt !== undefined ? getHrTimeDurationInMs(eventTimes.startAt, eventTimes.dnsLookupAt) : undefined,
    tcpConnection: getHrTimeDurationInMs(eventTimes.dnsLookupAt || eventTimes.startAt, eventTimes.tcpConnectionAt),
    tlsHandshake: eventTimes.tlsHandshakeAt !== undefined ? getHrTimeDurationInMs(eventTimes.tcpConnectionAt, eventTimes.tlsHandshakeAt) : undefined,
    firstByte: getHrTimeDurationInMs(eventTimes.tlsHandshakeAt || eventTimes.tcpConnectionAt, eventTimes.firstByteAt),
    contentTransfer: getHrTimeDurationInMs(eventTimes.firstByteAt, eventTimes.endAt),
    total: getHrTimeDurationInMs(eventTimes.startAt, eventTimes.endAt),
  };
}

function getHrTimeDurationInMs(startTime, endTime) {
  const secondDiff = endTime[0] - startTime[0];
  const nanoSecondDiff = endTime[1] - startTime[1];
  const diffInNanoSecond = secondDiff * 1e9 + nanoSecondDiff;
  return diffInNanoSecond / 1e6;
}

// 调用示例
fetchWithTimings('https://' + SERVICE_URL);

内容的提问来源于stack exchange,提问作者Bryan Pereyra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 03:12:33