Skip to content

Commit

Permalink
authority -> origin
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Feb 17, 2020
1 parent 269a1f9 commit 0f71a33
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 80 deletions.
72 changes: 34 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,9 @@ Usage example:
const http2 = require('http2-wrapper');

class MyAgent extends http2.Agent {
createConnection(authority, options) {
console.log(`Connecting to ${authority}`);
return http2.Agent.connect(authority, options);
createConnection(origin, options) {
console.log(`Connecting to ${origin}`);
return http2.Agent.connect(origin, options);
}
}

Expand Down Expand Up @@ -244,14 +244,9 @@ Default: `100`

The maximum amount of cached TLS sessions.

#### Agent.normalizeAuthority([authority](#authority), servername)
#### Agent.normalizeOrigin(url)

Normalizes the authority URL.

```js
Agent.normalizeAuthority('https://example.com:443');
// => 'https://example.com'
```
Returns a string representing the origin of the URL.

#### agent.settings

Expand All @@ -269,23 +264,23 @@ Agent.normalizeOptions({servername: 'example.com'});
// => ':example.com'
```

#### agent.getSession(authority, options)
#### agent.getSession(origin, options)

##### [authority](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
##### [origin](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)

Type: `string` `URL` `object`

Authority used to create a new session.
An origin used to create new session.

##### [options](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)

Type: `object`

Options used to create a new session.
The options used to create new session.

Returns a Promise giving free `Http2Session`. If no free sessions are found, a new one is created.

#### agent.getSession([authority](#authority), [options](options-1), listener)
#### agent.getSession([origin](#origin), [options](options-1), listener)

##### listener

Expand All @@ -300,13 +295,13 @@ Type: `object`

If the `listener` argument is present, the Promise will resolve immediately. It will use the `resolve` function to pass the session.

#### agent.request([authority](#authority), [options](#options-1), [headers](https://nodejs.org/api/http2.html#http2_headers_object))
#### agent.request([origin](#origin), [options](#options-1), [headers](https://nodejs.org/api/http2.html#http2_headers_object))

Returns a Promise giving `Http2Stream`.

#### agent.createConnection([authority](#authority), [options](#options-1))
#### agent.createConnection([origin](#origin), [options](#options-1))

Returns a new `TLSSocket`. It defaults to `Agent.connect(authority, options)`.
Returns a new `TLSSocket`. It defaults to `Agent.connect(origin, options)`.

#### agent.closeFreeSessions()

Expand All @@ -332,43 +327,44 @@ agent.on('session', session => {

## Benchmarks

CPU: Intel i7-7700k<br>
CPU: Intel i7-7700k (governor: performance)<br>
Server: H2O v2.2.5 [`h2o.conf`](h2o.conf)<br>
Node: v13.8.0

`auto` means `http2wrapper.auto`.

```
http2-wrapper x 10,598 ops/sec ±4.04% (84 runs sampled)
http2-wrapper - preconfigured session x 13,640 ops/sec ±1.71% (86 runs sampled)
http2-wrapper - auto x 9,629 ops/sec ±1.58% (84 runs sampled)
http2 x 16,540 ops/sec ±1.05% (82 runs sampled)
http2 - 2xPassThrough x 13,534 ops/sec ±0.99% (86 runs sampled)
https - auto - keepalive x 13,108 ops/sec ±2.67% (78 runs sampled)
https - keepalive x 13,113 ops/sec ±3.32% (80 runs sampled)
https x 1,541 ops/sec ±2.72% (79 runs sampled)
http x 5,957 ops/sec ±2.34% (72 runs sampled)
http2-wrapper x 11,961 ops/sec ±1.31% (82 runs sampled)
http2-wrapper - preconfigured session x 13,507 ops/sec ±1.98% (85 runs sampled)
http2-wrapper - auto x 10,798 ops/sec ±1.73% (86 runs sampled)
http2 x 17,272 ops/sec ±1.22% (85 runs sampled)
http2 - 2xPassThrough x 14,262 ops/sec ±1.07% (83 runs sampled)
https - auto - keepalive x 13,066 ops/sec ±3.14% (80 runs sampled)
https - keepalive x 13,639 ops/sec ±0.82% (86 runs sampled)
https x 1,632 ops/sec ±0.97% (83 runs sampled)
http x 6,095 ops/sec ±2.09% (80 runs sampled)
Fastest is http2
```

`http2-wrapper`:
- 36% slower than `http2` (22% compared to `http2 - 2xPassThrough`)
- 19% slower than `https - keepalive`
- 78% faster than `http`
- 31% less performant than `http2` (16% compared to `http2 - 2xPassThrough`)
- 12% less performant than `https - keepalive`
- 96% more performant than `http`

`http2-wrapper - preconfigured session`:
- 18% slower than `http2` (as performant as `http2 - 2xPassThrough`)
- 22% less performant than `http2` (5% compared to `http2 - 2xPassThrough`)
- as performant as `https - keepalive`
- 129% faster than `http`
- 122% more performant than `http`

`http2-wrapper - auto`:
- 27% slower than `https - keepalive`
- 62% faster than `http`
- 37% less performant than `http2` (24% compared to `http2 - 2xPassThrough`)
- 21% less performant than `https - keepalive`
- 77% more performant than `http`

`https - auto - keepalive`:
- 21% slower than `http2` (as performant as `http2 - 2xPassThrough`)
- as performant as `https - keepalive`
- 120% faster than `http`
- 24% less performant than `http2` (8% compared to `http2 - 2xPassThrough`)
- 4% less performant than `https - keepalive`
- 114% more performant than `http`

## Related

Expand Down
65 changes: 29 additions & 36 deletions source/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ const addSession = (where, name, session) => {
}
};

const getSessions = (where, name, normalizedAuthority) => {
const getSessions = (where, name, normalizedOrigin) => {
if (Reflect.has(where, name)) {
return where[name].filter(session => {
return !session.closed && !session.destroyed && session.originSet.includes(normalizedAuthority);
return !session.closed && !session.destroyed && session.originSet.includes(normalizedOrigin);
});
}

Expand Down Expand Up @@ -131,7 +131,7 @@ class Agent extends EventEmitter {
this.freeSessions = {};

// The queue for creating new sessions. It looks like this:
// QUEUE[NORMALIZED_OPTIONS][NORMALIZED_AUTHORITY] = ENTRY_FUNCTION
// QUEUE[NORMALIZED_OPTIONS][NORMALIZED_ORIGIN] = ENTRY_FUNCTION
//
// The entry function has `listeners`, `completed` and `destroyed` properties.
// `listeners` is an array of objects containing `resolve` and `reject` functions.
Expand Down Expand Up @@ -159,19 +159,12 @@ class Agent extends EventEmitter {
this.tlsSessionCache = new QuickLRU({maxSize: maxCachedTlsSessions});
}

static normalizeAuthority(authority, servername) {
if (typeof authority === 'string') {
authority = new URL(authority);
static normalizeOrigin(url) {
if (typeof url === 'string') {
url = new URL(url);
}

const host = servername || authority.hostname || authority.host || 'localhost';
const port = authority.port || 443;

if (port === 443) {
return `https://${host}`;
}

