.. | ||
source | ||
LICENSE | ||
package.json | ||
README.md |
http2-wrapper
HTTP/2 client, just with the familiar
https
API
This package was created to support HTTP/2 without the need to
rewrite your code.
I recommend adapting to the http2
module
if possible - it’s much simpler to use and has many cool features!
Tip: http2-wrapper
is very useful when
you rely on other modules that use the HTTP/1 API and you want to
support HTTP/2.
Pro Tip: While the native http2
doesn’t
have agents yet, you can use http2-wrapper
Agents and still
operate on the native HTTP/2 streams.
Installation
$ npm install http2-wrapper
$ yarn add http2-wrapper
Usage
const http2 = require('http2-wrapper');
const options = {
hostname: 'nghttp2.org',
protocol: 'https:',
path: '/httpbin/post',
method: 'POST',
headers: {
'content-length': 6
};
}
const request = http2.request(options, response => {
console.log('statusCode:', response.statusCode);
console.log('headers:', response.headers);
const body = [];
.on('data', chunk => {
response.push(chunk);
body;
}).on('end', () => {
responseconsole.log('body:', Buffer.concat(body).toString());
;
});
})
.on('error', console.error);
request
.write('123');
request.end('456');
request
// statusCode: 200
// headers: [Object: null prototype] {
// ':status': 200,
// date: 'Fri, 27 Sep 2019 19:45:46 GMT',
// 'content-type': 'application/json',
// 'access-control-allow-origin': '*',
// 'access-control-allow-credentials': 'true',
// 'content-length': '239',
// 'x-backend-header-rtt': '0.002516',
// 'strict-transport-security': 'max-age=31536000',
// server: 'nghttpx',
// via: '1.1 nghttpx',
// 'alt-svc': 'h3-23=":4433"; ma=3600',
// 'x-frame-options': 'SAMEORIGIN',
// 'x-xss-protection': '1; mode=block',
// 'x-content-type-options': 'nosniff'
// }
// body: {
// "args": {},
// "data": "123456",
// "files": {},
// "form": {},
// "headers": {
// "Content-Length": "6",
// "Host": "nghttp2.org"
// },
// "json": 123456,
// "origin": "xxx.xxx.xxx.xxx",
// "url": "https://nghttp2.org/httpbin/post"
// }
API
Note: The session
option was renamed to
tlsSession
for better readability.
http2.auto(url, options, callback)
Performs ALPN
negotiation. Returns a Promise giving proper ClientRequest
instance (depending on the ALPN).
Note: The agent
option represents an
object with http
, https
and http2
properties.
const http2 = require('http2-wrapper');
const options = {
hostname: 'httpbin.org',
protocol: 'http:', // Note the `http:` protocol here
path: '/post',
method: 'POST',
headers: {
'content-length': 6
};
}
async () => {
(try {
const request = await http2.auto(options, response => {
console.log('statusCode:', response.statusCode);
console.log('headers:', response.headers);
const body = [];
.on('data', chunk => body.push(chunk));
response.on('end', () => {
responseconsole.log('body:', Buffer.concat(body).toString());
;
});
})
.on('error', console.error);
request
.write('123');
request.end('456');
requestcatch (error) {
} console.error(error);
};
})()
// statusCode: 200
// headers: { connection: 'close',
// server: 'gunicorn/19.9.0',
// date: 'Sat, 15 Dec 2018 18:19:32 GMT',
// 'content-type': 'application/json',
// 'content-length': '259',
// 'access-control-allow-origin': '*',
// 'access-control-allow-credentials': 'true',
// via: '1.1 vegur' }
// body: {
// "args": {},
// "data": "123456",
// "files": {},
// "form": {},
// "headers": {
// "Connection": "close",
// "Content-Length": "6",
// "Host": "httpbin.org"
// },
// "json": 123456,
// "origin": "xxx.xxx.xxx.xxx",
// "url": "http://httpbin.org/post"
// }
http2.auto.protocolCache
An instance of quick-lru
used for ALPN cache.
There is a maximum of 100 entries. You can modify the limit through
protocolCache.maxSize
- note that the change will be
visible globally.
http2.request(url, options, callback)
Same as https.request
.
options.h2session
Type: Http2Session
The session used to make the actual request. If none provided, it
will use options.agent
.
http2.get(url, options, callback)
Same as https.get
.
new http2.ClientRequest(url, options, callback)
Same as https.ClientRequest
.
new http2.IncomingMessage(socket)
Same as https.IncomingMessage
.
new http2.Agent(options)
Note: this is not compatible with
the classic http.Agent
.
Usage example:
const http2 = require('http2-wrapper');
class MyAgent extends http2.Agent {
createConnection(origin, options) {
console.log(`Connecting to ${http2.Agent.normalizeOrigin(origin)}`);
return http2.Agent.connect(origin, options);
}
}
.get({
http2hostname: 'google.com',
agent: new MyAgent()
, res => {
}.on('data', chunk => console.log(`Received chunk of ${chunk.length} bytes`));
res; })
options
Each option is assigned to each Agent
instance and can
be changed later.
timeout
Type: number
Default: 60000
If there’s no activity after timeout
milliseconds, the
session will be closed.
maxSessions
Type: number
Default: Infinity
The maximum amount of sessions in total.
maxFreeSessions
Type: number
Default: 10
The maximum amount of free sessions in total. This only applies to sessions with no pending requests.
Note: It is possible that the amount will be exceeded when sessions have at least 1 pending request.
maxCachedTlsSessions
Type: number
Default: 100
The maximum amount of cached TLS sessions.
Agent.normalizeOrigin(url)
Returns a string representing the origin of the URL.
agent.settings
Type: object
Default:
{enablePush: false}
Settings used by the current agent instance.
agent.normalizeOptions(options)
Returns a string representing normalized options.
.normalizeOptions({servername: 'example.com'});
Agent// => ':example.com'
agent.getSession(origin, options)
origin
Type: string
URL
object
An origin used to create new session.
options
Type: object
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(origin, options, listener)
listener
Type: object
{
reject: error => void,
resolve: session => void
}
If the listener
argument is present, the Promise will
resolve immediately. It will use the resolve
function to
pass the session.
agent.request(origin, options, headers, streamOptions)
Returns a Promise giving Http2Stream
.
agent.createConnection(origin, 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 will be closed.
agent.destroy(reason)
Destroys all sessions.
Event: ‘session’
.on('session', session => {
agent// A new session has been created by the Agent.
; })
Proxy support
An example of a full-featured proxy server can be found here. It supports mirroring, custom authorities and the CONNECT protocol.
Mirroring
To mirror another server we need to use only http2-proxy
.
We don’t need the CONNECT protocol or custom authorities.
To see the result, just navigate to the server’s address.
HTTP/1 over HTTP/2
Since we don’t care about mirroring, the server needs to support the CONNECT protocol in this case.
The client looks like this:
const https = require('https');
const http2 = require('http2');
const session = http2.connect('https://localhost:8000', {
// For demo purposes only!
rejectUnauthorized: false
;
})
.ref();
session
.request('https://httpbin.org/anything', {
httpscreateConnection: options => {
return session.request({
':method': 'CONNECT',
':authority': `${options.host}:${options.port}`
;
})
}, response => {
}console.log('statusCode:', response.statusCode);
console.log('headers:', response.headers);
const body = [];
.on('data', chunk => {
response.push(chunk);
body;
}).on('end', () => {
responseconsole.log('body:', Buffer.concat(body).toString());
.unref();
session;
}).end(); })
HTTP/2 over HTTP/2
It’s a tricky one! We cannot create an HTTP/2 session on top of an
HTTP/2 stream. But… we can still specify the :authority
header, no need to use the CONNECT protocol here.
The client looks like this:
const http2 = require('../../source');
const {Agent} = http2;
class ProxyAgent extends Agent {
constructor(url, options) {
super(options);
this.origin = url;
}
request(origin, sessionOptions, headers, streamOptions) {
return super.request(this.origin, sessionOptions, {
...headers,
':authority': (new URL(origin)).host
, streamOptions);
}
}
}
const request = http2.request({
hostname: 'httpbin.org',
protocol: 'https:',
path: '/anything',
agent: new ProxyAgent('https://localhost:8000'),
// For demo purposes only!
rejectUnauthorized: false
, response => {
}console.log('statusCode:', response.statusCode);
console.log('headers:', response.headers);
const body = [];
.on('data', chunk => {
response.push(chunk);
body;
}).on('end', () => {
responseconsole.log('body:', Buffer.concat(body).toString());
;
});
})
.on('error', console.error);
request
.end(); request
Notes
- If you’re interested in WebSockets over HTTP/2, then check out this discussion.
- HTTP/2 sockets cannot be malformed, therefore modifying the socket will have no effect.
- You can make a custom Agent to support push streams.
Benchmarks
CPU: Intel i7-7700k (governor: performance)
Server: H2O v2.2.5 h2o.conf
Node: v14.5.0 Linux:
5.6.18-156.current
auto
means http2wrapper.auto
.
http2-wrapper x 12,181 ops/sec ±3.39% (75 runs sampled)
http2-wrapper - preconfigured session x 13,140 ops/sec ±2.51% (79 runs sampled)
http2-wrapper - auto x 11,412 ops/sec ±2.55% (78 runs sampled)
http2 x 16,050 ops/sec ±1.39% (86 runs sampled)
https - auto - keepalive x 12,288 ops/sec ±2.69% (79 runs sampled)
https - keepalive x 12,155 ops/sec ±3.32% (78 runs sampled)
https x 1,604 ops/sec ±2.03% (77 runs sampled)
http x 6,041 ops/sec ±3.82% (76 runs sampled)
Fastest is http2
http2-wrapper
: - 32% less performant
than http2
- as performant as
https - keepalive
- 100% more performant
than http
http2-wrapper - preconfigured session
: - 22%
less performant than http2
- 8%
more performant than https - keepalive
-
118% more performant than http
http2-wrapper - auto
: - 41% less
performant than http2
- 8% less performant
than https - keepalive
- 89% more
performant than http
https - auto - keepalive
: - 31% less
performant than http2
- as performant as
https - keepalive
- 103% more performant
than http
Related
got
- Simplified HTTP requests
License
MIT