@wiajs/request 3.0.0 → 3.0.2

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.
@@ -0,0 +1,1476 @@
1
+ /*!
2
+ * wia request v3.0.0
3
+ * (c) 2022-2024 Sibyl Yu and contributors
4
+ * Released under the MIT License.
5
+ */
6
+ 'use strict';
7
+
8
+ const log$2 = require('@wiajs/log');
9
+ const http = require('node:http');
10
+ const https = require('node:https');
11
+ const assert = require('node:assert');
12
+ const url = require('node:url');
13
+ const stream = require('node:stream');
14
+ const zlib = require('node:zlib');
15
+ const mime = require('mime-types');
16
+
17
+ class ZlibTransform extends stream.Transform {
18
+ __transform(chunk, encoding, callback) {
19
+ this.push(chunk);
20
+ callback();
21
+ }
22
+
23
+ _transform(chunk, encoding, callback) {
24
+ if (chunk.length !== 0) {
25
+ this._transform = this.__transform;
26
+
27
+ // Add Default Compression headers if no zlib headers are present
28
+ if (chunk[0] !== 120) {
29
+ // Hex: 78
30
+ const header = Buffer.alloc(2);
31
+ header[0] = 120; // Hex: 78
32
+ header[1] = 156; // Hex: 9C
33
+ this.push(header, encoding);
34
+ }
35
+ }
36
+
37
+ this.__transform(chunk, encoding, callback);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * utils for request
43
+ */
44
+
45
+ const {URL} = url;
46
+
47
+ // Whether to use the native URL object or the legacy url module
48
+ let useNativeURL = false;
49
+ try {
50
+ assert(new URL(''));
51
+ } catch (error) {
52
+ useNativeURL = error.code === 'ERR_INVALID_URL';
53
+ }
54
+
55
+ // URL fields to preserve in copy operations
56
+ const preservedUrlFields = [
57
+ 'auth',
58
+ 'host',
59
+ 'hostname',
60
+ 'href',
61
+ 'path',
62
+ 'pathname',
63
+ 'port',
64
+ 'protocol',
65
+ 'query',
66
+ 'search',
67
+ 'hash',
68
+ ];
69
+
70
+ /**
71
+ *
72
+ * @param {*} code
73
+ * @param {*} message
74
+ * @param {*} baseClass
75
+ * @returns
76
+ */
77
+ function createErrorType(code, message, baseClass) {
78
+ // Create constructor
79
+ function CustomError(properties) {
80
+ // istanbul ignore else
81
+ if (isFunction(Error.captureStackTrace)) {
82
+ Error.captureStackTrace(this, this.constructor);
83
+ }
84
+ Object.assign(this, properties || {});
85
+ this.code = code;
86
+ this.message = this.cause ? `${message}: ${this.cause.message}` : message;
87
+ }
88
+
89
+ // Attach constructor and set default properties
90
+ CustomError.prototype = new (baseClass || Error)();
91
+ Object.defineProperties(CustomError.prototype, {
92
+ constructor: {
93
+ value: CustomError,
94
+ enumerable: false,
95
+ },
96
+ name: {
97
+ value: `Error [${code}]`,
98
+ enumerable: false,
99
+ },
100
+ });
101
+ return CustomError
102
+ }
103
+
104
+ const InvalidUrlError = createErrorType('ERR_INVALID_URL', 'Invalid URL', TypeError);
105
+
106
+ // @ts-ignore
107
+ const typeOfTest = type => thing => typeof thing === type;
108
+
109
+ /**
110
+ * Determine if a value is a String
111
+ *
112
+ * @param {*} val The value to test
113
+ *
114
+ * @returns {boolean} True if value is a String, otherwise false
115
+ */
116
+ const isString = typeOfTest('string');
117
+
118
+ /**
119
+ * Determine if a value is an Array
120
+ *
121
+ * @param {Object} val The value to test
122
+ *
123
+ * @returns {boolean} True if value is an Array, otherwise false
124
+ */
125
+ const {isArray} = Array;
126
+
127
+ /**
128
+ * Determine if a value is undefined
129
+ *
130
+ * @param {*} val The value to test
131
+ *
132
+ * @returns {boolean} True if the value is undefined, otherwise false
133
+ */
134
+ const isUndefined = typeOfTest('undefined');
135
+
136
+ /**
137
+ * Determine if a value is a Buffer
138
+ *
139
+ * @param {*} val The value to test
140
+ *
141
+ * @returns {boolean} True if value is a Buffer, otherwise false
142
+ */
143
+ function isBuffer(val) {
144
+ return (
145
+ val !== null &&
146
+ !isUndefined(val) &&
147
+ val.constructor !== null &&
148
+ !isUndefined(val.constructor) &&
149
+ isFunction(val.constructor.isBuffer) &&
150
+ val.constructor.isBuffer(val)
151
+ )
152
+ }
153
+
154
+ /**
155
+ * Determine if a value is a Function
156
+ *
157
+ * @param {*} val The value to test
158
+ * @returns {boolean} True if value is a Function, otherwise false
159
+ */
160
+ const isFunction = typeOfTest('function');
161
+
162
+ /**
163
+ * Determine if a value is a Number
164
+ *
165
+ * @param {*} val The value to test
166
+ *
167
+ * @returns {boolean} True if value is a Number, otherwise false
168
+ */
169
+ const isNumber = typeOfTest('number');
170
+
171
+ /**
172
+ * Determine if a value is an Object
173
+ *
174
+ * @param {*} thing The value to test
175
+ *
176
+ * @returns {boolean} True if value is an Object, otherwise false
177
+ */
178
+ const isObject = thing => thing !== null && typeof thing === 'object';
179
+
180
+ /**
181
+ * Determine if a value is a Boolean
182
+ *
183
+ * @param {*} thing The value to test
184
+ * @returns {boolean} True if value is a Boolean, otherwise false
185
+ */
186
+ const isBoolean = thing => thing === true || thing === false;
187
+
188
+ const noop = () => {};
189
+
190
+ /**
191
+ *
192
+ * @param {*} value
193
+ * @returns
194
+ */
195
+ function isURL(value) {
196
+ return URL && value instanceof URL
197
+ }
198
+
199
+ function isReadStream(rs) {
200
+ return rs.readable && rs.path && rs.mode
201
+ }
202
+
203
+ /**
204
+ *
205
+ * @param {*} urlObject
206
+ * @param {*} target
207
+ * @returns
208
+ */
209
+ function spreadUrlObject(urlObject, target) {
210
+ const spread = target || {};
211
+ for (const key of preservedUrlFields) {
212
+ spread[key] = urlObject[key];
213
+ }
214
+
215
+ // Fix IPv6 hostname
216
+ if (spread.hostname.startsWith('[')) {
217
+ spread.hostname = spread.hostname.slice(1, -1);
218
+ }
219
+ // Ensure port is a number
220
+ if (spread.port !== '') {
221
+ spread.port = Number(spread.port);
222
+ }
223
+ // Concatenate path
224
+ spread.path = spread.search ? spread.pathname + spread.search : spread.pathname;
225
+
226
+ return spread
227
+ }
228
+
229
+ /**
230
+ *
231
+ * @param {*} input
232
+ * @returns
233
+ */
234
+ function parseUrl(input) {
235
+ let parsed;
236
+ // istanbul ignore else
237
+ if (useNativeURL) {
238
+ parsed = new URL(input);
239
+ } else {
240
+ // Ensure the URL is valid and absolute
241
+ parsed = validateUrl(url.parse(input));
242
+ if (!isString(parsed.protocol)) {
243
+ throw new InvalidUrlError({input})
244
+ }
245
+ }
246
+ return parsed
247
+ }
248
+
249
+ /**
250
+ *
251
+ * @param {*} input
252
+ * @returns
253
+ */
254
+ function validateUrl(input) {
255
+ if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
256
+ throw new InvalidUrlError({input: input.href || input})
257
+ }
258
+ if (/^\[/.test(input.host) && !/^\[[:0-9a-f]+\](:\d+)?$/i.test(input.host)) {
259
+ throw new InvalidUrlError({input: input.href || input})
260
+ }
261
+ return input
262
+ }
263
+
264
+ /**
265
+ *
266
+ * @param {*} relative
267
+ * @param {*} base
268
+ * @returns
269
+ */
270
+ function resolveUrl(relative, base) {
271
+ // istanbul ignore next
272
+ return useNativeURL ? new URL(relative, base) : parseUrl(url.resolve(base, relative))
273
+ }
274
+
275
+ /**
276
+ *
277
+ * @param {string} method
278
+ * @param {number} code
279
+ * @returns
280
+ */
281
+ function noBody(method, code) {
282
+ return (
283
+ method === 'HEAD' ||
284
+ // Informational
285
+ (code >= 100 && code < 200) ||
286
+ // No Content
287
+ code === 204 ||
288
+ // Not Modified
289
+ code === 304
290
+ )
291
+ }
292
+
293
+ const utils = {
294
+ createErrorType,
295
+ InvalidUrlError,
296
+ isString,
297
+ isArray,
298
+ isBuffer,
299
+ isUndefined,
300
+ isNumber,
301
+ isBoolean,
302
+ isFunction,
303
+ isObject,
304
+ isURL,
305
+ isReadStream,
306
+ noop,
307
+ parseUrl,
308
+ spreadUrlObject,
309
+ validateUrl,
310
+ resolveUrl,
311
+ noBody,
312
+ };
313
+
314
+ class Caseless {
315
+ /**
316
+ * @param {*} dict
317
+ */
318
+ constructor(dict) {
319
+ this.dict = dict || {};
320
+ }
321
+
322
+ /**
323
+ *
324
+ * @param {*} name
325
+ * @param {*} value
326
+ * @param {*} clobber
327
+ * @returns
328
+ */
329
+ set(name, value, clobber) {
330
+ if (typeof name === 'object') {
331
+ for (const n of name) {
332
+ this.set(n, name[n], value);
333
+ }
334
+ } else {
335
+ if (typeof clobber === 'undefined') clobber = true;
336
+ const has = this.has(name);
337
+
338
+ if (!clobber && has) this.dict[has] = this.dict[has] + ',' + value;
339
+ else this.dict[has || name] = value;
340
+ return has
341
+ }
342
+ }
343
+
344
+ /**
345
+ *
346
+ * @param {string} name
347
+ * @returns
348
+ */
349
+ has(name) {
350
+ const keys = Object.keys(this.dict);
351
+ name = name.toLowerCase();
352
+ for (let i = 0; i < keys.length; i++) {
353
+ if (keys[i].toLowerCase() === name) return keys[i]
354
+ }
355
+ return false
356
+ }
357
+
358
+ /**
359
+ *
360
+ * @param {string} name
361
+ * @returns
362
+ */
363
+ get(name) {
364
+ name = name.toLowerCase();
365
+ let result;
366
+ let _key;
367
+ const headers = this.dict;
368
+ for (const key of Object.keys(headers)) {
369
+ _key = key.toLowerCase();
370
+ if (name === _key) result = headers[key];
371
+ }
372
+ return result
373
+ }
374
+
375
+ /**
376
+ *
377
+ * @param {string} name
378
+ * @returns
379
+ */
380
+ swap(name) {
381
+ const has = this.has(name);
382
+ if (has === name) return
383
+ if (!has) throw new Error('There is no header than matches "' + name + '"')
384
+ this.dict[name] = this.dict[has];
385
+ delete this.dict[has];
386
+ }
387
+
388
+ del(name) {
389
+ name = String(name).toLowerCase();
390
+ let deleted = false;
391
+ let changed = 0;
392
+ const dict = this.dict;
393
+ for (const key of Object.keys(this.dict)) {
394
+ if (name === String(key).toLowerCase()) {
395
+ deleted = delete dict[key];
396
+ changed += 1;
397
+ }
398
+ }
399
+ return changed === 0 ? true : deleted
400
+ }
401
+ }
402
+
403
+ /**
404
+ * fork from follow-redirects
405
+ * https://github.com/follow-redirects/follow-redirects
406
+ */
407
+
408
+ const log$1 = log$2.log({env: `wia:req:${log$2.name(__filename)}`});
409
+
410
+ const httpModules = {'http:': http, 'https:': https};
411
+
412
+ const zlibOptions = {
413
+ flush: zlib.constants.Z_SYNC_FLUSH,
414
+ finishFlush: zlib.constants.Z_SYNC_FLUSH,
415
+ };
416
+
417
+ const brotliOptions = {
418
+ flush: zlib.constants.BROTLI_OPERATION_FLUSH,
419
+ finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,
420
+ };
421
+
422
+ const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
423
+
424
+ // Create handlers that pass events from native requests
425
+ const writeEvents = [
426
+ 'abort', // 弃用
427
+ 'aborted', // 弃用
428
+ 'close',
429
+ 'connect',
430
+ 'continue',
431
+ 'drain',
432
+ 'error',
433
+ 'finish',
434
+ 'information',
435
+ 'pipe',
436
+ // 'response', 由 processResponse 触发
437
+ 'socket',
438
+ 'timeout',
439
+ 'unpipe',
440
+ 'upgrade',
441
+ ];
442
+
443
+ const writeEventEmit = Object.create(null);
444
+ for (const ev of writeEvents)
445
+ writeEventEmit[ev] = function (...args) {
446
+ const m = this; // 事件回调,this === clientRequest 实例
447
+ log$1.debug('req event', {ev});
448
+ m.redirectReq.emit(ev, ...args); // req 事情映射到 redirectReq 上触发
449
+ };
450
+
451
+ // stream.Readable
452
+ // data 单独处理
453
+ const readEvents = ['close', 'end', 'error', 'pause', 'readable', 'resume'];
454
+ const readEventEmit = Object.create(null);
455
+ for (const ev of readEvents)
456
+ readEventEmit[ev] = function (...args) {
457
+ const m = this; // 事件回调,this === clientRequest 实例
458
+ log$1.debug('res event', {ev});
459
+ m.redirectReq.emit(ev, ...args); // 向上触发事件
460
+ };
461
+
462
+ // Error types with codes
463
+ const RedirectionError = utils.createErrorType('ERR_FR_REDIRECTION_FAILURE', 'Redirected request failed');
464
+
465
+ const TooManyRedirectsError = utils.createErrorType(
466
+ 'ERR_FR_TOO_MANY_REDIRECTS',
467
+ 'Maximum number of redirects exceeded',
468
+ RedirectionError
469
+ );
470
+
471
+ const MaxBodyLengthExceededError = utils.createErrorType(
472
+ 'ERR_FR_MAX_BODY_LENGTH_EXCEEDED',
473
+ 'Request body larger than maxBodyLength limit'
474
+ );
475
+
476
+ const WriteAfterEndError = utils.createErrorType('ERR_STREAM_WRITE_AFTER_END', 'write after end');
477
+
478
+ /**
479
+ * An HTTP(S) request that can be redirected
480
+ * wrap http.ClientRequest
481
+ */
482
+ class Request extends stream.Duplex {
483
+ _timeout = 0
484
+ /** @type {*} */
485
+ socket = null
486
+ /** @type {http.ClientRequest} */
487
+ _currentRequest = null
488
+ /** @type {stream.Readable} */
489
+ response = null
490
+ /** @type {stream.Readable} */
491
+ responseStream = null
492
+ timing = false
493
+ responseStarted = false
494
+ responseStartTime = 0
495
+ _destdata = false
496
+ _paused = false
497
+ _respended = false
498
+ /** @type {stream.Readable} */
499
+ pipesrc = null // 被 pipe 时的 src stream
500
+ /** @type {stream.Writable[]} */
501
+ pipedests = [] // pipe dest
502
+
503
+ /**
504
+ * responseCallback 原消息处理回调
505
+ * @param {*} options
506
+ * @param {*} resCallback
507
+ */
508
+ constructor(options, resCallback) {
509
+ super();
510
+ const m = this;
511
+
512
+ // log({options}, 'constructor');
513
+
514
+ // Initialize the request
515
+ m._sanitizeOptions(options);
516
+ m._options = options;
517
+ m.headers = options.headers;
518
+ m._ended = false;
519
+ m._ending = false;
520
+ m._redirectCount = 0;
521
+ /** @type {any[]} */
522
+ m._redirects = [];
523
+ m._requestBodyLength = 0;
524
+ /** @type {any[]} */
525
+ m._requestBodyBuffers = [];
526
+
527
+ // save the callback if passed
528
+ m.resCallback = resCallback;
529
+ // React to responses of native requests
530
+ // 接管 response 事件,非重定向,触发 response 事件
531
+ m._onResponse = response => {
532
+ try {
533
+ m.processResponse(response);
534
+ } catch (cause) {
535
+ m.emit('error', cause instanceof RedirectionError ? cause : new RedirectionError({cause: cause}));
536
+ }
537
+ };
538
+
539
+ // Proxy all other public ClientRequest methods
540
+ for (const method of ['flushHeaders', 'setNoDelay', 'setSocketKeepAlive']) {
541
+ m[method] = (a, b) => {
542
+ log$1.debug(method, {a, b});
543
+ m._currentRequest[method](a, b);
544
+ };
545
+ }
546
+
547
+ // Proxy all public ClientRequest properties
548
+ for (const property of ['aborted', 'connection', 'socket']) {
549
+ Object.defineProperty(m, property, {
550
+ get() {
551
+ const val = m._currentRequest[property];
552
+ log$1.debug('get property', {property});
553
+ return val
554
+ },
555
+ });
556
+ }
557
+
558
+ // 流模式
559
+ if (options.stream)
560
+ // 被 pipe 作为目标时触发,拷贝 src headers
561
+ m.on(
562
+ 'pipe',
563
+ /** @type {stream.Readable} */ src => {
564
+ // m.ntick &&
565
+ if (m._currentRequest) {
566
+ m.emit('error', new Error('You cannot pipe to this stream after the outbound request has started.'));
567
+ }
568
+
569
+ m.pipesrc = src;
570
+
571
+ if (utils.isReadStream(src)) {
572
+ if (!m.hasHeader('content-type')) {
573
+ m.setHeader('content-type', mime.lookup(src.path));
574
+ }
575
+ } else {
576
+ if (src.headers) {
577
+ for (const h of src.headers) {
578
+ if (!m.hasHeader(h)) {
579
+ m.setHeader(h, src.headers[h]);
580
+ }
581
+ }
582
+ }
583
+
584
+ // if (src.method && !self.explicitMethod) {
585
+ // m.method = src.method
586
+ // }
587
+ }
588
+ }
589
+ );
590
+
591
+ // Perform the first request
592
+ // m.request(); // 写入数据时执行,否则 pipe时无法写入header
593
+ }
594
+
595
+ /**
596
+ * Executes the next native request (initial or redirect)
597
+ * @returns http(s) 实例
598
+ */
599
+ request() {
600
+ let R = null;
601
+
602
+ try {
603
+ const m = this;
604
+ // read stream
605
+ m.response = null;
606
+ m.responseStarted = false;
607
+ m.responseStream = null;
608
+ m.timing = false;
609
+ m.responseStartTime = 0;
610
+ m._destdata = false;
611
+ m._paused = false;
612
+ m._respended = false;
613
+
614
+ // m.httpModule = httpModules[protocol];
615
+
616
+ // Load the native protocol
617
+ let {protocol} = m._options;
618
+ const {agents} = m._options;
619
+
620
+ // 代理以目的网址协议为准
621
+ // If specified, use the agent corresponding to the protocol
622
+ // (HTTP and HTTPS use different types of agents)
623
+ if (agents) {
624
+ const scheme = protocol.slice(0, -1);
625
+ m._options.agent = agents[scheme];
626
+ }
627
+
628
+ // http 非隧道代理模式,模块以代理主机为准,其他以目的网址为准
629
+ // 代理内部会根据代理协议选择 http(s) 发起请求创建连接
630
+ if (protocol === 'http:' && agents.http) {
631
+ protocol = agents.http.proxy && !agents.http.tunnel ? agents.http.proxy.protocol : protocol;
632
+ }
633
+
634
+ const httpModule = httpModules[protocol];
635
+ if (!httpModule) throw TypeError(`Unsupported protocol: ${protocol}`)
636
+
637
+ log$1.debug('request', {options: m._options, protocol});
638
+
639
+ debugger
640
+
641
+ // Create the native request and set up its event handlers
642
+ const req = httpModule.request(m._options, m._onResponse);
643
+ m._currentRequest = req;
644
+
645
+ req.redirectReq = m;
646
+ // 接收req事件,转发 到 redirectReq 发射
647
+ for (const ev of writeEvents) req.on(ev, writeEventEmit[ev]);
648
+
649
+ // RFC7230§5.3.1: When making a request directly to an origin server, […]
650
+ // a client MUST send only the absolute path […] as the request-target.
651
+ // When making a request to a proxy, […]
652
+ // a client MUST send the target URI in absolute-form […].
653
+ m._currentUrl = /^\//.test(m._options.path) ? url.format(m._options) : m._options.path;
654
+
655
+ // End a redirected request
656
+ // (The first request must be ended explicitly with RedirectableRequest#end)
657
+ if (m._isRedirect) {
658
+ // Write the request entity and end
659
+ let i = 0;
660
+ const buffers = m._requestBodyBuffers
661
+ ;(function writeNext(error) {
662
+ // Only write if this request has not been redirected yet
663
+ /* istanbul ignore else */
664
+ if (req === m._currentRequest) {
665
+ // Report any write errors
666
+ /* istanbul ignore if */
667
+ if (error) m.emit('error', error);
668
+ // Write the next buffer if there are still left
669
+ else if (i < buffers.length) {
670
+ const buf = buffers[i++];
671
+ /* istanbul ignore else */
672
+ if (!req.finished) req.write(buf.data, buf.encoding, writeNext);
673
+ }
674
+ // End the request if `end` has been called on us
675
+ else if (m._ended) req.end();
676
+ }
677
+ })();
678
+ }
679
+
680
+ R = req;
681
+ } catch (e) {
682
+ log$1.err(e, 'request');
683
+ throw e
684
+ }
685
+
686
+ return R
687
+ }
688
+
689
+ abort() {
690
+ destroyRequest(this._currentRequest);
691
+ this._currentRequest.abort();
692
+ this.emit('abort');
693
+ }
694
+
695
+ /**
696
+ * 析构
697
+ * @param {*} error
698
+ * @returns
699
+ */
700
+ destroy(error) {
701
+ const m = this;
702
+ if (!m._ended) m.end();
703
+ else if (m.response) m.response.destroy();
704
+ else if (m.responseStream) m.responseStream.destroy();
705
+
706
+ // m.clearTimeout();
707
+ destroyRequest(m._currentRequest, error);
708
+ super.destroy(error);
709
+ return this
710
+ }
711
+
712
+ /**
713
+ * Writes buffered data to the current native request
714
+ * 如 request 不存在,则创建连接,pipe 时可写入 header
715
+ * @param {*} chunk
716
+ * @param {BufferEncoding=} encoding
717
+ * @param {(error: Error) => void} [cb]
718
+ * @returns {boolean}
719
+ */
720
+ write(chunk, encoding, cb) {
721
+ const m = this;
722
+
723
+ log$1.debug('write', {data: chunk, encoding, callback: cb});
724
+
725
+ // Writing is not allowed if end has been called
726
+ if (m._ending) throw new WriteAfterEndError()
727
+
728
+ // ! 数据写入时连接,pipe 时可设置 header
729
+ if (!m._currentRequest) m.request();
730
+
731
+ // Validate input and shift parameters if necessary
732
+ if (!utils.isString(chunk) && !utils.isBuffer(chunk))
733
+ throw new TypeError('data should be a string, Buffer or Uint8Array')
734
+
735
+ if (utils.isFunction(encoding)) {
736
+ cb = encoding;
737
+ encoding = null;
738
+ }
739
+
740
+ // Ignore empty buffers, since writing them doesn't invoke the callback
741
+ // https://github.com/nodejs/node/issues/22066
742
+ if (chunk.length === 0) {
743
+ if (cb) cb();
744
+ return
745
+ }
746
+
747
+ // Only write when we don't exceed the maximum body length
748
+ if (m._requestBodyLength + chunk.length <= m._options.maxBodyLength) {
749
+ m._requestBodyLength += chunk.length;
750
+ m._requestBodyBuffers.push({data: chunk, encoding});
751
+ m._currentRequest.write(chunk, encoding, cb);
752
+ }
753
+ // Error when we exceed the maximum body length
754
+ else {
755
+ m.emit('error', new MaxBodyLengthExceededError());
756
+ m.abort();
757
+ }
758
+ }
759
+
760
+ /**
761
+ * Ends the current native request
762
+ * @param {*} data
763
+ * @param {*} encoding
764
+ * @param {*} callback
765
+ */
766
+ end(data, encoding, callback) {
767
+ const m = this;
768
+
769
+ // Shift parameters if necessary
770
+ if (utils.isFunction(data)) {
771
+ callback = data;
772
+ data = null;
773
+ encoding = null;
774
+ } else if (utils.isFunction(encoding)) {
775
+ callback = encoding;
776
+ encoding = null;
777
+ }
778
+
779
+ // ! 数据写入时连接,pipe 时可设置 header
780
+ if (!m._currentRequest) m.request();
781
+
782
+ // Write data if needed and end
783
+ if (!data) {
784
+ m._ended = true;
785
+ m._ending = true;
786
+ m._currentRequest.end(null, null, callback);
787
+ } else {
788
+ const currentRequest = m._currentRequest;
789
+ m.write(data, encoding, () => {
790
+ m._ended = true;
791
+ currentRequest.end(null, null, callback);
792
+ });
793
+
794
+ m._ending = true;
795
+ }
796
+ }
797
+
798
+ /**
799
+ *
800
+ * @param {string} name
801
+ * @returns
802
+ */
803
+ hasHeader(name) {
804
+ return this._options.headers.includes(name)
805
+ }
806
+
807
+ /**
808
+ *
809
+ * @param {string} name
810
+ * @returns {string}
811
+ */
812
+ getHeader(name) {
813
+ return this._options.headers[name]
814
+ }
815
+
816
+ /**
817
+ * Sets a header value on the current native request
818
+ * @param {string} name
819
+ */
820
+ setHeader(name, value) {
821
+ this._options.headers[name] = value;
822
+ this._currentRequest?.setHeader(name, value);
823
+ }
824
+
825
+ /**
826
+ * Clears a header value on the current native request
827
+ * @param {string} name
828
+ */
829
+ removeHeader(name) {
830
+ delete this._options.headers[name];
831
+ this._currentRequest?.removeHeader(name);
832
+ }
833
+
834
+ /**
835
+ * 标头是否已发送
836
+ * @returns
837
+ */
838
+ get headersSent() {
839
+ return this._currentRequest?.headersSent
840
+ }
841
+
842
+ // Global timeout for all underlying requests
843
+ /**
844
+ *
845
+ * @param {*} msecs
846
+ * @param {*} callback
847
+ * @returns
848
+ */
849
+ setTimeout(msecs, callback) {
850
+ const m = this;
851
+
852
+ // Destroys the socket on timeout
853
+ /**
854
+ *
855
+ * @param {*} socket
856
+ */
857
+ function destroyOnTimeout(socket) {
858
+ socket.setTimeout(msecs);
859
+ socket.removeListener('timeout', socket.destroy);
860
+ socket.addListener('timeout', socket.destroy);
861
+ }
862
+
863
+ // Sets up a timer to trigger a timeout event
864
+ /**
865
+ *
866
+ * @param {*} socket
867
+ */
868
+ function startTimer(socket) {
869
+ if (m._timeout) {
870
+ clearTimeout(m._timeout);
871
+ }
872
+ m._timeout = setTimeout(() => {
873
+ m.emit('timeout');
874
+ clearTimer();
875
+ }, msecs);
876
+ destroyOnTimeout(socket);
877
+ }
878
+
879
+ // Stops a timeout from triggering
880
+ function clearTimer() {
881
+ // Clear the timeout
882
+ if (m._timeout) {
883
+ clearTimeout(m._timeout);
884
+ m._timeout = null;
885
+ }
886
+
887
+ // Clean up all attached listeners
888
+ m.removeListener('abort', clearTimer);
889
+ m.removeListener('error', clearTimer);
890
+ m.removeListener('response', clearTimer);
891
+ m.removeListener('close', clearTimer);
892
+
893
+ if (callback) {
894
+ m.removeListener('timeout', callback);
895
+ }
896
+ if (!m.socket) {
897
+ m._currentRequest.removeListener('socket', startTimer);
898
+ }
899
+ }
900
+
901
+ // Attach callback if passed
902
+ if (callback) m.on('timeout', callback);
903
+
904
+ // Start the timer if or when the socket is opened
905
+ if (m.socket) startTimer(m.socket);
906
+ else m._currentRequest.once('socket', startTimer);
907
+
908
+ // Clean up on events
909
+ m.on('socket', destroyOnTimeout);
910
+ m.on('abort', clearTimer);
911
+ m.on('error', clearTimer);
912
+ m.on('response', clearTimer);
913
+ m.on('close', clearTimer);
914
+ return m
915
+ }
916
+
917
+ _sanitizeOptions(options) {
918
+ // Ensure headers are always present
919
+ if (!options.headers) options.headers = {};
920
+
921
+ // Since http.request treats host as an alias of hostname,
922
+ // but the url module interprets host as hostname plus port,
923
+ // eliminate the host property to avoid confusion.
924
+ if (options.host) {
925
+ // Use hostname if set, because it has precedence
926
+ if (!options.hostname) {
927
+ options.hostname = options.host;
928
+ }
929
+ options.host = undefined;
930
+ }
931
+
932
+ // Complete the URL object when necessary
933
+ if (!options.pathname && options.path) {
934
+ const searchPos = options.path.indexOf('?');
935
+ if (searchPos < 0) {
936
+ options.pathname = options.path;
937
+ } else {
938
+ options.pathname = options.path.substring(0, searchPos);
939
+ options.search = options.path.substring(searchPos);
940
+ }
941
+ }
942
+ }
943
+
944
+ /**
945
+ * Processes a response from the current native request
946
+ * @param {*} response
947
+ * @returns
948
+ */
949
+ processResponse(response) {
950
+ const m = this;
951
+
952
+ // Store the redirected response
953
+ const {statusCode} = response;
954
+ if (m._options.trackRedirects) {
955
+ m._redirects.push({
956
+ url: m._currentUrl,
957
+ headers: response.headers,
958
+ statusCode,
959
+ });
960
+ }
961
+
962
+ // RFC7231§6.4: The 3xx (Redirection) class of status code indicates
963
+ // that further action needs to be taken by the user agent in order to
964
+ // fulfill the request. If a Location header field is provided,
965
+ // the user agent MAY automatically redirect its request to the URI
966
+ // referenced by the Location field value,
967
+ // even if the specific status code is not understood.
968
+
969
+ // If the response is not a redirect; return it as-is
970
+ const {location} = response.headers;
971
+
972
+ log$1('processResponse', {statusCode, headers: response.headers});
973
+
974
+ if (!location || m._options.followRedirects === false || statusCode < 300 || statusCode >= 400) {
975
+ // 非重定向,返回给原始回调处理
976
+ response.responseUrl = m._currentUrl;
977
+ response.redirects = m._redirects;
978
+ m.response = response;
979
+ // Be a good stream and emit end when the response is finished.
980
+ // Hack to emit end on close because of a core bug that never fires end
981
+ response.on('close', () => {
982
+ if (!m._respended) {
983
+ m.response.emit('end');
984
+ }
985
+ });
986
+
987
+ response.once('end', () => {
988
+ m._respended = true;
989
+ });
990
+
991
+ const responseStream = m.processStream(response);
992
+ // NOTE: responseStartTime is deprecated in favor of .timings
993
+ response.responseStartTime = m.responseStartTime;
994
+
995
+ // 触发原回调函数
996
+ m.resCallback?.(response, responseStream);
997
+
998
+ // 类似 ClientRequest,触发 response 事件
999
+ m.emit('response', response, responseStream);
1000
+
1001
+ // Clean up
1002
+ m._requestBodyBuffers = [];
1003
+ return
1004
+ }
1005
+
1006
+ // The response is a redirect, so abort the current request
1007
+ destroyRequest(m._currentRequest);
1008
+ // Discard the remainder of the response to avoid waiting for data
1009
+ response.destroy();
1010
+
1011
+ // RFC7231§6.4: A client SHOULD detect and intervene
1012
+ // in cyclical redirections (i.e., "infinite" redirection loops).
1013
+ if (++m._redirectCount > m._options.maxRedirects) throw new TooManyRedirectsError()
1014
+
1015
+ // Store the request headers if applicable
1016
+ let requestHeaders;
1017
+ const {beforeRedirect} = m._options;
1018
+ if (beforeRedirect) {
1019
+ requestHeaders = {
1020
+ // The Host header was set by nativeProtocol.request
1021
+ Host: response.req.getHeader('host'),
1022
+ ...m._options.headers,
1023
+ };
1024
+ }
1025
+
1026
+ // RFC7231§6.4: Automatic redirection needs to done with
1027
+ // care for methods not known to be safe, […]
1028
+ // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
1029
+ // the request method from POST to GET for the subsequent request.
1030
+ const {method} = m._options;
1031
+ if (
1032
+ ((statusCode === 301 || statusCode === 302) && m._options.method === 'POST') ||
1033
+ // RFC7231§6.4.4: The 303 (See Other) status code indicates that
1034
+ // the server is redirecting the user agent to a different resource […]
1035
+ // A user agent can perform a retrieval request targeting that URI
1036
+ // (a GET or HEAD request if using HTTP) […]
1037
+ (statusCode === 303 && !/^(?:GET|HEAD)$/.test(m._options.method))
1038
+ ) {
1039
+ m._options.method = 'GET';
1040
+ // Drop a possible entity and headers related to it
1041
+ m._requestBodyBuffers = [];
1042
+ removeMatchingHeaders(/^content-/i, m._options.headers);
1043
+ }
1044
+
1045
+ // Drop the Host header, as the redirect might lead to a different host
1046
+ const currentHostHeader = removeMatchingHeaders(/^host$/i, m._options.headers);
1047
+
1048
+ // If the redirect is relative, carry over the host of the last request
1049
+ const currentUrlParts = utils.parseUrl(m._currentUrl);
1050
+ const currentHost = currentHostHeader || currentUrlParts.host;
1051
+ const currentUrl = /^\w+:/.test(location)
1052
+ ? m._currentUrl
1053
+ : url.format(Object.assign(currentUrlParts, {host: currentHost}));
1054
+
1055
+ // Create the redirected request
1056
+ const redirectUrl = utils.resolveUrl(location, currentUrl);
1057
+ log$1({redirectUrl}, 'redirecting to');
1058
+ m._isRedirect = true;
1059
+ // 覆盖原 url 解析部分,包括 protocol、hostname、port等
1060
+ utils.spreadUrlObject(redirectUrl, m._options);
1061
+
1062
+ // Drop confidential headers when redirecting to a less secure protocol
1063
+ // or to a different domain that is not a superdomain
1064
+ if (
1065
+ (redirectUrl.protocol !== currentUrlParts.protocol && redirectUrl.protocol !== 'https:') ||
1066
+ (redirectUrl.host !== currentHost && !isSubdomain(redirectUrl.host, currentHost))
1067
+ ) {
1068
+ removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers);
1069
+ }
1070
+
1071
+ // Evaluate the beforeRedirect callback
1072
+ if (utils.isFunction(beforeRedirect)) {
1073
+ const responseDetails = {
1074
+ headers: response.headers,
1075
+ statusCode,
1076
+ };
1077
+ const requestDetails = {
1078
+ url: currentUrl,
1079
+ method,
1080
+ headers: requestHeaders,
1081
+ };
1082
+
1083
+ beforeRedirect(m._options, responseDetails, requestDetails);
1084
+ m._sanitizeOptions(m._options);
1085
+ }
1086
+
1087
+ // Perform the redirected request
1088
+ m.request(); // 重新执行请求
1089
+ }
1090
+
1091
+ /**
1092
+ * 处理响应stream
1093
+ * 如:解压,透传流,需设置 decompress = false,避免解压数据
1094
+ * @param {*} res
1095
+ */
1096
+ processStream(res) {
1097
+ const m = this;
1098
+ const {_options: opts} = m;
1099
+
1100
+ const streams = [res];
1101
+
1102
+ // 'transfer-encoding': 'chunked'时,无content-length,axios v1.2 不能自动解压
1103
+ const responseLength = +res.headers['content-length'];
1104
+
1105
+ log$1('processStream', {
1106
+ statusCode: res.statusCode,
1107
+ responseLength,
1108
+ headers: res.headers,
1109
+ });
1110
+
1111
+ if (opts.transformStream) {
1112
+ opts.transformStream.responseLength = responseLength;
1113
+ streams.push(opts.transformStream);
1114
+ }
1115
+
1116
+ const empty = utils.noBody(opts.method, res.statusCode);
1117
+ // decompress the response body transparently if required
1118
+ if (opts.decompress !== false && res.headers['content-encoding']) {
1119
+ // if decompress disabled we should not decompress
1120
+ // 压缩内容,加入 解压 stream,自动解压,axios v1.2 存在bug,不能自动解压
1121
+ // if no content, but headers still say that it is encoded,
1122
+ // remove the header not confuse downstream operations
1123
+ // if ((!responseLength || res.statusCode === 204) && res.headers['content-encoding']) {
1124
+ if (empty && res.headers['content-encoding']) res.headers['content-encoding'] = undefined;
1125
+
1126
+ // 'content-encoding': 'gzip',
1127
+ switch ((res.headers['content-encoding'] || '').toLowerCase()) {
1128
+ /*eslint default-case:0*/
1129
+ case 'gzip':
1130
+ case 'x-gzip':
1131
+ case 'compress':
1132
+ case 'x-compress':
1133
+ // add the unzipper to the body stream processing pipeline
1134
+ streams.push(zlib.createUnzip(zlibOptions));
1135
+
1136
+ // remove the content-encoding in order to not confuse downstream operations
1137
+ res.headers['content-encoding'] = undefined;
1138
+ break
1139
+
1140
+ case 'deflate':
1141
+ streams.push(new ZlibTransform());
1142
+
1143
+ // add the unzipper to the body stream processing pipeline
1144
+ streams.push(zlib.createUnzip(zlibOptions));
1145
+
1146
+ // remove the content-encoding in order to not confuse downstream operations
1147
+ res.headers['content-encoding'] = undefined;
1148
+ break
1149
+
1150
+ case 'br':
1151
+ if (isBrotliSupported) {
1152
+ streams.push(zlib.createBrotliDecompress(brotliOptions));
1153
+ res.headers['content-encoding'] = undefined;
1154
+ }
1155
+ break
1156
+ }
1157
+ }
1158
+
1159
+ const responseStream = streams.length > 1 ? stream.pipeline(streams, utils.noop) : streams[0];
1160
+ // 将内部 responseStream 可读流 映射到 redirectReq
1161
+
1162
+ m.responseStream = responseStream;
1163
+ responseStream.redirectReq = m; // 事情触发时引用
1164
+
1165
+ // stream 模式,事件透传到 请求类
1166
+ if (opts.stream) {
1167
+ if (m._paused) responseStream.pause();
1168
+ // 写入目的流
1169
+ for (const dest of m.pipedests) m.pipeDest(dest);
1170
+
1171
+ // 接收responseStream事件,转发 到 redirectReq 发射
1172
+ for (const ev of readEvents) responseStream.on(ev, readEventEmit[ev]);
1173
+
1174
+ // @ts-ignore
1175
+ responseStream.on('data', chunk => {
1176
+ if (m.timing && !m.responseStarted) {
1177
+ m.responseStartTime = new Date().getTime();
1178
+ }
1179
+ m._destdata = true;
1180
+ m.emit('data', chunk); // 向上触发
1181
+ });
1182
+ }
1183
+
1184
+ // 可读流结束,触发 finished,方便上层清理
1185
+ // A cleanup function which removes all registered listeners.
1186
+ const offListeners = stream.finished(responseStream, () => {
1187
+ offListeners(); // cleanup
1188
+ this.emit('finished');
1189
+ });
1190
+
1191
+ return responseStream
1192
+ }
1193
+
1194
+ // Read Stream API
1195
+
1196
+ /**
1197
+ * read stream to write stream
1198
+ * pipe 只是建立连接管道,后续自动传输数据
1199
+ * @param {stream.Writable} dest
1200
+ * @param {*} opts
1201
+ * @returns {stream.Writable}
1202
+ */
1203
+ pipe(dest, opts) {
1204
+ const m = this;
1205
+
1206
+ // 请求已响应
1207
+ if (m.responseStream) {
1208
+ // 已有数据,不可pipe
1209
+ if (m._destdata) m.emit('error', new Error('You cannot pipe after data has been emitted from the response.'));
1210
+ else if (m._respended) m.emit('error', new Error('You cannot pipe after the response has been ended.'));
1211
+ else {
1212
+ // stream.Stream.prototype.pipe.call(self, dest, opts);
1213
+ super.pipe(dest, opts); // 建立连接管道,自动传输数据
1214
+ m.pipeDest(dest);
1215
+ return dest // 返回写入 stream
1216
+ }
1217
+ } else {
1218
+ // 已请求还未响应
1219
+ m.pipedests.push(dest);
1220
+ // stream.Stream.prototype.pipe.call(self, dest, opts);
1221
+ super.pipe(dest, opts); // 建立连接管道
1222
+ return dest // 返回写入 stream
1223
+ }
1224
+ }
1225
+
1226
+ /**
1227
+ * 分离先前使用pipe()方法附加的Writable流。
1228
+ * @param {stream.Writable} dest
1229
+ * @returns
1230
+ */
1231
+ unpipe(dest) {
1232
+ const m = this;
1233
+
1234
+ // 请求已响应
1235
+ if (m.responseStream) {
1236
+ // 已有数据,不可 unpipe
1237
+ if (m._destdata) m.emit('error', new Error('You cannot unpipe after data has been emitted from the response.'));
1238
+ else if (m._respended) m.emit('error', new Error('You cannot unpipe after the response has been ended.'));
1239
+ else {
1240
+ // stream.Stream.prototype.pipe.call(self, dest, opts);
1241
+ super.unpipe(dest); // 建立连接管道,自动传输数据
1242
+ m.pipedests = m.pipedests.filter(v => v !== dest);
1243
+ return m
1244
+ }
1245
+ } else {
1246
+ // 已请求还未响应
1247
+ m.pipedests = m.pipedests.filter(v => v !== dest);
1248
+ super.unpipe(dest); // 从连接管道中分离
1249
+ return m
1250
+ }
1251
+ }
1252
+
1253
+ /**
1254
+ * 收请求响应,传输数据到可写流之前,设置可写流 header
1255
+ * content-type 和 content-length,实现数据 透传,比如图片
1256
+ * 流模式透传,需设置 decompress = false,避免解压数据
1257
+ * (await req.stream('http://google.com/img.png')).pipe(await req.stream('http://mysite.com/img.png'))
1258
+ * pipe to dest
1259
+ * @param {*} dest
1260
+ */
1261
+ pipeDest(dest) {
1262
+ const m = this;
1263
+ const {response, _options} = m;
1264
+
1265
+ // Called after the response is received
1266
+ if (response?.headers && dest.headers && !dest.headersSent) {
1267
+ const caseless = new Caseless(response.headers);
1268
+ if (caseless.has('content-type')) {
1269
+ const ctname = caseless.has('content-type');
1270
+ if (dest.setHeader) {
1271
+ dest.setHeader(ctname, response.headers[ctname]);
1272
+ } else {
1273
+ dest.headers[ctname] = response.headers[ctname];
1274
+ }
1275
+ }
1276
+
1277
+ if (caseless.has('content-length')) {
1278
+ const clname = caseless.has('content-length');
1279
+ if (dest.setHeader) {
1280
+ dest.setHeader(clname, response.headers[clname]);
1281
+ } else {
1282
+ dest.headers[clname] = response.headers[clname];
1283
+ }
1284
+ }
1285
+ }
1286
+
1287
+ if (response?.headers && dest.setHeader && !dest.headersSent) {
1288
+ for (const h of response.headers) {
1289
+ dest.setHeader(h, response.headers[h]);
1290
+ }
1291
+ dest.statusCode = response.statusCode;
1292
+ }
1293
+
1294
+ // if (m.pipefilter) {
1295
+ // m.pipefilter(response, dest)
1296
+ // }
1297
+ }
1298
+
1299
+ /**
1300
+ * 暂停read流
1301
+ * @param {...any} args
1302
+ */
1303
+ pause(...args) {
1304
+ const m = this;
1305
+ // 没有流
1306
+ if (!m.responseStream) m._paused = true;
1307
+ else m.responseStream.pause(...args);
1308
+ return m
1309
+ }
1310
+
1311
+ /**
1312
+ * 继续read流
1313
+ * @param {...any} args
1314
+ */
1315
+ resume(...args) {
1316
+ const m = this;
1317
+ if (!m.responseStream) m._paused = false;
1318
+ else m.responseStream.resume(...args);
1319
+ return m
1320
+ }
1321
+
1322
+ isPaused() {
1323
+ return this._paused
1324
+ }
1325
+ }
1326
+
1327
+ /**
1328
+ *
1329
+ * @param {*} request
1330
+ * @param {*} error
1331
+ */
1332
+ function destroyRequest(request, error) {
1333
+ for (const ev of writeEvents) {
1334
+ request.removeListener(ev, writeEventEmit[ev]);
1335
+ }
1336
+ request.on('error', utils.noop);
1337
+ request.destroy(error);
1338
+ }
1339
+
1340
+ function removeMatchingHeaders(regex, headers) {
1341
+ let lastValue;
1342
+ Object.keys(headers).forEach(k => {
1343
+ if (regex.test(k)) {
1344
+ lastValue = headers[k];
1345
+ delete headers[k];
1346
+ }
1347
+ });
1348
+
1349
+ return lastValue === null || typeof lastValue === 'undefined' ? undefined : String(lastValue).trim()
1350
+ }
1351
+
1352
+ function isSubdomain(subdomain, domain) {
1353
+ assert(utils.isString(subdomain) && utils.isString(domain));
1354
+ const dot = subdomain.length - domain.length - 1;
1355
+ return dot > 0 && subdomain[dot] === '.' && subdomain.endsWith(domain)
1356
+ }
1357
+
1358
+ /**
1359
+ * from 'https://github.com/follow-redirects/follow-redirects'
1360
+ * 修改以支持http、https 代理服务器
1361
+ * 代理模式下,http or https 请求,取决于 proxy 代理服务器,而不是目的服务器。
1362
+ */
1363
+
1364
+
1365
+ const log = log$2.log({env: `wia:req:${log$2.name(__filename)}`})
1366
+
1367
+ // Preventive platform detection
1368
+ // istanbul ignore next
1369
+ ;(function detectUnsupportedEnvironment() {
1370
+ const looksLikeNode = typeof process !== 'undefined';
1371
+ const looksLikeBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
1372
+ const looksLikeV8 = utils.isFunction(Error.captureStackTrace);
1373
+ if (!looksLikeNode && (looksLikeBrowser || !looksLikeV8)) {
1374
+ log.warn('The follow-redirects package should be excluded from browser builds.');
1375
+ }
1376
+ })();
1377
+
1378
+ /**
1379
+ * 封装http(s),实现重定向
1380
+ * 重定向可能切换http、https
1381
+ * 支持隧道及非隧道、http(s)代理
1382
+ */
1383
+
1384
+ /**
1385
+ * 初始化参数
1386
+ * @param {*} uri
1387
+ * @param {*} options
1388
+ * @param {*} callback
1389
+ * @returns
1390
+ */
1391
+ function init(uri, options, callback) {
1392
+ try {
1393
+ // Parse parameters, ensuring that input is an object
1394
+ if (utils.isURL(uri)) uri = utils.spreadUrlObject(uri);
1395
+ else if (utils.isString(uri)) uri = utils.spreadUrlObject(utils.parseUrl(uri));
1396
+ else {
1397
+ callback = options;
1398
+ options = utils.validateUrl(uri);
1399
+ uri = {};
1400
+ }
1401
+
1402
+ if (utils.isFunction(options)) {
1403
+ callback = options;
1404
+ options = null;
1405
+ }
1406
+
1407
+ // copy options
1408
+ options = {
1409
+ ...uri,
1410
+ ...options,
1411
+ };
1412
+
1413
+ if (!utils.isString(options.host) && !utils.isString(options.hostname)) options.hostname = '::1';
1414
+
1415
+ R = {opts: options, cb: callback};
1416
+ } catch (e) {
1417
+ log.err(e, 'init');
1418
+ }
1419
+
1420
+ return R
1421
+ }
1422
+
1423
+ /**
1424
+ * Executes a request, following redirects
1425
+ * 替换原 http(s).request,参数类似
1426
+ * 注意变参 (options[, callback]) or (url[, options][, callback])
1427
+ maxRedirects: _.maxRedirects,
1428
+ maxBodyLength: _.maxBodyLength,
1429
+ * @param {*} uri/options
1430
+ * @param {*} options/callback
1431
+ * @param {*} callback/null
1432
+ * @returns
1433
+ */
1434
+ function request(uri, options, callback) {
1435
+ let R = null;
1436
+
1437
+ try {
1438
+ const {opts, cb} = init(uri, options, callback);
1439
+ // log.debug('request', {options})
1440
+ R = new Request(opts, cb);
1441
+ } catch (e) {
1442
+ log.err(e, 'request');
1443
+ }
1444
+
1445
+ return R
1446
+ }
1447
+
1448
+ /**
1449
+ * 执行简单的非stream数据请求
1450
+ * 复杂数据,请使用 @wiajs/req库(fork from axios),该库封装了当前库,提供了更多功能
1451
+ * organize params for patch, post, put, head, del
1452
+ * @param {string} verb
1453
+ * @returns {Request} Duplex 流
1454
+ */
1455
+ function fn(verb) {
1456
+ const method = verb.toUpperCase();
1457
+ return (uri, options, callback) => {
1458
+ const {opts, cb} = init(uri, options, callback);
1459
+ opts.method = method;
1460
+ const req = new Request(opts, cb);
1461
+ req.end();
1462
+ return req
1463
+ }
1464
+ }
1465
+
1466
+ // define like this to please codeintel/intellisense IDEs
1467
+ request.get = fn('get');
1468
+ request.head = fn('head');
1469
+ request.options = fn('options');
1470
+ request.post = fn('post');
1471
+ request.put = fn('put');
1472
+ request.patch = fn('patch');
1473
+ request.del = fn('delete');
1474
+ request['delete'] = fn('delete');
1475
+
1476
+ module.exports = request;