You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
19 KiB
158 lines
19 KiB
|
2 months ago
|
"use strict";
|
||
|
|
|
||
|
|
const http2 = require('http2');
|
||
|
|
const Stream = require('stream');
|
||
|
|
const net = require('net');
|
||
|
|
const tls = require('tls');
|
||
|
|
const {
|
||
|
|
HTTP2_HEADER_PATH,
|
||
|
|
HTTP2_HEADER_STATUS,
|
||
|
|
HTTP2_HEADER_METHOD,
|
||
|
|
HTTP2_HEADER_AUTHORITY,
|
||
|
|
HTTP2_HEADER_HOST,
|
||
|
|
HTTP2_HEADER_SET_COOKIE,
|
||
|
|
NGHTTP2_CANCEL
|
||
|
|
} = http2.constants;
|
||
|
|
function setProtocol(protocol) {
|
||
|
|
return {
|
||
|
|
request(options) {
|
||
|
|
return new Request(protocol, options);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function normalizeIpv6Host(host) {
|
||
|
|
return net.isIP(host) === 6 ? `[${host}]` : host;
|
||
|
|
}
|
||
|
|
class Request extends Stream {
|
||
|
|
constructor(protocol, options) {
|
||
|
|
super();
|
||
|
|
const defaultPort = protocol === 'https:' ? 443 : 80;
|
||
|
|
const defaultHost = 'localhost';
|
||
|
|
const port = options.port || defaultPort;
|
||
|
|
const host = options.host || defaultHost;
|
||
|
|
delete options.port;
|
||
|
|
delete options.host;
|
||
|
|
this.method = options.method;
|
||
|
|
this.path = options.path;
|
||
|
|
this.protocol = protocol;
|
||
|
|
this.host = host;
|
||
|
|
delete options.method;
|
||
|
|
delete options.path;
|
||
|
|
const sessionOptions = {
|
||
|
|
...options
|
||
|
|
};
|
||
|
|
if (options.socketPath) {
|
||
|
|
sessionOptions.socketPath = options.socketPath;
|
||
|
|
sessionOptions.createConnection = this.createUnixConnection.bind(this);
|
||
|
|
}
|
||
|
|
this._headers = {};
|
||
|
|
const normalizedHost = normalizeIpv6Host(host);
|
||
|
|
const session = http2.connect(`${protocol}//${normalizedHost}:${port}`, sessionOptions);
|
||
|
|
this.setHeader('host', `${normalizedHost}:${port}`);
|
||
|
|
session.on('error', error => this.emit('error', error));
|
||
|
|
this.session = session;
|
||
|
|
}
|
||
|
|
createUnixConnection(authority, options) {
|
||
|
|
switch (this.protocol) {
|
||
|
|
case 'http:':
|
||
|
|
return net.connect(options.socketPath);
|
||
|
|
case 'https:':
|
||
|
|
options.ALPNProtocols = ['h2'];
|
||
|
|
options.servername = this.host;
|
||
|
|
options.allowHalfOpen = true;
|
||
|
|
return tls.connect(options.socketPath, options);
|
||
|
|
default:
|
||
|
|
throw new Error('Unsupported protocol', this.protocol);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
setNoDelay(bool) {
|
||
|
|
// We can not use setNoDelay with HTTP/2.
|
||
|
|
// Node 10 limits http2session.socket methods to ones safe to use with HTTP/2.
|
||
|
|
// See also https://nodejs.org/api/http2.html#http2_http2session_socket
|
||
|
|
}
|
||
|
|
getFrame() {
|
||
|
|
if (this.frame) {
|
||
|
|
return this.frame;
|
||
|
|
}
|
||
|
|
const method = {
|
||
|
|
[HTTP2_HEADER_PATH]: this.path,
|
||
|
|
[HTTP2_HEADER_METHOD]: this.method
|
||
|
|
};
|
||
|
|
let headers = this.mapToHttp2Header(this._headers);
|
||
|
|
headers = Object.assign(headers, method);
|
||
|
|
const frame = this.session.request(headers);
|
||
|
|
frame.once('response', (headers, flags) => {
|
||
|
|
headers = this.mapToHttpHeader(headers);
|
||
|
|
frame.headers = headers;
|
||
|
|
frame.statusCode = headers[HTTP2_HEADER_STATUS];
|
||
|
|
frame.status = frame.statusCode;
|
||
|
|
this.emit('response', frame);
|
||
|
|
});
|
||
|
|
this._headerSent = true;
|
||
|
|
frame.once('drain', () => this.emit('drain'));
|
||
|
|
frame.on('error', error => this.emit('error', error));
|
||
|
|
frame.on('close', () => this.session.close());
|
||
|
|
this.frame = frame;
|
||
|
|
return frame;
|
||
|
|
}
|
||
|
|
mapToHttpHeader(headers) {
|
||
|
|
const keys = Object.keys(headers);
|
||
|
|
const http2Headers = {};
|
||
|
|
for (let key of keys) {
|
||
|
|
let value = headers[key];
|
||
|
|
key = key.toLowerCase();
|
||
|
|
switch (key) {
|
||
|
|
case HTTP2_HEADER_SET_COOKIE:
|
||
|
|
value = Array.isArray(value) ? value : [value];
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
http2Headers[key] = value;
|
||
|
|
}
|
||
|
|
return http2Headers;
|
||
|
|
}
|
||
|
|
mapToHttp2Header(headers) {
|
||
|
|
const keys = Object.keys(headers);
|
||
|
|
const http2Headers = {};
|
||
|
|
for (let key of keys) {
|
||
|
|
let value = headers[key];
|
||
|
|
key = key.toLowerCase();
|
||
|
|
switch (key) {
|
||
|
|
case HTTP2_HEADER_HOST:
|
||
|
|
key = HTTP2_HEADER_AUTHORITY;
|
||
|
|
value = /^http:\/\/|^https:\/\//.test(value) ? new URL(value).host : value;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
http2Headers[key] = value;
|
||
|
|
}
|
||
|
|
return http2Headers;
|
||
|
|
}
|
||
|
|
setHeader(name, value) {
|
||
|
|
this._headers[name.toLowerCase()] = value;
|
||
|
|
}
|
||
|
|
getHeader(name) {
|
||
|
|
return this._headers[name.toLowerCase()];
|
||
|
|
}
|
||
|
|
write(data, encoding) {
|
||
|
|
const frame = this.getFrame();
|
||
|
|
return frame.write(data, encoding);
|
||
|
|
}
|
||
|
|
pipe(stream, options) {
|
||
|
|
const frame = this.getFrame();
|
||
|
|
return frame.pipe(stream, options);
|
||
|
|
}
|
||
|
|
end(data) {
|
||
|
|
const frame = this.getFrame();
|
||
|
|
frame.end(data);
|
||
|
|
}
|
||
|
|
abort(data) {
|
||
|
|
const frame = this.getFrame();
|
||
|
|
frame.close(NGHTTP2_CANCEL);
|
||
|
|
this.session.destroy();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
exports.setProtocol = setProtocol;
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJodHRwMiIsInJlcXVpcmUiLCJTdHJlYW0iLCJuZXQiLCJ0bHMiLCJIVFRQMl9IRUFERVJfUEFUSCIsIkhUVFAyX0hFQURFUl9TVEFUVVMiLCJIVFRQMl9IRUFERVJfTUVUSE9EIiwiSFRUUDJfSEVBREVSX0FVVEhPUklUWSIsIkhUVFAyX0hFQURFUl9IT1NUIiwiSFRUUDJfSEVBREVSX1NFVF9DT09LSUUiLCJOR0hUVFAyX0NBTkNFTCIsImNvbnN0YW50cyIsInNldFByb3RvY29sIiwicHJvdG9jb2wiLCJyZXF1ZXN0Iiwib3B0aW9ucyIsIlJlcXVlc3QiLCJub3JtYWxpemVJcHY2SG9zdCIsImhvc3QiLCJpc0lQIiwiY29uc3RydWN0b3IiLCJkZWZhdWx0UG9ydCIsImRlZmF1bHRIb3N0IiwicG9ydCIsIm1ldGhvZCIsInBhdGgiLCJzZXNzaW9uT3B0aW9ucyIsInNvY2tldFBhdGgiLCJjcmVhdGVDb25uZWN0aW9uIiwiY3JlYXRlVW5peENvbm5lY3Rpb24iLCJiaW5kIiwiX2hlYWRlcnMiLCJub3JtYWxpemVkSG9zdCIsInNlc3Npb24iLCJjb25uZWN0Iiwic2V0SGVhZGVyIiwib24iLCJlcnJvciIsImVtaXQiLCJhdXRob3JpdHkiLCJBTFBOUHJvdG9jb2xzIiwic2VydmVybmFtZSIsImFsbG93SGFsZk9wZW4iLCJFcnJvciIsInNldE5vRGVsYXkiLCJib29sIiwiZ2V0RnJhbWUiLCJmcmFtZSIsImhlYWRlcnMiLCJtYXBUb0h0dHAySGVhZGVyIiwiT2JqZWN0IiwiYXNzaWduIiwib25jZSIsImZsYWdzIiwibWFwVG9IdHRwSGVhZGVyIiwic3RhdHVzQ29kZSIsInN0YXR1cyIsIl9oZWFkZXJTZW50IiwiY2xvc2UiLCJrZXlzIiwiaHR0cDJIZWFkZXJzIiwia2V5IiwidmFsdWUiLCJ0b0xvd2VyQ2FzZSIsIkFycmF5IiwiaXNBcnJheSIsInRlc3QiLCJVUkwiLCJuYW1lIiwiZ2V0SGVhZGVyIiwid3JpdGUiLCJkYXRhIiwiZW5jb2RpbmciLCJwaXBlIiwic3RyZWFtIiwiZW5kIiwiYWJvcnQiLCJkZXN0cm95IiwiZXhwb3J0cyJdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ub2RlL2h0dHAyd3JhcHBlci5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBodHRwMiA9IHJlcXVpcmUoJ2h0dHAyJyk7XG5jb25zdCBTdHJlYW0gPSByZXF1aXJlKCdzdHJlYW0nKTtcbmNvbnN0IG5ldCA9IHJlcXVpcmUoJ25ldCcpO1xuY29uc3QgdGxzID0gcmVxdWlyZSgndGxzJyk7XG5cbmNvbnN0IHtcbiAgSFRUUDJfSEVBREVSX1BBVEgsXG4gIEhUVFAyX0hFQURFUl9TVEFUVVMsXG4gIEhUVFAyX0hFQURFUl9NRVRIT0QsXG4gIEhUVFAyX0hFQURFUl9BVVRIT1JJVFksXG4gIEhUVFAyX0hFQURFUl9IT1NULFxuICBIVFRQMl9IRUFERVJfU0VUX0NPT0tJRSxcbiAgTkdIVFRQMl9DQU5DRUxcbn0gPSBodHRwMi5jb25zdGFudHM7XG5cbmZ1bmN0aW9uIHNldFByb3RvY29sKHByb3RvY29sKSB7XG4gIHJldHVybiB7XG4gICAgcmVxdWVzdChvcHRpb25zKSB7XG4gICAgICByZXR1cm4gbmV3IFJlcXVlc3QocHJvdG9jb2wsIG9wdGlvbnMpO1xuICAgIH1cbiAgfTtcbn1cblxuZnVuY3Rpb24gbm9ybWFsaXplSXB2Nkhvc3QoaG9zdCkge1xuICByZXR1cm4gbmV0LmlzSVAoaG9zdCkgPT09IDYgPyBgWyR7aG9zdH1dYCA6IGhvc3Q7XG59XG5cbmNsYXNzIFJlcXVlc3QgZXh0ZW5kcyBTdHJlYW0ge1xuICBjb25zdHJ1Y3Rvcihwcm90b2NvbCwgb3B0aW9ucykge1xuICAgIHN1cGVyKCk7XG4gICAgY29uc3QgZGVmYXVsdFBvcnQgPSBwcm90b2NvbCA9PT0gJ2h0dHBzOicgPyA0NDMgOiA4MDtcbiAgICBjb25zdCBkZWZhdWx0SG9zdCA9ICdsb2NhbGhvc3QnO1xuICAgIGNvbnN0IHBvcnQgPSBvcHRpb25zLnBvcnQgfHwgZGVmYXVsdFBvcnQ7XG4gICAgY29uc3QgaG9zdCA9IG9wdGlvbnMuaG9zdCB8fCBkZWZhdWx0SG9zdDtcblxuICAgIGRlbGV0ZSBvcHRpb25zLnBvcnQ7XG4gICAgZGVsZXRlIG9wdGlvbnMuaG9zdDtcblxuICAgIHRoaXMubWV0aG9kID0gb3B0aW9ucy5tZXRob2Q7XG4gICAgdGhpcy5wYXRoID0gb3B0aW9ucy5wYXRoO1xuICAgIHRoaXMucHJvdG9jb2wgPSBwcm90b2NvbDtcbiAgICB0aGlzLmhvc3QgPSBob3N0O1xuXG4gICAgZGVsZXRlIG9wdGlvbnMubWV0aG9kO1xuICAgIGRlbGV0ZSBvcHRpb25zLnBhdGg7XG5cbiAgICBjb25zdCBzZXNzaW9uT3B0aW9ucyA9IHsgLi4ub3B0aW9ucyB9O1xuICAgIGlmIChvcHRpb25zLnNvY2tldFBhdGgpIHtcbiAgICAgIHNlc3Npb25PcHRpb25zLnNvY2tldFBhdGggPSBvcHRpb25zLnNvY2tldFBhdGg7XG4gICAgICBzZXNzaW9uT3B0aW9ucy5jcmVhdGVDb25uZWN0aW9uID0gdGhpcy5jcmVhdGVVbml4Q29ubmVjdGlvbi5iaW5kKHRoaXMpO1xuICAgIH1cblxuICAgIHRoaXMuX2hlYWRlcnMgPSB7fTtcblxuICAgIGNvbnN0IG5vcm1hbGl6ZWRIb3N0ID0gbm9ybWFsaXplSXB2Nkhvc3QoaG9zdCk7XG4gICAgY29uc3Qgc2Vzc2lvbiA9IGh0dHAyLmNvbm5lY3QoXG4gICAgICBgJHtwcm90b2NvbH0vLyR7bm9ybWFsaXplZEhvc3R9OiR7cG9ydH1gLFxuICAgICAgc2Vzc2lvbk9wdGlvbnNcbiAgICApO1xuICAgIHRoaXMuc2V0SGVhZGVyKCdob3N0JywgYCR7bm9ybWFsaXplZEhvc3R9OiR7cG9ydH1gKTtcblxuICAgIHNlc3Npb24ub24oJ2Vycm9yJywgKGVycm9yKSA9PiB0aGlzLmVtaXQoJ2Vycm9yJywgZXJyb3IpKTtcblxuICAgIHRoaXMuc2Vzc2lvbiA9IHNlc3Npb247XG4gIH1cblxuICBjcmVhdGVVbml4Q29ubmVjdGlvbihhdXRob3JpdHksIG9wdGlvbnMpIHtcbiAgICBzd2l0Y2ggKHRoaXMucHJvdG9jb2wpIHtcbiAgICAgIGNhc2UgJ2h0dHA6JzpcbiAgICAgICAgcmV0dXJuIG5ldC5jb25uZWN0KG9wdGlvbnMuc29ja2V0UGF0aCk7XG4gICAgICBjYXNlICdodHRwczonOlxuICAgICAgICBvcHRpb25zLkFMUE5Qcm90b2NvbHMgPSBbJ2gyJ107XG4gICAgICAgIG9wdGlvbnMuc2VydmVybmFtZSA9IHRoaXMuaG9zdDtcbiAgICAgICAgb3B0aW9ucy5hbGxvd0hhbGZPcGVuID0gdHJ1ZTtcbiAgICAgICAgcmV0dXJuIHRscy5jb25uZWN0KG9wdGlvbnMuc29ja2V0UGF0aCwgb3B0aW9ucyk7XG4gICAgICBkZWZhdWx0OlxuICAgICAgICB0aHJvdyBuZXc
|