urllib 2.38.1 → 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.
Files changed (55) hide show
  1. package/README.md +75 -264
  2. package/package.json +62 -59
  3. package/src/HttpAgent.ts +72 -0
  4. package/src/HttpClient.ts +514 -0
  5. package/src/Request.ts +118 -0
  6. package/src/Response.ts +41 -0
  7. package/src/cjs/HttpAgent.d.ts +16 -0
  8. package/src/cjs/HttpAgent.js +62 -0
  9. package/src/cjs/HttpAgent.js.map +1 -0
  10. package/src/cjs/HttpClient.d.ts +39 -0
  11. package/src/cjs/HttpClient.js +466 -0
  12. package/src/cjs/HttpClient.js.map +1 -0
  13. package/src/cjs/Request.d.ts +114 -0
  14. package/src/cjs/Request.js +3 -0
  15. package/src/cjs/Request.js.map +1 -0
  16. package/src/cjs/Response.d.ts +36 -0
  17. package/src/cjs/Response.js +3 -0
  18. package/src/cjs/Response.js.map +1 -0
  19. package/src/cjs/index.d.ts +7 -0
  20. package/src/cjs/index.js +18 -0
  21. package/src/cjs/index.js.map +1 -0
  22. package/src/cjs/package.json +3 -0
  23. package/src/cjs/utils.d.ts +3 -0
  24. package/src/cjs/utils.js +56 -0
  25. package/src/cjs/utils.js.map +1 -0
  26. package/src/esm/HttpAgent.d.ts +16 -0
  27. package/src/esm/HttpAgent.js +58 -0
  28. package/src/esm/HttpAgent.js.map +1 -0
  29. package/src/esm/HttpClient.d.ts +39 -0
  30. package/src/esm/HttpClient.js +462 -0
  31. package/src/esm/HttpClient.js.map +1 -0
  32. package/src/esm/Request.d.ts +114 -0
  33. package/src/esm/Request.js +2 -0
  34. package/src/esm/Request.js.map +1 -0
  35. package/src/esm/Response.d.ts +36 -0
  36. package/src/esm/Response.js +2 -0
  37. package/src/esm/Response.js.map +1 -0
  38. package/src/esm/index.d.ts +7 -0
  39. package/src/esm/index.js +13 -0
  40. package/src/esm/index.js.map +1 -0
  41. package/src/esm/package.json +3 -0
  42. package/src/esm/utils.d.ts +3 -0
  43. package/src/esm/utils.js +51 -0
  44. package/src/esm/utils.js.map +1 -0
  45. package/src/index.ts +16 -0
  46. package/src/utils.ts +53 -0
  47. package/History.md +0 -804
  48. package/lib/detect_proxy_agent.js +0 -31
  49. package/lib/get_proxy_from_uri.js +0 -81
  50. package/lib/httpclient.js +0 -61
  51. package/lib/httpclient2.js +0 -83
  52. package/lib/index.d.ts +0 -279
  53. package/lib/index.js +0 -21
  54. package/lib/index.test-d.ts +0 -19
  55. package/lib/urllib.js +0 -1317
package/lib/urllib.js DELETED
@@ -1,1317 +0,0 @@
1
- 'use strict';
2
-
3
- var debug = require('debug')('urllib');
4
- var path = require('path');
5
- var dns = require('dns');
6
- var http = require('http');
7
- var https = require('https');
8
- var urlutil = require('url');
9
- var URL = urlutil.URL;
10
- var util = require('util');
11
- var qs = require('qs');
12
- var ip = require('ip');
13
- var querystring = require('querystring');
14
- var zlib = require('zlib');
15
- var ua = require('default-user-agent');
16
- var digestAuthHeader = require('digest-header');
17
- var ms = require('humanize-ms');
18
- var statuses = require('statuses');
19
- var contentTypeParser = require('content-type');
20
- var first = require('ee-first');
21
- var pump = require('pump');
22
- var utility = require('utility');
23
- var FormStream = require('formstream');
24
- var detectProxyAgent = require('./detect_proxy_agent');
25
-
26
- var _Promise;
27
- var _iconv;
28
-
29
- var pkg = require('../package.json');
30
-
31
- var USER_AGENT = exports.USER_AGENT = ua('node-urllib', pkg.version);
32
- var NODE_MAJOR_VERSION = parseInt(process.versions.node.split('.')[0]);
33
-
34
- // change Agent.maxSockets to 1000
35
- exports.agent = new http.Agent();
36
- exports.agent.maxSockets = 1000;
37
-
38
- exports.httpsAgent = new https.Agent();
39
- exports.httpsAgent.maxSockets = 1000;
40
-
41
- var LONG_STACK_DELIMITER = '\n --------------------\n';
42
-
43
- /**
44
- * The default request timeout(in milliseconds).
45
- * @type {Number}
46
- * @const
47
- */
48
-
49
- exports.TIMEOUT = ms('5s');
50
- exports.TIMEOUTS = [ms('5s'), ms('5s')];
51
-
52
- var REQUEST_ID = 0;
53
- var MAX_VALUE = Math.pow(2, 31) - 10;
54
- var isNode010 = /^v0\.10\.\d+$/.test(process.version);
55
- var isNode012 = /^v0\.12\.\d+$/.test(process.version);
56
-
57
- /**
58
- * support data types
59
- * will auto decode response body
60
- * @type {Array}
61
- */
62
- var TEXT_DATA_TYPES = [
63
- 'json',
64
- 'text'
65
- ];
66
-
67
- var PROTO_RE = /^https?:\/\//i;
68
-
69
- // Keep-Alive: timeout=5, max=100
70
- var KEEP_ALIVE_RE = /^timeout=(\d+)/i;
71
-
72
- var SOCKET_REQUEST_COUNT = '_URLLIB_SOCKET_REQUEST_COUNT';
73
- var SOCKET_RESPONSE_COUNT = '_URLLIB_SOCKET_RESPONSE_COUNT';
74
-
75
- /**
76
- * Handle all http request, both http and https support well.
77
- *
78
- * @example
79
- *
80
- * ```js
81
- * // GET https://nodejs.org
82
- * urllib.request('https://nodejs.org', function(err, data, res) {});
83
- * // POST https://nodejs.org
84
- * var args = { type: 'post', data: { foo: 'bar' } };
85
- * urllib.request('https://nodejs.org', args, function(err, data, res) {});
86
- * ```
87
- *
88
- * @param {String|Object} url: the request full URL.
89
- * @param {Object} [args]: optional
90
- * - {Object} [data]: request data, will auto be query stringify.
91
- * - {Boolean} [dataAsQueryString]: force convert `data` to query string.
92
- * - {String|Buffer} [content]: optional, if set content, `data` will ignore.
93
- * - {ReadStream} [stream]: read stream to sent.
94
- * - {WriteStream} [writeStream]: writable stream to save response data.
95
- * If you use this, callback's data should be null.
96
- * We will just `pipe(ws, {end: true})`.
97
- * - {consumeWriteStream} [true]: consume the writeStream, invoke the callback after writeStream close.
98
- * - {Array<ReadStream|Buffer|String>|Object|ReadStream|Buffer|String} [files]: optional,
99
- * The files will send with `multipart/form-data` format, base on `formstream`.
100
- * If `method` not set, will use `POST` method by default.
101
- * - {String} [method]: optional, could be GET | POST | DELETE | PUT, default is GET
102
- * - {String} [contentType]: optional, request data type, could be `json`, default is undefined
103
- * - {String} [dataType]: optional, response data type, could be `text` or `json`, default is buffer
104
- * - {Boolean|Function} [fixJSONCtlChars]: optional, fix the control characters (U+0000 through U+001F)
105
- * before JSON parse response. Default is `false`.
106
- * `fixJSONCtlChars` can be a function, will pass data to the first argument. e.g.: `data = fixJSONCtlChars(data)`
107
- * - {Object} [headers]: optional, request headers
108
- * - {Boolean} [keepHeaderCase]: optional, by default will convert header keys to lowercase
109
- * - {Number|Array} [timeout]: request timeout(in milliseconds), default is `exports.TIMEOUTS containing connect timeout and response timeout`
110
- * - {Agent} [agent]: optional, http agent. Set `false` if you does not use agent.
111
- * - {Agent} [httpsAgent]: optional, https agent. Set `false` if you does not use agent.
112
- * - {String} [auth]: Basic authentication i.e. 'user:password' to compute an Authorization header.
113
- * - {String} [digestAuth]: Digest authentication i.e. 'user:password' to compute an Authorization header.
114
- * - {String|Buffer|Array} [ca]: An array of strings or Buffers of trusted certificates.
115
- * If this is omitted several well known "root" CAs will be used, like VeriSign.
116
- * These are used to authorize connections.
117
- * Notes: This is necessary only if the server uses the self-signed certificate
118
- * - {Boolean} [rejectUnauthorized]: If true, the server certificate is verified against the list of supplied CAs.
119
- * An 'error' event is emitted if verification fails. Default: true.
120
- * - {String|Buffer} [pfx]: A string or Buffer containing the private key,
121
- * certificate and CA certs of the server in PFX or PKCS12 format.
122
- * - {String|Buffer} [key]: A string or Buffer containing the private key of the client in PEM format.
123
- * Notes: This is necessary only if using the client certificate authentication
124
- * - {String|Buffer} [cert]: A string or Buffer containing the certificate key of the client in PEM format.
125
- * Notes: This is necessary only if using the client certificate authentication
126
- * - {String} [passphrase]: A string of passphrase for the private key or pfx.
127
- * - {String} [ciphers]: A string describing the ciphers to use or exclude.
128
- * - {String} [secureProtocol]: The SSL method to use, e.g. SSLv3_method to force SSL version 3.
129
- * The possible values depend on your installation of OpenSSL and are defined in the constant SSL_METHODS.
130
- * - {Boolean} [followRedirect]: Follow HTTP 3xx responses as redirects. defaults to false.
131
- * - {Number} [maxRedirects]: The maximum number of redirects to follow, defaults to 10.
132
- * - {Function(from, to)} [formatRedirectUrl]: Format the redirect url by your self. Default is `url.resolve(from, to)`
133
- * - {Function(options)} [beforeRequest]: Before request hook, you can change every thing here.
134
- * - {Boolean} [streaming]: let you get the res object when request connected, default is `false`. alias `customResponse`
135
- * - {Boolean} [gzip]: Accept gzip response content and auto decode it, default is `false`.
136
- * - {Boolean} [timing]: Enable timing or not, default is `false`.
137
- * - {Function} [lookup]: Custom DNS lookup function, default is `dns.lookup`.
138
- * Require node >= 4.0.0 and only work on `http` protocol.
139
- * - {Boolean} [enableProxy]: optional, enable proxy request. Default is `false`.
140
- * - {String|Object} [proxy]: optional proxy agent uri or options. Default is `null`.
141
- * - {String} [socketPath]: optional, unix domain socket file path.
142
- * - {Function} checkAddress: optional, check request address to protect from SSRF and similar attacks.
143
- * @param {Function} [callback]: callback(error, data, res). If missing callback, will return a promise object.
144
- * @return {HttpRequest} req object.
145
- * @api public
146
- */
147
- exports.request = function request(url, args, callback) {
148
- // request(url, callback)
149
- if (arguments.length === 2 && typeof args === 'function') {
150
- callback = args;
151
- args = null;
152
- }
153
- if (typeof callback === 'function') {
154
- return exports.requestWithCallback(url, args, callback);
155
- }
156
-
157
- // Promise
158
- if (!_Promise) {
159
- _Promise = require('any-promise');
160
- }
161
- return new _Promise(function (resolve, reject) {
162
- exports.requestWithCallback(url, args, makeCallback(resolve, reject));
163
- });
164
- };
165
-
166
- // alias to curl
167
- exports.curl = exports.request;
168
-
169
- function makeCallback(resolve, reject) {
170
- return function (err, data, res) {
171
- if (err) {
172
- return reject(err);
173
- }
174
- resolve({
175
- data: data,
176
- status: res.statusCode,
177
- headers: res.headers,
178
- res: res
179
- });
180
- };
181
- }
182
-
183
- // yield urllib.requestThunk(url, args)
184
- exports.requestThunk = function requestThunk(url, args) {
185
- return function (callback) {
186
- exports.requestWithCallback(url, args, function (err, data, res) {
187
- if (err) {
188
- return callback(err);
189
- }
190
- callback(null, {
191
- data: data,
192
- status: res.statusCode,
193
- headers: res.headers,
194
- res: res
195
- });
196
- });
197
- };
198
- };
199
-
200
- function requestWithCallback(url, args, callback) {
201
- var req;
202
- // requestWithCallback(url, callback)
203
- if (!url || (typeof url !== 'string' && typeof url !== 'object')) {
204
- var msg = util.format('expect request url to be a string or a http request options, but got %j', url);
205
- throw new Error(msg);
206
- }
207
-
208
- if (arguments.length === 2 && typeof args === 'function') {
209
- callback = args;
210
- args = null;
211
- }
212
-
213
- args = args || {};
214
- if (REQUEST_ID >= MAX_VALUE) {
215
- REQUEST_ID = 0;
216
- }
217
- var reqId = ++REQUEST_ID;
218
-
219
- args.requestUrls = args.requestUrls || [];
220
-
221
- args.timeout = args.timeout || exports.TIMEOUTS;
222
- args.maxRedirects = args.maxRedirects || 10;
223
- args.streaming = args.streaming || args.customResponse;
224
- var requestStartTime = Date.now();
225
- var parsedUrl;
226
-
227
- if (typeof url === 'string') {
228
- if (!PROTO_RE.test(url)) {
229
- // Support `request('www.server.com')`
230
- url = 'http://' + url;
231
- }
232
- if (URL) {
233
- parsedUrl = urlutil.parse(new URL(url).href);
234
- } else {
235
- parsedUrl = urlutil.parse(url);
236
- }
237
- } else {
238
- parsedUrl = url;
239
- }
240
-
241
- var reqMeta = {
242
- requestId: reqId,
243
- url: parsedUrl.href,
244
- args: args,
245
- ctx: args.ctx,
246
- };
247
- if (args.emitter) {
248
- args.emitter.emit('request', reqMeta);
249
- }
250
-
251
- var method = (args.type || args.method || parsedUrl.method || 'GET').toUpperCase();
252
- var port = parsedUrl.port || 80;
253
- var httplib = http;
254
- var agent = getAgent(args.agent, exports.agent);
255
- var fixJSONCtlChars = args.fixJSONCtlChars;
256
-
257
- if (parsedUrl.protocol === 'https:') {
258
- httplib = https;
259
- agent = getAgent(args.httpsAgent, exports.httpsAgent);
260
-
261
- if (!parsedUrl.port) {
262
- port = 443;
263
- }
264
- }
265
-
266
- // request through proxy tunnel
267
- var proxyTunnelAgent = detectProxyAgent(parsedUrl, args);
268
- if (proxyTunnelAgent) {
269
- agent = proxyTunnelAgent;
270
- }
271
-
272
- var lookup = args.lookup;
273
- // check address to protect from SSRF and similar attacks
274
- if (args.checkAddress) {
275
- var _lookup = lookup || dns.lookup;
276
- lookup = function(host, dnsopts, callback) {
277
- _lookup(host, dnsopts, function emitLookup(err, ip, family) {
278
- // add check address logic in custom dns lookup
279
- if (!err && !args.checkAddress(ip, family)) {
280
- err = new Error('illegal address');
281
- err.name = 'IllegalAddressError';
282
- err.hostname = host;
283
- err.ip = ip;
284
- err.family = family;
285
- }
286
- callback(err, ip, family);
287
- });
288
- };
289
- }
290
-
291
- var requestSize = 0;
292
- var options = {
293
- host: parsedUrl.hostname || parsedUrl.host || 'localhost',
294
- path: parsedUrl.path || '/',
295
- method: method,
296
- port: port,
297
- agent: agent,
298
- headers: {},
299
- // default is dns.lookup
300
- // https://github.com/nodejs/node/blob/master/lib/net.js#L986
301
- // custom dnslookup require node >= 4.0.0 (for http), node >=8 (for https)
302
- // https://github.com/nodejs/node/blob/archived-io.js-v0.12/lib/net.js#L952
303
- lookup: lookup,
304
- };
305
-
306
- var originHeaderKeys = {};
307
- if (args.headers) {
308
- // only allow enumerable and ownProperty value of args.headers
309
- var names = utility.getOwnEnumerables(args.headers, true);
310
- for (var i = 0; i < names.length; i++) {
311
- var name = names[i];
312
- var key = name.toLowerCase();
313
- if (key !== name) {
314
- originHeaderKeys[key] = name;
315
- }
316
- options.headers[key] = args.headers[name];
317
- }
318
- }
319
- if (args.socketPath) {
320
- options.socketPath = args.socketPath;
321
- }
322
-
323
- var sslNames = [
324
- 'pfx',
325
- 'key',
326
- 'passphrase',
327
- 'cert',
328
- 'ca',
329
- 'ciphers',
330
- 'rejectUnauthorized',
331
- 'secureProtocol',
332
- 'secureOptions',
333
- ];
334
- for (var i = 0; i < sslNames.length; i++) {
335
- var name = sslNames[i];
336
- if (args.hasOwnProperty(name)) {
337
- options[name] = args[name];
338
- }
339
- }
340
-
341
- // fix rejectUnauthorized when major version < 12
342
- if (NODE_MAJOR_VERSION < 12) {
343
- if (options.rejectUnauthorized === false && !options.hasOwnProperty('secureOptions')) {
344
- options.secureOptions = require('constants').SSL_OP_NO_TLSv1_2;
345
- }
346
- }
347
-
348
- var auth = args.auth || parsedUrl.auth;
349
- if (auth) {
350
- options.auth = auth;
351
- }
352
-
353
- var body = null;
354
- var dataAsQueryString = false;
355
-
356
- if (args.files) {
357
- if (!options.method || options.method === 'GET' || options.method === 'HEAD') {
358
- options.method = 'POST';
359
- }
360
- var files = args.files;
361
- var uploadFiles = [];
362
- if (Array.isArray(files)) {
363
- for (var i = 0; i < files.length; i++) {
364
- var field = 'file' + (i === 0 ? '' : i);
365
- uploadFiles.push([ field, files[i] ]);
366
- }
367
- } else {
368
- if (Buffer.isBuffer(files) || typeof files.pipe === 'function' || typeof files === 'string') {
369
- uploadFiles.push([ 'file', files ]);
370
- } else if (typeof files === 'object') {
371
- for (var field in files) {
372
- uploadFiles.push([ field, files[field] ]);
373
- }
374
- }
375
- }
376
- var form = new FormStream();
377
- // set normal fields first
378
- if (args.data) {
379
- for (var fieldName in args.data) {
380
- form.field(fieldName, args.data[fieldName]);
381
- }
382
- }
383
-
384
- for (var i = 0; i < uploadFiles.length; i++) {
385
- var item = uploadFiles[i];
386
- if (Buffer.isBuffer(item[1])) {
387
- form.buffer(item[0], item[1], 'bufferfile' + i);
388
- } else if (typeof item[1].pipe === 'function') {
389
- var filename = item[1].path || ('streamfile' + i);
390
- filename = path.basename(filename);
391
- form.stream(item[0], item[1], filename);
392
- } else {
393
- form.file(item[0], item[1]);
394
- }
395
- }
396
-
397
- var formHeaders = form.headers();
398
- var formHeaderNames = utility.getOwnEnumerables(formHeaders, true);
399
- for (var i = 0; i < formHeaderNames.length; i++) {
400
- var name = formHeaderNames[i];
401
- options.headers[name.toLowerCase()] = formHeaders[name];
402
- }
403
- debug('set multipart headers: %j, method: %s', formHeaders, options.method);
404
- args.stream = form;
405
- } else {
406
- body = args.content || args.data;
407
- dataAsQueryString = method === 'GET' || method === 'HEAD' || args.dataAsQueryString;
408
- if (!args.content) {
409
- if (body && !(typeof body === 'string' || Buffer.isBuffer(body))) {
410
- if (dataAsQueryString) {
411
- // read: GET, HEAD, use query string
412
- body = args.nestedQuerystring ? qs.stringify(body) : querystring.stringify(body);
413
- } else {
414
- var contentType = options.headers['content-type'];
415
- // auto add application/x-www-form-urlencoded when using urlencode form request
416
- if (!contentType) {
417
- if (args.contentType === 'json') {
418
- contentType = 'application/json';
419
- } else {
420
- contentType = 'application/x-www-form-urlencoded';
421
- }
422
- options.headers['content-type'] = contentType;
423
- }
424
-
425
- if (parseContentType(contentType).type === 'application/json') {
426
- body = JSON.stringify(body);
427
- } else {
428
- // 'application/x-www-form-urlencoded'
429
- body = args.nestedQuerystring ? qs.stringify(body) : querystring.stringify(body);
430
- }
431
- }
432
- }
433
- }
434
- }
435
-
436
- if (body) {
437
- // if it's a GET or HEAD request, data should be sent as query string
438
- if (dataAsQueryString) {
439
- options.path += (parsedUrl.query ? '&' : '?') + body;
440
- body = null;
441
- }
442
-
443
- if (body) {
444
- var length = body.length;
445
- if (!Buffer.isBuffer(body)) {
446
- length = Buffer.byteLength(body);
447
- }
448
- requestSize = length;
449
-
450
- options.headers['content-length'] = length.toString();
451
- }
452
- }
453
-
454
- if (args.dataType === 'json') {
455
- if (!options.headers.accept) {
456
- options.headers.accept = 'application/json';
457
- }
458
- }
459
-
460
- if (typeof args.beforeRequest === 'function') {
461
- // you can use this hook to change every thing.
462
- args.beforeRequest(options);
463
- }
464
-
465
- var connectTimer = null;
466
- var responseTimer = null;
467
- var __err = null;
468
- var connected = false; // socket connected or not
469
- var keepAliveSocket = false; // request with keepalive socket
470
- var socketHandledRequests = 0; // socket already handled request count
471
- var socketHandledResponses = 0; // socket already handled response count
472
- var responseSize = 0;
473
- var statusCode = -1;
474
- var statusMessage = null;
475
- var responseAborted = false;
476
- var remoteAddress = '';
477
- var remotePort = '';
478
- var timing = null;
479
- if (args.timing) {
480
- timing = {
481
- // socket assigned
482
- queuing: 0,
483
- // dns lookup time
484
- dnslookup: 0,
485
- // socket connected
486
- connected: 0,
487
- // request sent
488
- requestSent: 0,
489
- // Time to first byte (TTFB)
490
- waiting: 0,
491
- contentDownload: 0,
492
- };
493
- }
494
-
495
- function cancelConnectTimer() {
496
- if (connectTimer) {
497
- clearTimeout(connectTimer);
498
- connectTimer = null;
499
- debug('Request#%d connect timer canceled', reqId);
500
- }
501
- }
502
- function cancelResponseTimer() {
503
- if (responseTimer) {
504
- clearTimeout(responseTimer);
505
- responseTimer = null;
506
- debug('Request#%d response timer canceled', reqId);
507
- }
508
- }
509
-
510
- function done(err, data, res) {
511
- cancelConnectTimer();
512
- cancelResponseTimer();
513
- if (!callback) {
514
- console.warn('[urllib:warn] [%s] [%s] [worker:%s] %s %s callback twice!!!',
515
- Date(), reqId, process.pid, options.method, url);
516
- // https://github.com/node-modules/urllib/pull/30
517
- if (err) {
518
- console.warn('[urllib:warn] [%s] [%s] [worker:%s] %s: %s\nstack: %s',
519
- Date(), reqId, process.pid, err.name, err.message, err.stack);
520
- }
521
- return;
522
- }
523
-
524
- var cb = callback;
525
- callback = null;
526
- var headers = {};
527
- if (res) {
528
- statusCode = res.statusCode;
529
- statusMessage = res.statusMessage;
530
- headers = res.headers;
531
- }
532
-
533
- if (handleDigestAuth(res, cb)) {
534
- return;
535
- }
536
-
537
- var response = createCallbackResponse(data, res);
538
-
539
- debug('[%sms] done, %s bytes HTTP %s %s %s %s, keepAliveSocket: %s, timing: %j, socketHandledRequests: %s, socketHandledResponses: %s',
540
- response.requestUseTime, responseSize, statusCode, options.method, options.host, options.path,
541
- keepAliveSocket, timing, socketHandledRequests, socketHandledResponses);
542
-
543
- if (err) {
544
- var agentStatus = '';
545
- if (agent && typeof agent.getCurrentStatus === 'function') {
546
- // add current agent status to error message for logging and debug
547
- agentStatus = ', agent status: ' + JSON.stringify(agent.getCurrentStatus());
548
- }
549
- err.message += ', ' + options.method + ' ' + url + ' ' + statusCode
550
- + ' (connected: ' + connected + ', keepalive socket: ' + keepAliveSocket + agentStatus
551
- + ', socketHandledRequests: ' + socketHandledRequests
552
- + ', socketHandledResponses: ' + socketHandledResponses + ')'
553
- + '\nheaders: ' + JSON.stringify(headers);
554
- err.data = data;
555
- err.path = options.path;
556
- err.status = statusCode;
557
- err.headers = headers;
558
- err.res = response;
559
- addLongStackTrace(err, req);
560
- }
561
-
562
- // only support agentkeepalive module for now
563
- // agentkeepalive@4: agent.options.freeSocketTimeout
564
- // agentkeepalive@3: agent.freeSocketKeepAliveTimeout
565
- var freeSocketTimeout = agent && (agent.options && agent.options.freeSocketTimeout || agent.freeSocketKeepAliveTimeout);
566
- if (agent && agent.keepAlive && freeSocketTimeout > 0 &&
567
- statusCode >= 200 && headers.connection === 'keep-alive' && headers['keep-alive']) {
568
- // adjust freeSocketTimeout on the socket
569
- var m = KEEP_ALIVE_RE.exec(headers['keep-alive']);
570
- if (m) {
571
- var seconds = parseInt(m[1]);
572
- if (seconds > 0) {
573
- // network delay 500ms
574
- var serverSocketTimeout = seconds * 1000 - 500;
575
- if (serverSocketTimeout < freeSocketTimeout) {
576
- // https://github.com/node-modules/agentkeepalive/blob/master/lib/agent.js#L127
577
- // agentkeepalive@4
578
- var socket = res.socket || (req && req.socket);
579
- if (agent.options && agent.options.freeSocketTimeout) {
580
- socket.freeSocketTimeout = serverSocketTimeout;
581
- } else {
582
- socket.freeSocketKeepAliveTimeout = serverSocketTimeout;
583
- }
584
- }
585
- }
586
- }
587
- }
588
-
589
- cb(err, data, args.streaming ? res : response);
590
-
591
- emitResponseEvent(err, response);
592
- }
593
-
594
- function createAndEmitResponseEvent(data, res) {
595
- var response = createCallbackResponse(data, res);
596
- emitResponseEvent(null, response);
597
- }
598
-
599
- function createCallbackResponse(data, res) {
600
- var requestUseTime = Date.now() - requestStartTime;
601
- if (timing) {
602
- timing.contentDownload = requestUseTime;
603
- }
604
-
605
- var headers = res && res.headers || {};
606
- var resStatusCode = res && res.statusCode || statusCode;
607
- var resStatusMessage = res && res.statusMessage || statusMessage;
608
-
609
- return {
610
- status: resStatusCode,
611
- statusCode: resStatusCode,
612
- statusMessage: resStatusMessage,
613
- headers: headers,
614
- size: responseSize,
615
- aborted: responseAborted,
616
- rt: requestUseTime,
617
- keepAliveSocket: keepAliveSocket,
618
- data: data,
619
- requestUrls: args.requestUrls,
620
- timing: timing,
621
- remoteAddress: remoteAddress,
622
- remotePort: remotePort,
623
- socketHandledRequests: socketHandledRequests,
624
- socketHandledResponses: socketHandledResponses,
625
- };
626
- }
627
-
628
- function emitResponseEvent(err, response) {
629
- if (args.emitter) {
630
- // keep to use the same reqMeta object on request event before
631
- reqMeta.url = parsedUrl.href;
632
- reqMeta.socket = req && req.connection;
633
- reqMeta.options = options;
634
- reqMeta.size = requestSize;
635
-
636
- args.emitter.emit('response', {
637
- requestId: reqId,
638
- error: err,
639
- ctx: args.ctx,
640
- req: reqMeta,
641
- res: response,
642
- });
643
- }
644
- }
645
-
646
- function handleDigestAuth(res, cb) {
647
- var headers = {};
648
- if (res && res.headers) {
649
- headers = res.headers;
650
- }
651
- // handle digest auth
652
- if (statusCode === 401 && headers['www-authenticate']
653
- && !options.headers.authorization && args.digestAuth) {
654
- var authenticate = headers['www-authenticate'];
655
- if (authenticate.indexOf('Digest ') >= 0) {
656
- debug('Request#%d %s: got digest auth header WWW-Authenticate: %s', reqId, url, authenticate);
657
- options.headers.authorization = digestAuthHeader(options.method, options.path, authenticate, args.digestAuth);
658
- debug('Request#%d %s: auth with digest header: %s', reqId, url, options.headers.authorization);
659
- if (res.headers['set-cookie']) {
660
- options.headers.cookie = res.headers['set-cookie'].join(';');
661
- }
662
- args.headers = options.headers;
663
- exports.requestWithCallback(url, args, cb);
664
- return true;
665
- }
666
- }
667
- return false;
668
- }
669
-
670
- function handleRedirect(res) {
671
- var err = null;
672
- if (args.followRedirect && statuses.redirect[res.statusCode]) { // handle redirect
673
- args._followRedirectCount = (args._followRedirectCount || 0) + 1;
674
- var location = res.headers.location;
675
- if (!location) {
676
- err = new Error('Got statusCode ' + res.statusCode + ' but cannot resolve next location from headers');
677
- err.name = 'FollowRedirectError';
678
- } else if (args._followRedirectCount > args.maxRedirects) {
679
- err = new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + url);
680
- err.name = 'MaxRedirectError';
681
- } else {
682
- var newUrl = args.formatRedirectUrl ? args.formatRedirectUrl(url, location) : urlutil.resolve(url, location);
683
- debug('Request#%d %s: `redirected` from %s to %s', reqId, options.path, url, newUrl);
684
- // make sure timer stop
685
- cancelResponseTimer();
686
- // should clean up headers.host on `location: http://other-domain/url`
687
- if (options.headers.host && PROTO_RE.test(location)) {
688
- options.headers.host = null;
689
- args.headers = options.headers;
690
- }
691
- // avoid done will be execute in the future change.
692
- var cb = callback;
693
- callback = null;
694
- exports.requestWithCallback(newUrl, args, cb);
695
- return {
696
- redirect: true,
697
- error: null
698
- };
699
- }
700
- }
701
- return {
702
- redirect: false,
703
- error: err
704
- };
705
- }
706
-
707
- // don't set user-agent
708
- if (args.headers && (args.headers['User-Agent'] === null || args.headers['user-agent'] === null)) {
709
- if (options.headers['user-agent']) {
710
- delete options.headers['user-agent'];
711
- }
712
- } else {
713
- // need to set user-agent
714
- var hasAgentHeader = options.headers['user-agent'];
715
- if (!hasAgentHeader) {
716
- options.headers['user-agent'] = USER_AGENT;
717
- }
718
- }
719
-
720
- if (args.gzip) {
721
- var isAcceptEncodingNull = (args.headers && (args.headers['Accept-Encoding'] === null || args.headers['accept-encoding'] === null));
722
- if (!isAcceptEncodingNull) {
723
- var hasAcceptEncodingHeader = options.headers['accept-encoding'];
724
- if (!hasAcceptEncodingHeader) {
725
- options.headers['accept-encoding'] = 'gzip, deflate';
726
- }
727
- }
728
- }
729
-
730
- function decodeContent(res, body, cb) {
731
- if (responseAborted) {
732
- // err = new Error('Remote socket was terminated before `response.end()` was called');
733
- // err.name = 'RemoteSocketClosedError';
734
- debug('Request#%d %s: Remote socket was terminated before `response.end()` was called', reqId, url);
735
- var err = responseError || new Error('Remote socket was terminated before `response.end()` was called');
736
- return cb(err);
737
- }
738
- var encoding = res.headers['content-encoding'];
739
- if (body.length === 0 || !encoding) {
740
- return cb(null, body, encoding);
741
- }
742
-
743
- encoding = encoding.toLowerCase();
744
- switch (encoding) {
745
- case 'gzip':
746
- case 'deflate':
747
- debug('unzip %d length body', body.length);
748
- zlib.unzip(body, function(err, data) {
749
- if (err && err.name === 'Error') {
750
- err.name = 'UnzipError';
751
- }
752
- cb(err, data);
753
- });
754
- break;
755
- default:
756
- cb(null, body, encoding);
757
- }
758
- }
759
-
760
- var writeStream = args.writeStream;
761
- var isWriteStreamClose = false;
762
-
763
- debug('Request#%d %s %s with headers %j, options.path: %s',
764
- reqId, method, url, options.headers, options.path);
765
-
766
- args.requestUrls.push(parsedUrl.href);
767
-
768
- var hasResponse = false;
769
- var responseError;
770
- function onResponse(res) {
771
- hasResponse = true;
772
- socketHandledResponses = res.socket[SOCKET_RESPONSE_COUNT] = (res.socket[SOCKET_RESPONSE_COUNT] || 0) + 1;
773
- if (timing) {
774
- timing.waiting = Date.now() - requestStartTime;
775
- }
776
- debug('Request#%d %s `req response` event emit: status %d, headers: %j',
777
- reqId, url, res.statusCode, res.headers);
778
-
779
- if (args.streaming) {
780
- var result = handleRedirect(res);
781
- if (result.redirect) {
782
- res.resume();
783
- createAndEmitResponseEvent(null, res);
784
- return;
785
- }
786
- if (result.error) {
787
- res.resume();
788
- return done(result.error, null, res);
789
- }
790
-
791
- return done(null, null, res);
792
- }
793
-
794
- res.on('error', function (err) {
795
- responseError = err;
796
- debug('Request#%d %s: `res error` event emit, total size %d, socket handled %s requests and %s responses',
797
- reqId, url, responseSize, socketHandledRequests, socketHandledResponses);
798
- });
799
-
800
- res.on('aborted', function () {
801
- responseAborted = true;
802
- debug('Request#%d %s: `res aborted` event emit, total size %d',
803
- reqId, url, responseSize);
804
- });
805
-
806
- if (writeStream) {
807
- // If there's a writable stream to recieve the response data, just pipe the
808
- // response stream to that writable stream and call the callback when it has
809
- // finished writing.
810
- //
811
- // NOTE that when the response stream `res` emits an 'end' event it just
812
- // means that it has finished piping data to another stream. In the
813
- // meanwhile that writable stream may still writing data to the disk until
814
- // it emits a 'close' event.
815
- //
816
- // That means that we should not apply callback until the 'close' of the
817
- // writable stream is emited.
818
- //
819
- // See also:
820
- // - https://github.com/TBEDP/urllib/commit/959ac3365821e0e028c231a5e8efca6af410eabb
821
- // - http://nodejs.org/api/stream.html#stream_event_end
822
- // - http://nodejs.org/api/stream.html#stream_event_close_1
823
- var result = handleRedirect(res);
824
- if (result.redirect) {
825
- res.resume();
826
- createAndEmitResponseEvent(null, res);
827
- return;
828
- }
829
- if (result.error) {
830
- res.resume();
831
- // end ths stream first
832
- writeStream.end();
833
- done(result.error, null, res);
834
- return;
835
- }
836
-
837
- // you can set consumeWriteStream false that only wait response end
838
- if (args.consumeWriteStream === false) {
839
- res.on('end', done.bind(null, null, null, res));
840
- pump(res, writeStream, function(err) {
841
- if (isWriteStreamClose) {
842
- return;
843
- }
844
- isWriteStreamClose = true;
845
- debug('Request#%d %s: writeStream close, error: %s', reqId, url, err);
846
- });
847
- return;
848
- }
849
-
850
- // node 0.10, 0.12: only emit res aborted, writeStream close not fired
851
- if (isNode010 || isNode012) {
852
- first([
853
- [ writeStream, 'close' ],
854
- [ res, 'aborted' ],
855
- ], function(_, stream, event) {
856
- debug('Request#%d %s: writeStream or res %s event emitted', reqId, url, event);
857
- done(__err || null, null, res);
858
- });
859
- res.pipe(writeStream);
860
- return;
861
- }
862
-
863
- debug('Request#%d %s: pump res to writeStream', reqId, url);
864
- pump(res, writeStream, function(err) {
865
- debug('Request#%d %s: writeStream close event emitted, error: %s, isWriteStreamClose: %s',
866
- reqId, url, err, isWriteStreamClose);
867
- if (isWriteStreamClose) {
868
- return;
869
- }
870
- isWriteStreamClose = true;
871
- done(__err || err, null, res);
872
- });
873
- return;
874
- }
875
-
876
- // Otherwise, just concat those buffers.
877
- //
878
- // NOTE that the `chunk` is not a String but a Buffer. It means that if
879
- // you simply concat two chunk with `+` you're actually converting both
880
- // Buffers into Strings before concating them. It'll cause problems when
881
- // dealing with multi-byte characters.
882
- //
883
- // The solution is to store each chunk in an array and concat them with
884
- // 'buffer-concat' when all chunks is recieved.
885
- //
886
- // See also:
887
- // http://cnodejs.org/topic/4faf65852e8fb5bc65113403
888
-
889
- var chunks = [];
890
-
891
- res.on('data', function (chunk) {
892
- debug('Request#%d %s: `res data` event emit, size %d', reqId, url, chunk.length);
893
- responseSize += chunk.length;
894
- chunks.push(chunk);
895
- });
896
-
897
- var isEmitted = false;
898
- function handleResponseCloseAndEnd(event) {
899
- debug('Request#%d %s: `res %s` event emit, total size %d, socket handled %s requests and %s responses',
900
- reqId, url, event, responseSize, socketHandledRequests, socketHandledResponses);
901
- if (isEmitted) {
902
- return;
903
- }
904
- isEmitted = true;
905
-
906
- var body = Buffer.concat(chunks, responseSize);
907
- debug('Request#%d %s: _dumped: %s',
908
- reqId, url, res._dumped);
909
-
910
- if (__err) {
911
- // req.abort() after `res data` event emit.
912
- return done(__err, body, res);
913
- }
914
-
915
- var result = handleRedirect(res);
916
- if (result.error) {
917
- return done(result.error, body, res);
918
- }
919
- if (result.redirect) {
920
- createAndEmitResponseEvent(null, res);
921
- return;
922
- }
923
-
924
- decodeContent(res, body, function (err, data, encoding) {
925
- if (err) {
926
- return done(err, body, res);
927
- }
928
- // if body not decode, dont touch it
929
- if (!encoding && TEXT_DATA_TYPES.indexOf(args.dataType) >= 0) {
930
- // try to decode charset
931
- try {
932
- data = decodeBodyByCharset(data, res);
933
- } catch (e) {
934
- debug('decodeBodyByCharset error: %s', e);
935
- // if error, dont touch it
936
- return done(null, data, res);
937
- }
938
-
939
- if (args.dataType === 'json') {
940
- if (responseSize === 0) {
941
- data = null;
942
- } else {
943
- var r = parseJSON(data, fixJSONCtlChars);
944
- if (r.error) {
945
- err = r.error;
946
- } else {
947
- data = r.data;
948
- }
949
- }
950
- }
951
- }
952
-
953
- done(err, data, res);
954
- });
955
- }
956
-
957
- // node >= 14 only emit close if req abort
958
- res.on('close', function () {
959
- handleResponseCloseAndEnd('close');
960
- });
961
- res.on('end', function () {
962
- handleResponseCloseAndEnd('end');
963
- });
964
- }
965
-
966
- var connectTimeout, responseTimeout;
967
- if (Array.isArray(args.timeout)) {
968
- connectTimeout = ms(args.timeout[0]);
969
- responseTimeout = ms(args.timeout[1]);
970
- } else { // set both timeout equal
971
- connectTimeout = responseTimeout = ms(args.timeout);
972
- }
973
- debug('ConnectTimeout: %d, ResponseTimeout: %d', connectTimeout, responseTimeout);
974
-
975
- function startConnectTimer() {
976
- debug('Connect timer ticking, timeout: %d', connectTimeout);
977
- connectTimer = setTimeout(function () {
978
- connectTimer = null;
979
- if (statusCode === -1) {
980
- statusCode = -2;
981
- }
982
- var msg = 'Connect timeout for ' + connectTimeout + 'ms';
983
- var errorName = 'ConnectionTimeoutError';
984
- if (!req.socket) {
985
- errorName = 'SocketAssignTimeoutError';
986
- msg += ', working sockets is full';
987
- }
988
- __err = new Error(msg);
989
- __err.name = errorName;
990
- __err.requestId = reqId;
991
- debug('ConnectTimeout: Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected);
992
- abortRequest();
993
- }, connectTimeout);
994
- }
995
-
996
- function startResposneTimer() {
997
- debug('Response timer ticking, timeout: %d', responseTimeout);
998
- responseTimer = setTimeout(function () {
999
- responseTimer = null;
1000
- var msg = 'Response timeout for ' + responseTimeout + 'ms';
1001
- var errorName = 'ResponseTimeoutError';
1002
- __err = new Error(msg);
1003
- __err.name = errorName;
1004
- __err.requestId = reqId;
1005
- debug('ResponseTimeout: Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected);
1006
- abortRequest();
1007
- }, responseTimeout);
1008
- }
1009
-
1010
- if (args.checkAddress) {
1011
- var hostname = parsedUrl.hostname;
1012
- // if request hostname is ip, custom lookup wont excute
1013
- var family = null;
1014
- if (ip.isV4Format(hostname)) {
1015
- family = 4;
1016
- } else if (ip.isV6Format(hostname)) {
1017
- family = 6;
1018
- }
1019
- if (family) {
1020
- if (!args.checkAddress(hostname, family)) {
1021
- var err = new Error('illegal address');
1022
- err.name = 'IllegalAddressError';
1023
- err.hostname = hostname;
1024
- err.ip = hostname;
1025
- err.family = family;
1026
- return done(err);
1027
- }
1028
- }
1029
- }
1030
-
1031
- // request headers checker will throw error
1032
- try {
1033
- var finalOptions = options;
1034
-
1035
- // restore origin header key
1036
- if (args.keepHeaderCase) {
1037
- var originKeys = Object.keys(originHeaderKeys);
1038
- if (originKeys.length) {
1039
- var finalHeaders = {};
1040
- var names = utility.getOwnEnumerables(options.headers, true);
1041
- for (var i = 0; i < names.length; i++) {
1042
- var name = names[i];
1043
- finalHeaders[originHeaderKeys[name] || name] = options.headers[name];
1044
- }
1045
-
1046
- finalOptions = Object.assign({}, options);
1047
- finalOptions.headers = finalHeaders;
1048
- }
1049
- }
1050
-
1051
- req = httplib.request(finalOptions, onResponse);
1052
- if (args.trace) {
1053
- req._callSite = {};
1054
- Error.captureStackTrace(req._callSite, requestWithCallback);
1055
- }
1056
- } catch (err) {
1057
- return done(err);
1058
- }
1059
-
1060
- // environment detection: browser or nodejs
1061
- if (typeof(window) === 'undefined') {
1062
- // start connect timer just after `request` return, and just in nodejs environment
1063
- startConnectTimer();
1064
- }
1065
-
1066
- var isRequestAborted = false;
1067
- function abortRequest() {
1068
- if (isRequestAborted) {
1069
- return;
1070
- }
1071
- isRequestAborted = true;
1072
-
1073
- debug('Request#%d %s abort, connected: %s', reqId, url, connected);
1074
- // it wont case error event when req haven't been assigned a socket yet.
1075
- if (!req.socket) {
1076
- __err.noSocket = true;
1077
- done(__err);
1078
- }
1079
- req.abort();
1080
- }
1081
-
1082
- if (timing) {
1083
- // request sent
1084
- req.on('finish', function() {
1085
- timing.requestSent = Date.now() - requestStartTime;
1086
- });
1087
- }
1088
-
1089
- req.once('socket', function (socket) {
1090
- if (timing) {
1091
- // socket queuing time
1092
- timing.queuing = Date.now() - requestStartTime;
1093
- }
1094
-
1095
- // https://github.com/nodejs/node/blob/master/lib/net.js#L377
1096
- // https://github.com/nodejs/node/blob/v0.10.40-release/lib/net.js#L352
1097
- // should use socket.socket on 0.10.x
1098
- if (isNode010 && socket.socket) {
1099
- socket = socket.socket;
1100
- }
1101
-
1102
- var orginalSocketTimeout = getSocketTimeout(socket);
1103
- if (orginalSocketTimeout && orginalSocketTimeout < responseTimeout) {
1104
- // make sure socket live longer than the response timer
1105
- var socketTimeout = responseTimeout + 500;
1106
- debug('Request#%d socket.timeout(%s) < responseTimeout(%s), reset socket timeout to %s',
1107
- reqId, orginalSocketTimeout, responseTimeout, socketTimeout);
1108
- socket.setTimeout(socketTimeout);
1109
- }
1110
-
1111
- socketHandledRequests = socket[SOCKET_REQUEST_COUNT] = (socket[SOCKET_REQUEST_COUNT] || 0) + 1;
1112
- if (socket[SOCKET_RESPONSE_COUNT]) {
1113
- socketHandledResponses = socket[SOCKET_RESPONSE_COUNT];
1114
- }
1115
-
1116
- var readyState = socket.readyState;
1117
- if (readyState === 'opening') {
1118
- socket.once('lookup', function(err, ip, addressType) {
1119
- debug('Request#%d %s lookup: %s, %s, %s', reqId, url, err, ip, addressType);
1120
- if (timing) {
1121
- timing.dnslookup = Date.now() - requestStartTime;
1122
- }
1123
- if (ip) {
1124
- remoteAddress = ip;
1125
- }
1126
- });
1127
- socket.once('connect', function() {
1128
- if (timing) {
1129
- // socket connected
1130
- timing.connected = Date.now() - requestStartTime;
1131
- }
1132
-
1133
- // cancel socket timer at first and start tick for TTFB
1134
- cancelConnectTimer();
1135
- startResposneTimer();
1136
-
1137
- debug('Request#%d %s new socket connected', reqId, url);
1138
- connected = true;
1139
- if (!remoteAddress) {
1140
- remoteAddress = socket.remoteAddress;
1141
- }
1142
- remotePort = socket.remotePort;
1143
- });
1144
- return;
1145
- }
1146
-
1147
- debug('Request#%d %s reuse socket connected, readyState: %s', reqId, url, readyState);
1148
- connected = true;
1149
- keepAliveSocket = true;
1150
- if (!remoteAddress) {
1151
- remoteAddress = socket.remoteAddress;
1152
- }
1153
- remotePort = socket.remotePort;
1154
-
1155
- // reuse socket, timer should be canceled.
1156
- cancelConnectTimer();
1157
- startResposneTimer();
1158
- });
1159
-
1160
- if (writeStream) {
1161
- writeStream.once('error', function(err) {
1162
- err.message += ' (writeStream "error")';
1163
- __err = err;
1164
- debug('Request#%d %s `writeStream error` event emit, %s: %s', reqId, url, err.name, err.message);
1165
- abortRequest();
1166
- });
1167
- }
1168
-
1169
- var isRequestDone = false;
1170
- function handleRequestError(err) {
1171
- if (!err) {
1172
- return;
1173
- }
1174
- // only ignore request error if response has been received
1175
- // if response has not received, socket error will emit on req
1176
- if (isRequestDone && hasResponse) {
1177
- return;
1178
- }
1179
- isRequestDone = true;
1180
-
1181
- if (err.name === 'Error') {
1182
- err.name = connected ? 'ResponseError' : 'RequestError';
1183
- }
1184
- debug('Request#%d %s `req error` event emit, %s: %s', reqId, url, err.name, err.message);
1185
- done(__err || err);
1186
- }
1187
- if (args.stream) {
1188
- debug('Request#%d pump args.stream to req', reqId);
1189
- pump(args.stream, req, handleRequestError);
1190
- } else {
1191
- req.end(body, function () {
1192
- isRequestDone = true;
1193
- });
1194
- }
1195
- // when stream already consumed, req's `finish` event is emitted and pump will ignore error after pipe finished
1196
- // but if server response timeout later, we will abort the request and emit an error in req
1197
- // so we must always manually listen to req's `error` event here to ensure this error is handled
1198
- req.on('error', handleRequestError);
1199
- req.requestId = reqId;
1200
- return req;
1201
- }
1202
-
1203
- exports.requestWithCallback = requestWithCallback;
1204
-
1205
- var JSONCtlCharsMap = {
1206
- '"': '\\"', // \u0022
1207
- '\\': '\\\\', // \u005c
1208
- '\b': '\\b', // \u0008
1209
- '\f': '\\f', // \u000c
1210
- '\n': '\\n', // \u000a
1211
- '\r': '\\r', // \u000d
1212
- '\t': '\\t' // \u0009
1213
- };
1214
- var JSONCtlCharsRE = /[\u0000-\u001F\u005C]/g;
1215
-
1216
- function _replaceOneChar(c) {
1217
- return JSONCtlCharsMap[c] || '\\u' + (c.charCodeAt(0) + 0x10000).toString(16).substr(1);
1218
- }
1219
-
1220
- function replaceJSONCtlChars(str) {
1221
- return str.replace(JSONCtlCharsRE, _replaceOneChar);
1222
- }
1223
-
1224
- function parseJSON(data, fixJSONCtlChars) {
1225
- var result = {
1226
- error: null,
1227
- data: null
1228
- };
1229
- if (fixJSONCtlChars) {
1230
- if (typeof fixJSONCtlChars === 'function') {
1231
- data = fixJSONCtlChars(data);
1232
- } else {
1233
- // https://github.com/node-modules/urllib/pull/77
1234
- // remote the control characters (U+0000 through U+001F)
1235
- data = replaceJSONCtlChars(data);
1236
- }
1237
- }
1238
- try {
1239
- result.data = JSON.parse(data);
1240
- } catch (err) {
1241
- if (err.name === 'SyntaxError') {
1242
- err.name = 'JSONResponseFormatError';
1243
- }
1244
- if (data.length > 1024) {
1245
- // show 0~512 ... -512~end data
1246
- err.message += ' (data json format: ' +
1247
- JSON.stringify(data.slice(0, 512)) + ' ...skip... ' + JSON.stringify(data.slice(data.length - 512)) + ')';
1248
- } else {
1249
- err.message += ' (data json format: ' + JSON.stringify(data) + ')';
1250
- }
1251
- result.error = err;
1252
- }
1253
- return result;
1254
- }
1255
-
1256
-
1257
- /**
1258
- * decode response body by parse `content-type`'s charset
1259
- * @param {Buffer} data
1260
- * @param {Http(s)Response} res
1261
- * @return {String}
1262
- */
1263
- function decodeBodyByCharset(data, res) {
1264
- var type = res.headers['content-type'];
1265
- if (!type) {
1266
- return data.toString();
1267
- }
1268
-
1269
- var type = parseContentType(type);
1270
- var charset = type.parameters.charset || 'utf-8';
1271
-
1272
- if (!Buffer.isEncoding(charset)) {
1273
- if (!_iconv) {
1274
- _iconv = require('iconv-lite');
1275
- }
1276
- return _iconv.decode(data, charset);
1277
- }
1278
-
1279
- return data.toString(charset);
1280
- }
1281
-
1282
- function getAgent(agent, defaultAgent) {
1283
- return agent === undefined ? defaultAgent : agent;
1284
- }
1285
-
1286
- function parseContentType(str) {
1287
- try {
1288
- return contentTypeParser.parse(str);
1289
- } catch (err) {
1290
- // ignore content-type error, tread as default
1291
- return { parameters: {} };
1292
- }
1293
- }
1294
-
1295
- function addLongStackTrace(err, req) {
1296
- if (!req) {
1297
- return;
1298
- }
1299
- var callSiteStack = req._callSite && req._callSite.stack;
1300
- if (!callSiteStack || typeof callSiteStack !== 'string') {
1301
- return;
1302
- }
1303
- if (err._longStack) {
1304
- return;
1305
- }
1306
- var index = callSiteStack.indexOf('\n');
1307
- if (index !== -1) {
1308
- err._longStack = true;
1309
- err.stack += LONG_STACK_DELIMITER + callSiteStack.substr(index + 1);
1310
- }
1311
- }
1312
-
1313
- // node 8 don't has timeout attribute on socket
1314
- // https://github.com/nodejs/node/pull/21204/files#diff-e6ef024c3775d787c38487a6309e491dR408
1315
- function getSocketTimeout(socket) {
1316
- return socket.timeout || socket._idleTimeout;
1317
- }