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