Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite Got #1051

Merged
merged 29 commits into from Apr 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
200 changes: 200 additions & 0 deletions benchmark/index.ts
@@ -0,0 +1,200 @@
'use strict';
import {URL} from 'url';
import https = require('https');
import axios from 'axios';
import Benchmark = require('benchmark');
import fetch from 'node-fetch';
import request = require('request');
import got from '../source';
import PromisableRequest from '../source/as-promise/core';
import Request, {kIsNormalizedAlready} from '../source/core';

const {normalizeArguments} = PromisableRequest;

// Configuration
const httpsAgent = new https.Agent({
keepAlive: true,
rejectUnauthorized: false
});

const url = new URL('https://127.0.0.1:8080');
const urlString = url.toString();

const gotOptions = {
agent: {
https: httpsAgent
},
rejectUnauthorized: false,
retry: 0
};

const normalizedGotOptions = normalizeArguments(url, gotOptions);
normalizedGotOptions[kIsNormalizedAlready] = true;

const requestOptions = {
strictSSL: false,
agent: httpsAgent
};

const fetchOptions = {
agent: httpsAgent
};

const axiosOptions = {
url: urlString,
httpsAgent,
rejectUnauthorized: false
};

const axiosStreamOptions: typeof axiosOptions & {responseType: 'stream'} = {
...axiosOptions,
responseType: 'stream'
};

const httpsOptions = {
rejectUnauthorized: false,
agent: httpsAgent
};

const suite = new Benchmark.Suite();

// Benchmarking
suite.add('got - promise', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
await got(url, gotOptions);
deferred.resolve();
}
}).add('got - stream', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
got.stream(url, gotOptions).resume().once('end', () => {
deferred.resolve();
});
}
}).add('got - promise core', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
const stream = new PromisableRequest(url, gotOptions);
stream.resume().once('end', () => {
deferred.resolve();
});
}
}).add('got - stream core', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
const stream = new Request(url, gotOptions);
stream.resume().once('end', () => {
deferred.resolve();
});
}
}).add('got - stream core - normalized options', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
const stream = new Request(undefined as any, normalizedGotOptions);
stream.resume().once('end', () => {
deferred.resolve();
});
}
}).add('request - callback', {
defer: true,
fn: (deferred: {resolve(): void}) => {
request(urlString, requestOptions, (error: Error) => {
if (error) {
throw error;
}

deferred.resolve();
});
}
}).add('request - stream', {
defer: true,
fn: (deferred: {resolve(): void}) => {
const stream = request(urlString, requestOptions);
stream.resume();
stream.once('end', () => {
deferred.resolve();
});
}
}).add('node-fetch - promise', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
const response = await fetch(url, fetchOptions);
await response.text();

deferred.resolve();
}
}).add('node-fetch - stream', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
const {body} = await fetch(url, fetchOptions);

body.resume();
body.once('end', () => {
deferred.resolve();
});
}
}).add('axios - promise', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
await axios.request(axiosOptions);
deferred.resolve();
}
}).add('axios - stream', {
defer: true,
fn: async (deferred: {resolve(): void}) => {
const {data} = await axios.request(axiosStreamOptions);
data.resume();
data.once('end', () => {
deferred.resolve();
});
}
}).add('https - stream', {
defer: true,
fn: (deferred: {resolve(): void}) => {
https.request(urlString, httpsOptions, response => {
response.resume();
response.once('end', () => {
deferred.resolve();
});
}).end();
}
}).on('cycle', (event: Benchmark.Event) => {
console.log(String(event.target));
}).on('complete', function (this: any) {
console.log(`Fastest is ${this.filter('fastest').map('name') as string}`);

internalBenchmark();
}).run();

const internalBenchmark = (): void => {
console.log();

const internalSuite = new Benchmark.Suite();
internalSuite.add('got - normalize options', {
fn: () => {
normalizeArguments(url, gotOptions);
}
}).on('cycle', (event: Benchmark.Event) => {
console.log(String(event.target));
});

internalSuite.run();
};

// Results (i7-7700k, CPU governor: performance):
// got - promise x 3,092 ops/sec ±5.25% (73 runs sampled)
// got - stream x 4,313 ops/sec ±5.61% (72 runs sampled)
Comment on lines +186 to +187
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why, but the got function cuts out 3k-4k req/s.

// got - promise core x 6,756 ops/sec ±5.32% (80 runs sampled)
// got - stream core x 6,863 ops/sec ±4.68% (76 runs sampled)
// got - stream core - normalized options x 7,960 ops/sec ±3.83% (81 runs sampled)
Copy link
Collaborator Author

@szmarczak szmarczak Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normalizing cuts out ~1k req/s but the time is needed to duplicate the options, so the original ones won't be altered if you use hooks to modify the request. Hooks are our main adventage while being compared to other HTTP clients.

// request - callback x 6,912 ops/sec ±6.50% (76 runs sampled)
// request - stream x 7,821 ops/sec ±4.28% (80 runs sampled)
// node-fetch - promise x 7,036 ops/sec ±8.17% (78 runs sampled)
// node-fetch - stream x 7,877 ops/sec ±4.17% (80 runs sampled)
// axios - promise x 6,613 ops/sec ±3.22% (76 runs sampled)
// axios - stream x 8,642 ops/sec ±2.84% (79 runs sampled)
// https - stream x 9,955 ops/sec ±6.36% (76 runs sampled)
// Fastest is https - stream

