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
package/source/auto.js
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
'use strict';
|
2
|
+
// See https://github.com/facebook/jest/issues/2549
|
3
|
+
// eslint-disable-next-line node/prefer-global/url
|
4
|
+
const {URL, urlToHttpOptions} = require('url');
|
5
|
+
const http = require('http');
|
6
|
+
const https = require('https');
|
7
|
+
const resolveALPN = require('resolve-alpn');
|
8
|
+
const QuickLRU = require('quick-lru');
|
9
|
+
const {Agent, globalAgent} = require('./agent.js');
|
10
|
+
const Http2ClientRequest = require('./client-request.js');
|
11
|
+
const calculateServerName = require('./utils/calculate-server-name.js');
|
12
|
+
const delayAsyncDestroy = require('./utils/delay-async-destroy.js');
|
13
|
+
|
14
|
+
const cache = new QuickLRU({maxSize: 100});
|
15
|
+
const queue = new Map();
|
16
|
+
|
17
|
+
const installSocket = (agent, socket, options) => {
|
18
|
+
socket._httpMessage = {shouldKeepAlive: true};
|
19
|
+
|
20
|
+
const onFree = () => {
|
21
|
+
agent.emit('free', socket, options);
|
22
|
+
};
|
23
|
+
|
24
|
+
socket.on('free', onFree);
|
25
|
+
|
26
|
+
const onClose = () => {
|
27
|
+
agent.removeSocket(socket, options);
|
28
|
+
};
|
29
|
+
|
30
|
+
socket.on('close', onClose);
|
31
|
+
|
32
|
+
const onTimeout = () => {
|
33
|
+
const {freeSockets} = agent;
|
34
|
+
|
35
|
+
for (const sockets of Object.values(freeSockets)) {
|
36
|
+
if (sockets.includes(socket)) {
|
37
|
+
socket.destroy();
|
38
|
+
return;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
};
|
42
|
+
|
43
|
+
socket.on('timeout', onTimeout);
|
44
|
+
|
45
|
+
const onRemove = () => {
|
46
|
+
agent.removeSocket(socket, options);
|
47
|
+
socket.off('close', onClose);
|
48
|
+
socket.off('free', onFree);
|
49
|
+
socket.off('timeout', onTimeout);
|
50
|
+
socket.off('agentRemove', onRemove);
|
51
|
+
};
|
52
|
+
|
53
|
+
socket.on('agentRemove', onRemove);
|
54
|
+
|
55
|
+
agent.emit('free', socket, options);
|
56
|
+
};
|
57
|
+
|
58
|
+
const createResolveProtocol = (cache, queue = new Map(), connect = undefined) => {
|
59
|
+
return async options => {
|
60
|
+
const name = `${options.host}:${options.port}:${options.ALPNProtocols.sort()}`;
|
61
|
+
|
62
|
+
if (!cache.has(name)) {
|
63
|
+
if (queue.has(name)) {
|
64
|
+
const result = await queue.get(name);
|
65
|
+
return {alpnProtocol: result.alpnProtocol};
|
66
|
+
}
|
67
|
+
|
68
|
+
const {path} = options;
|
69
|
+
options.path = options.socketPath;
|
70
|
+
|
71
|
+
const resultPromise = resolveALPN(options, connect);
|
72
|
+
queue.set(name, resultPromise);
|
73
|
+
|
74
|
+
try {
|
75
|
+
const result = await resultPromise;
|
76
|
+
|
77
|
+
cache.set(name, result.alpnProtocol);
|
78
|
+
queue.delete(name);
|
79
|
+
|
80
|
+
options.path = path;
|
81
|
+
|
82
|
+
return result;
|
83
|
+
} catch (error) {
|
84
|
+
queue.delete(name);
|
85
|
+
|
86
|
+
options.path = path;
|
87
|
+
|
88
|
+
throw error;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
return {alpnProtocol: cache.get(name)};
|
93
|
+
};
|
94
|
+
};
|
95
|
+
|
96
|
+
const defaultResolveProtocol = createResolveProtocol(cache, queue);
|
97
|
+
|
98
|
+
module.exports = async (input, options, callback) => {
|
99
|
+
if (typeof input === 'string') {
|
100
|
+
input = urlToHttpOptions(new URL(input));
|
101
|
+
} else if (input instanceof URL) {
|
102
|
+
input = urlToHttpOptions(input);
|
103
|
+
} else {
|
104
|
+
input = {...input};
|
105
|
+
}
|
106
|
+
|
107
|
+
if (typeof options === 'function' || options === undefined) {
|
108
|
+
// (options, callback)
|
109
|
+
callback = options;
|
110
|
+
options = input;
|
111
|
+
} else {
|
112
|
+
// (input, options, callback)
|
113
|
+
options = Object.assign(input, options);
|
114
|
+
}
|
115
|
+
|
116
|
+
options.ALPNProtocols = options.ALPNProtocols || ['h2', 'http/1.1'];
|
117
|
+
|
118
|
+
if (!Array.isArray(options.ALPNProtocols) || options.ALPNProtocols.length === 0) {
|
119
|
+
throw new Error('The `ALPNProtocols` option must be an Array with at least one entry');
|
120
|
+
}
|
121
|
+
|
122
|
+
options.protocol = options.protocol || 'https:';
|
123
|
+
const isHttps = options.protocol === 'https:';
|
124
|
+
|
125
|
+
options.host = options.hostname || options.host || 'localhost';
|
126
|
+
options.session = options.tlsSession;
|
127
|
+
options.servername = options.servername || calculateServerName((options.headers && options.headers.host) || options.host);
|
128
|
+
options.port = options.port || (isHttps ? 443 : 80);
|
129
|
+
options._defaultAgent = isHttps ? https.globalAgent : http.globalAgent;
|
130
|
+
|
131
|
+
const resolveProtocol = options.resolveProtocol || defaultResolveProtocol;
|
132
|
+
|
133
|
+
// Note: We don't support `h2session` here
|
134
|
+
|
135
|
+
let {agent} = options;
|
136
|
+
if (agent !== undefined && agent !== false && agent.constructor.name !== 'Object') {
|
137
|
+
throw new Error('The `options.agent` can be only an object `http`, `https` or `http2` properties');
|
138
|
+
}
|
139
|
+
|
140
|
+
if (isHttps) {
|
141
|
+
options.resolveSocket = true;
|
142
|
+
|
143
|
+
let {socket, alpnProtocol, timeout} = await resolveProtocol(options);
|
144
|
+
|
145
|
+
if (timeout) {
|
146
|
+
if (socket) {
|
147
|
+
socket.destroy();
|
148
|
+
}
|
149
|
+
|
150
|
+
const error = new Error(`Timed out resolving ALPN: ${options.timeout} ms`);
|
151
|
+
error.code = 'ETIMEDOUT';
|
152
|
+
error.ms = options.timeout;
|
153
|
+
|
154
|
+
throw error;
|
155
|
+
}
|
156
|
+
|
157
|
+
// We can't accept custom `createConnection` because the API is different for HTTP/2
|
158
|
+
if (socket && options.createConnection) {
|
159
|
+
socket.destroy();
|
160
|
+
socket = undefined;
|
161
|
+
}
|
162
|
+
|
163
|
+
delete options.resolveSocket;
|
164
|
+
|
165
|
+
const isHttp2 = alpnProtocol === 'h2';
|
166
|
+
|
167
|
+
if (agent) {
|
168
|
+
agent = isHttp2 ? agent.http2 : agent.https;
|
169
|
+
options.agent = agent;
|
170
|
+
}
|
171
|
+
|
172
|
+
if (agent === undefined) {
|
173
|
+
agent = isHttp2 ? globalAgent : https.globalAgent;
|
174
|
+
}
|
175
|
+
|
176
|
+
if (socket) {
|
177
|
+
if (agent === false) {
|
178
|
+
socket.destroy();
|
179
|
+
} else {
|
180
|
+
const defaultCreateConnection = (isHttp2 ? Agent : https.Agent).prototype.createConnection;
|
181
|
+
|
182
|
+
if (agent.createConnection === defaultCreateConnection) {
|
183
|
+
if (isHttp2) {
|
184
|
+
options._reuseSocket = socket;
|
185
|
+
} else {
|
186
|
+
installSocket(agent, socket, options);
|
187
|
+
}
|
188
|
+
} else {
|
189
|
+
socket.destroy();
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
if (isHttp2) {
|
195
|
+
return delayAsyncDestroy(new Http2ClientRequest(options, callback));
|
196
|
+
}
|
197
|
+
} else if (agent) {
|
198
|
+
options.agent = agent.http;
|
199
|
+
}
|
200
|
+
|
201
|
+
// If we're sending HTTP/1.1, handle any explicitly set H2 headers in the options:
|
202
|
+
if (options.headers) {
|
203
|
+
options.headers = {...options.headers};
|
204
|
+
|
205
|
+
// :authority is equivalent to the HTTP/1.1 host header
|
206
|
+
if (options.headers[':authority']) {
|
207
|
+
if (!options.headers.host) {
|
208
|
+
options.headers.host = options.headers[':authority'];
|
209
|
+
}
|
210
|
+
|
211
|
+
delete options.headers[':authority'];
|
212
|
+
}
|
213
|
+
|
214
|
+
// Remove other HTTP/2 headers as they have their counterparts in the options
|
215
|
+
delete options.headers[':method'];
|
216
|
+
delete options.headers[':scheme'];
|
217
|
+
delete options.headers[':path'];
|
218
|
+
}
|
219
|
+
|
220
|
+
return delayAsyncDestroy(http.request(options, callback));
|
221
|
+
};
|
222
|
+
|
223
|
+
module.exports.protocolCache = cache;
|
224
|
+
module.exports.resolveProtocol = defaultResolveProtocol;
|
225
|
+
module.exports.createResolveProtocol = createResolveProtocol;
|