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
相关产品推荐
相关产品推荐

