Skip to content

Commit

Permalink
5.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Aug 20, 2021
1 parent a44ca1a commit b8dabf9
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 99 deletions.
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: node_js
node_js:
- '13'
- '12'
- '10'
- '16'
- '14'
after_success: npm run coveralls
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Yarn:
> - Sending a chunk greater than [`highWaterMark`](https://nodejs.org/api/stream.html#stream_new_stream_writable_options) will result in invalid `upload` and `response` timings. You can avoid this by splitting the payload into smaller chunks.
```js
const https = require('https');
const timer = require('@szmarczak/http-timer');
import https from 'https';
import timer from '@szmarczak/http-timer';

const request = https.get('https://httpbin.org/anything');
timer(request);
Expand Down
52 changes: 34 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
{
"name": "@szmarczak/http-timer",
"version": "4.0.6",
"version": "5.0.0",
"description": "Timings for HTTP requests",
"main": "dist/source",
"type": "module",
"exports": "./dist/source/index.js",
"engines": {
"node": ">=10"
"node": ">=14.16"
},
"scripts": {
"test": "xo && tsc --noEmit && nyc ava",
"test": "xo && ava",
"build": "del-cli dist && tsc",
"prepare": "npm run build",
"coveralls": "nyc report --reporter=text-lcov | coveralls"
"coveralls": "exit 0 && nyc report --reporter=text-lcov | coveralls"
},
"files": [
"dist/source"
],
"keywords": [
"http",
"https",
"http2",
"timer",
"timings"
"timings",
"performance",
"measure"
],
"repository": {
"type": "git",
Expand All @@ -32,20 +36,21 @@
},
"homepage": "https://github.com/szmarczak/http-timer#readme",
"dependencies": {
"defer-to-connect": "^2.0.0"
"defer-to-connect": "^2.0.1"
},
"devDependencies": {
"@ava/typescript": "^2.0.0",
"@sindresorhus/tsconfig": "^1.0.2",
"@types/node": "^16.3.1",
"@types/node": "^16.7.0",
"ava": "^3.15.0",
"coveralls": "^3.1.1",
"del-cli": "^3.0.1",
"http2-wrapper": "^2.0.7",
"del-cli": "^4.0.1",
"http2-wrapper": "^2.1.4",
"nyc": "^15.1.0",
"p-event": "^4.2.0",
"ts-node": "^10.2.1",
"typescript": "^4.3.5",
"xo": "^0.39.1"
"xo": "^0.44.0"
},
"types": "dist/source",
"nyc": {
Expand All @@ -58,15 +63,26 @@
},
"xo": {
"rules": {
"@typescript-eslint/no-non-null-assertion": "off"
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-call": "off"
}
},
"ava": {
"typescript": {
"compile": false,
"rewritePaths": {
"tests/": "dist/tests/"
}
}
"files": [
"tests/*"
],
"timeout": "1m",
"nonSemVerExperiments": {
"nextGenConfig": true,
"configurableModuleFormat": true
},
"extensions": {
"ts": "module"
},
"nodeArguments": [
"--loader=ts-node/esm"
]
}
}
67 changes: 22 additions & 45 deletions source/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {EventEmitter} from 'events';
import {Socket} from 'net';
import {ClientRequest, IncomingMessage} from 'http';
import {errorMonitor} from 'node:events';
import {types} from 'node:util';
import type {EventEmitter} from 'node:events';
import type {Socket} from 'node:net';
import type {ClientRequest, IncomingMessage} from 'node:http';
import deferToConnect from 'defer-to-connect';
import {types} from 'util';

export interface Timings {
start: number;
Expand Down Expand Up @@ -35,8 +36,6 @@ export interface IncomingMessageWithTimings extends IncomingMessage {
timings?: Timings;
}

const nodejsMajorVersion = Number(process.versions.node.split('.')[0]);

const timer = (request: ClientRequestWithTimings): Timings => {
if (request.timings) {
return request.timings;
Expand All @@ -61,38 +60,24 @@ const timer = (request: ClientRequestWithTimings): Timings => {
request: undefined,
firstByte: undefined,
download: undefined,
total: undefined
}
total: undefined,
},
};

request.timings = timings;

const handleError = (origin: EventEmitter): void => {
const emit = origin.emit.bind(origin);
origin.emit = (event, ...args: unknown[]) => {
// Catches the `error` event
if (event === 'error') {
timings.error = Date.now();
timings.phases.total = timings.error - timings.start;

origin.emit = emit;
}

// Saves the original behavior
return emit(event, ...args);
};
origin.once(errorMonitor, () => {
timings.error = Date.now();
timings.phases.total = timings.error - timings.start;
});
};

handleError(request);

const onAbort = (): void => {
timings.abort = Date.now();

// Let the `end` response event be responsible for setting the total phase,
// unless the Node.js major version is >= 13.
if (!timings.response || nodejsMajorVersion >= 13) {
timings.phases.total = Date.now() - timings.start;
}
timings.phases.total = timings.abort - timings.start;
};

request.prependOnceListener('abort', onAbort);
Expand Down Expand Up @@ -123,14 +108,11 @@ const timer = (request: ClientRequestWithTimings): Timings => {
}

timings.phases.tcp = timings.connect - timings.lookup;

// This callback is called before flushing any data,
// so we don't need to set `timings.phases.request` here.
},
secureConnect: () => {
timings.secureConnect = Date.now();
timings.phases.tls = timings.secureConnect - timings.connect!;
}
},
});
};

Expand All @@ -145,16 +127,7 @@ const timer = (request: ClientRequestWithTimings): Timings => {
timings.phases.request = timings.upload - (timings.secureConnect ?? timings.connect!);
};

const writableFinished = (): boolean => {
if (typeof request.writableFinished === 'boolean') {
return request.writableFinished;
}

// Node.js doesn't have `request.writableFinished` property
return request.finished && (request as any).outputSize === 0 && (!request.socket || request.socket.writableLength === 0);
};

if (writableFinished()) {
if (request.writableFinished) {
onUpload();
} else {
request.prependOnceListener('finish', onUpload);
Expand All @@ -169,6 +142,14 @@ const timer = (request: ClientRequestWithTimings): Timings => {
handleError(response);

response.prependOnceListener('end', () => {
request.off('abort', onAbort);
response.off('aborted', onAbort);

if (timings.phases.total) {
// Aborted or errored
return;
}

timings.end = Date.now();
timings.phases.download = timings.end - timings.response!;
timings.phases.total = timings.end - timings.start;
Expand All @@ -181,7 +162,3 @@ const timer = (request: ClientRequestWithTimings): Timings => {
};

export default timer;

// For CommonJS default export support
module.exports = timer;
module.exports.default = timer;
33 changes: 23 additions & 10 deletions tests/test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {EventEmitter} from 'events';
import http = require('http');
import {ClientRequest, IncomingMessage} from 'http';
import https = require('https');
import {AddressInfo} from 'net';
import util = require('util');
import process from 'node:process';
import {URL} from 'node:url';
import {EventEmitter} from 'node:events';
import http, {ClientRequest, IncomingMessage} from 'node:http';
import https from 'node:https';
import {AddressInfo} from 'node:net';
import util from 'node:util';
import pEvent from 'p-event';
import test from 'ava';
import timer, {Timings, ClientRequestWithTimings, IncomingMessageWithTimings} from '../source';
import http2 = require('http2-wrapper');
import http2 from 'http2-wrapper';
import timer, {Timings, ClientRequestWithTimings, IncomingMessageWithTimings} from '../source/index.js';

let server: http.Server & {
url?: string;
Expand Down Expand Up @@ -71,7 +72,7 @@ test('by default everything is set to undefined', t => {
request: undefined,
firstByte: undefined,
download: undefined,
total: undefined
total: undefined,
});
});

Expand Down Expand Up @@ -157,7 +158,7 @@ test('no memory leak (`lookup` event)', async t => {

test('sets `total` on request error', async t => {
const request = http.get(server.url!, {
timeout: 1
timeout: 1,
});
request.on('timeout', () => {
request.abort();
Expand Down Expand Up @@ -356,6 +357,18 @@ test('sets `abort` on response.destroy()', async t => {
t.is(typeof timings.abort, 'number');
});

test.cb('works on already writableFinished request', t => {
const request = http.get(server.url!);
request.end(() => {
const timings = timer(request);

t.is(typeof timings.upload, 'number');
t.is(typeof timings.phases.request, 'number');

t.end();
});
});

const version = process.versions.node.split('.').map(v => Number(v)) as [number, number, number];
if (version[0] >= 16) {
test('skips proxy-wrapped sockets', async t => {
Expand Down
26 changes: 5 additions & 21 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
{
"extends": "@sindresorhus/tsconfig",
"compilerOptions": {
// https://github.com/sindresorhus/tsconfig/blob/3f5f189a9b895e0d9da72078b574df7b917f5ec8/tsconfig.json
"outDir": "dist",
"target": "es2018", // Node.js 10
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"jsx": "react",
"declaration": true,
"pretty": true,
"newLine": "lf",
"stripInternal": true,
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"noEmitOnError": true,
"useDefineForClassFields": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
"target": "es2020", // Node.js 14
"lib": [
"es2020"
]
},
"include": [
"source",
Expand Down

0 comments on commit b8dabf9

Please sign in to comment.