@wiajs/request 3.0.0 → 3.0.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/.github/ISSUE_TEMPLATE.md +56 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/.github/stale.yml +19 -0
- package/.swcrc +57 -0
- package/.travis.yml +21 -0
- package/CHANGELOG.md +717 -0
- package/CONTRIBUTING.md +81 -0
- package/biome.json +44 -0
- package/codecov.yml +2 -0
- package/disabled.appveyor.yml +36 -0
- package/dist/request.cjs +1476 -0
- package/dist/request.mjs +1474 -0
- package/examples/README.md +135 -0
- package/gulpfile.js +71 -0
- package/package.json +1 -2
- package/release.sh +45 -0
- package/tests/browser/karma.conf.js +57 -0
- package/tests/browser/ssl/ca.crt +14 -0
- package/tests/browser/ssl/server.crt +14 -0
- package/tests/browser/ssl/server.key +15 -0
- package/tests/browser/start.js +37 -0
- package/tests/browser/test.js +34 -0
- package/tests/fixtures/har.json +158 -0
- package/tests/googledoodle.jpg +0 -0
- package/tests/server.js +142 -0
- package/tests/squid.conf +76 -0
- package/tests/ssl/ca/README.md +8 -0
- package/tests/ssl/ca/ca.cnf +20 -0
- package/tests/ssl/ca/ca.crl +0 -0
- package/tests/ssl/ca/ca.crt +17 -0
- package/tests/ssl/ca/ca.csr +13 -0
- package/tests/ssl/ca/ca.key +18 -0
- package/tests/ssl/ca/ca.srl +1 -0
- package/tests/ssl/ca/client-enc.key +30 -0
- package/tests/ssl/ca/client.cnf +20 -0
- package/tests/ssl/ca/client.crt +20 -0
- package/tests/ssl/ca/client.csr +18 -0
- package/tests/ssl/ca/client.key +27 -0
- package/tests/ssl/ca/gen-all-certs.sh +6 -0
- package/tests/ssl/ca/gen-client.sh +25 -0
- package/tests/ssl/ca/gen-localhost.sh +22 -0
- package/tests/ssl/ca/gen-server.sh +18 -0
- package/tests/ssl/ca/localhost.cnf +20 -0
- package/tests/ssl/ca/localhost.crt +20 -0
- package/tests/ssl/ca/localhost.csr +18 -0
- package/tests/ssl/ca/localhost.js +33 -0
- package/tests/ssl/ca/localhost.key +27 -0
- package/tests/ssl/ca/server.cnf +19 -0
- package/tests/ssl/ca/server.crt +25 -0
- package/tests/ssl/ca/server.csr +29 -0
- package/tests/ssl/ca/server.js +34 -0
- package/tests/ssl/ca/server.key +51 -0
- package/tests/ssl/npm-ca.crt +16 -0
- package/tests/ssl/test.crt +15 -0
- package/tests/ssl/test.key +15 -0
- package/tests/test-agent.js +102 -0
- package/tests/test-agentOptions.js +51 -0
- package/tests/test-api.js +33 -0
- package/tests/test-aws.js +123 -0
- package/tests/test-baseUrl.js +133 -0
- package/tests/test-basic-auth.js +221 -0
- package/tests/test-bearer-auth.js +187 -0
- package/tests/test-body.js +154 -0
- package/tests/test-cookies.js +130 -0
- package/tests/test-defaults.js +340 -0
- package/tests/test-digest-auth.js +232 -0
- package/tests/test-emptyBody.js +56 -0
- package/tests/test-errors.js +108 -0
- package/tests/test-event-forwarding.js +39 -0
- package/tests/test-follow-all-303.js +45 -0
- package/tests/test-follow-all.js +57 -0
- package/tests/test-form-data-error.js +85 -0
- package/tests/test-form-data.js +133 -0
- package/tests/test-form-urlencoded.js +73 -0
- package/tests/test-form.js +101 -0
- package/tests/test-gzip.js +296 -0
- package/tests/test-har.js +175 -0
- package/tests/test-hawk.js +187 -0
- package/tests/test-headers.js +305 -0
- package/tests/test-http-signature.js +110 -0
- package/tests/test-httpModule.js +112 -0
- package/tests/test-https.js +116 -0
- package/tests/test-isUrl.js +120 -0
- package/tests/test-json-request.js +117 -0
- package/tests/test-localAddress.js +49 -0
- package/tests/test-multipart-encoding.js +147 -0
- package/tests/test-multipart.js +129 -0
- package/tests/test-node-debug.js +95 -0
- package/tests/test-oauth.js +721 -0
- package/tests/test-onelineproxy.js +61 -0
- package/tests/test-option-reuse.js +54 -0
- package/tests/test-options-convenience-method.js +52 -0
- package/tests/test-params.js +101 -0
- package/tests/test-piped-redirect.js +55 -0
- package/tests/test-pipes.js +383 -0
- package/tests/test-pool.js +148 -0
- package/tests/test-promise.js +53 -0
- package/tests/test-proxy-connect.js +80 -0
- package/tests/test-proxy.js +304 -0
- package/tests/test-qs.js +135 -0
- package/tests/test-redirect-auth.js +131 -0
- package/tests/test-redirect-complex.js +93 -0
- package/tests/test-redirect.js +449 -0
- package/tests/test-rfc3986.js +106 -0
- package/tests/test-stream.js +36 -0
- package/tests/test-timeout.js +260 -0
- package/tests/test-timing.js +147 -0
- package/tests/test-toJSON.js +45 -0
- package/tests/test-tunnel.js +466 -0
- package/tests/test-unix.js +74 -0
- package/tests/unicycle.jpg +0 -0
- package/request.js +0 -1553
- package/src/ZlibTransform.js +0 -27
- package/src/caseless.js +0 -118
- package/src/index.js +0 -122
- package/src/request.js +0 -967
- package/src/utils.js +0 -274
package/src/request.js
DELETED
|
@@ -1,967 +0,0 @@
|
|
|
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
|
-
}
|