return `https://${host}:${port}`;
return url.origin;
}

normalizeOptions(options) {
Expand All @@ -188,14 +181,14 @@ class Agent extends EventEmitter {
return normalized;
}

_tryToCreateNewSession(normalizedOptions, normalizedAuthority) {
if (!Reflect.has(this.queue, normalizedOptions) || !Reflect.has(this.queue[normalizedOptions], normalizedAuthority)) {
_tryToCreateNewSession(normalizedOptions, normalizedOrigin) {
if (!Reflect.has(this.queue, normalizedOptions) || !Reflect.has(this.queue[normalizedOptions], normalizedOrigin)) {
return;
}

// We need the busy sessions length to check if a session can be created.
const busyLength = getSessions(this.busySessions, normalizedOptions, normalizedAuthority).length;
const item = this.queue[normalizedOptions][normalizedAuthority];
const busyLength = getSessions(this.busySessions, normalizedOptions, normalizedOrigin).length;
const item = this.queue[normalizedOptions][normalizedOrigin];

// The entry function can be run only once.
if (busyLength < this.maxSessions && !item.completed) {
Expand All @@ -210,7 +203,7 @@ class Agent extends EventEmitter {
closeCoveredSessions(this.busySessions, normalizedOptions, session);
}

getSession(authority, options, listeners) {
getSession(origin, options, listeners) {
return new Promise((resolve, reject) => {
if (Array.isArray(listeners)) {
listeners = [...listeners];
Expand All @@ -223,11 +216,11 @@ class Agent extends EventEmitter {
}

const normalizedOptions = this.normalizeOptions(options);
const normalizedAuthority = Agent.normalizeAuthority(authority, options && options.servername);
const normalizedOrigin = Agent.normalizeOrigin(origin);

if (Reflect.has(this.freeSessions, normalizedOptions)) {
// Look for all available free sessions.
const freeSessions = getSessions(this.freeSessions, normalizedOptions, normalizedAuthority);
const freeSessions = getSessions(this.freeSessions, normalizedOptions, normalizedOrigin);

if (freeSessions.length !== 0) {
// Use session which has the biggest stream capacity in order to use the smallest number of sessions possible.
Expand All @@ -251,9 +244,9 @@ class Agent extends EventEmitter {
}

if (Reflect.has(this.queue, normalizedOptions)) {
if (Reflect.has(this.queue[normalizedOptions], normalizedAuthority)) {
if (Reflect.has(this.queue[normalizedOptions], normalizedOrigin)) {
// There's already an item in the queue, just attach ourselves to it.
this.queue[normalizedOptions][normalizedAuthority].listeners.push(...listeners);
this.queue[normalizedOptions][normalizedOrigin].listeners.push(...listeners);

return;
}
Expand All @@ -266,8 +259,8 @@ class Agent extends EventEmitter {
// 2. an error occurs.
const removeFromQueue = () => {
// Our entry can be replaced. We cannot remove the new one.
if (Reflect.has(this.queue, normalizedOptions) && this.queue[normalizedOptions][normalizedAuthority] === entry) {
delete this.queue[normalizedOptions][normalizedAuthority];
if (Reflect.has(this.queue, normalizedOptions) && this.queue[normalizedOptions][normalizedOrigin] === entry) {
delete this.queue[normalizedOptions][normalizedOrigin];

if (Object.keys(this.queue[normalizedOptions]).length === 0) {
delete this.queue[normalizedOptions];
Expand All @@ -277,14 +270,14 @@ class Agent extends EventEmitter {

// The main logic is here
const entry = () => {
const name = `${normalizedAuthority}:${normalizedOptions}`;
const name = `${normalizedOrigin}:${normalizedOptions}`;
let receivedSettings = false;
let servername;

try {
const tlsSessionCache = this.tlsSessionCache.get(name);

const session = http2.connect(authority, {
const session = http2.connect(origin, {
createConnection: this.createConnection,
settings: this.settings,
session: tlsSessionCache ? tlsSessionCache.session : undefined,
Expand Down Expand Up @@ -365,7 +358,7 @@ class Agent extends EventEmitter {
removeSession(this.freeSessions, normalizedOptions, session);

// There may be another session awaiting.
this._tryToCreateNewSession(normalizedOptions, normalizedAuthority);
this._tryToCreateNewSession(normalizedOptions, normalizedOrigin);
});

// Iterates over the queue and processes listeners.
Expand Down Expand Up @@ -455,7 +448,7 @@ class Agent extends EventEmitter {
// Check if we haven't managed to execute all listeners.
if (listeners.length !== 0) {
// Request for a new session with predefined listeners.
this.getSession(normalizedAuthority, options, listeners);
this.getSession(normalizedOrigin, options, listeners);
listeners.length = 0;
}

Expand Down Expand Up @@ -534,8 +527,8 @@ class Agent extends EventEmitter {
entry.completed = false;
entry.destroyed = false;

this.queue[normalizedOptions][normalizedAuthority] = entry;
this._tryToCreateNewSession(normalizedOptions, normalizedAuthority);
this.queue[normalizedOptions][normalizedOrigin] = entry;
this._tryToCreateNewSession(normalizedOptions, normalizedOrigin);
});
}

Expand All @@ -550,15 +543,15 @@ class Agent extends EventEmitter {
});
}

createConnection(authority, options) {
return Agent.connect(authority, options);
createConnection(origin, options) {
return Agent.connect(origin, options);
}

static connect(authority, options) {
static connect(origin, options) {
options.ALPNProtocols = ['h2'];

const port = authority.port || 443;
const host = authority.hostname || authority.host;
const port = origin.port || 443;
const host = origin.hostname || origin.host;

if (typeof options.servername === 'undefined') {
options.servername = host;
Expand Down
12 changes: 7 additions & 5 deletions source/client-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const {
} = http2.constants;

const kHeaders = Symbol('headers');
const kAuthority = Symbol('authority');
const kOrigin = Symbol('origin');
const kSession = Symbol('session');
const kOptions = Symbol('options');
const kFlushedHeaders = Symbol('flushedHeaders');
Expand Down Expand Up @@ -106,14 +106,16 @@ class ClientRequest extends Writable {
options.path = options.socketPath;

this[kOptions] = options;
this[kAuthority] = Agent.normalizeAuthority(options, options.servername);

if (!Reflect.has(this[kHeaders], ':authority')) {
this[kHeaders][':authority'] = this[kAuthority].slice(8);
this[kHeaders][':authority'] = `${options.servername || options.host}:${options.port}`;
}

options.origin = `https://${options.host}:${options.port}`;
this[kOrigin] = options;

if (this.agent && options.preconnect !== false) {
this.agent.getSession(this[kAuthority], options).catch(() => {});
this.agent.getSession(this[kOrigin], options).catch(() => {});
}

if (timeout) {
Expand Down Expand Up @@ -291,7 +293,7 @@ class ClientRequest extends Writable {
this.reusedSocket = true;

try {
onStream(await this.agent.request(this[kAuthority], this[kOptions], this[kHeaders]));
onStream(await this.agent.request(this[kOrigin], this[kOptions], this[kHeaders]));
} catch (error) {
this.emit('error', error);
}
Expand Down
2 changes: 1 addition & 1 deletion test/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ test('sets proper `:authority` header', wrapper, async (t, server) => {
const response = await pEvent(request, 'response');
const body = await getStream(response);

t.is(body, 'example.com');
t.is(body, 'example.com:443');

agent.destroy();
});
Expand Down

0 comments on commit 0f71a33

Please sign in to comment.