Socket
Socket
Sign inDemoInstall

http2-wrapper

Package Overview
Dependencies
Maintainers
1
Versions
59
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

http2-wrapper - npm Package Compare versions

Comparing version 0.8.0 to 1.0.0-alpha.0

9

package.json
{
"name": "http2-wrapper",
"version": "0.8.0",
"version": "1.0.0-alpha.0",
"description": "HTTP2 client, just with the familiar `https` API",

@@ -10,3 +10,3 @@ "main": "source",

"scripts": {
"test": "xo && nyc ava",
"test": "echo XO $(xo --version) && echo AVA $(ava --version) && xo && nyc ava",
"coveralls": "nyc report --reporter=text-lcov | coveralls"

@@ -34,2 +34,3 @@ },

"dependencies": {
"quick-lru": "^4.0.1",
"resolve-alpn": "^1.0.0"

@@ -42,9 +43,7 @@ },

"coveralls": "^3.0.5",
"delay": "^4.3.0",
"create-cert": "^1.0.6",
"get-stream": "^5.1.0",
"nyc": "^14.1.1",
"p-event": "^4.1.0",
"pem": "^1.14.2",
"tempy": "^0.3.0",
"timekeeper": "^2.2.0",
"to-readable-stream": "^2.1.0",

@@ -51,0 +50,0 @@ "xo": "^0.24.0"

@@ -9,4 +9,2 @@ # http2-wrapper

**This package is under heavy development. It may contain bugs. Don't forget to report them if you notice any.**
This package was created to support HTTP2 without the need to rewrite your code.<br>

@@ -87,3 +85,3 @@ 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!

**Note:** the `session` option accepts an instance of [`Http2Session`](https://nodejs.org/api/http2.html#http2_class_http2session). To pass a SSL session, use `socketSession` instead.
**Note:** the `session` option accepts an instance of [`Http2Session`](https://nodejs.org/api/http2.html#http2_class_http2session). To pass a TLS session, use `tlsSession` instead.

@@ -173,12 +171,6 @@ ### http2.auto(url, options, callback)

An object storing cache for detecting ALPN. It looks like:
An instance of [`quick-lru`](https://github.com/sindresorhus/quick-lru) used for caching ALPN.
```js
{
'hostname:port:alpn1,alpn2': 'alpn1'
}
```
There is a maximum of 100 entries. You can modify the limit through `protocolCache.maxSize` - note that the change will be visible globally.
There are maximum 100 entries.
### http2.request(url, options, callback)

@@ -232,3 +224,3 @@

Type: `number`<br>
Default: `30000`
Default: `60000`

@@ -261,3 +253,3 @@ If there's no activity in given time (milliseconds), the session is closed.

Type: `string`
Type: `string` `URL` `Object`

@@ -278,3 +270,3 @@ Authority used to create a new session.

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

@@ -285,3 +277,3 @@ #### agent.closeFreeSessions()

#### agent.destroy()
#### agent.destroy(reason)

@@ -294,2 +286,3 @@ Destroys **all** sessions.

- [HTTP2 sockets cannot be malformed](https://github.com/nodejs/node/blob/cc8250fab86486632fdeb63892be735d7628cd13/lib/internal/http2/core.js#L725), therefore modifying the socket will have no effect.
- HTTP2 is a binary protocol. Headers are sent without any validation.

@@ -300,12 +293,11 @@ ## Benchmarks

Server: H2O 2.2.5 [`h2o.conf`](h2o.conf)<br>
Node: v12.6.0<br>
Version: master<br>
Node: v12.6.0
```
http2-wrapper x 10,839 ops/sec ±2.19% (84 runs sampled)
http2-wrapper - preconfigured session x 13,005 ops/sec ±3.65% (84 runs sampled)
http2 x 17,100 ops/sec ±3.40% (81 runs sampled)
http2 - using PassThrough proxies x 14,831 ops/sec ±2.22% (84 runs sampled)
https x 1,623 ops/sec ±3.40% (76 runs sampled)
http x 6,761 ops/sec ±3.46% (76 runs sampled)
http2-wrapper x 11,886 ops/sec ±1.90% (84 runs sampled)
http2-wrapper - preconfigured session x 14,815 ops/sec ±1.58% (87 runs sampled)
http2 x 18,272 ops/sec ±1.76% (80 runs sampled)
http2 - using PassThrough proxies x 15,215 ops/sec ±2.18% (85 runs sampled)
https x 1,613 ops/sec ±4.56% (75 runs sampled)
http x 6,676 ops/sec ±5.17% (78 runs sampled)
Fastest is http2

@@ -316,13 +308,13 @@ ```

- It's `1.58x` slower than `http2`.
- It's `1.37x` slower than `http2` with `PassThrough`.
- It's `6.68x` faster than `https`.
- It's `1.60x` faster than `http`.
- It's `1.537x` slower than `http2`.
- It's `1.280x` slower than `http2` with `PassThrough`.
- It's `7.369x` faster than `https`.
- It's `1.780x` faster than `http`.
`http2-wrapper - preconfigured session`:
- It's `1.31x` slower than `http2`.
- It's `1.14x` slower than `http2` with `PassThrough`.
- It's `8.01x` faster than `https`.
- It's `1.92x` faster than `http`.
- It's `1.233x` slower than `http2`.
- It's `1.027x` slower than `http2` with `PassThrough`.
- It's `9.185x` faster than `https`.
- It's `2.219x` faster than `http`.

@@ -329,0 +321,0 @@ ## Related

@@ -49,3 +49,3 @@ 'use strict';

const removeSession = (where, name, session) => {
if (where[name]) {
if (Reflect.has(where, name)) {
const index = where[name].indexOf(session);

@@ -92,3 +92,3 @@

for (const key of nameKeys) {
if (options[key]) {
if (Reflect.has(options, key)) {
if (typeof options[key] === 'object') {

@@ -106,12 +106,12 @@ name += `:${JSON.stringify(options[key])}`;

_processQueue(name) {
const busyLength = this.busySessions[name] ? this.busySessions[name].length : 0;
const busyLength = Reflect.has(this.busySessions, name) ? this.busySessions[name].length : 0;
if (busyLength < this.maxSessions && this.queue[name] && !this.queue[name].completed) {
if (busyLength < this.maxSessions && Reflect.has(this.queue, name) && !this.queue[name].completed) {
this.queue[name].completed = true;
this.queue[name]();
this.queue[name].completed = true;
}
}
async getSession(authority, options, preconnectOnly = false) {
async getSession(authority, options) {
return new Promise((resolve, reject) => {

@@ -121,3 +121,3 @@ const name = this.getName(authority, options);

if (this.freeSessions[name]) {
if (Reflect.has(this.freeSessions, name)) {
resolve(this.freeSessions[name][0]);

@@ -128,19 +128,7 @@

if (this.queue[name]) {
let listenersLength = this.queue[name].listeners.length;
if (Reflect.has(this.queue, name)) {
// TODO: limit the maximum amount of listeners
this.queue[name].listeners.push(detached);
if (this.queue[name].preconnectOnly) {
listenersLength--;
}
if (listenersLength < this.maxFreeSessions) {
this.queue[name].listeners.push(detached);
if (this.queue[name].listeners.length === this.maxFreeSessions) {
// All seats are taken, remove entry from the queue.
delete this.queue[name];
}
return;
}
return;
}

@@ -153,3 +141,3 @@

// Or the entry will be `undefined` if all seats are taken.
if (this.queue[name] && this.queue[name].completed) {
if (Reflect.has(this.queue, name) && this.queue[name].completed) {
delete this.queue[name];

@@ -161,2 +149,4 @@ }

try {
let receivedSettings = false;
const session = http2.connect(authority, {

@@ -169,3 +159,4 @@ createConnection: this.createConnection,

session.setTimeout(this.timeout, () => {
session.close();
// `.close()` would wait until all streams all closed
session.destroy();
});

@@ -182,2 +173,8 @@

session.once('close', () => {
if (!receivedSettings) {
for (const listener of listeners) {
listener.reject(new Error('Session closed without receiving a SETTINGS frame'));
}
}
free();

@@ -192,3 +189,7 @@

this.freeSessions[name] = [session];
if (Reflect.has(this.freeSessions, name)) {
this.freeSessions[name].push(session);
} else {
this.freeSessions[name] = [session];
}

@@ -198,2 +199,4 @@ for (const listener of listeners) {

}
receivedSettings = true;
});

@@ -212,3 +215,3 @@

if (this.busySessions[name]) {
if (Reflect.has(this.busySessions, name)) {
this.busySessions[name].push(session);

@@ -222,7 +225,9 @@ } else {

if (--session[kCurrentStreamsCount] < session.remoteSettings.maxConcurrentStreams) {
session.unref();
if (session[kCurrentStreamsCount] === 0) {
session.unref();
}
if (removeSession(this.busySessions, name, session) && !session.destroyed) {
if ((this.freeSessions[name] || []).length < this.maxFreeSessions) {
if (this.freeSessions[name]) {
if (Reflect.has(this.freeSessions, name)) {
this.freeSessions[name].push(session);

@@ -245,2 +250,4 @@ } else {

}
delete this.queue[name];
}

@@ -250,3 +257,2 @@ };

this.queue[name].listeners = listeners;
this.queue[name].preconnectOnly = preconnectOnly;
this.queue[name].completed = false;

@@ -276,3 +282,3 @@ this._processQueue(name);

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

@@ -292,6 +298,6 @@ return tls.connect(port, host, options);

destroy(error) {
destroy(reason) {
for (const busySessions of Object.values(this.busySessions)) {
for (const session of busySessions) {
session.destroy(error);
session.destroy(reason);
}

@@ -302,3 +308,3 @@ }

for (const session of freeSessions) {
session.destroy(error);
session.destroy(reason);
}

@@ -305,0 +311,0 @@ }

@@ -5,2 +5,3 @@ 'use strict';

const https = require('https');
const QuickLRU = require('quick-lru');
const isCompatible = require('./utils/is-compatible');

@@ -11,3 +12,3 @@ const Http2ClientRequest = isCompatible ? require('./client-request') : undefined;

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

@@ -21,13 +22,7 @@ const prepareRequest = async options => {

let alpnProtocol = cache[name];
let alpnProtocol = cache.get(name);
if (typeof alpnProtocol === 'undefined') {
alpnProtocol = (await httpResolveALPN(options)).alpnProtocol;
cache[name] = alpnProtocol;
const keys = Object.keys(cache);
while (keys.length > 100) {
delete cache[keys.pop()];
}
cache.set(name, alpnProtocol);
}

@@ -52,3 +47,3 @@

_defaultAgent: https.globalAgent,
session: options.socketSession
session: options.tlsSession
};

@@ -55,0 +50,0 @@

@@ -25,3 +25,2 @@ 'use strict';

const kAuthority = Symbol('authority');
const kAgent = Symbol('agent');
const kSession = Symbol('session');

@@ -48,13 +47,29 @@ const kOptions = Symbol('options');

const defaultPort = options.defaultPort || (this.agent && this.agent.defaultPort);
if (options.agent) {
if (typeof options.agent.request !== 'function') {
throw new ERR_INVALID_ARG_TYPE('options.agent', ['Agent-like Object', 'undefined', 'false'], options.agent);
}
options = {
protocol: 'https:',
port: defaultPort || 443,
preconnect: true,
...options,
host: options.hostname || options.host || 'localhost'
};
this.agent = options.agent;
} else if (options.session) {
this[kSession] = options.session;
} else if (options.agent === false) {
this.agent = new Agent({maxFreeSessions: 0});
} else if (options.agent === null || typeof options.agent === 'undefined') {
if (typeof options.createConnection === 'function') {
// This is a workaround - we don't have to create the session on our own.
this.agent = new Agent({maxFreeSessions: 0});
this.agent.createConnection = options.createConnection;
} else {
this.agent = globalAgent;
}
}
if (options.protocol !== 'https:') {
if (!options.port) {
options.port = options.defaultPort || (this.agent && this.agent.defaultPort) || 443;
}
options.host = options.hostname || options.host || 'localhost';
if (options.protocol && options.protocol !== 'https:') {
throw new ERR_INVALID_PROTOCOL(options.protocol, 'https:');

@@ -87,25 +102,10 @@ }

if (options.agent) {
this[kAgent] = options.agent;
} else if (options.session) {
this[kSession] = options.session;
} else if (options.agent === false) {
this[kAgent] = new Agent({maxFreeSessions: 0});
} else if (options.agent === null || typeof options.agent === 'undefined') {
if (typeof options.createConnection !== 'function') {
this[kAgent] = globalAgent;
}
} else if (typeof options.agent.request !== 'function') {
throw new ERR_INVALID_ARG_TYPE('options.agent', ['Agent-like Object', 'undefined', 'false'], options.agent);
}
options.ALPNProtocols = ['h2'];
options.session = options.socketSession;
options.session = options.tlsSession;
options.path = options.socketPath;
this[kOptions] = options;
this[kAuthority] = options.authority || `https://${options.hostname || options.host}:${options.port}`;
this[kAuthority] = options.authority || new URL(`https://${options.hostname || options.host}:${options.port}`);
if (this[kAgent] && options.preconnect) {
this[kAgent].getSession(this[kAuthority], options, true).catch(() => {});
if (this.agent && (options.preconnect || typeof options.preconnect === 'undefined')) {
this.agent.getSession(this[kAuthority], options).catch(() => {});
}

@@ -191,3 +191,3 @@

flushHeaders() {
if (this[kFlushedHeaders]) {
if (this[kFlushedHeaders] && !this.destroyed && !this.aborted) {
return;

@@ -280,3 +280,3 @@ }

// eslint-disable-next-line promise/prefer-await-to-then
this[kAgent].request(this[kAuthority], this[kOptions], this[kHeaders]).then(onStream, error => {
this.agent.request(this[kAuthority], this[kOptions], this[kHeaders]).then(onStream, error => {
this.emit('error', error);

@@ -283,0 +283,0 @@ });

@@ -16,3 +16,11 @@ 'use strict';

const type = args[0].includes('.') ? 'property' : 'argument';
return `The "${args[0]}" ${type} must be of type ${args[1]}. Received ${typeof args[2]}`;
let valid = args[1];
const isManyTypes = Array.isArray(valid);
if (isManyTypes) {
valid = `${valid.slice(0, valid.length - 1).join(', ')} or ${valid[valid.length - 1]}`;
}
return `The "${args[0]}" ${type} must be ${isManyTypes ? 'one of' : 'of'} type ${valid}. Received ${typeof args[2]}`;
});

@@ -19,0 +27,0 @@

@@ -14,3 +14,3 @@ 'use strict';

port: options.port || 443,
host: options.hostname || options.host || 'localhost',
host: options.hostname || options.host,
path: options.socketPath,

@@ -17,0 +17,0 @@ session: options.socketSession,

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc