Skip to content

Instantly share code, notes, and snippets.

@luketn
Created June 14, 2019 05:11
Show Gist options
  • Save luketn/76108bab91a20fa1b3b70e0a3b8b8e57 to your computer and use it in GitHub Desktop.
Save luketn/76108bab91a20fa1b3b70e0a3b8b8e57 to your computer and use it in GitHub Desktop.
Timed NodeJS HTTP Get
/**
* Broadly inspired by https://blog.risingstack.com/measuring-http-timings-node-js/
* And https://github.com/RisingStack/example-http-timings
*/
import {IncomingHttpHeaders} from "http";
const https = require('https');
const http = require('http');
const url_lib = require('url');
const NS_PER_SEC = 1e9;
const MS_PER_NS = 1e6;
export interface HttpTimedGetResponse {
body: string;
status: number;
headers: IncomingHttpHeaders;
remoteIp: string;
timings: HttpTimings;
}
export interface HttpTimings {
started: Date;
dnsLookup: number | undefined;
tcpConnection: number | undefined;
tlsHandshake: number | undefined;
firstByte: number;
contentTransfer: number;
total: number;
ended: Date;
}
const http_agent = new http.Agent({
keepAlive: true
});
const https_agent = new https.Agent({
keepAlive: true
});
export const http_timed_get = (url: string, timeoutMs=30000): Promise<HttpTimedGetResponse> => {
let eventTimes = {
started: Date.now(),
startAt: process.hrtime(),
dnsLookupAt: undefined,
tcpConnectionAt: undefined,
tlsHandshakeAt: undefined,
firstByteAt: undefined,
endAt: undefined
};
return new Promise<HttpTimedGetResponse>((resolve, reject) => {
let lib = url.startsWith('https') ? https : http;
let agent = url.startsWith('https') ? https_agent : http_agent;
let q = url_lib.parse(url, true);
let req = lib.get({
path: q.pathname,
host: q.hostname,
port: q.port,
agent: agent
}, (res) => {
let responseBody = '';
req.setTimeout(timeoutMs);
// Response events
res.once('readable', () => {
eventTimes.firstByteAt = process.hrtime();
});
res.on('data', (chunk) => { responseBody += chunk });
res.on('end', () => {
eventTimes.endAt = process.hrtime();
resolve({
body: responseBody,
status: res.statusCode,
headers: res.headers,
remoteIp: req.connection.remoteAddress,
timings: getTimings(eventTimes)
});
});
});
req.on('socket', (socket) => {
socket.on('lookup', () => {
eventTimes.dnsLookupAt = process.hrtime()
});
socket.on('connect', () => {
eventTimes.tcpConnectionAt = process.hrtime()
});
socket.on('secureConnect', () => {
eventTimes.tlsHandshakeAt = process.hrtime()
});
socket.on('timeout', () => {
req.abort();
reject(new Error('ETIMEDOUT'));
})
});
req.on('error', (err)=>reject(err));
req.end();
})
};
function getTimings (eventTimes): HttpTimings {
let total_time_taken_ms = getHrTimeDurationInMs(eventTimes.startAt, eventTimes.endAt);
return {
started: new Date(eventTimes.started),
dnsLookup: eventTimes.dnsLookupAt !== undefined ? getHrTimeDurationInMs(eventTimes.startAt, eventTimes.dnsLookupAt) : undefined,
tcpConnection: eventTimes.dnsLookupAt !== undefined ? getHrTimeDurationInMs(eventTimes.dnsLookupAt || eventTimes.startAt, eventTimes.tcpConnectionAt) : undefined,
tlsHandshake: eventTimes.tlsHandshakeAt !== undefined ? (getHrTimeDurationInMs(eventTimes.tcpConnectionAt, eventTimes.tlsHandshakeAt)) : undefined,
firstByte: getHrTimeDurationInMs((eventTimes.tlsHandshakeAt || eventTimes.tcpConnectionAt || eventTimes.startAt), eventTimes.firstByteAt),
contentTransfer: getHrTimeDurationInMs(eventTimes.firstByteAt, eventTimes.endAt),
total: total_time_taken_ms,
ended: new Date(eventTimes.started + total_time_taken_ms)
}
}
function getHrTimeDurationInMs (startTime, endTime): number {
const secondDiff = endTime[0] - startTime[0];
const nanoSecondDiff = endTime[1] - startTime[1];
const diffInNanoSecond = secondDiff * NS_PER_SEC + nanoSecondDiff;
return round(diffInNanoSecond / MS_PER_NS, 2);
}
function round(n: number, digits: number) {
return +(n.toFixed(digits));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment