http2wrap 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/7ikmq6vc.cjs +1 -0
- package/LICENSE +21 -0
- package/README.md +459 -0
- package/index.d.ts +141 -0
- package/package.json +87 -0
- package/source/agent.js +796 -0
- package/source/auto.js +225 -0
- package/source/client-request.js +563 -0
- package/source/incoming-message.js +73 -0
- package/source/index.js +50 -0
- package/source/proxies/get-auth-headers.js +17 -0
- package/source/proxies/h1-over-h2.js +90 -0
- package/source/proxies/h2-over-h1.js +48 -0
- package/source/proxies/h2-over-h2.js +32 -0
- package/source/proxies/h2-over-hx.js +40 -0
- package/source/proxies/initialize.js +21 -0
- package/source/proxies/unexpected-status-code-error.js +11 -0
- package/source/utils/calculate-server-name.js +29 -0
- package/source/utils/check-type.js +20 -0
- package/source/utils/delay-async-destroy.js +33 -0
- package/source/utils/errors.js +51 -0
- package/source/utils/is-request-pseudo-header.js +13 -0
- package/source/utils/js-stream-socket.js +8 -0
- package/source/utils/proxy-events.js +7 -0
- package/source/utils/proxy-socket-handler.js +102 -0
- package/source/utils/validate-header-name.js +11 -0
- package/source/utils/validate-header-value.js +17 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
'use strict';
|
2
|
+
const tls = require('tls');
|
3
|
+
const http = require('http');
|
4
|
+
const https = require('https');
|
5
|
+
const JSStreamSocket = require('../utils/js-stream-socket.js');
|
6
|
+
const {globalAgent} = require('../agent.js');
|
7
|
+
const UnexpectedStatusCodeError = require('./unexpected-status-code-error.js');
|
8
|
+
const initialize = require('./initialize.js');
|
9
|
+
const getAuthorizationHeaders = require('./get-auth-headers.js');
|
10
|
+
|
11
|
+
const createConnection = (self, options, callback) => {
|
12
|
+
(async () => {
|
13
|
+
try {
|
14
|
+
const {proxyOptions} = self;
|
15
|
+
const {url, headers, raw} = proxyOptions;
|
16
|
+
|
17
|
+
const stream = await globalAgent.request(url, proxyOptions, {
|
18
|
+
...getAuthorizationHeaders(self),
|
19
|
+
...headers,
|
20
|
+
':method': 'CONNECT',
|
21
|
+
':authority': `${options.host}:${options.port}`
|
22
|
+
});
|
23
|
+
|
24
|
+
stream.once('error', callback);
|
25
|
+
stream.once('response', headers => {
|
26
|
+
const statusCode = headers[':status'];
|
27
|
+
|
28
|
+
if (statusCode !== 200) {
|
29
|
+
callback(new UnexpectedStatusCodeError(statusCode, ''));
|
30
|
+
return;
|
31
|
+
}
|
32
|
+
|
33
|
+
const encrypted = self instanceof https.Agent;
|
34
|
+
|
35
|
+
if (raw && encrypted) {
|
36
|
+
options.socket = stream;
|
37
|
+
const secureStream = tls.connect(options);
|
38
|
+
|
39
|
+
secureStream.once('close', () => {
|
40
|
+
stream.destroy();
|
41
|
+
});
|
42
|
+
|
43
|
+
callback(null, secureStream);
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
|
47
|
+
const socket = new JSStreamSocket(stream);
|
48
|
+
socket.encrypted = false;
|
49
|
+
socket._handle.getpeername = out => {
|
50
|
+
out.family = undefined;
|
51
|
+
out.address = undefined;
|
52
|
+
out.port = undefined;
|
53
|
+
};
|
54
|
+
|
55
|
+
callback(null, socket);
|
56
|
+
});
|
57
|
+
} catch (error) {
|
58
|
+
callback(error);
|
59
|
+
}
|
60
|
+
})();
|
61
|
+
};
|
62
|
+
|
63
|
+
class HttpOverHttp2 extends http.Agent {
|
64
|
+
constructor(options) {
|
65
|
+
super(options);
|
66
|
+
|
67
|
+
initialize(this, options.proxyOptions);
|
68
|
+
}
|
69
|
+
|
70
|
+
createConnection(options, callback) {
|
71
|
+
createConnection(this, options, callback);
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
class HttpsOverHttp2 extends https.Agent {
|
76
|
+
constructor(options) {
|
77
|
+
super(options);
|
78
|
+
|
79
|
+
initialize(this, options.proxyOptions);
|
80
|
+
}
|
81
|
+
|
82
|
+
createConnection(options, callback) {
|
83
|
+
createConnection(this, options, callback);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
module.exports = {
|
88
|
+
HttpOverHttp2,
|
89
|
+
HttpsOverHttp2
|
90
|
+
};
|
@@ -0,0 +1,48 @@
|
|
1
|
+
'use strict';
|
2
|
+
const http = require('http');
|
3
|
+
const https = require('https');
|
4
|
+
const Http2OverHttpX = require('./h2-over-hx.js');
|
5
|
+
const getAuthorizationHeaders = require('./get-auth-headers.js');
|
6
|
+
|
7
|
+
const getStream = request => new Promise((resolve, reject) => {
|
8
|
+
const onConnect = (response, socket, head) => {
|
9
|
+
socket.unshift(head);
|
10
|
+
|
11
|
+
request.off('error', reject);
|
12
|
+
resolve([socket, response.statusCode, response.statusMessage]);
|
13
|
+
};
|
14
|
+
|
15
|
+
request.once('error', reject);
|
16
|
+
request.once('connect', onConnect);
|
17
|
+
});
|
18
|
+
|
19
|
+
class Http2OverHttp extends Http2OverHttpX {
|
20
|
+
async _getProxyStream(authority) {
|
21
|
+
const {proxyOptions} = this;
|
22
|
+
const {url, headers} = this.proxyOptions;
|
23
|
+
|
24
|
+
const network = url.protocol === 'https:' ? https : http;
|
25
|
+
|
26
|
+
// `new URL('https://localhost/httpbin.org:443')` results in
|
27
|
+
// a `/httpbin.org:443` path, which has an invalid leading slash.
|
28
|
+
const request = network.request({
|
29
|
+
...proxyOptions,
|
30
|
+
hostname: url.hostname,
|
31
|
+
port: url.port,
|
32
|
+
path: authority,
|
33
|
+
headers: {
|
34
|
+
...getAuthorizationHeaders(this),
|
35
|
+
...headers,
|
36
|
+
host: authority
|
37
|
+
},
|
38
|
+
method: 'CONNECT'
|
39
|
+
}).end();
|
40
|
+
|
41
|
+
return getStream(request);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
module.exports = {
|
46
|
+
Http2OverHttp,
|
47
|
+
Http2OverHttps: Http2OverHttp
|
48
|
+
};
|
@@ -0,0 +1,32 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {globalAgent} = require('../agent.js');
|
3
|
+
const Http2OverHttpX = require('./h2-over-hx.js');
|
4
|
+
const getAuthorizationHeaders = require('./get-auth-headers.js');
|
5
|
+
|
6
|
+
const getStatusCode = stream => new Promise((resolve, reject) => {
|
7
|
+
stream.once('error', reject);
|
8
|
+
stream.once('response', headers => {
|
9
|
+
stream.off('error', reject);
|
10
|
+
resolve(headers[':status']);
|
11
|
+
});
|
12
|
+
});
|
13
|
+
|
14
|
+
class Http2OverHttp2 extends Http2OverHttpX {
|
15
|
+
async _getProxyStream(authority) {
|
16
|
+
const {proxyOptions} = this;
|
17
|
+
|
18
|
+
const headers = {
|
19
|
+
...getAuthorizationHeaders(this),
|
20
|
+
...proxyOptions.headers,
|
21
|
+
':method': 'CONNECT',
|
22
|
+
':authority': authority
|
23
|
+
};
|
24
|
+
|
25
|
+
const stream = await globalAgent.request(proxyOptions.url, proxyOptions, headers);
|
26
|
+
const statusCode = await getStatusCode(stream);
|
27
|
+
|
28
|
+
return [stream, statusCode, ''];
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
module.exports = Http2OverHttp2;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {Agent} = require('../agent.js');
|
3
|
+
const JSStreamSocket = require('../utils/js-stream-socket.js');
|
4
|
+
const UnexpectedStatusCodeError = require('./unexpected-status-code-error.js');
|
5
|
+
const initialize = require('./initialize.js');
|
6
|
+
|
7
|
+
class Http2OverHttpX extends Agent {
|
8
|
+
constructor(options) {
|
9
|
+
super(options);
|
10
|
+
|
11
|
+
initialize(this, options.proxyOptions);
|
12
|
+
}
|
13
|
+
|
14
|
+
async createConnection(origin, options) {
|
15
|
+
const authority = `${origin.hostname}:${origin.port || 443}`;
|
16
|
+
|
17
|
+
const [stream, statusCode, statusMessage] = await this._getProxyStream(authority);
|
18
|
+
if (statusCode !== 200) {
|
19
|
+
throw new UnexpectedStatusCodeError(statusCode, statusMessage);
|
20
|
+
}
|
21
|
+
|
22
|
+
if (this.proxyOptions.raw) {
|
23
|
+
options.socket = stream;
|
24
|
+
} else {
|
25
|
+
const socket = new JSStreamSocket(stream);
|
26
|
+
socket.encrypted = false;
|
27
|
+
socket._handle.getpeername = out => {
|
28
|
+
out.family = undefined;
|
29
|
+
out.address = undefined;
|
30
|
+
out.port = undefined;
|
31
|
+
};
|
32
|
+
|
33
|
+
return socket;
|
34
|
+
}
|
35
|
+
|
36
|
+
return super.createConnection(origin, options);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
module.exports = Http2OverHttpX;
|
@@ -0,0 +1,21 @@
|
|
1
|
+
'use strict';
|
2
|
+
// See https://github.com/facebook/jest/issues/2549
|
3
|
+
// eslint-disable-next-line node/prefer-global/url
|
4
|
+
const {URL} = require('url');
|
5
|
+
const checkType = require('../utils/check-type.js');
|
6
|
+
|
7
|
+
module.exports = (self, proxyOptions) => {
|
8
|
+
checkType('proxyOptions', proxyOptions, ['object']);
|
9
|
+
checkType('proxyOptions.headers', proxyOptions.headers, ['object', 'undefined']);
|
10
|
+
checkType('proxyOptions.raw', proxyOptions.raw, ['boolean', 'undefined']);
|
11
|
+
checkType('proxyOptions.url', proxyOptions.url, [URL, 'string']);
|
12
|
+
|
13
|
+
const url = new URL(proxyOptions.url);
|
14
|
+
|
15
|
+
self.proxyOptions = {
|
16
|
+
raw: true,
|
17
|
+
...proxyOptions,
|
18
|
+
headers: {...proxyOptions.headers},
|
19
|
+
url
|
20
|
+
};
|
21
|
+
};
|
@@ -0,0 +1,11 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
class UnexpectedStatusCodeError extends Error {
|
4
|
+
constructor(statusCode, statusMessage = '') {
|
5
|
+
super(`The proxy server rejected the request with status code ${statusCode} (${statusMessage || 'empty status message'})`);
|
6
|
+
this.statusCode = statusCode;
|
7
|
+
this.statusMessage = statusMessage;
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
module.exports = UnexpectedStatusCodeError;
|
@@ -0,0 +1,29 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {isIP} = require('net');
|
3
|
+
const assert = require('assert');
|
4
|
+
|
5
|
+
const getHost = host => {
|
6
|
+
if (host[0] === '[') {
|
7
|
+
const idx = host.indexOf(']');
|
8
|
+
|
9
|
+
assert(idx !== -1);
|
10
|
+
return host.slice(1, idx);
|
11
|
+
}
|
12
|
+
|
13
|
+
const idx = host.indexOf(':');
|
14
|
+
if (idx === -1) {
|
15
|
+
return host;
|
16
|
+
}
|
17
|
+
|
18
|
+
return host.slice(0, idx);
|
19
|
+
};
|
20
|
+
|
21
|
+
module.exports = host => {
|
22
|
+
const servername = getHost(host);
|
23
|
+
|
24
|
+
if (isIP(servername)) {
|
25
|
+
return '';
|
26
|
+
}
|
27
|
+
|
28
|
+
return servername;
|
29
|
+
};
|
@@ -0,0 +1,20 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const checkType = (name, value, types) => {
|
4
|
+
const valid = types.some(type => {
|
5
|
+
const typeofType = typeof type;
|
6
|
+
if (typeofType === 'string') {
|
7
|
+
return typeof value === type;
|
8
|
+
}
|
9
|
+
|
10
|
+
return value instanceof type;
|
11
|
+
});
|
12
|
+
|
13
|
+
if (!valid) {
|
14
|
+
const names = types.map(type => typeof type === 'string' ? type : type.name);
|
15
|
+
|
16
|
+
throw new TypeError(`Expected '${name}' to be a type of ${names.join(' or ')}, got ${typeof value}`);
|
17
|
+
}
|
18
|
+
};
|
19
|
+
|
20
|
+
module.exports = checkType;
|
@@ -0,0 +1,33 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
module.exports = stream => {
|
4
|
+
if (stream.listenerCount('error') !== 0) {
|
5
|
+
return stream;
|
6
|
+
}
|
7
|
+
|
8
|
+
stream.__destroy = stream._destroy;
|
9
|
+
stream._destroy = (...args) => {
|
10
|
+
const callback = args.pop();
|
11
|
+
|
12
|
+
stream.__destroy(...args, async error => {
|
13
|
+
await Promise.resolve();
|
14
|
+
callback(error);
|
15
|
+
});
|
16
|
+
};
|
17
|
+
|
18
|
+
const onError = error => {
|
19
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
20
|
+
Promise.resolve().then(() => {
|
21
|
+
stream.emit('error', error);
|
22
|
+
});
|
23
|
+
};
|
24
|
+
|
25
|
+
stream.once('error', onError);
|
26
|
+
|
27
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
28
|
+
Promise.resolve().then(() => {
|
29
|
+
stream.off('error', onError);
|
30
|
+
});
|
31
|
+
|
32
|
+
return stream;
|
33
|
+
};
|
@@ -0,0 +1,51 @@
|
|
1
|
+
'use strict';
|
2
|
+
/* istanbul ignore file: https://github.com/nodejs/node/blob/master/lib/internal/errors.js */
|
3
|
+
|
4
|
+
const makeError = (Base, key, getMessage) => {
|
5
|
+
module.exports[key] = class NodeError extends Base {
|
6
|
+
constructor(...args) {
|
7
|
+
super(typeof getMessage === 'string' ? getMessage : getMessage(args));
|
8
|
+
this.name = `${super.name} [${key}]`;
|
9
|
+
this.code = key;
|
10
|
+
}
|
11
|
+
};
|
12
|
+
};
|
13
|
+
|
14
|
+
makeError(TypeError, 'ERR_INVALID_ARG_TYPE', args => {
|
15
|
+
const type = args[0].includes('.') ? 'property' : 'argument';
|
16
|
+
|
17
|
+
let valid = args[1];
|
18
|
+
const isManyTypes = Array.isArray(valid);
|
19
|
+
|
20
|
+
if (isManyTypes) {
|
21
|
+
valid = `${valid.slice(0, -1).join(', ')} or ${valid.slice(-1)}`;
|
22
|
+
}
|
23
|
+
|
24
|
+
return `The "${args[0]}" ${type} must be ${isManyTypes ? 'one of' : 'of'} type ${valid}. Received ${typeof args[2]}`;
|
25
|
+
});
|
26
|
+
|
27
|
+
makeError(TypeError, 'ERR_INVALID_PROTOCOL', args =>
|
28
|
+
`Protocol "${args[0]}" not supported. Expected "${args[1]}"`
|
29
|
+
);
|
30
|
+
|
31
|
+
makeError(Error, 'ERR_HTTP_HEADERS_SENT', args =>
|
32
|
+
`Cannot ${args[0]} headers after they are sent to the client`
|
33
|
+
);
|
34
|
+
|
35
|
+
makeError(TypeError, 'ERR_INVALID_HTTP_TOKEN', args =>
|
36
|
+
`${args[0]} must be a valid HTTP token [${args[1]}]`
|
37
|
+
);
|
38
|
+
|
39
|
+
makeError(TypeError, 'ERR_HTTP_INVALID_HEADER_VALUE', args =>
|
40
|
+
`Invalid value "${args[0]} for header "${args[1]}"`
|
41
|
+
);
|
42
|
+
|
43
|
+
makeError(TypeError, 'ERR_INVALID_CHAR', args =>
|
44
|
+
`Invalid character in ${args[0]} [${args[1]}]`
|
45
|
+
);
|
46
|
+
|
47
|
+
makeError(
|
48
|
+
Error,
|
49
|
+
'ERR_HTTP2_NO_SOCKET_MANIPULATION',
|
50
|
+
'HTTP/2 sockets should not be directly manipulated (e.g. read and written)'
|
51
|
+
);
|
@@ -0,0 +1,102 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {ERR_HTTP2_NO_SOCKET_MANIPULATION} = require('./errors.js');
|
3
|
+
|
4
|
+
/* istanbul ignore file */
|
5
|
+
/* https://github.com/nodejs/node/blob/6eec858f34a40ffa489c1ec54bb24da72a28c781/lib/internal/http2/compat.js#L195-L272 */
|
6
|
+
|
7
|
+
const proxySocketHandler = {
|
8
|
+
has(stream, property) {
|
9
|
+
// Replaced [kSocket] with .socket
|
10
|
+
const reference = stream.session === undefined ? stream : stream.session.socket;
|
11
|
+
return (property in stream) || (property in reference);
|
12
|
+
},
|
13
|
+
|
14
|
+
get(stream, property) {
|
15
|
+
switch (property) {
|
16
|
+
case 'on':
|
17
|
+
case 'once':
|
18
|
+
case 'end':
|
19
|
+
case 'emit':
|
20
|
+
case 'destroy':
|
21
|
+
return stream[property].bind(stream);
|
22
|
+
case 'writable':
|
23
|
+
case 'destroyed':
|
24
|
+
return stream[property];
|
25
|
+
case 'readable':
|
26
|
+
if (stream.destroyed) {
|
27
|
+
return false;
|
28
|
+
}
|
29
|
+
|
30
|
+
return stream.readable;
|
31
|
+
case 'setTimeout': {
|
32
|
+
const {session} = stream;
|
33
|
+
if (session !== undefined) {
|
34
|
+
return session.setTimeout.bind(session);
|
35
|
+
}
|
36
|
+
|
37
|
+
return stream.setTimeout.bind(stream);
|
38
|
+
}
|
39
|
+
|
40
|
+
case 'write':
|
41
|
+
case 'read':
|
42
|
+
case 'pause':
|
43
|
+
case 'resume':
|
44
|
+
throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
|
45
|
+
default: {
|
46
|
+
// Replaced [kSocket] with .socket
|
47
|
+
const reference = stream.session === undefined ? stream : stream.session.socket;
|
48
|
+
const value = reference[property];
|
49
|
+
|
50
|
+
return typeof value === 'function' ? value.bind(reference) : value;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
},
|
54
|
+
|
55
|
+
getPrototypeOf(stream) {
|
56
|
+
if (stream.session !== undefined) {
|
57
|
+
// Replaced [kSocket] with .socket
|
58
|
+
return Reflect.getPrototypeOf(stream.session.socket);
|
59
|
+
}
|
60
|
+
|
61
|
+
return Reflect.getPrototypeOf(stream);
|
62
|
+
},
|
63
|
+
|
64
|
+
set(stream, property, value) {
|
65
|
+
switch (property) {
|
66
|
+
case 'writable':
|
67
|
+
case 'readable':
|
68
|
+
case 'destroyed':
|
69
|
+
case 'on':
|
70
|
+
case 'once':
|
71
|
+
case 'end':
|
72
|
+
case 'emit':
|
73
|
+
case 'destroy':
|
74
|
+
stream[property] = value;
|
75
|
+
return true;
|
76
|
+
case 'setTimeout': {
|
77
|
+
const {session} = stream;
|
78
|
+
if (session === undefined) {
|
79
|
+
stream.setTimeout = value;
|
80
|
+
} else {
|
81
|
+
session.setTimeout = value;
|
82
|
+
}
|
83
|
+
|
84
|
+
return true;
|
85
|
+
}
|
86
|
+
|
87
|
+
case 'write':
|
88
|
+
case 'read':
|
89
|
+
case 'pause':
|
90
|
+
case 'resume':
|
91
|
+
throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
|
92
|
+
default: {
|
93
|
+
// Replaced [kSocket] with .socket
|
94
|
+
const reference = stream.session === undefined ? stream : stream.session.socket;
|
95
|
+
reference[property] = value;
|
96
|
+
return true;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
};
|
101
|
+
|
102
|
+
module.exports = proxySocketHandler;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {ERR_INVALID_HTTP_TOKEN} = require('./errors.js');
|
3
|
+
const isRequestPseudoHeader = require('./is-request-pseudo-header.js');
|
4
|
+
|
5
|
+
const isValidHttpToken = /^[\^`\-\w!#$%&*+.|~]+$/;
|
6
|
+
|
7
|
+
module.exports = name => {
|
8
|
+
if (typeof name !== 'string' || (!isValidHttpToken.test(name) && !isRequestPseudoHeader(name))) {
|
9
|
+
throw new ERR_INVALID_HTTP_TOKEN('Header name', name);
|
10
|
+
}
|
11
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {
|
3
|
+
ERR_HTTP_INVALID_HEADER_VALUE,
|
4
|
+
ERR_INVALID_CHAR
|
5
|
+
} = require('./errors.js');
|
6
|
+
|
7
|
+
const isInvalidHeaderValue = /[^\t\u0020-\u007E\u0080-\u00FF]/;
|
8
|
+
|
9
|
+
module.exports = (name, value) => {
|
10
|
+
if (typeof value === 'undefined') {
|
11
|
+
throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
|
12
|
+
}
|
13
|
+
|
14
|
+
if (isInvalidHeaderValue.test(value)) {
|
15
|
+
throw new ERR_INVALID_CHAR('header content', name);
|
16
|
+
}
|
17
|
+
};
|