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,563 @@
|
|
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 http2 = require('http2');
|
6
|
+
const {Writable} = require('stream');
|
7
|
+
const {Agent, globalAgent} = require('./agent.js');
|
8
|
+
const IncomingMessage = require('./incoming-message.js');
|
9
|
+
const proxyEvents = require('./utils/proxy-events.js');
|
10
|
+
const {
|
11
|
+
ERR_INVALID_ARG_TYPE,
|
12
|
+
ERR_INVALID_PROTOCOL,
|
13
|
+
ERR_HTTP_HEADERS_SENT
|
14
|
+
} = require('./utils/errors.js');
|
15
|
+
const validateHeaderName = require('./utils/validate-header-name.js');
|
16
|
+
const validateHeaderValue = require('./utils/validate-header-value.js');
|
17
|
+
const proxySocketHandler = require('./utils/proxy-socket-handler.js');
|
18
|
+
|
19
|
+
const {
|
20
|
+
HTTP2_HEADER_STATUS,
|
21
|
+
HTTP2_HEADER_METHOD,
|
22
|
+
HTTP2_HEADER_PATH,
|
23
|
+
HTTP2_HEADER_AUTHORITY,
|
24
|
+
HTTP2_METHOD_CONNECT
|
25
|
+
} = http2.constants;
|
26
|
+
|
27
|
+
const kHeaders = Symbol('headers');
|
28
|
+
const kOrigin = Symbol('origin');
|
29
|
+
const kSession = Symbol('session');
|
30
|
+
const kOptions = Symbol('options');
|
31
|
+
const kFlushedHeaders = Symbol('flushedHeaders');
|
32
|
+
const kJobs = Symbol('jobs');
|
33
|
+
const kPendingAgentPromise = Symbol('pendingAgentPromise');
|
34
|
+
|
35
|
+
class ClientRequest extends Writable {
|
36
|
+
constructor(input, options, callback) {
|
37
|
+
super({
|
38
|
+
autoDestroy: false,
|
39
|
+
emitClose: false
|
40
|
+
});
|
41
|
+
|
42
|
+
if (typeof input === 'string') {
|
43
|
+
input = urlToHttpOptions(new URL(input));
|
44
|
+
} else if (input instanceof URL) {
|
45
|
+
input = urlToHttpOptions(input);
|
46
|
+
} else {
|
47
|
+
input = {...input};
|
48
|
+
}
|
49
|
+
|
50
|
+
if (typeof options === 'function' || options === undefined) {
|
51
|
+
// (options, callback)
|
52
|
+
callback = options;
|
53
|
+
options = input;
|
54
|
+
} else {
|
55
|
+
// (input, options, callback)
|
56
|
+
options = Object.assign(input, options);
|
57
|
+
}
|
58
|
+
|
59
|
+
if (options.h2session) {
|
60
|
+
this[kSession] = options.h2session;
|
61
|
+
|
62
|
+
if (this[kSession].destroyed) {
|
63
|
+
throw new Error('The session has been closed already');
|
64
|
+
}
|
65
|
+
|
66
|
+
this.protocol = this[kSession].socket.encrypted ? 'https:' : 'http:';
|
67
|
+
} else if (options.agent === false) {
|
68
|
+
this.agent = new Agent({maxEmptySessions: 0});
|
69
|
+
} else if (typeof options.agent === 'undefined' || options.agent === null) {
|
70
|
+
this.agent = globalAgent;
|
71
|
+
} else if (typeof options.agent.request === 'function') {
|
72
|
+
this.agent = options.agent;
|
73
|
+
} else {
|
74
|
+
throw new ERR_INVALID_ARG_TYPE('options.agent', ['http2wrapper.Agent-like Object', 'undefined', 'false'], options.agent);
|
75
|
+
}
|
76
|
+
|
77
|
+
if (this.agent) {
|
78
|
+
this.protocol = this.agent.protocol;
|
79
|
+
}
|
80
|
+
|
81
|
+
if (options.protocol && options.protocol !== this.protocol) {
|
82
|
+
throw new ERR_INVALID_PROTOCOL(options.protocol, this.protocol);
|
83
|
+
}
|
84
|
+
|
85
|
+
if (!options.port) {
|
86
|
+
options.port = options.defaultPort || (this.agent && this.agent.defaultPort) || 443;
|
87
|
+
}
|
88
|
+
|
89
|
+
options.host = options.hostname || options.host || 'localhost';
|
90
|
+
|
91
|
+
// Unused
|
92
|
+
delete options.hostname;
|
93
|
+
|
94
|
+
const {timeout} = options;
|
95
|
+
options.timeout = undefined;
|
96
|
+
|
97
|
+
this[kHeaders] = Object.create(null);
|
98
|
+
this[kJobs] = [];
|
99
|
+
|
100
|
+
this[kPendingAgentPromise] = undefined;
|
101
|
+
|
102
|
+
this.socket = null;
|
103
|
+
this.connection = null;
|
104
|
+
|
105
|
+
this.method = options.method || 'GET';
|
106
|
+
|
107
|
+
if (!(this.method === 'CONNECT' && (options.path === '/' || options.path === undefined))) {
|
108
|
+
this.path = options.path;
|
109
|
+
}
|
110
|
+
|
111
|
+
this.res = null;
|
112
|
+
this.aborted = false;
|
113
|
+
this.reusedSocket = false;
|
114
|
+
|
115
|
+
const {headers} = options;
|
116
|
+
if (headers) {
|
117
|
+
// eslint-disable-next-line guard-for-in
|
118
|
+
for (const header in headers) {
|
119
|
+
this.setHeader(header, headers[header]);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
if (options.auth && !('authorization' in this[kHeaders])) {
|
124
|
+
this[kHeaders].authorization = 'Basic ' + Buffer.from(options.auth).toString('base64');
|
125
|
+
}
|
126
|
+
|
127
|
+
options.session = options.tlsSession;
|
128
|
+
options.path = options.socketPath;
|
129
|
+
|
130
|
+
this[kOptions] = options;
|
131
|
+
|
132
|
+
// Clients that generate HTTP/2 requests directly SHOULD use the :authority pseudo-header field instead of the Host header field.
|
133
|
+
this[kOrigin] = new URL(`${this.protocol}//${options.servername || options.host}:${options.port}`);
|
134
|
+
|
135
|
+
// A socket is being reused
|
136
|
+
const reuseSocket = options._reuseSocket;
|
137
|
+
if (reuseSocket) {
|
138
|
+
options.createConnection = (...args) => {
|
139
|
+
if (reuseSocket.destroyed) {
|
140
|
+
return this.agent.createConnection(...args);
|
141
|
+
}
|
142
|
+
|
143
|
+
return reuseSocket;
|
144
|
+
};
|
145
|
+
|
146
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
147
|
+
this.agent.getSession(this[kOrigin], this[kOptions]).catch(() => {});
|
148
|
+
}
|
149
|
+
|
150
|
+
if (timeout) {
|
151
|
+
this.setTimeout(timeout);
|
152
|
+
}
|
153
|
+
|
154
|
+
if (callback) {
|
155
|
+
this.once('response', callback);
|
156
|
+
}
|
157
|
+
|
158
|
+
this[kFlushedHeaders] = false;
|
159
|
+
}
|
160
|
+
|
161
|
+
get method() {
|
162
|
+
return this[kHeaders][HTTP2_HEADER_METHOD];
|
163
|
+
}
|
164
|
+
|
165
|
+
set method(value) {
|
166
|
+
if (value) {
|
167
|
+
this[kHeaders][HTTP2_HEADER_METHOD] = value.toUpperCase();
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
get path() {
|
172
|
+
const header = this.method === 'CONNECT' ? HTTP2_HEADER_AUTHORITY : HTTP2_HEADER_PATH;
|
173
|
+
|
174
|
+
return this[kHeaders][header];
|
175
|
+
}
|
176
|
+
|
177
|
+
set path(value) {
|
178
|
+
if (value) {
|
179
|
+
const header = this.method === 'CONNECT' ? HTTP2_HEADER_AUTHORITY : HTTP2_HEADER_PATH;
|
180
|
+
|
181
|
+
this[kHeaders][header] = value;
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
get host() {
|
186
|
+
return this[kOrigin].hostname;
|
187
|
+
}
|
188
|
+
|
189
|
+
set host(_value) {
|
190
|
+
// Do nothing as this is read only.
|
191
|
+
}
|
192
|
+
|
193
|
+
get _mustNotHaveABody() {
|
194
|
+
return this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE';
|
195
|
+
}
|
196
|
+
|
197
|
+
_write(chunk, encoding, callback) {
|
198
|
+
// https://github.com/nodejs/node/blob/654df09ae0c5e17d1b52a900a545f0664d8c7627/lib/internal/http2/util.js#L148-L156
|
199
|
+
if (this._mustNotHaveABody) {
|
200
|
+
callback(new Error('The GET, HEAD and DELETE methods must NOT have a body'));
|
201
|
+
/* istanbul ignore next: Node.js 12 throws directly */
|
202
|
+
return;
|
203
|
+
}
|
204
|
+
|
205
|
+
this.flushHeaders();
|
206
|
+
|
207
|
+
const callWrite = () => this._request.write(chunk, encoding, callback);
|
208
|
+
if (this._request) {
|
209
|
+
callWrite();
|
210
|
+
} else {
|
211
|
+
this[kJobs].push(callWrite);
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
_final(callback) {
|
216
|
+
this.flushHeaders();
|
217
|
+
|
218
|
+
const callEnd = () => {
|
219
|
+
// For GET, HEAD and DELETE and CONNECT
|
220
|
+
if (this._mustNotHaveABody || this.method === 'CONNECT') {
|
221
|
+
callback();
|
222
|
+
return;
|
223
|
+
}
|
224
|
+
|
225
|
+
this._request.end(callback);
|
226
|
+
};
|
227
|
+
|
228
|
+
if (this._request) {
|
229
|
+
callEnd();
|
230
|
+
} else {
|
231
|
+
this[kJobs].push(callEnd);
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
abort() {
|
236
|
+
if (this.res && this.res.complete) {
|
237
|
+
return;
|
238
|
+
}
|
239
|
+
|
240
|
+
if (!this.aborted) {
|
241
|
+
process.nextTick(() => this.emit('abort'));
|
242
|
+
}
|
243
|
+
|
244
|
+
this.aborted = true;
|
245
|
+
|
246
|
+
this.destroy();
|
247
|
+
}
|
248
|
+
|
249
|
+
async _destroy(error, callback) {
|
250
|
+
if (this.res) {
|
251
|
+
this.res._dump();
|
252
|
+
}
|
253
|
+
|
254
|
+
if (this._request) {
|
255
|
+
this._request.destroy();
|
256
|
+
} else {
|
257
|
+
process.nextTick(() => {
|
258
|
+
this.emit('close');
|
259
|
+
});
|
260
|
+
}
|
261
|
+
|
262
|
+
try {
|
263
|
+
await this[kPendingAgentPromise];
|
264
|
+
} catch (internalError) {
|
265
|
+
if (this.aborted) {
|
266
|
+
error = internalError;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
callback(error);
|
271
|
+
}
|
272
|
+
|
273
|
+
async flushHeaders() {
|
274
|
+
if (this[kFlushedHeaders] || this.destroyed) {
|
275
|
+
return;
|
276
|
+
}
|
277
|
+
|
278
|
+
this[kFlushedHeaders] = true;
|
279
|
+
|
280
|
+
const isConnectMethod = this.method === HTTP2_METHOD_CONNECT;
|
281
|
+
|
282
|
+
// The real magic is here
|
283
|
+
const onStream = stream => {
|
284
|
+
this._request = stream;
|
285
|
+
|
286
|
+
if (this.destroyed) {
|
287
|
+
stream.destroy();
|
288
|
+
return;
|
289
|
+
}
|
290
|
+
|
291
|
+
// Forwards `timeout`, `continue`, `close` and `error` events to this instance.
|
292
|
+
if (!isConnectMethod) {
|
293
|
+
// TODO: Should we proxy `close` here?
|
294
|
+
proxyEvents(stream, this, ['timeout', 'continue']);
|
295
|
+
}
|
296
|
+
|
297
|
+
stream.once('error', error => {
|
298
|
+
this.destroy(error);
|
299
|
+
});
|
300
|
+
|
301
|
+
stream.once('aborted', () => {
|
302
|
+
const {res} = this;
|
303
|
+
if (res) {
|
304
|
+
res.aborted = true;
|
305
|
+
res.emit('aborted');
|
306
|
+
res.destroy();
|
307
|
+
} else {
|
308
|
+
this.destroy(new Error('The server aborted the HTTP/2 stream'));
|
309
|
+
}
|
310
|
+
});
|
311
|
+
|
312
|
+
const onResponse = (headers, flags, rawHeaders) => {
|
313
|
+
// If we were to emit raw request stream, it would be as fast as the native approach.
|
314
|
+
// Note that wrapping the raw stream in a Proxy instance won't improve the performance (already tested it).
|
315
|
+
const response = new IncomingMessage(this.socket, stream.readableHighWaterMark);
|
316
|
+
this.res = response;
|
317
|
+
|
318
|
+
// Undocumented, but it is used by `cacheable-request`
|
319
|
+
response.url = `${this[kOrigin].origin}${this.path}`;
|
320
|
+
|
321
|
+
response.req = this;
|
322
|
+
response.statusCode = headers[HTTP2_HEADER_STATUS];
|
323
|
+
response.headers = headers;
|
324
|
+
response.rawHeaders = rawHeaders;
|
325
|
+
|
326
|
+
response.once('end', () => {
|
327
|
+
response.complete = true;
|
328
|
+
|
329
|
+
// Has no effect, just be consistent with the Node.js behavior
|
330
|
+
response.socket = null;
|
331
|
+
response.connection = null;
|
332
|
+
});
|
333
|
+
|
334
|
+
if (isConnectMethod) {
|
335
|
+
response.upgrade = true;
|
336
|
+
|
337
|
+
// The HTTP1 API says the socket is detached here,
|
338
|
+
// but we can't do that so we pass the original HTTP2 request.
|
339
|
+
if (this.emit('connect', response, stream, Buffer.alloc(0))) {
|
340
|
+
this.emit('close');
|
341
|
+
} else {
|
342
|
+
// No listeners attached, destroy the original request.
|
343
|
+
stream.destroy();
|
344
|
+
}
|
345
|
+
} else {
|
346
|
+
// Forwards data
|
347
|
+
stream.on('data', chunk => {
|
348
|
+
if (!response._dumped && !response.push(chunk)) {
|
349
|
+
stream.pause();
|
350
|
+
}
|
351
|
+
});
|
352
|
+
|
353
|
+
stream.once('end', () => {
|
354
|
+
if (!this.aborted) {
|
355
|
+
response.push(null);
|
356
|
+
}
|
357
|
+
});
|
358
|
+
|
359
|
+
if (!this.emit('response', response)) {
|
360
|
+
// No listeners attached, dump the response.
|
361
|
+
response._dump();
|
362
|
+
}
|
363
|
+
}
|
364
|
+
};
|
365
|
+
|
366
|
+
// This event tells we are ready to listen for the data.
|
367
|
+
stream.once('response', onResponse);
|
368
|
+
|
369
|
+
// Emits `information` event
|
370
|
+
stream.once('headers', headers => this.emit('information', {statusCode: headers[HTTP2_HEADER_STATUS]}));
|
371
|
+
|
372
|
+
stream.once('trailers', (trailers, flags, rawTrailers) => {
|
373
|
+
const {res} = this;
|
374
|
+
|
375
|
+
// https://github.com/nodejs/node/issues/41251
|
376
|
+
if (res === null) {
|
377
|
+
onResponse(trailers, flags, rawTrailers);
|
378
|
+
return;
|
379
|
+
}
|
380
|
+
|
381
|
+
// Assigns trailers to the response object.
|
382
|
+
res.trailers = trailers;
|
383
|
+
res.rawTrailers = rawTrailers;
|
384
|
+
});
|
385
|
+
|
386
|
+
stream.once('close', () => {
|
387
|
+
const {aborted, res} = this;
|
388
|
+
if (res) {
|
389
|
+
if (aborted) {
|
390
|
+
res.aborted = true;
|
391
|
+
res.emit('aborted');
|
392
|
+
res.destroy();
|
393
|
+
}
|
394
|
+
|
395
|
+
const finish = () => {
|
396
|
+
res.emit('close');
|
397
|
+
|
398
|
+
this.destroy();
|
399
|
+
this.emit('close');
|
400
|
+
};
|
401
|
+
|
402
|
+
if (res.readable) {
|
403
|
+
res.once('end', finish);
|
404
|
+
} else {
|
405
|
+
finish();
|
406
|
+
}
|
407
|
+
|
408
|
+
return;
|
409
|
+
}
|
410
|
+
|
411
|
+
if (!this.destroyed) {
|
412
|
+
this.destroy(new Error('The HTTP/2 stream has been early terminated'));
|
413
|
+
this.emit('close');
|
414
|
+
return;
|
415
|
+
}
|
416
|
+
|
417
|
+
this.destroy();
|
418
|
+
this.emit('close');
|
419
|
+
});
|
420
|
+
|
421
|
+
this.socket = new Proxy(stream, proxySocketHandler);
|
422
|
+
|
423
|
+
for (const job of this[kJobs]) {
|
424
|
+
job();
|
425
|
+
}
|
426
|
+
|
427
|
+
this[kJobs].length = 0;
|
428
|
+
|
429
|
+
this.emit('socket', this.socket);
|
430
|
+
};
|
431
|
+
|
432
|
+
if (!(HTTP2_HEADER_AUTHORITY in this[kHeaders]) && !isConnectMethod) {
|
433
|
+
this[kHeaders][HTTP2_HEADER_AUTHORITY] = this[kOrigin].host;
|
434
|
+
}
|
435
|
+
|
436
|
+
// Makes a HTTP2 request
|
437
|
+
if (this[kSession]) {
|
438
|
+
try {
|
439
|
+
onStream(this[kSession].request(this[kHeaders]));
|
440
|
+
} catch (error) {
|
441
|
+
this.destroy(error);
|
442
|
+
}
|
443
|
+
} else {
|
444
|
+
this.reusedSocket = true;
|
445
|
+
|
446
|
+
try {
|
447
|
+
const promise = this.agent.request(this[kOrigin], this[kOptions], this[kHeaders]);
|
448
|
+
this[kPendingAgentPromise] = promise;
|
449
|
+
|
450
|
+
onStream(await promise);
|
451
|
+
|
452
|
+
this[kPendingAgentPromise] = false;
|
453
|
+
} catch (error) {
|
454
|
+
this[kPendingAgentPromise] = false;
|
455
|
+
|
456
|
+
this.destroy(error);
|
457
|
+
}
|
458
|
+
}
|
459
|
+
}
|
460
|
+
|
461
|
+
get connection() {
|
462
|
+
return this.socket;
|
463
|
+
}
|
464
|
+
|
465
|
+
set connection(value) {
|
466
|
+
this.socket = value;
|
467
|
+
}
|
468
|
+
|
469
|
+
getHeaderNames() {
|
470
|
+
return Object.keys(this[kHeaders]);
|
471
|
+
}
|
472
|
+
|
473
|
+
hasHeader(name) {
|
474
|
+
if (typeof name !== 'string') {
|
475
|
+
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
476
|
+
}
|
477
|
+
|
478
|
+
return Boolean(this[kHeaders][name.toLowerCase()]);
|
479
|
+
}
|
480
|
+
|
481
|
+
getHeader(name) {
|
482
|
+
if (typeof name !== 'string') {
|
483
|
+
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
484
|
+
}
|
485
|
+
|
486
|
+
return this[kHeaders][name.toLowerCase()];
|
487
|
+
}
|
488
|
+
|
489
|
+
get headersSent() {
|
490
|
+
return this[kFlushedHeaders];
|
491
|
+
}
|
492
|
+
|
493
|
+
removeHeader(name) {
|
494
|
+
if (typeof name !== 'string') {
|
495
|
+
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
|
496
|
+
}
|
497
|
+
|
498
|
+
if (this.headersSent) {
|
499
|
+
throw new ERR_HTTP_HEADERS_SENT('remove');
|
500
|
+
}
|
501
|
+
|
502
|
+
delete this[kHeaders][name.toLowerCase()];
|
503
|
+
}
|
504
|
+
|
505
|
+
setHeader(name, value) {
|
506
|
+
if (this.headersSent) {
|
507
|
+
throw new ERR_HTTP_HEADERS_SENT('set');
|
508
|
+
}
|
509
|
+
|
510
|
+
validateHeaderName(name);
|
511
|
+
validateHeaderValue(name, value);
|
512
|
+
|
513
|
+
const lowercased = name.toLowerCase();
|
514
|
+
|
515
|
+
if (lowercased === 'connection') {
|
516
|
+
if (value.toLowerCase() === 'keep-alive') {
|
517
|
+
return;
|
518
|
+
}
|
519
|
+
|
520
|
+
throw new Error(`Invalid 'connection' header: ${value}`);
|
521
|
+
}
|
522
|
+
|
523
|
+
if (lowercased === 'host' && this.method === 'CONNECT') {
|
524
|
+
this[kHeaders][HTTP2_HEADER_AUTHORITY] = value;
|
525
|
+
} else {
|
526
|
+
this[kHeaders][lowercased] = value;
|
527
|
+
}
|
528
|
+
}
|
529
|
+
|
530
|
+
setNoDelay() {
|
531
|
+
// HTTP2 sockets cannot be malformed, do nothing.
|
532
|
+
}
|
533
|
+
|
534
|
+
setSocketKeepAlive() {
|
535
|
+
// HTTP2 sockets cannot be malformed, do nothing.
|
536
|
+
}
|
537
|
+
|
538
|
+
setTimeout(ms, callback) {
|
539
|
+
const applyTimeout = () => this._request.setTimeout(ms, callback);
|
540
|
+
|
541
|
+
if (this._request) {
|
542
|
+
applyTimeout();
|
543
|
+
} else {
|
544
|
+
this[kJobs].push(applyTimeout);
|
545
|
+
}
|
546
|
+
|
547
|
+
return this;
|
548
|
+
}
|
549
|
+
|
550
|
+
get maxHeadersCount() {
|
551
|
+
if (!this.destroyed && this._request) {
|
552
|
+
return this._request.session.localSettings.maxHeaderListSize;
|
553
|
+
}
|
554
|
+
|
555
|
+
return undefined;
|
556
|
+
}
|
557
|
+
|
558
|
+
set maxHeadersCount(_value) {
|
559
|
+
// Updating HTTP2 settings would affect all requests, do nothing.
|
560
|
+
}
|
561
|
+
}
|
562
|
+
|
563
|
+
module.exports = ClientRequest;
|
@@ -0,0 +1,73 @@
|
|
1
|
+
'use strict';
|
2
|
+
const {Readable} = require('stream');
|
3
|
+
|
4
|
+
class IncomingMessage extends Readable {
|
5
|
+
constructor(socket, highWaterMark) {
|
6
|
+
super({
|
7
|
+
emitClose: false,
|
8
|
+
autoDestroy: true,
|
9
|
+
highWaterMark
|
10
|
+
});
|
11
|
+
|
12
|
+
this.statusCode = null;
|
13
|
+
this.statusMessage = '';
|
14
|
+
this.httpVersion = '2.0';
|
15
|
+
this.httpVersionMajor = 2;
|
16
|
+
this.httpVersionMinor = 0;
|
17
|
+
this.headers = {};
|
18
|
+
this.trailers = {};
|
19
|
+
this.req = null;
|
20
|
+
|
21
|
+
this.aborted = false;
|
22
|
+
this.complete = false;
|
23
|
+
this.upgrade = null;
|
24
|
+
|
25
|
+
this.rawHeaders = [];
|
26
|
+
this.rawTrailers = [];
|
27
|
+
|
28
|
+
this.socket = socket;
|
29
|
+
|
30
|
+
this._dumped = false;
|
31
|
+
}
|
32
|
+
|
33
|
+
get connection() {
|
34
|
+
return this.socket;
|
35
|
+
}
|
36
|
+
|
37
|
+
set connection(value) {
|
38
|
+
this.socket = value;
|
39
|
+
}
|
40
|
+
|
41
|
+
_destroy(error, callback) {
|
42
|
+
if (!this.readableEnded) {
|
43
|
+
this.aborted = true;
|
44
|
+
}
|
45
|
+
|
46
|
+
// See https://github.com/nodejs/node/issues/35303
|
47
|
+
callback();
|
48
|
+
|
49
|
+
this.req._request.destroy(error);
|
50
|
+
}
|
51
|
+
|
52
|
+
setTimeout(ms, callback) {
|
53
|
+
this.req.setTimeout(ms, callback);
|
54
|
+
return this;
|
55
|
+
}
|
56
|
+
|
57
|
+
_dump() {
|
58
|
+
if (!this._dumped) {
|
59
|
+
this._dumped = true;
|
60
|
+
|
61
|
+
this.removeAllListeners('data');
|
62
|
+
this.resume();
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
_read() {
|
67
|
+
if (this.req) {
|
68
|
+
this.req._request.resume();
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
module.exports = IncomingMessage;
|
package/source/index.js
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
'use strict';
|
2
|
+
const http2 = require('http2');
|
3
|
+
const {
|
4
|
+
Agent,
|
5
|
+
globalAgent
|
6
|
+
} = require('./agent.js');
|
7
|
+
const ClientRequest = require('./client-request.js');
|
8
|
+
const IncomingMessage = require('./incoming-message.js');
|
9
|
+
const auto = require('./auto.js');
|
10
|
+
const {
|
11
|
+
HttpOverHttp2,
|
12
|
+
HttpsOverHttp2
|
13
|
+
} = require('./proxies/h1-over-h2.js');
|
14
|
+
const Http2OverHttp2 = require('./proxies/h2-over-h2.js');
|
15
|
+
const {
|
16
|
+
Http2OverHttp,
|
17
|
+
Http2OverHttps
|
18
|
+
} = require('./proxies/h2-over-h1.js');
|
19
|
+
const validateHeaderName = require('./utils/validate-header-name.js');
|
20
|
+
const validateHeaderValue = require('./utils/validate-header-value.js');
|
21
|
+
|
22
|
+
const request = (url, options, callback) => new ClientRequest(url, options, callback);
|
23
|
+
|
24
|
+
const get = (url, options, callback) => {
|
25
|
+
// eslint-disable-next-line unicorn/prevent-abbreviations
|
26
|
+
const req = new ClientRequest(url, options, callback);
|
27
|
+
req.end();
|
28
|
+
|
29
|
+
return req;
|
30
|
+
};
|
31
|
+
|
32
|
+
module.exports = {
|
33
|
+
...http2,
|
34
|
+
ClientRequest,
|
35
|
+
IncomingMessage,
|
36
|
+
Agent,
|
37
|
+
globalAgent,
|
38
|
+
request,
|
39
|
+
get,
|
40
|
+
auto,
|
41
|
+
proxies: {
|
42
|
+
HttpOverHttp2,
|
43
|
+
HttpsOverHttp2,
|
44
|
+
Http2OverHttp2,
|
45
|
+
Http2OverHttp,
|
46
|
+
Http2OverHttps
|
47
|
+
},
|
48
|
+
validateHeaderName,
|
49
|
+
validateHeaderValue
|
50
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
module.exports = self => {
|
4
|
+
const {username, password} = self.proxyOptions.url;
|
5
|
+
|
6
|
+
if (username || password) {
|
7
|
+
const data = `${username}:${password}`;
|
8
|
+
const authorization = `Basic ${Buffer.from(data).toString('base64')}`;
|
9
|
+
|
10
|
+
return {
|
11
|
+
'proxy-authorization': authorization,
|
12
|
+
authorization
|
13
|
+
};
|
14
|
+
}
|
15
|
+
|
16
|
+
return {};
|
17
|
+
};
|