@wiajs/request 3.0.0

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/lib/request.js ADDED
@@ -0,0 +1,828 @@
1
+ /**
2
+ * fork from follow-redirects
3
+ * https://github.com/follow-redirects/follow-redirects
4
+ */ import http from 'node:http';
5
+ import https from 'node:https';
6
+ import assert from 'node:assert';
7
+ import url from 'node:url';
8
+ import stream from 'node:stream';
9
+ import { Duplex } from 'node:stream'; // Writable 流改为读写双工
10
+ import zlib from 'node:zlib';
11
+ import mime from 'mime-types';
12
+ import { log as Log, name } from '@wiajs/log';
13
+ import ZlibTransform from './ZlibTransform.js';
14
+ import utils from './utils.js';
15
+ import Caseless from './caseless.js';
16
+ const log = Log({
17
+ env: `wia:req:${name(__filename)}`
18
+ });
19
+ const httpModules = {
20
+ 'http:': http,
21
+ 'https:': https
22
+ };
23
+ const zlibOptions = {
24
+ flush: zlib.constants.Z_SYNC_FLUSH,
25
+ finishFlush: zlib.constants.Z_SYNC_FLUSH
26
+ };
27
+ const brotliOptions = {
28
+ flush: zlib.constants.BROTLI_OPERATION_FLUSH,
29
+ finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH
30
+ };
31
+ const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
32
+ // Create handlers that pass events from native requests
33
+ const writeEvents = [
34
+ 'abort',
35
+ 'aborted',
36
+ 'close',
37
+ 'connect',
38
+ 'continue',
39
+ 'drain',
40
+ 'error',
41
+ 'finish',
42
+ 'information',
43
+ 'pipe',
44
+ // 'response', 由 processResponse 触发
45
+ 'socket',
46
+ 'timeout',
47
+ 'unpipe',
48
+ 'upgrade'
49
+ ];
50
+ const writeEventEmit = Object.create(null);
51
+ for (const ev of writeEvents)writeEventEmit[ev] = function(...args) {
52
+ const m = this // 事件回调,this === clientRequest 实例
53
+ ;
54
+ log.debug('req event', {
55
+ ev
56
+ });
57
+ m.redirectReq.emit(ev, ...args) // req 事情映射到 redirectReq 上触发
58
+ ;
59
+ };
60
+ // stream.Readable
61
+ // data 单独处理
62
+ const readEvents = [
63
+ 'close',
64
+ 'end',
65
+ 'error',
66
+ 'pause',
67
+ 'readable',
68
+ 'resume'
69
+ ];
70
+ const readEventEmit = Object.create(null);
71
+ for (const ev of readEvents)readEventEmit[ev] = function(...args) {
72
+ const m = this // 事件回调,this === clientRequest 实例
73
+ ;
74
+ log.debug('res event', {
75
+ ev
76
+ });
77
+ m.redirectReq.emit(ev, ...args) // 向上触发事件
78
+ ;
79
+ };
80
+ // Error types with codes
81
+ const RedirectionError = utils.createErrorType('ERR_FR_REDIRECTION_FAILURE', 'Redirected request failed');
82
+ const TooManyRedirectsError = utils.createErrorType('ERR_FR_TOO_MANY_REDIRECTS', 'Maximum number of redirects exceeded', RedirectionError);
83
+ const MaxBodyLengthExceededError = utils.createErrorType('ERR_FR_MAX_BODY_LENGTH_EXCEEDED', 'Request body larger than maxBodyLength limit');
84
+ const WriteAfterEndError = utils.createErrorType('ERR_STREAM_WRITE_AFTER_END', 'write after end');
85
+ /**
86
+ * An HTTP(S) request that can be redirected
87
+ * wrap http.ClientRequest
88
+ */ export default class Request extends Duplex {
89
+ /**
90
+ * responseCallback 原消息处理回调
91
+ * @param {*} options
92
+ * @param {*} resCallback
93
+ */ constructor(options, resCallback){
94
+ super(), this._timeout = 0, /** @type {*} */ this.socket = null, /** @type {http.ClientRequest} */ this._currentRequest = null, /** @type {stream.Readable} */ this.response = null, /** @type {stream.Readable} */ this.responseStream = null, this.timing = false, this.responseStarted = false, this.responseStartTime = 0, this._destdata = false, this._paused = false, this._respended = false, /** @type {stream.Readable} */ this.pipesrc = null // 被 pipe 时的 src stream
95
+ , /** @type {stream.Writable[]} */ this.pipedests = [] // pipe dest
96
+ ;
97
+ const m = this;
98
+ // log({options}, 'constructor');
99
+ // Initialize the request
100
+ m._sanitizeOptions(options);
101
+ m._options = options;
102
+ m.headers = options.headers;
103
+ m._ended = false;
104
+ m._ending = false;
105
+ m._redirectCount = 0;
106
+ /** @type {any[]} */ m._redirects = [];
107
+ m._requestBodyLength = 0;
108
+ /** @type {any[]} */ m._requestBodyBuffers = [];
109
+ // save the callback if passed
110
+ m.resCallback = resCallback;
111
+ // React to responses of native requests
112
+ // 接管 response 事件,非重定向,触发 response 事件
113
+ m._onResponse = (response)=>{
114
+ try {
115
+ m.processResponse(response);
116
+ } catch (cause) {
117
+ m.emit('error', cause instanceof RedirectionError ? cause : new RedirectionError({
118
+ cause: cause
119
+ }));
120
+ }
121
+ };
122
+ // Proxy all other public ClientRequest methods
123
+ for (const method of [
124
+ 'flushHeaders',
125
+ 'setNoDelay',
126
+ 'setSocketKeepAlive'
127
+ ]){
128
+ m[method] = (a, b)=>{
129
+ log.debug(method, {
130
+ a,
131
+ b
132
+ });
133
+ m._currentRequest[method](a, b);
134
+ };
135
+ }
136
+ // Proxy all public ClientRequest properties
137
+ for (const property of [
138
+ 'aborted',
139
+ 'connection',
140
+ 'socket'
141
+ ]){
142
+ Object.defineProperty(m, property, {
143
+ get () {
144
+ const val = m._currentRequest[property];
145
+ log.debug('get property', {
146
+ property
147
+ });
148
+ return val;
149
+ }
150
+ });
151
+ }
152
+ // 流模式
153
+ if (options.stream) // 被 pipe 作为目标时触发,拷贝 src headers
154
+ m.on('pipe', /** @type {stream.Readable} */ (src)=>{
155
+ // m.ntick &&
156
+ if (m._currentRequest) {
157
+ m.emit('error', new Error('You cannot pipe to this stream after the outbound request has started.'));
158
+ }
159
+ m.pipesrc = src;
160
+ if (utils.isReadStream(src)) {
161
+ if (!m.hasHeader('content-type')) {
162
+ m.setHeader('content-type', mime.lookup(src.path));
163
+ }
164
+ } else {
165
+ if (src.headers) {
166
+ for (const h of src.headers){
167
+ if (!m.hasHeader(h)) {
168
+ m.setHeader(h, src.headers[h]);
169
+ }
170
+ }
171
+ }
172
+ // if (src.method && !self.explicitMethod) {
173
+ // m.method = src.method
174
+ // }
175
+ }
176
+ });
177
+ // Perform the first request
178
+ // m.request(); // 写入数据时执行,否则 pipe时无法写入header
179
+ }
180
+ /**
181
+ * Executes the next native request (initial or redirect)
182
+ * @returns http(s) 实例
183
+ */ request() {
184
+ let R = null;
185
+ try {
186
+ const m = this;
187
+ // read stream
188
+ m.response = null;
189
+ m.responseStarted = false;
190
+ m.responseStream = null;
191
+ m.timing = false;
192
+ m.responseStartTime = 0;
193
+ m._destdata = false;
194
+ m._paused = false;
195
+ m._respended = false;
196
+ // m.httpModule = httpModules[protocol];
197
+ // Load the native protocol
198
+ let { protocol } = m._options;
199
+ const { agents } = m._options;
200
+ // 代理以目的网址协议为准
201
+ // If specified, use the agent corresponding to the protocol
202
+ // (HTTP and HTTPS use different types of agents)
203
+ if (agents) {
204
+ const scheme = protocol.slice(0, -1);
205
+ m._options.agent = agents[scheme];
206
+ }
207
+ // http 非隧道代理模式,模块以代理主机为准,其他以目的网址为准
208
+ // 代理内部会根据代理协议选择 http(s) 发起请求创建连接
209
+ if (protocol === 'http:' && agents.http) {
210
+ protocol = agents.http.proxy && !agents.http.tunnel ? agents.http.proxy.protocol : protocol;
211
+ }
212
+ const httpModule = httpModules[protocol];
213
+ if (!httpModule) throw TypeError(`Unsupported protocol: ${protocol}`);
214
+ log.debug('request', {
215
+ options: m._options,
216
+ protocol
217
+ });
218
+ debugger;
219
+ // Create the native request and set up its event handlers
220
+ const req = httpModule.request(m._options, m._onResponse);
221
+ m._currentRequest = req;
222
+ req.redirectReq = m;
223
+ // 接收req事件,转发 到 redirectReq 发射
224
+ for (const ev of writeEvents)req.on(ev, writeEventEmit[ev]);
225
+ // RFC7230§5.3.1: When making a request directly to an origin server, […]
226
+ // a client MUST send only the absolute path […] as the request-target.
227
+ // When making a request to a proxy, […]
228
+ // a client MUST send the target URI in absolute-form […].
229
+ m._currentUrl = /^\//.test(m._options.path) ? url.format(m._options) : m._options.path;
230
+ // End a redirected request
231
+ // (The first request must be ended explicitly with RedirectableRequest#end)
232
+ if (m._isRedirect) {
233
+ // Write the request entity and end
234
+ let i = 0;
235
+ const buffers = m._requestBodyBuffers;
236
+ (function writeNext(error) {
237
+ // Only write if this request has not been redirected yet
238
+ /* istanbul ignore else */ if (req === m._currentRequest) {
239
+ // Report any write errors
240
+ /* istanbul ignore if */ if (error) m.emit('error', error);
241
+ else if (i < buffers.length) {
242
+ const buf = buffers[i++];
243
+ /* istanbul ignore else */ if (!req.finished) req.write(buf.data, buf.encoding, writeNext);
244
+ } else if (m._ended) req.end();
245
+ }
246
+ })();
247
+ }
248
+ R = req;
249
+ } catch (e) {
250
+ log.err(e, 'request');
251
+ throw e;
252
+ }
253
+ return R;
254
+ }
255
+ abort() {
256
+ destroyRequest(this._currentRequest);
257
+ this._currentRequest.abort();
258
+ this.emit('abort');
259
+ }
260
+ /**
261
+ * 析构
262
+ * @param {*} error
263
+ * @returns
264
+ */ destroy(error) {
265
+ const m = this;
266
+ if (!m._ended) m.end();
267
+ else if (m.response) m.response.destroy();
268
+ else if (m.responseStream) m.responseStream.destroy();
269
+ // m.clearTimeout();
270
+ destroyRequest(m._currentRequest, error);
271
+ super.destroy(error);
272
+ return this;
273
+ }
274
+ /**
275
+ * Writes buffered data to the current native request
276
+ * 如 request 不存在,则创建连接,pipe 时可写入 header
277
+ * @param {*} chunk
278
+ * @param {BufferEncoding=} encoding
279
+ * @param {(error: Error) => void} [cb]
280
+ * @returns {boolean}
281
+ */ write(chunk, encoding, cb) {
282
+ const m = this;
283
+ log.debug('write', {
284
+ data: chunk,
285
+ encoding,
286
+ callback: cb
287
+ });
288
+ // Writing is not allowed if end has been called
289
+ if (m._ending) throw new WriteAfterEndError();
290
+ // ! 数据写入时连接,pipe 时可设置 header
291
+ if (!m._currentRequest) m.request();
292
+ // Validate input and shift parameters if necessary
293
+ if (!utils.isString(chunk) && !utils.isBuffer(chunk)) throw new TypeError('data should be a string, Buffer or Uint8Array');
294
+ if (utils.isFunction(encoding)) {
295
+ cb = encoding;
296
+ encoding = null;
297
+ }
298
+ // Ignore empty buffers, since writing them doesn't invoke the callback
299
+ // https://github.com/nodejs/node/issues/22066
300
+ if (chunk.length === 0) {
301
+ if (cb) cb();
302
+ return;
303
+ }
304
+ // Only write when we don't exceed the maximum body length
305
+ if (m._requestBodyLength + chunk.length <= m._options.maxBodyLength) {
306
+ m._requestBodyLength += chunk.length;
307
+ m._requestBodyBuffers.push({
308
+ data: chunk,
309
+ encoding
310
+ });
311
+ m._currentRequest.write(chunk, encoding, cb);
312
+ } else {
313
+ m.emit('error', new MaxBodyLengthExceededError());
314
+ m.abort();
315
+ }
316
+ }
317
+ /**
318
+ * Ends the current native request
319
+ * @param {*} data
320
+ * @param {*} encoding
321
+ * @param {*} callback
322
+ */ end(data, encoding, callback) {
323
+ const m = this;
324
+ // Shift parameters if necessary
325
+ if (utils.isFunction(data)) {
326
+ callback = data;
327
+ data = null;
328
+ encoding = null;
329
+ } else if (utils.isFunction(encoding)) {
330
+ callback = encoding;
331
+ encoding = null;
332
+ }
333
+ // ! 数据写入时连接,pipe 时可设置 header
334
+ if (!m._currentRequest) m.request();
335
+ // Write data if needed and end
336
+ if (!data) {
337
+ m._ended = true;
338
+ m._ending = true;
339
+ m._currentRequest.end(null, null, callback);
340
+ } else {
341
+ const currentRequest = m._currentRequest;
342
+ m.write(data, encoding, ()=>{
343
+ m._ended = true;
344
+ currentRequest.end(null, null, callback);
345
+ });
346
+ m._ending = true;
347
+ }
348
+ }
349
+ /**
350
+ *
351
+ * @param {string} name
352
+ * @returns
353
+ */ hasHeader(name) {
354
+ return this._options.headers.includes(name);
355
+ }
356
+ /**
357
+ *
358
+ * @param {string} name
359
+ * @returns {string}
360
+ */ getHeader(name) {
361
+ return this._options.headers[name];
362
+ }
363
+ /**
364
+ * Sets a header value on the current native request
365
+ * @param {string} name
366
+ */ setHeader(name, value) {
367
+ this._options.headers[name] = value;
368
+ this._currentRequest?.setHeader(name, value);
369
+ }
370
+ /**
371
+ * Clears a header value on the current native request
372
+ * @param {string} name
373
+ */ removeHeader(name) {
374
+ delete this._options.headers[name];
375
+ this._currentRequest?.removeHeader(name);
376
+ }
377
+ /**
378
+ * 标头是否已发送
379
+ * @returns
380
+ */ get headersSent() {
381
+ return this._currentRequest?.headersSent;
382
+ }
383
+ // Global timeout for all underlying requests
384
+ /**
385
+ *
386
+ * @param {*} msecs
387
+ * @param {*} callback
388
+ * @returns
389
+ */ setTimeout(msecs, callback) {
390
+ const m = this;
391
+ // Destroys the socket on timeout
392
+ /**
393
+ *
394
+ * @param {*} socket
395
+ */ function destroyOnTimeout(socket) {
396
+ socket.setTimeout(msecs);
397
+ socket.removeListener('timeout', socket.destroy);
398
+ socket.addListener('timeout', socket.destroy);
399
+ }
400
+ // Sets up a timer to trigger a timeout event
401
+ /**
402
+ *
403
+ * @param {*} socket
404
+ */ function startTimer(socket) {
405
+ if (m._timeout) {
406
+ clearTimeout(m._timeout);
407
+ }
408
+ m._timeout = setTimeout(()=>{
409
+ m.emit('timeout');
410
+ clearTimer();
411
+ }, msecs);
412
+ destroyOnTimeout(socket);
413
+ }
414
+ // Stops a timeout from triggering
415
+ function clearTimer() {
416
+ // Clear the timeout
417
+ if (m._timeout) {
418
+ clearTimeout(m._timeout);
419
+ m._timeout = null;
420
+ }
421
+ // Clean up all attached listeners
422
+ m.removeListener('abort', clearTimer);
423
+ m.removeListener('error', clearTimer);
424
+ m.removeListener('response', clearTimer);
425
+ m.removeListener('close', clearTimer);
426
+ if (callback) {
427
+ m.removeListener('timeout', callback);
428
+ }
429
+ if (!m.socket) {
430
+ m._currentRequest.removeListener('socket', startTimer);
431
+ }
432
+ }
433
+ // Attach callback if passed
434
+ if (callback) m.on('timeout', callback);
435
+ // Start the timer if or when the socket is opened
436
+ if (m.socket) startTimer(m.socket);
437
+ else m._currentRequest.once('socket', startTimer);
438
+ // Clean up on events
439
+ m.on('socket', destroyOnTimeout);
440
+ m.on('abort', clearTimer);
441
+ m.on('error', clearTimer);
442
+ m.on('response', clearTimer);
443
+ m.on('close', clearTimer);
444
+ return m;
445
+ }
446
+ _sanitizeOptions(options) {
447
+ // Ensure headers are always present
448
+ if (!options.headers) options.headers = {};
449
+ // Since http.request treats host as an alias of hostname,
450
+ // but the url module interprets host as hostname plus port,
451
+ // eliminate the host property to avoid confusion.
452
+ if (options.host) {
453
+ // Use hostname if set, because it has precedence
454
+ if (!options.hostname) {
455
+ options.hostname = options.host;
456
+ }
457
+ options.host = undefined;
458
+ }
459
+ // Complete the URL object when necessary
460
+ if (!options.pathname && options.path) {
461
+ const searchPos = options.path.indexOf('?');
462
+ if (searchPos < 0) {
463
+ options.pathname = options.path;
464
+ } else {
465
+ options.pathname = options.path.substring(0, searchPos);
466
+ options.search = options.path.substring(searchPos);
467
+ }
468
+ }
469
+ }
470
+ /**
471
+ * Processes a response from the current native request
472
+ * @param {*} response
473
+ * @returns
474
+ */ processResponse(response) {
475
+ const m = this;
476
+ // Store the redirected response
477
+ const { statusCode } = response;
478
+ if (m._options.trackRedirects) {
479
+ m._redirects.push({
480
+ url: m._currentUrl,
481
+ headers: response.headers,
482
+ statusCode
483
+ });
484
+ }
485
+ // RFC7231§6.4: The 3xx (Redirection) class of status code indicates
486
+ // that further action needs to be taken by the user agent in order to
487
+ // fulfill the request. If a Location header field is provided,
488
+ // the user agent MAY automatically redirect its request to the URI
489
+ // referenced by the Location field value,
490
+ // even if the specific status code is not understood.
491
+ // If the response is not a redirect; return it as-is
492
+ const { location } = response.headers;
493
+ log('processResponse', {
494
+ statusCode,
495
+ headers: response.headers
496
+ });
497
+ if (!location || m._options.followRedirects === false || statusCode < 300 || statusCode >= 400) {
498
+ // 非重定向,返回给原始回调处理
499
+ response.responseUrl = m._currentUrl;
500
+ response.redirects = m._redirects;
501
+ m.response = response;
502
+ // Be a good stream and emit end when the response is finished.
503
+ // Hack to emit end on close because of a core bug that never fires end
504
+ response.on('close', ()=>{
505
+ if (!m._respended) {
506
+ m.response.emit('end');
507
+ }
508
+ });
509
+ response.once('end', ()=>{
510
+ m._respended = true;
511
+ });
512
+ const responseStream = m.processStream(response);
513
+ // NOTE: responseStartTime is deprecated in favor of .timings
514
+ response.responseStartTime = m.responseStartTime;
515
+ // 触发原回调函数
516
+ m.resCallback?.(response, responseStream);
517
+ // 类似 ClientRequest,触发 response 事件
518
+ m.emit('response', response, responseStream);
519
+ // Clean up
520
+ m._requestBodyBuffers = [];
521
+ return;
522
+ }
523
+ // The response is a redirect, so abort the current request
524
+ destroyRequest(m._currentRequest);
525
+ // Discard the remainder of the response to avoid waiting for data
526
+ response.destroy();
527
+ // RFC7231§6.4: A client SHOULD detect and intervene
528
+ // in cyclical redirections (i.e., "infinite" redirection loops).
529
+ if (++m._redirectCount > m._options.maxRedirects) throw new TooManyRedirectsError();
530
+ // Store the request headers if applicable
531
+ let requestHeaders;
532
+ const { beforeRedirect } = m._options;
533
+ if (beforeRedirect) {
534
+ requestHeaders = {
535
+ // The Host header was set by nativeProtocol.request
536
+ Host: response.req.getHeader('host'),
537
+ ...m._options.headers
538
+ };
539
+ }
540
+ // RFC7231§6.4: Automatic redirection needs to done with
541
+ // care for methods not known to be safe, […]
542
+ // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
543
+ // the request method from POST to GET for the subsequent request.
544
+ const { method } = m._options;
545
+ if ((statusCode === 301 || statusCode === 302) && m._options.method === 'POST' || // RFC7231§6.4.4: The 303 (See Other) status code indicates that
546
+ // the server is redirecting the user agent to a different resource […]
547
+ // A user agent can perform a retrieval request targeting that URI
548
+ // (a GET or HEAD request if using HTTP) […]
549
+ statusCode === 303 && !/^(?:GET|HEAD)$/.test(m._options.method)) {
550
+ m._options.method = 'GET';
551
+ // Drop a possible entity and headers related to it
552
+ m._requestBodyBuffers = [];
553
+ removeMatchingHeaders(/^content-/i, m._options.headers);
554
+ }
555
+ // Drop the Host header, as the redirect might lead to a different host
556
+ const currentHostHeader = removeMatchingHeaders(/^host$/i, m._options.headers);
557
+ // If the redirect is relative, carry over the host of the last request
558
+ const currentUrlParts = utils.parseUrl(m._currentUrl);
559
+ const currentHost = currentHostHeader || currentUrlParts.host;
560
+ const currentUrl = /^\w+:/.test(location) ? m._currentUrl : url.format(Object.assign(currentUrlParts, {
561
+ host: currentHost
562
+ }));
563
+ // Create the redirected request
564
+ const redirectUrl = utils.resolveUrl(location, currentUrl);
565
+ log({
566
+ redirectUrl
567
+ }, 'redirecting to');
568
+ m._isRedirect = true;
569
+ // 覆盖原 url 解析部分,包括 protocol、hostname、port等
570
+ utils.spreadUrlObject(redirectUrl, m._options);
571
+ // Drop confidential headers when redirecting to a less secure protocol
572
+ // or to a different domain that is not a superdomain
573
+ if (redirectUrl.protocol !== currentUrlParts.protocol && redirectUrl.protocol !== 'https:' || redirectUrl.host !== currentHost && !isSubdomain(redirectUrl.host, currentHost)) {
574
+ removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers);
575
+ }
576
+ // Evaluate the beforeRedirect callback
577
+ if (utils.isFunction(beforeRedirect)) {
578
+ const responseDetails = {
579
+ headers: response.headers,
580
+ statusCode
581
+ };
582
+ const requestDetails = {
583
+ url: currentUrl,
584
+ method,
585
+ headers: requestHeaders
586
+ };
587
+ beforeRedirect(m._options, responseDetails, requestDetails);
588
+ m._sanitizeOptions(m._options);
589
+ }
590
+ // Perform the redirected request
591
+ m.request() // 重新执行请求
592
+ ;
593
+ }
594
+ /**
595
+ * 处理响应stream
596
+ * 如:解压,透传流,需设置 decompress = false,避免解压数据
597
+ * @param {*} res
598
+ */ processStream(res) {
599
+ const m = this;
600
+ const { _options: opts } = m;
601
+ const streams = [
602
+ res
603
+ ];
604
+ // 'transfer-encoding': 'chunked'时,无content-length,axios v1.2 不能自动解压
605
+ const responseLength = +res.headers['content-length'];
606
+ log('processStream', {
607
+ statusCode: res.statusCode,
608
+ responseLength,
609
+ headers: res.headers
610
+ });
611
+ if (opts.transformStream) {
612
+ opts.transformStream.responseLength = responseLength;
613
+ streams.push(opts.transformStream);
614
+ }
615
+ const empty = utils.noBody(opts.method, res.statusCode);
616
+ // decompress the response body transparently if required
617
+ if (opts.decompress !== false && res.headers['content-encoding']) {
618
+ // if decompress disabled we should not decompress
619
+ // 压缩内容,加入 解压 stream,自动解压,axios v1.2 存在bug,不能自动解压
620
+ // if no content, but headers still say that it is encoded,
621
+ // remove the header not confuse downstream operations
622
+ // if ((!responseLength || res.statusCode === 204) && res.headers['content-encoding']) {
623
+ if (empty && res.headers['content-encoding']) res.headers['content-encoding'] = undefined;
624
+ // 'content-encoding': 'gzip',
625
+ switch((res.headers['content-encoding'] || '').toLowerCase()){
626
+ /*eslint default-case:0*/ case 'gzip':
627
+ case 'x-gzip':
628
+ case 'compress':
629
+ case 'x-compress':
630
+ // add the unzipper to the body stream processing pipeline
631
+ streams.push(zlib.createUnzip(zlibOptions));
632
+ // remove the content-encoding in order to not confuse downstream operations
633
+ res.headers['content-encoding'] = undefined;
634
+ break;
635
+ case 'deflate':
636
+ streams.push(new ZlibTransform());
637
+ // add the unzipper to the body stream processing pipeline
638
+ streams.push(zlib.createUnzip(zlibOptions));
639
+ // remove the content-encoding in order to not confuse downstream operations
640
+ res.headers['content-encoding'] = undefined;
641
+ break;
642
+ case 'br':
643
+ if (isBrotliSupported) {
644
+ streams.push(zlib.createBrotliDecompress(brotliOptions));
645
+ res.headers['content-encoding'] = undefined;
646
+ }
647
+ break;
648
+ default:
649
+ }
650
+ }
651
+ const responseStream = streams.length > 1 ? stream.pipeline(streams, utils.noop) : streams[0];
652
+ // 将内部 responseStream 可读流 映射到 redirectReq
653
+ m.responseStream = responseStream;
654
+ responseStream.redirectReq = m // 事情触发时引用
655
+ ;
656
+ // stream 模式,事件透传到 请求类
657
+ if (opts.stream) {
658
+ if (m._paused) responseStream.pause();
659
+ // 写入目的流
660
+ for (const dest of m.pipedests)m.pipeDest(dest);
661
+ // 接收responseStream事件,转发 到 redirectReq 发射
662
+ for (const ev of readEvents)responseStream.on(ev, readEventEmit[ev]);
663
+ // @ts-ignore
664
+ responseStream.on('data', (chunk)=>{
665
+ if (m.timing && !m.responseStarted) {
666
+ m.responseStartTime = new Date().getTime();
667
+ }
668
+ m._destdata = true;
669
+ m.emit('data', chunk) // 向上触发
670
+ ;
671
+ });
672
+ }
673
+ // 可读流结束,触发 finished,方便上层清理
674
+ // A cleanup function which removes all registered listeners.
675
+ const offListeners = stream.finished(responseStream, ()=>{
676
+ offListeners() // cleanup
677
+ ;
678
+ this.emit('finished');
679
+ });
680
+ return responseStream;
681
+ }
682
+ // Read Stream API
683
+ /**
684
+ * read stream to write stream
685
+ * pipe 只是建立连接管道,后续自动传输数据
686
+ * @param {stream.Writable} dest
687
+ * @param {*} opts
688
+ * @returns {stream.Writable}
689
+ */ pipe(dest, opts) {
690
+ const m = this;
691
+ // 请求已响应
692
+ if (m.responseStream) {
693
+ // 已有数据,不可pipe
694
+ if (m._destdata) m.emit('error', new Error('You cannot pipe after data has been emitted from the response.'));
695
+ else if (m._respended) m.emit('error', new Error('You cannot pipe after the response has been ended.'));
696
+ else {
697
+ // stream.Stream.prototype.pipe.call(self, dest, opts);
698
+ super.pipe(dest, opts) // 建立连接管道,自动传输数据
699
+ ;
700
+ m.pipeDest(dest);
701
+ return dest // 返回写入 stream
702
+ ;
703
+ }
704
+ } else {
705
+ // 已请求还未响应
706
+ m.pipedests.push(dest);
707
+ // stream.Stream.prototype.pipe.call(self, dest, opts);
708
+ super.pipe(dest, opts) // 建立连接管道
709
+ ;
710
+ return dest // 返回写入 stream
711
+ ;
712
+ }
713
+ }
714
+ /**
715
+ * 分离先前使用pipe()方法附加的Writable流。
716
+ * @param {stream.Writable} dest
717
+ * @returns
718
+ */ unpipe(dest) {
719
+ const m = this;
720
+ // 请求已响应
721
+ if (m.responseStream) {
722
+ // 已有数据,不可 unpipe
723
+ if (m._destdata) m.emit('error', new Error('You cannot unpipe after data has been emitted from the response.'));
724
+ else if (m._respended) m.emit('error', new Error('You cannot unpipe after the response has been ended.'));
725
+ else {
726
+ // stream.Stream.prototype.pipe.call(self, dest, opts);
727
+ super.unpipe(dest) // 建立连接管道,自动传输数据
728
+ ;
729
+ m.pipedests = m.pipedests.filter((v)=>v !== dest);
730
+ return m;
731
+ }
732
+ } else {
733
+ // 已请求还未响应
734
+ m.pipedests = m.pipedests.filter((v)=>v !== dest);
735
+ super.unpipe(dest) // 从连接管道中分离
736
+ ;
737
+ return m;
738
+ }
739
+ }
740
+ /**
741
+ * 收请求响应,传输数据到可写流之前,设置可写流 header
742
+ * content-type 和 content-length,实现数据 透传,比如图片
743
+ * 流模式透传,需设置 decompress = false,避免解压数据
744
+ * (await req.stream('http://google.com/img.png')).pipe(await req.stream('http://mysite.com/img.png'))
745
+ * pipe to dest
746
+ * @param {*} dest
747
+ */ pipeDest(dest) {
748
+ const m = this;
749
+ const { response, _options } = m;
750
+ // Called after the response is received
751
+ if (response?.headers && dest.headers && !dest.headersSent) {
752
+ const caseless = new Caseless(response.headers);
753
+ if (caseless.has('content-type')) {
754
+ const ctname = caseless.has('content-type');
755
+ if (dest.setHeader) {
756
+ dest.setHeader(ctname, response.headers[ctname]);
757
+ } else {
758
+ dest.headers[ctname] = response.headers[ctname];
759
+ }
760
+ }
761
+ if (caseless.has('content-length')) {
762
+ const clname = caseless.has('content-length');
763
+ if (dest.setHeader) {
764
+ dest.setHeader(clname, response.headers[clname]);
765
+ } else {
766
+ dest.headers[clname] = response.headers[clname];
767
+ }
768
+ }
769
+ }
770
+ if (response?.headers && dest.setHeader && !dest.headersSent) {
771
+ for (const h of response.headers){
772
+ dest.setHeader(h, response.headers[h]);
773
+ }
774
+ dest.statusCode = response.statusCode;
775
+ }
776
+ // if (m.pipefilter) {
777
+ // m.pipefilter(response, dest)
778
+ // }
779
+ }
780
+ /**
781
+ * 暂停read流
782
+ * @param {...any} args
783
+ */ pause(...args) {
784
+ const m = this;
785
+ // 没有流
786
+ if (!m.responseStream) m._paused = true;
787
+ else m.responseStream.pause(...args);
788
+ return m;
789
+ }
790
+ /**
791
+ * 继续read流
792
+ * @param {...any} args
793
+ */ resume(...args) {
794
+ const m = this;
795
+ if (!m.responseStream) m._paused = false;
796
+ else m.responseStream.resume(...args);
797
+ return m;
798
+ }
799
+ isPaused() {
800
+ return this._paused;
801
+ }
802
+ }
803
+ /**
804
+ *
805
+ * @param {*} request
806
+ * @param {*} error
807
+ */ function destroyRequest(request, error) {
808
+ for (const ev of writeEvents){
809
+ request.removeListener(ev, writeEventEmit[ev]);
810
+ }
811
+ request.on('error', utils.noop);
812
+ request.destroy(error);
813
+ }
814
+ function removeMatchingHeaders(regex, headers) {
815
+ let lastValue;
816
+ Object.keys(headers).forEach((k)=>{
817
+ if (regex.test(k)) {
818
+ lastValue = headers[k];
819
+ delete headers[k];
820
+ }
821
+ });
822
+ return lastValue === null || typeof lastValue === 'undefined' ? undefined : String(lastValue).trim();
823
+ }
824
+ function isSubdomain(subdomain, domain) {
825
+ assert(utils.isString(subdomain) && utils.isString(domain));
826
+ const dot = subdomain.length - domain.length - 1;
827
+ return dot > 0 && subdomain[dot] === '.' && subdomain.endsWith(domain);
828
+ }