skip to Main Content

I am working on a solution that makes http requests to different web services, and I need to measure the response times (detailed), I am currently using the "node:https" module, but I would like to know if there is a Library or package that is not " request" since it is deprecated and with axios I have not managed to do it

I leave here an example of the code I use

import https from "node:https";

const NS_PER_SEC = 1e9;
const MS_PER_NS = 1e6;
const timings = {
  // use process.hrtime() as it's not a subject of clock drift
  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 {
    // There is no DNS lookup with IP address
    dnsLookup:
      eventTimes.dnsLookupAt !== undefined
        ? getHrTimeDurationInMs(eventTimes.startAt, eventTimes.dnsLookupAt)
        : undefined,
    tcpConnection: getHrTimeDurationInMs(
      eventTimes.dnsLookupAt || eventTimes.startAt,
      eventTimes.tcpConnectionAt
    ),
    // There is no TLS handshake without https
    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),
  };
}

/**
 * Get duration in milliseconds from process.hrtime()
 * @function getHrTimeDurationInMs
 * @param {Array} startTime - [seconds, nanoseconds]
 * @param {Array} endTime - [seconds, nanoseconds]
 * @return {Number} durationInMs
 */
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();

2

Answers


  1. got provides timings:

    import got from 'got';
    
    const response = await got.get('https://stackoverflow.com');
    
    console.log(response.timings);
    

    Result:

    {
      start: 1731862688177,
      socket: 1731862688178,
      lookup: 1731862688191,
      connect: 1731862689031,
      secureConnect: 1731862689048,
      upload: 1731862689048,
      response: 1731862689214,
      end: 1731862689228,
      error: undefined,
      abort: undefined,
      phases: {
        wait: 1,
        dns: 13,
        tcp: 840,
        tls: 17,
        request: 0,
        firstByte: 166,
        download: 14,
        total: 1051
      }
    }
    
    Login or Signup to reply.
  2. To measure detailed HTTP response timings in Node.js, your current implementation with the node:https module is well-structured. If you’d prefer to simplify or enhance your implementation without reinventing the wheel, there are libraries specifically designed for this purpose. Here’s a guide and some recommendations:
    Got
    got is a modern, powerful HTTP request library with built-in timing support.

    Example:

    import got from 'got';
    
    const timings = await got('https://example.com', {
      hooks: {
        beforeRequest: [(options) => {
          console.log('Request started at:', process.hrtime());
        }],
        beforeRedirect: [(options) => {
          console.log('Redirecting to:', options.url.href);
        }],
        beforeRetry: [(options) => {
          console.log('Retrying request...');
        }],
        afterResponse: [(response) => {
          console.log('Request timings:', response.timings);
          return response;
        }]
      },
      responseType: 'json', // Optional
      retry: 1 // Optional
    });
    
    console.log('Total time:', timings.timings.phases.total);

    Undici
    undici is a fast and lightweight HTTP/1.1 client developed by the Node.js team. It provides a raw interface but supports detailed timing.

    npm install undici

    Example:

    import { request } from 'undici';
    
    const { statusCode, headers, trailers, body, context } = await request('https://example.com', {
      method: 'GET',
      opaque: { timings: {} },
      dispatcher: {
        // Hook to measure timings
        onConnect(socket) {
          this.opaque.timings.connected = process.hrtime();
        },
        onRequest() {
          this.opaque.timings.start = process.hrtime();
        }
      }
    });
    
    console.log('Response status:', statusCode);
    console.log('Timing details:', context);

    Manual Approach (Enhancing Your Code)
    If you want to stick with node:https but enhance your code:

    Use Events for Timing: You’re already doing this well with process.hrtime.
    Integrate a Monitoring Library: Use perf_hooks for even more granular timing.
    Enhanced Code Example:

    import https from 'node:https';
    import { PerformanceObserver, performance } from 'perf_hooks';
    
    const obs = new PerformanceObserver((items) => {
      console.log(items.getEntries());
    });
    obs.observe({ entryTypes: ['measure'] });
    
    performance.mark('start');
    
    const req = https.request({
      hostname: 'example.com',
      port: 443,
      path: '/',
      method: 'GET'
    }, (res) => {
      performance.mark('firstByte');
      
      res.on('data', () => {});
      res.on('end', () => {
        performance.mark('end');
        performance.measure('Request to First Byte', 'start', 'firstByte');
        performance.measure('First Byte to End', 'firstByte', 'end');
        performance.measure('Total Request Time', 'start', 'end');
      });
    });
    
    req.on('socket', (socket) => {
      socket.on('lookup', () => performance.mark('dnsLookup'));
      socket.on('connect', () => performance.mark('tcpConnect'));
      socket.on('secureConnect', () => performance.mark('tlsHandshake'));
    });
    
    req.end();

    For most cases, got is ideal due to its ease of use and built-in automatic timing. undici offers excellent performance and advanced control for low-level needs, while https provides maximum flexibility but requires manual implementation of features like timing.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search