Skip to content

Commit

Permalink
Optimize http2.auto
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Nov 30, 2019
1 parent 5a845fc commit 4ec9cd9
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 117 deletions.
51 changes: 23 additions & 28 deletions README.md
Expand Up @@ -153,19 +153,6 @@ const options = {
// }
```

### http2.auto.prepareRequest(options)

Performs [ALPN](https://nodejs.org/api/tls.html#tls_alpn_and_sni) negotiation.
Returns a Promise giving proper request function depending on the ALPN protocol.

**Note:** the request function takes only two arguments: `options` and `callback`.

**Tip:** the `agent` option also accepts an object with `http`, `https` and `http2` properties.

### http2.auto.resolveALPN(options)

Returns a Promise giving the best ALPN protocol possible. It can be either `h2` or `http/1.1`.

### http2.auto.protocolCache

An instance of [`quick-lru`](https://github.com/sindresorhus/quick-lru) used for ALPN cache.
Expand Down Expand Up @@ -347,31 +334,39 @@ agent.on('session', session => {

CPU: Intel i7-7700k<br>
Server: H2O 2.2.5 [`h2o.conf`](h2o.conf)<br>
Node: v12.10.0
Node: 13.0.1

```
http2-wrapper x 9,954 ops/sec ±3.72% (81 runs sampled)
http2-wrapper - preconfigured session x 12,309 ops/sec ±1.48% (87 runs sampled)
http2 x 14,664 ops/sec ±1.63% (78 runs sampled)
http2 - using PassThrough proxies x 11,884 ops/sec ±2.43% (82 runs sampled)
https x 1,586 ops/sec ±4.05% (79 runs sampled)
http x 5,886 ops/sec ±2.73% (76 runs sampled)
http2-wrapper x 10,246 ops/sec ±2.95% (82 runs sampled)
http2-wrapper - preconfigured session x 12,956 ops/sec ±1.45% (84 runs sampled)
http2-wrapper - auto x 9,621 ops/sec ±1.97% (80 runs sampled)
http2 x 14,860 ops/sec ±1.68% (80 runs sampled)
http2 - using PassThrough proxies x 12,154 ops/sec ±3.08% (81 runs sampled)
https x 1,490 ops/sec ±4.35% (72 runs sampled)
http x 5,997 ops/sec ±3.63% (76 runs sampled)
Fastest is http2
```

`http2-wrapper`:

- It's `1.473x` slower than `http2`.
- It's `1.194x` slower than `http2` with `2xPassThrough`.
- It's `6.276x` faster than `https`.
- It's `1.691x` faster than `http`.
- It's `1.4503x` slower than `http2`.
- It's `1.1862x` slower than `http2` with `2xPassThrough`.
- It's `6.8765x` faster than `https`.
- It's `1.7085x` faster than `http`.

`http2-wrapper - preconfigured session`:

- It's `1.191x` slower than `http2`.
- It's `1.036x` faster than `http2` with `2xPassThrough`.
- It's `7.761x` faster than `https`.
- It's `2.091x` faster than `http`.
- It's `1.1470x` slower than `http2`.
- It's almost the same as `http2` with `2xPassThrough`.
- It's `8.6953x` faster than `https`.
- It's `2.1604x` faster than `http`.

`http2-wrapper - auto`:

- It's `1.5445x` slower than `http2`.
- It's `1.2633x` slower than `http2` with `2xPassThrough`.
- It's `6.4570x` faster than `https`.
- It's `1.6043x` faster than `http`.

## Related

Expand Down
16 changes: 15 additions & 1 deletion benchmark.js
Expand Up @@ -19,6 +19,10 @@ const session = http2.connect(destination);
const wrapperSession = http2.connect(destination);

const destinationOpts = urlToOptions(destination);
const destinationOptsWithSession = {
...destinationOpts,
h2session: wrapperSession
};
const destinationHTTPOpts = urlToOptions(destinationHTTP);

suite.add('http2-wrapper', {
Expand All @@ -34,7 +38,7 @@ suite.add('http2-wrapper', {
}).add('http2-wrapper - preconfigured session', {
defer: true,
fn: deferred => {
wrapper.get(destinationOpts, {session: wrapperSession}, response => {
wrapper.get(destinationOptsWithSession, response => {
response.resume();
response.once('end', () => {
deferred.resolve();
Expand All @@ -44,6 +48,16 @@ suite.add('http2-wrapper', {
onComplete: () => {
wrapperSession.close();
}
}).add('http2-wrapper - auto', {
defer: true,
fn: async deferred => {
(await wrapper.auto(destinationOpts, response => {
response.resume();
response.once('end', () => {
deferred.resolve();
});
})).end();
}
}).add('http2', {
defer: true,
fn: deferred => {
Expand Down
114 changes: 58 additions & 56 deletions source/auto.js
@@ -1,86 +1,88 @@
'use strict';
const http = require('http');
const https = require('https');
const resolveALPN = require('resolve-alpn');
const QuickLRU = require('quick-lru');
const Http2ClientRequest = require('./client-request');
const httpResolveALPN = require('./utils/http-resolve-alpn');
const calculateServerName = require('./utils/calculate-server-name');
const urlToOptions = require('./utils/url-to-options');

const cache = new QuickLRU({maxSize: 100});

const prepareRequest = async options => {
const resolveProtocol = async options => {
const name = `${options.host}:${options.port}:${options.ALPNProtocols.sort()}`;

if (!cache.has(name)) {
const result = (await resolveALPN(options)).alpnProtocol;
cache.set(name, result);

return result;
}

return cache.get(name);
};

module.exports = async (input, options, callback) => {
if (typeof input === 'string' || input instanceof URL) {
input = urlToOptions(new URL(input));
}

if (typeof options === 'function') {
callback = options;
}

options = {
ALPNProtocols: ['h2', 'http/1.1'],
protocol: 'https:',
...input,
...options,
resolveSocket: false
};

options.host = options.hostname || options.host || 'localhost';
options.session = options.tlsSession;
options.servername = options.servername || calculateServerName(options);

if (options.protocol === 'https:') {
const host = options.hostname || options.host || 'localhost';
const port = options.port || 443;
options.port = options.port || 443;

const ALPNProtocols = options.ALPNProtocols || ['h2', 'http/1.1'];
const name = `${host}:${port}:${ALPNProtocols.sort()}`;
const {path} = options;
options.path = options.socketPath;

let alpnProtocol = cache.get(name);
const protocol = await resolveProtocol(options);

if (typeof alpnProtocol === 'undefined') {
alpnProtocol = (await httpResolveALPN(options)).alpnProtocol;
cache.set(name, alpnProtocol);
}
options.path = path;

if (alpnProtocol === 'h2') {
return (options, callback) => {
if (options.agent && options.agent.http2) {
options = {
...options,
agent: options.agent.http2
};
}

return Http2ClientRequest.request(options, callback);
};
}
if (protocol === 'h2') {
if (options.agent && options.agent.http2) {
options.agent = options.agent.http2;
}

return (options, callback) => {
options = {
...options,
_defaultAgent: https.globalAgent,
session: options.tlsSession
};
return new Http2ClientRequest(options, callback);
}

if (protocol === 'http/1.1') {
if (options.agent && options.agent.https) {
options.agent = options.agent.https;
}

return http.request(options, callback);
};
}

return (options, callback) => {
options = {
...options,
_defaultAgent: http.globalAgent
};
options._defaultAgent = https.globalAgent;

if (options.agent && options.agent.http) {
options.agent = options.agent.http;
return http.request(options, callback);
}

return http.request(options, callback);
};
};

module.exports = async (input, options, callback) => {
if (typeof input === 'string' || input instanceof URL) {
input = urlToOptions(new URL(input));
throw new Error('Unknown ALPN protocol');
}

options = {
protocol: 'https:',
...input,
...options
};
options.port = options.port || 80;

if (options.agent && options.agent.http) {
options.agent = options.agent.http;
}

const request = await prepareRequest(options);
options._defaultAgent = http.globalAgent;

return request(options, callback);
return http.request(options, callback);
};

module.exports.resolveALPN = httpResolveALPN;
module.exports.prepareRequest = prepareRequest;
module.exports.protocolCache = cache;
5 changes: 0 additions & 5 deletions source/client-request.js
Expand Up @@ -372,9 +372,4 @@ class ClientRequest extends Writable {
}
}

const request = (url, options, callback) => {
return new ClientRequest(url, options, callback);
};

module.exports = ClientRequest;
module.exports.request = request;
14 changes: 9 additions & 5 deletions source/index.js
Expand Up @@ -5,19 +5,23 @@ const ClientRequest = require('./client-request');
const IncomingMessage = require('./incoming-message');
const auto = require('./auto');

const request = (url, options, callback) => {
return new ClientRequest(url, options, callback);
};

const get = (url, options, callback) => {
const req = ClientRequest.request(url, options, callback);
const req = new ClientRequest(url, options, callback);
req.end();

return req;
};

module.exports = {
...http2,
ClientRequest,
IncomingMessage,
...agent,
auto,
request: ClientRequest.request,
request,
get,
ClientRequest,
IncomingMessage
auto
};
8 changes: 4 additions & 4 deletions source/utils/calculate-server-name.js
@@ -1,18 +1,18 @@
'use strict';
const net = require('net');
/* istanbul ignore file: https://github.com/nodejs/node/blob/v12.10.0/lib/_http_agent.js */
/* istanbul ignore file: https://github.com/nodejs/node/blob/v13.0.1/lib/_http_agent.js */

module.exports = (options, headers) => {
module.exports = options => {
let servername = options.host;
const hostHeader = headers.host;
const hostHeader = options.headers && options.headers.host;

if (hostHeader) {
if (hostHeader.startsWith('[')) {
const index = hostHeader.indexOf(']');
if (index === -1) {
servername = hostHeader;
} else {
servername = hostHeader.substr(1, index - 1);
servername = hostHeader.slice(1, -1);
}
} else {
servername = hostHeader.split(':', 1)[0];
Expand Down
18 changes: 0 additions & 18 deletions source/utils/http-resolve-alpn.js

This file was deleted.

0 comments on commit 4ec9cd9

Please sign in to comment.