http2-wrapper
Advanced tools
Comparing version 1.0.0-beta.3 to 1.0.0-beta.4
{ | ||
"name": "http2-wrapper", | ||
"version": "1.0.0-beta.3", | ||
"version": "1.0.0-beta.4", | ||
"description": "HTTP2 client, just with the familiar `https` API", | ||
@@ -33,29 +33,25 @@ "main": "source", | ||
"dependencies": { | ||
"quick-lru": "^4.0.1", | ||
"quick-lru": "^5.0.0", | ||
"resolve-alpn": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"@sindresorhus/is": "^1.0.0", | ||
"ava": "^2.4.0", | ||
"@sindresorhus/is": "^2.0.0", | ||
"ava": "^3.3.0", | ||
"benchmark": "^2.1.4", | ||
"coveralls": "^3.0.5", | ||
"coveralls": "^3.0.9", | ||
"create-cert": "^1.0.6", | ||
"get-stream": "^5.1.0", | ||
"got": "^10.0.0-alpha.3", | ||
"lolex": "^4.2.0", | ||
"got": "^10.5.5", | ||
"lolex": "^6.0.0", | ||
"many-keys-map": "^1.0.2", | ||
"nyc": "^14.1.1", | ||
"nyc": "^15.0.0", | ||
"p-event": "^4.1.0", | ||
"tempy": "^0.3.0", | ||
"tempy": "^0.4.0", | ||
"to-readable-stream": "^2.1.0", | ||
"tsd": "^0.8.0", | ||
"xo": "^0.24.0" | ||
"tsd": "^0.11.0", | ||
"xo": "^0.26.1" | ||
}, | ||
"ava": { | ||
"timeout": "2m", | ||
"concurrency": "4", | ||
"helpers": [ | ||
"test/helpers/*" | ||
] | ||
"timeout": "2m" | ||
} | ||
} |
105
README.md
@@ -10,3 +10,3 @@ # http2-wrapper | ||
This package was created to support HTTP2 without the need to rewrite your code.<br> | ||
I recommend adapting to the [`http2`](https://nodejs.org/api/http2.html) module if possible - it's much simpler to use and has many cool features! | ||
I recommend adapting to the [`http2`](https://nodejs.org/api/http2.html) module if possible - it's much simpler to use and has many cool features! Well, it doesn't have agents yet... | ||
@@ -88,3 +88,3 @@ **Tip**: `http2-wrapper` is very useful when you rely on other modules that use the HTTP1 API and you want to support HTTP2. | ||
**Note:** the `session` option was renamed to `tlsSession` for better readability. | ||
**Note:** The `session` option was renamed to `tlsSession` for better readability. | ||
@@ -96,3 +96,3 @@ ### http2.auto(url, options, callback) | ||
**Tip**: the `agent` option also accepts an object with `http`, `https` and `http2` properties. | ||
**Note**: The `agent` option represents an object with `http`, `https` and `http2` properties. | ||
@@ -204,5 +204,5 @@ ```js | ||
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 ${http2.Agent.normalizeOrigin(origin)}`); | ||
return http2.Agent.connect(origin, options); | ||
} | ||
@@ -251,15 +251,17 @@ } | ||
#### Agent.normalizeAuthority([authority](#authority), servername) | ||
#### Agent.normalizeOrigin(url) | ||
Normalizes the authority URL. | ||
Returns a string representing the origin of the URL. | ||
```js | ||
Agent.normalizeAuthority('https://example.com:443'); | ||
// => 'https://example.com' | ||
``` | ||
#### agent.settings | ||
#### Agent.normalizeOptions([options](https://github.com/szmarczak/http2-wrapper/blob/master/source/agent.js)) | ||
Type: `object`<br> | ||
Default: `{enablePush: false}` | ||
Returns a string containing normalized options. | ||
[Settings](https://nodejs.org/api/http2.html#http2_settings_object) used by the current agent instance. | ||
#### agent.normalizeOptions([options](https://github.com/szmarczak/http2-wrapper/blob/master/source/agent.js)) | ||
Returns a string representing normalized options. | ||
```js | ||
@@ -270,16 +272,9 @@ Agent.normalizeOptions({servername: 'example.com'}); | ||
#### agent.settings | ||
#### agent.getSession(origin, options) | ||
Type: `object`<br> | ||
Default: `{enablePush: false}` | ||
##### [origin](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener) | ||
[Settings](https://nodejs.org/api/http2.html#http2_settings_object) used by the current agent instance. | ||
#### agent.getSession(authority, options) | ||
##### [authority](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. | ||
@@ -290,7 +285,7 @@ ##### [options](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener) | ||
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) | ||
@@ -310,13 +305,13 @@ ##### listener | ||
#### 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() | ||
Makes an attempt to close free sessions. Only sessions with 0 concurrent streams are closed. | ||
Makes an attempt to close free sessions. Only sessions with 0 concurrent streams will be closed. | ||
@@ -343,14 +338,17 @@ #### agent.destroy(reason) | ||
CPU: Intel i7-7700k<br> | ||
Server: H2O 2.2.5 [`h2o.conf`](h2o.conf)<br> | ||
Node: 13.0.1 | ||
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,943 ops/sec ±4.19% (80 runs sampled) | ||
http2-wrapper - preconfigured session x 13,600 ops/sec ±1.78% (85 runs sampled) | ||
http2-wrapper - auto x 10,080 ops/sec ±4.22% (80 runs sampled) | ||
http2 x 16,078 ops/sec ±1.67% (85 runs sampled) | ||
http2 - using PassThrough proxies x 13,090 ops/sec ±6.36% (85 runs sampled) | ||
https x 1,471 ops/sec ±4.05% (74 runs sampled) | ||
http x 6,100 ops/sec ±4.89% (72 runs sampled) | ||
http2-wrapper x 12,417 ops/sec ±3.72% (83 runs sampled) | ||
http2-wrapper - preconfigured session x 14,517 ops/sec ±1.39% (83 runs sampled) | ||
http2-wrapper - auto x 11,373 ops/sec ±3.17% (84 runs sampled) | ||
http2 x 16,172 ops/sec ±1.21% (85 runs sampled) | ||
https - auto - keepalive x 13,251 ops/sec ±3.84% (79 runs sampled) | ||
https - keepalive x 13,158 ops/sec ±2.88% (78 runs sampled) | ||
https x 1,618 ops/sec ±2.07% (82 runs sampled) | ||
http x 5,922 ops/sec ±2.87% (79 runs sampled) | ||
Fastest is http2 | ||
@@ -360,21 +358,20 @@ ``` | ||
`http2-wrapper`: | ||
- 23% less performant than `http2` | ||
- 6% less performant than `https - keepalive` | ||
- 110% more performant than `http` | ||
- It's `1.4692x` slower than `http2`. | ||
- It's `1.1962x` slower than `http2` with `2xPassThrough`. | ||
- It's `7.4392x` faster than `https`. | ||
- It's `1.7939x` faster than `http`. | ||
`http2-wrapper - preconfigured session`: | ||
- 10% less performant than `http2` | ||
- 10% more performant than `https - keepalive` | ||
- 145% more performant than `http` | ||
- It's `1.1822x` slower than `http2`. | ||
- It's almost the same as `http2` with `2xPassThrough`. | ||
- It's `9.2454x` faster than `https`. | ||
- It's `2.2295x` faster than `http`. | ||
`http2-wrapper - auto`: | ||
- 30% less performant than `http2` | ||
- 14% less performant than `https - keepalive` | ||
- 92% more performant than `http` | ||
- It's `1.5950x` slower than `http2`. | ||
- It's `1.2986x` slower than `http2` with `2xPassThrough`. | ||
- It's `6.8525x` faster than `https`. | ||
- It's `1.6525x` faster than `http`. | ||
`https - auto - keepalive`: | ||
- 18% less performant than `http2` | ||
- as performant as `https - keepalive` | ||
- 124% more performant than `http` | ||
@@ -381,0 +378,0 @@ ## Related |
@@ -9,2 +9,3 @@ 'use strict'; | ||
const kRequest = Symbol('request'); | ||
const kOriginSet = Symbol('cachedOriginSet'); | ||
@@ -72,6 +73,6 @@ const nameKeys = [ | ||
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[kOriginSet].includes(normalizedOrigin); | ||
}); | ||
@@ -95,6 +96,6 @@ } | ||
// The set is a proper subset when its length is less than the other set. | ||
coveredSession.originSet.length < session.originSet.length && | ||
coveredSession[kOriginSet].length < session[kOriginSet].length && | ||
// And the other set includes all elements of the subset. | ||
coveredSession.originSet.every(origin => session.originSet.includes(origin)) && | ||
coveredSession[kOriginSet].every(origin => session[kOriginSet].includes(origin)) && | ||
@@ -115,4 +116,4 @@ // Makes sure that the session can handle all requests from the covered session. | ||
if ( | ||
coveredSession.originSet.length < session.originSet.length && | ||
coveredSession.originSet.every(origin => session.originSet.includes(origin)) && | ||
coveredSession[kOriginSet].length < session[kOriginSet].length && | ||
coveredSession[kOriginSet].every(origin => session[kOriginSet].includes(origin)) && | ||
coveredSession[kCurrentStreamsCount] + session[kCurrentStreamsCount] <= session.remoteSettings.maxConcurrentStreams | ||
@@ -138,3 +139,3 @@ ) { | ||
// The queue for creating new sessions. It looks like this: | ||
// QUEUE[NORMALIZED_OPTIONS][NORMALIZED_AUTHORITY] = ENTRY_FUNCTION | ||
// QUEUE[NORMALIZED_OPTIONS][NORMALIZED_ORIGIN] = ENTRY_FUNCTION | ||
// | ||
@@ -167,18 +168,11 @@ // The entry function has `listeners`, `completed` and `destroyed` properties. | ||
static normalizeAuthority(authority, servername) { | ||
if (typeof authority === 'string') { | ||
authority = new URL(authority); | ||
static normalizeOrigin(url, servername) { | ||
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 servername ? `https://${servername}:${url.port || 443}` : url.origin; | ||
} | ||
static normalizeOptions(options) { | ||
normalizeOptions(options) { | ||
let normalized = ''; | ||
@@ -197,4 +191,4 @@ | ||
_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; | ||
@@ -204,4 +198,4 @@ } | ||
// 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]; | ||
@@ -221,3 +215,3 @@ // The entry function can be run only once. | ||
getSession(authority, options, listeners) { | ||
getSession(origin, options, listeners) { | ||
return new Promise((resolve, reject) => { | ||
@@ -234,8 +228,13 @@ if (Array.isArray(listeners)) { | ||
const normalizedOptions = Agent.normalizeOptions(options); | ||
const normalizedAuthority = Agent.normalizeAuthority(authority, options && options.servername); | ||
const normalizedOptions = this.normalizeOptions(options); | ||
const normalizedOrigin = Agent.normalizeOrigin(origin, options && options.servername); | ||
if (normalizedOrigin === undefined) { | ||
reject(new TypeError('The `origin` argument needs to be a string or an URL object')); | ||
return; | ||
} | ||
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); | ||
@@ -264,5 +263,5 @@ if (freeSessions.length !== 0) { | ||
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); | ||
@@ -280,4 +279,4 @@ return; | ||
// 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]; | ||
@@ -292,3 +291,3 @@ if (Object.keys(this.queue[normalizedOptions]).length === 0) { | ||
const entry = () => { | ||
const name = `${normalizedAuthority}:${normalizedOptions}`; | ||
const name = `${normalizedOrigin}:${normalizedOptions}`; | ||
let receivedSettings = false; | ||
@@ -300,3 +299,3 @@ let servername; | ||
const session = http2.connect(authority, { | ||
const session = http2.connect(origin, { | ||
createConnection: this.createConnection, | ||
@@ -312,3 +311,3 @@ settings: this.settings, | ||
// Fetch the smallest amount of free sessions of any origin we have. | ||
const freeSessionsCount = session.originSet.reduce((accumulator, origin) => { | ||
const freeSessionsCount = session[kOriginSet].reduce((accumulator, origin) => { | ||
return Math.min(accumulator, getSessions(this.freeSessions, normalizedOptions, origin).length); | ||
@@ -383,3 +382,3 @@ }, Infinity); | ||
// There may be another session awaiting. | ||
this._tryToCreateNewSession(normalizedOptions, normalizedAuthority); | ||
this._tryToCreateNewSession(normalizedOptions, normalizedOrigin); | ||
}); | ||
@@ -393,3 +392,3 @@ | ||
for (const origin of session.originSet) { | ||
for (const origin of session[kOriginSet]) { | ||
if (Reflect.has(this.queue[normalizedOptions], origin)) { | ||
@@ -424,2 +423,4 @@ const {listeners} = this.queue[normalizedOptions][origin]; | ||
session.once('origin', () => { | ||
session[kOriginSet] = session.originSet; | ||
if (!isFree()) { | ||
@@ -454,2 +455,3 @@ // The session is full. | ||
session[kOriginSet] = session.originSet; | ||
this.emit('session', session); | ||
@@ -477,3 +479,3 @@ | ||
// Request for a new session with predefined listeners. | ||
this.getSession(normalizedAuthority, options, listeners); | ||
this.getSession(normalizedOrigin, options, listeners); | ||
listeners.length = 0; | ||
@@ -557,10 +559,10 @@ } | ||
this.queue[normalizedOptions][normalizedAuthority] = entry; | ||
this._tryToCreateNewSession(normalizedOptions, normalizedAuthority); | ||
this.queue[normalizedOptions][normalizedOrigin] = entry; | ||
this._tryToCreateNewSession(normalizedOptions, normalizedOrigin); | ||
}); | ||
} | ||
request(authority, options, headers) { | ||
request(origin, options, headers) { | ||
return new Promise((resolve, reject) => { | ||
this.getSession(authority, options, [{ | ||
this.getSession(origin, options, [{ | ||
reject, | ||
@@ -574,11 +576,11 @@ resolve: session => { | ||
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; | ||
@@ -585,0 +587,0 @@ if (typeof options.servername === 'undefined') { |
@@ -11,3 +11,31 @@ 'use strict'; | ||
const cache = new QuickLRU({maxSize: 100}); | ||
const queue = new Map(); | ||
const installSocket = (agent, socket, options) => { | ||
socket._httpMessage = {shouldKeepAlive: true}; | ||
const onFree = () => { | ||
agent.emit('free', socket, options); | ||
}; | ||
socket.on('free', onFree); | ||
const onClose = () => { | ||
agent.removeSocket(socket, options); | ||
}; | ||
socket.on('close', onClose); | ||
const onRemove = () => { | ||
agent.removeSocket(socket, options); | ||
socket.off('close', onClose); | ||
socket.off('free', onFree); | ||
socket.off('agentRemove', onRemove); | ||
}; | ||
socket.on('agentRemove', onRemove); | ||
agent.emit('free', socket, options); | ||
}; | ||
const resolveProtocol = async options => { | ||
@@ -17,6 +45,42 @@ const name = `${options.host}:${options.port}:${options.ALPNProtocols.sort()}`; | ||
if (!cache.has(name)) { | ||
const result = (await resolveALPN(options)).alpnProtocol; | ||
cache.set(name, result); | ||
if (queue.has(name)) { | ||
const result = await queue.get(name); | ||
return result.alpnProtocol; | ||
} | ||
return result; | ||
const {path} = options; | ||
options.path = options.socketPath; | ||
const resultPromise = resolveALPN(options); | ||
queue.set(name, resultPromise); | ||
try { | ||
const {socket, alpnProtocol} = await resultPromise; | ||
cache.set(name, alpnProtocol); | ||
options.path = path; | ||
if (alpnProtocol === 'h2') { | ||
// TODO: Reuse socket | ||
socket.end(); | ||
} else { | ||
const agent = options.agent || https.globalAgent; | ||
if (options.createConnection) { | ||
socket.end(); | ||
} else if (agent.keepAlive) { | ||
installSocket(agent, socket, options); | ||
} else { | ||
options.createConnection = () => socket; | ||
} | ||
} | ||
queue.delete(name); | ||
return alpnProtocol; | ||
} catch (error) { | ||
queue.delete(name); | ||
throw error; | ||
} | ||
} | ||
@@ -34,2 +98,3 @@ | ||
callback = options; | ||
options = undefined; | ||
} | ||
@@ -42,22 +107,28 @@ | ||
...options, | ||
resolveSocket: false | ||
resolveSocket: true | ||
}; | ||
const isHttps = options.protocol === 'https:'; | ||
const agents = options.agent; | ||
options.host = options.hostname || options.host || 'localhost'; | ||
options.session = options.tlsSession; | ||
options.servername = options.servername || calculateServerName(options); | ||
options.port = options.port || (isHttps ? 443 : 80); | ||
options._defaultAgent = isHttps ? https.globalAgent : http.globalAgent; | ||
if (options.protocol === 'https:') { | ||
options.port = options.port || 443; | ||
if (agents) { | ||
if (agents.addRequest) { | ||
throw new Error('The `options.agent` object can contain only `http`, `https` or `http2` properties'); | ||
} | ||
const {path} = options; | ||
options.path = options.socketPath; | ||
options.agent = agents[isHttps ? 'https' : 'http']; | ||
} | ||
if (isHttps) { | ||
const protocol = await resolveProtocol(options); | ||
options.path = path; | ||
if (protocol === 'h2') { | ||
if (options.agent && options.agent.http2) { | ||
options.agent = options.agent.http2; | ||
if (agents) { | ||
options.agent = agents.http2; | ||
} | ||
@@ -67,20 +138,4 @@ | ||
} | ||
if (options.agent && options.agent.https) { | ||
options.agent = options.agent.https; | ||
} | ||
options._defaultAgent = https.globalAgent; | ||
return http.request(options, callback); | ||
} | ||
options.port = options.port || 80; | ||
if (options.agent && options.agent.http) { | ||
options.agent = options.agent.http; | ||
} | ||
options._defaultAgent = http.globalAgent; | ||
return http.request(options, callback); | ||
@@ -87,0 +142,0 @@ }; |
@@ -26,3 +26,3 @@ 'use strict'; | ||
const kHeaders = Symbol('headers'); | ||
const kAuthority = Symbol('authority'); | ||
const kOrigin = Symbol('origin'); | ||
const kSession = Symbol('session'); | ||
@@ -33,3 +33,3 @@ const kOptions = Symbol('options'); | ||
const isValidHttpToken = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; | ||
const isInvalidHeaderValue = /[^\t\u0020-\u007e\u0080-\u00ff]/; | ||
const isInvalidHeaderValue = /[^\t\u0020-\u007E\u0080-\u00FF]/; | ||
@@ -45,3 +45,3 @@ class ClientRequest extends Writable { | ||
if (typeof options === 'function') { | ||
if (typeof options === 'function' || options === undefined) { | ||
// (options, callback) | ||
@@ -84,3 +84,3 @@ callback = options; | ||
const {timeout} = options; | ||
delete options.timeout; | ||
options.timeout = undefined; | ||
@@ -113,10 +113,12 @@ this[kHeaders] = Object.create(null); | ||
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(() => {}); | ||
} | ||
@@ -135,2 +137,6 @@ | ||
get method() { | ||
return this[kHeaders][HTTP2_HEADER_METHOD]; | ||
} | ||
set method(value) { | ||
@@ -142,4 +148,4 @@ if (value) { | ||
get method() { | ||
return this[kHeaders][HTTP2_HEADER_METHOD]; | ||
get path() { | ||
return this[kHeaders][HTTP2_HEADER_PATH]; | ||
} | ||
@@ -153,6 +159,2 @@ | ||
get path() { | ||
return this[kHeaders][HTTP2_HEADER_PATH]; | ||
} | ||
_write(chunk, encoding, callback) { | ||
@@ -208,3 +210,3 @@ this.flushHeaders(); | ||
flushHeaders() { | ||
async flushHeaders() { | ||
if (this[kFlushedHeaders] || this.destroyed || this.aborted) { | ||
@@ -223,3 +225,3 @@ return; | ||
if (this.destroyed || this.aborted) { | ||
this._request.close(NGHTTP2_CANCEL); | ||
stream.close(NGHTTP2_CANCEL); | ||
return; | ||
@@ -230,21 +232,23 @@ } | ||
if (!isConnectMethod) { | ||
proxyEvents(this._request, this, ['timeout', 'continue', 'close', 'error']); | ||
proxyEvents(stream, this, ['timeout', 'continue', 'close', 'error']); | ||
} | ||
// This event tells we are ready to listen for the data. | ||
this._request.once('response', (headers, flags, rawHeaders) => { | ||
stream.once('response', (headers, flags, rawHeaders) => { | ||
// If we were to emit raw request stream, it would be as fast as the native approach. | ||
// Note that wrapping the raw stream in a Proxy instance won't improve the performance (already tested it). | ||
this.res = new IncomingMessage(this.socket, this._request.readableHighWaterMark); | ||
this.res.req = this; | ||
this.res.statusCode = headers[HTTP2_HEADER_STATUS]; | ||
this.res.headers = headers; | ||
this.res.rawHeaders = rawHeaders; | ||
const response = new IncomingMessage(this.socket, stream.readableHighWaterMark); | ||
this.res = response; | ||
this.res.once('end', () => { | ||
response.req = this; | ||
response.statusCode = headers[HTTP2_HEADER_STATUS]; | ||
response.headers = headers; | ||
response.rawHeaders = rawHeaders; | ||
response.once('end', () => { | ||
if (this.aborted) { | ||
this.res.aborted = true; | ||
this.res.emit('aborted'); | ||
response.aborted = true; | ||
response.emit('aborted'); | ||
} else { | ||
this.res.complete = true; | ||
response.complete = true; | ||
} | ||
@@ -254,19 +258,27 @@ }); | ||
if (isConnectMethod) { | ||
this.res.upgrade = true; | ||
response.upgrade = true; | ||
// The HTTP1 API says the socket is detached here, | ||
// but we can't do that so we pass the original HTTP2 request. | ||
if (this.emit('connect', this.res, this._request, Buffer.alloc(0))) { | ||
if (this.emit('connect', response, stream, Buffer.alloc(0))) { | ||
this.emit('close'); | ||
} else { | ||
// No listeners attached, destroy the original request. | ||
this._request.destroy(); | ||
stream.destroy(); | ||
} | ||
} else { | ||
// Forwards data | ||
this._request.pipe(this.res); | ||
stream.on('data', chunk => { | ||
if (!response.push(chunk)) { | ||
this.pause(); | ||
} | ||
}); | ||
if (!this.emit('response', this.res)) { | ||
stream.once('end', () => { | ||
response.push(null); | ||
}); | ||
if (!this.emit('response', response)) { | ||
// No listeners attached, dump the response. | ||
this.res._dump(); | ||
response._dump(); | ||
} | ||
@@ -277,5 +289,5 @@ } | ||
// Emits `information` event | ||
this._request.once('headers', headers => this.emit('information', {statusCode: headers[HTTP2_HEADER_STATUS]})); | ||
stream.once('headers', headers => this.emit('information', {statusCode: headers[HTTP2_HEADER_STATUS]})); | ||
this._request.once('trailers', (trailers, flags, rawTrailers) => { | ||
stream.once('trailers', (trailers, flags, rawTrailers) => { | ||
// Assigns trailers to the response object. | ||
@@ -286,4 +298,4 @@ this.res.trailers = trailers; | ||
this.socket = this._request.session.socket; | ||
this.connection = this._request.session.socket; | ||
this.socket = stream.session.socket; | ||
this.connection = stream.session.socket; | ||
@@ -307,6 +319,7 @@ process.nextTick(() => { | ||
// eslint-disable-next-line promise/prefer-await-to-then | ||
this.agent.request(this[kAuthority], this[kOptions], this[kHeaders]).then(onStream, error => { | ||
try { | ||
onStream(await this.agent.request(this[kOrigin], this[kOptions], this[kHeaders])); | ||
} catch (error) { | ||
this.emit('error', error); | ||
}); | ||
} | ||
} | ||
@@ -313,0 +326,0 @@ } |
'use strict'; | ||
const {PassThrough} = require('stream'); | ||
const {Readable} = require('stream'); | ||
class IncomingMessage extends PassThrough { | ||
class IncomingMessage extends Readable { | ||
constructor(socket, highWaterMark) { | ||
@@ -47,4 +47,10 @@ super({highWaterMark}); | ||
} | ||
_read() { | ||
if (this.req) { | ||
this.req._request.resume(); | ||
} | ||
} | ||
} | ||
module.exports = IncomingMessage; |
@@ -21,3 +21,3 @@ 'use strict'; | ||
if (isManyTypes) { | ||
valid = `${valid.slice(0, valid.length - 1).join(', ')} or ${valid[valid.length - 1]}`; | ||
valid = `${valid.slice(0, -1).join(', ')} or ${valid.slice(-1)}`; | ||
} | ||
@@ -24,0 +24,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47601
1065
375
+ Addedquick-lru@5.1.1(transitive)
- Removedquick-lru@4.0.1(transitive)
Updatedquick-lru@^5.0.0