// got - normalize options x 166,389 ops/sec ±0.63% (91 runs sampled)
16 changes: 16 additions & 0 deletions benchmark/server.ts
@@ -0,0 +1,16 @@
import {AddressInfo} from 'net';
import https = require('https');
// @ts-ignore No types
import createCert = require('create-cert');

(async () => {
const keys = await createCert({days: 365, commonName: 'localhost'});

const server = https.createServer(keys, (_request, response) => {
response.end('ok');
}).listen(8080, () => {
const {port} = server.address() as AddressInfo;

console.log(`Listening at https://localhost:${port}`);
});
})();
12 changes: 6 additions & 6 deletions documentation/migration-guides.md
Expand Up @@ -141,9 +141,9 @@ Hooks are powerful, aren't they? [Read more](../readme.md#hooks) to see what els
Let's take a quick look at another example from Request's readme:

```js
http.createServer((request, response) => {
if (request.url === '/doodle.png') {
request.pipe(request('https://example.com/doodle.png')).pipe(response);
http.createServer((serverRequest, serverResponse) => {
if (serverRequest.url === '/doodle.png') {
serverRequest.pipe(request('https://example.com/doodle.png')).pipe(serverResponse);
}
});
```
Expand All @@ -157,15 +157,15 @@ const got = require('got');

const pipeline = promisify(stream.pipeline);

http.createServer(async (request, response) => {
if (request.url === '/doodle.png') {
http.createServer(async (serverRequest, serverResponse) => {
if (serverRequest.url === '/doodle.png') {
// When someone makes a request to our server, we receive a body and some headers.
// These are passed to Got. Got proxies downloaded data to our server response,
// so you don't have to do `response.writeHead(statusCode, headers)` and `response.end(body)`.
// It's done automatically.
await pipeline(
got.stream('https://example.com/doodle.png'),
response
serverResponse
);
}
});
Expand Down
43 changes: 20 additions & 23 deletions package.json
Expand Up @@ -7,10 +7,10 @@
"funding": "https://github.com/sindresorhus/got?sponsor=1",
"main": "dist/source",
"engines": {
"node": ">=10"
"node": ">=10.19.0"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@szmarczak Sorry to be commenting on a merged PR, but is there a reason you had to bump this? This should probably be mentioned as a breaking change?

},
"scripts": {
"test": "xo && tsc --noEmit && nyc ava",
"test": "xo && tsc --noEmit && nyc --reporter=html --reporter=text ava",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very useful to debug later. Generates both HTML output and text output as usual.

"release": "np",
"build": "del-cli dist && tsc",
"prepare": "npm run build"
Expand All @@ -33,71 +33,68 @@
"fetch",
"net",
"network",
"electron",
"gzip",
"brotli",
"requests",
"human-friendly",
"axios",
"superagent"
"superagent",
"node-fetch",
"ky"
],
"dependencies": {
"@sindresorhus/is": "^2.0.0",
"@sindresorhus/is": "^2.1.0",
"@szmarczak/http-timer": "^4.0.0",
"@types/cacheable-request": "^6.0.1",
"@types/keyv": "3.1.1",
"@types/responselike": "1.0.0",
"cacheable-lookup": "^2.0.0",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^4.1.1",
"cacheable-request": "^7.0.1",
"decompress-response": "^5.0.0",
"duplexer3": "^0.1.4",
"get-stream": "^5.0.0",
"http2-wrapper": "^1.0.0-beta.4.3",
"lowercase-keys": "^2.0.0",
"mimic-response": "^2.1.0",
"p-cancelable": "^2.0.0",
"p-event": "^4.0.0",
"responselike": "^2.0.0",
"to-readable-stream": "^2.0.0",
"type-fest": "^0.10.0"
"responselike": "^2.0.0"
},
"devDependencies": {
"@ava/typescript": "^1.1.1",
"@sindresorhus/tsconfig": "^0.7.0",
"@types/duplexer3": "^0.1.0",
"@types/benchmark": "^1.0.31",
"@types/express": "^4.17.2",
"@types/lolex": "^5.1.0",
"@types/node": "13.1.2",
"@types/proxyquire": "^1.3.28",
"@types/node-fetch": "^2.5.5",
"@types/request": "^2.48.4",
"@types/sinon": "^7.0.13",
"@types/tough-cookie": "^2.3.5",
"@typescript-eslint/eslint-plugin": "^2.19.2",
"@typescript-eslint/parser": "^2.19.2",
"ava": "^3.3.0",
"axios": "^0.19.2",
"benchmark": "^2.1.4",
"coveralls": "^3.0.4",
"create-test-server": "^3.0.1",
"del-cli": "^3.0.0",
"delay": "^4.3.0",
"eslint-config-xo-typescript": "^0.26.0",
"express": "^4.17.1",
"form-data": "^3.0.0",
"get-port": "^5.0.0",
"keyv": "^4.0.0",
"lolex": "^6.0.0",
"nock": "^12.0.0",
"node-fetch": "^2.6.0",
"np": "^6.0.0",
"nyc": "^15.0.0",
"proxyquire": "^2.0.1",
"p-event": "^4.0.0",
"sinon": "^8.1.1",
"slow-stream": "0.0.4",
"tempy": "^0.4.0",
"to-readable-stream": "^2.1.0",
"tough-cookie": "^3.0.0",
"typescript": "3.7.5",
"xo": "^0.26.0"
"xo": "^0.26.1"
},
"types": "dist/source",
"sideEffects": false,
"browser": {
"electron": false
},
"ava": {
"files": [
"test/*"
Expand Down