@datanimbus/postman-request 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/LICENSE +55 -0
- package/README.md +1315 -0
- package/index.js +166 -0
- package/lib/auth.js +167 -0
- package/lib/autohttp/agent.js +206 -0
- package/lib/autohttp/headerValidations.js +40 -0
- package/lib/autohttp/index.js +8 -0
- package/lib/autohttp/request.js +192 -0
- package/lib/autohttp/requestName.js +86 -0
- package/lib/cookies.js +20 -0
- package/lib/getProxyFromURI.js +79 -0
- package/lib/har.js +200 -0
- package/lib/hawk.js +89 -0
- package/lib/helpers.js +91 -0
- package/lib/http2/agent.js +92 -0
- package/lib/http2/index.js +8 -0
- package/lib/http2/request.js +369 -0
- package/lib/inflate.js +64 -0
- package/lib/multipart.js +112 -0
- package/lib/oauth.js +147 -0
- package/lib/querystring.js +50 -0
- package/lib/redirect.js +218 -0
- package/lib/socks.js +45 -0
- package/lib/tunnel.js +175 -0
- package/lib/url-parse.js +187 -0
- package/package.json +77 -0
- package/request.js +2098 -0
package/request.js
ADDED
|
@@ -0,0 +1,2098 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var tls = require('tls')
|
|
4
|
+
var http = require('http')
|
|
5
|
+
var https = require('https')
|
|
6
|
+
var fsPromise = require('fs/promises')
|
|
7
|
+
var events = require('events')
|
|
8
|
+
var http2 = require('./lib/http2')
|
|
9
|
+
var autohttp2 = require('./lib/autohttp')
|
|
10
|
+
var url = require('url')
|
|
11
|
+
var util = require('util')
|
|
12
|
+
var stream = require('stream')
|
|
13
|
+
var zlib = require('zlib')
|
|
14
|
+
var aws2 = require('aws-sign2')
|
|
15
|
+
var aws4 = require('aws4')
|
|
16
|
+
var uuid = require('uuid').v4
|
|
17
|
+
var httpSignature = require('http-signature')
|
|
18
|
+
var mime = require('mime-types')
|
|
19
|
+
var caseless = require('caseless')
|
|
20
|
+
var ForeverAgent = require('forever-agent')
|
|
21
|
+
var FormData = require('@postman/form-data')
|
|
22
|
+
var extend = require('extend')
|
|
23
|
+
var isstream = require('isstream')
|
|
24
|
+
var streamLength = require('stream-length')
|
|
25
|
+
var isTypedArray = require('is-typedarray').strict
|
|
26
|
+
var helpers = require('./lib/helpers')
|
|
27
|
+
var cookies = require('./lib/cookies')
|
|
28
|
+
var getProxyFromURI = require('./lib/getProxyFromURI')
|
|
29
|
+
var Querystring = require('./lib/querystring').Querystring
|
|
30
|
+
var Har = require('./lib/har').Har
|
|
31
|
+
var Auth = require('./lib/auth').Auth
|
|
32
|
+
var OAuth = require('./lib/oauth').OAuth
|
|
33
|
+
var hawk = require('./lib/hawk')
|
|
34
|
+
var Multipart = require('./lib/multipart').Multipart
|
|
35
|
+
var Redirect = require('./lib/redirect').Redirect
|
|
36
|
+
var Tunnel = require('./lib/tunnel').Tunnel
|
|
37
|
+
var SocksProxy = require('./lib/socks').SocksProxy
|
|
38
|
+
var Buffer = require('safe-buffer').Buffer
|
|
39
|
+
var inflate = require('./lib/inflate')
|
|
40
|
+
var urlParse = require('./lib/url-parse')
|
|
41
|
+
var safeStringify = helpers.safeStringify
|
|
42
|
+
var isReadStream = helpers.isReadStream
|
|
43
|
+
var toBase64 = helpers.toBase64
|
|
44
|
+
var defer = helpers.defer
|
|
45
|
+
var copy = helpers.copy
|
|
46
|
+
var version = helpers.version
|
|
47
|
+
var now = helpers.now
|
|
48
|
+
var SizeTrackerStream = helpers.SizeTrackerStream
|
|
49
|
+
var globalCookieJar = cookies.jar()
|
|
50
|
+
|
|
51
|
+
var globalPool = {}
|
|
52
|
+
|
|
53
|
+
function filterForNonReserved (reserved, options) {
|
|
54
|
+
// Filter out properties that are not reserved.
|
|
55
|
+
// Reserved values are passed in at call site.
|
|
56
|
+
|
|
57
|
+
var object = {}
|
|
58
|
+
for (var i in options) {
|
|
59
|
+
var notReserved = (reserved.indexOf(i) === -1)
|
|
60
|
+
if (notReserved) {
|
|
61
|
+
object[i] = options[i]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return object
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function filterOutReservedFunctions (reserved, options) {
|
|
68
|
+
// Filter out properties that are functions and are reserved.
|
|
69
|
+
// Reserved values are passed in at call site.
|
|
70
|
+
|
|
71
|
+
var object = {}
|
|
72
|
+
for (var i in options) {
|
|
73
|
+
var isReserved = !(reserved.indexOf(i) === -1)
|
|
74
|
+
var isFunction = (typeof options[i] === 'function')
|
|
75
|
+
if (!(isReserved && isFunction)) {
|
|
76
|
+
object[i] = options[i]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return object
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function transformFormData (formData) {
|
|
83
|
+
// Transform the object representation of form-data fields to array representation.
|
|
84
|
+
// This might not preserve the order of form fields defined in object representation.
|
|
85
|
+
// But, this transformation is required to support backward compatibility.
|
|
86
|
+
//
|
|
87
|
+
// Form-Data should be stored as an array to respect the fields order.
|
|
88
|
+
// RFC 7578#section-5.2 Ordered Fields and Duplicated Field Names
|
|
89
|
+
// https://tools.ietf.org/html/rfc7578#section-5.2
|
|
90
|
+
|
|
91
|
+
var transformedFormData = []
|
|
92
|
+
var appendFormParam = function (key, param) {
|
|
93
|
+
transformedFormData.push({
|
|
94
|
+
key: key,
|
|
95
|
+
value: param && param.hasOwnProperty('value') ? param.value : param,
|
|
96
|
+
options: param && param.hasOwnProperty('options') ? param.options : undefined
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
for (var formKey in formData) {
|
|
100
|
+
if (formData.hasOwnProperty(formKey)) {
|
|
101
|
+
var formValue = formData[formKey]
|
|
102
|
+
if (Array.isArray(formValue)) {
|
|
103
|
+
for (var j = 0; j < formValue.length; j++) {
|
|
104
|
+
appendFormParam(formKey, formValue[j])
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
appendFormParam(formKey, formValue)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return transformedFormData
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Return a simpler request object to allow serialization
|
|
115
|
+
function requestToJSON () {
|
|
116
|
+
var self = this
|
|
117
|
+
return {
|
|
118
|
+
uri: self.uri,
|
|
119
|
+
method: self.method,
|
|
120
|
+
headers: self.headers
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Return a simpler response object to allow serialization
|
|
125
|
+
function responseToJSON () {
|
|
126
|
+
var self = this
|
|
127
|
+
return {
|
|
128
|
+
statusCode: self.statusCode,
|
|
129
|
+
body: self.body,
|
|
130
|
+
headers: self.headers,
|
|
131
|
+
request: requestToJSON.call(self.request)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Return request headers in [{key: headerName, value: headerValue}] form
|
|
137
|
+
* @param {String} [headerString] - headers string created by Node stored in ClientRequest._header
|
|
138
|
+
*
|
|
139
|
+
* */
|
|
140
|
+
function parseRequestHeaders (headerString) {
|
|
141
|
+
var arr = headerString.split('\r\n')
|
|
142
|
+
var acc = []
|
|
143
|
+
|
|
144
|
+
// first element of accumulator is not a header
|
|
145
|
+
// last two elements are empty strings
|
|
146
|
+
for (var i = 1; i < arr.length - 2; i++) {
|
|
147
|
+
// HTTP/2 specific headers beging with :, so we find the index of the first colon skipping the first character
|
|
148
|
+
var splitIndex = arr[i].indexOf(':', 1)
|
|
149
|
+
|
|
150
|
+
acc.push({
|
|
151
|
+
key: arr[i].slice(0, splitIndex),
|
|
152
|
+
value: arr[i].slice(splitIndex + 2)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return acc
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Return response headers in [{key: headerName, value: headerValue}] form
|
|
161
|
+
* @param {Array} [rawHeaders] - https://nodejs.org/api/http.html#http_message_rawheaders
|
|
162
|
+
*
|
|
163
|
+
* */
|
|
164
|
+
function parseResponseHeaders (rawHeaders) {
|
|
165
|
+
var acc = []
|
|
166
|
+
|
|
167
|
+
for (var i = 0; i < rawHeaders.length; i = i + 2) {
|
|
168
|
+
acc.push({
|
|
169
|
+
key: rawHeaders[i],
|
|
170
|
+
value: rawHeaders[i + 1]
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return acc
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function Request (options) {
|
|
178
|
+
// if given the method property in options, set property explicitMethod to true
|
|
179
|
+
|
|
180
|
+
// extend the Request instance with any non-reserved properties
|
|
181
|
+
// remove any reserved functions from the options object
|
|
182
|
+
// set Request instance to be readable and writable
|
|
183
|
+
// call init
|
|
184
|
+
|
|
185
|
+
var self = this
|
|
186
|
+
|
|
187
|
+
// start with HAR, then override with additional options
|
|
188
|
+
if (options.har) {
|
|
189
|
+
self._har = new Har(self)
|
|
190
|
+
options = self._har.options(options)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// transform `formData` for backward compatibility
|
|
194
|
+
// don't check for explicit object type to support legacy shenanigans
|
|
195
|
+
if (options.formData && !Array.isArray(options.formData)) {
|
|
196
|
+
options.formData = transformFormData(options.formData)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// use custom URL parser if provided, fallback to url.parse and url.resolve
|
|
200
|
+
if (!(
|
|
201
|
+
options.urlParser &&
|
|
202
|
+
typeof options.urlParser.parse === 'function' &&
|
|
203
|
+
typeof options.urlParser.resolve === 'function'
|
|
204
|
+
)) {
|
|
205
|
+
options.urlParser = {
|
|
206
|
+
parse: url.parse,
|
|
207
|
+
resolve: url.resolve
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
stream.Stream.call(self)
|
|
212
|
+
var reserved = Object.keys(Request.prototype)
|
|
213
|
+
var nonReserved = filterForNonReserved(reserved, options)
|
|
214
|
+
|
|
215
|
+
extend(self, nonReserved)
|
|
216
|
+
options = filterOutReservedFunctions(reserved, options)
|
|
217
|
+
|
|
218
|
+
self.readable = true
|
|
219
|
+
self.writable = true
|
|
220
|
+
self._debug = []
|
|
221
|
+
if (options.method) {
|
|
222
|
+
self.explicitMethod = true
|
|
223
|
+
}
|
|
224
|
+
self._qs = new Querystring(self)
|
|
225
|
+
self._auth = new Auth(self)
|
|
226
|
+
self._oauth = new OAuth(self)
|
|
227
|
+
self._multipart = new Multipart(self)
|
|
228
|
+
self._redirect = new Redirect(self)
|
|
229
|
+
self._tunnel = new Tunnel(self)
|
|
230
|
+
self._socks = new SocksProxy(self)
|
|
231
|
+
self.init(options)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
util.inherits(Request, stream.Stream)
|
|
235
|
+
|
|
236
|
+
// Debugging
|
|
237
|
+
Request.debug = process.env.NODE_DEBUG && /\brequest\b/.test(process.env.NODE_DEBUG)
|
|
238
|
+
|
|
239
|
+
function debug () {
|
|
240
|
+
if (Request.debug) {
|
|
241
|
+
console.error('REQUEST %s', util.format.apply(util, arguments))
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Request.prototype.debug = debug
|
|
246
|
+
|
|
247
|
+
Request.prototype.init = function (options) {
|
|
248
|
+
// init() contains all the code to setup the request object.
|
|
249
|
+
// the actual outgoing request is not started until start() is called
|
|
250
|
+
// this function is called from both the constructor and on redirect.
|
|
251
|
+
var self = this
|
|
252
|
+
if (!options) {
|
|
253
|
+
options = {}
|
|
254
|
+
}
|
|
255
|
+
self.headers = self.headers ? copy(self.headers) : {}
|
|
256
|
+
|
|
257
|
+
// for this request (or redirect) store its debug logs in `_reqResInfo` and
|
|
258
|
+
// store its reference in `_debug` which holds debug logs of every request
|
|
259
|
+
self._reqResInfo = {}
|
|
260
|
+
self._debug.push(self._reqResInfo)
|
|
261
|
+
|
|
262
|
+
// additional postman feature starts
|
|
263
|
+
// bind default events sent via options
|
|
264
|
+
if (options.bindOn) {
|
|
265
|
+
Object.keys(options.bindOn).forEach(function (eventName) {
|
|
266
|
+
!Array.isArray(options.bindOn[eventName]) && (options.bindOn[eventName] = [options.bindOn[eventName]])
|
|
267
|
+
options.bindOn[eventName].forEach(function (listener) {
|
|
268
|
+
self.on(eventName, listener)
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
if (options.once) {
|
|
273
|
+
Object.keys(options.once).forEach(function (eventName) {
|
|
274
|
+
!Array.isArray(options.bindOnce[eventName]) && (options.bindOnce[eventName] = [options.bindOnce[eventName]])
|
|
275
|
+
options.bindOnce[eventName].forEach(function (listener) {
|
|
276
|
+
self.once(eventName, listener)
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
// additional postman feature ends
|
|
281
|
+
|
|
282
|
+
// Delete headers with value undefined or HTTP/2 specific pseudoheaders since they break
|
|
283
|
+
// ClientRequest.OutgoingMessage.setHeader in node 0.12
|
|
284
|
+
for (var headerName in self.headers) {
|
|
285
|
+
if (typeof self.headers[headerName] === 'undefined' || headerName.startsWith(':')) {
|
|
286
|
+
delete self.headers[headerName]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
caseless.httpify(self, self.headers)
|
|
291
|
+
|
|
292
|
+
if (!self.method) {
|
|
293
|
+
self.method = options.method || 'GET'
|
|
294
|
+
}
|
|
295
|
+
if (!self.localAddress) {
|
|
296
|
+
self.localAddress = options.localAddress
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
self._qs.init(options)
|
|
300
|
+
|
|
301
|
+
debug(options)
|
|
302
|
+
if (!self.pool && self.pool !== false) {
|
|
303
|
+
self.pool = globalPool
|
|
304
|
+
}
|
|
305
|
+
self.dests = self.dests || []
|
|
306
|
+
self.__isRequestRequest = true
|
|
307
|
+
|
|
308
|
+
// Protect against double callback
|
|
309
|
+
if (!self._callback && self.callback) {
|
|
310
|
+
self._callback = self.callback
|
|
311
|
+
self.callback = function (error, response, body) {
|
|
312
|
+
if (self._callbackCalled) {
|
|
313
|
+
return // Print a warning maybe?
|
|
314
|
+
}
|
|
315
|
+
self._callbackCalled = true
|
|
316
|
+
self._callback(error, response, body, self._debug)
|
|
317
|
+
}
|
|
318
|
+
self.on('error', self.callback.bind())
|
|
319
|
+
self.on('complete', self.callback.bind(self, null))
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// People use this property instead all the time, so support it
|
|
323
|
+
if (!self.uri && self.url) {
|
|
324
|
+
self.uri = self.url
|
|
325
|
+
delete self.url
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// If there's a baseUrl, then use it as the base URL (i.e. uri must be
|
|
329
|
+
// specified as a relative path and is appended to baseUrl).
|
|
330
|
+
if (self.baseUrl) {
|
|
331
|
+
if (typeof self.baseUrl !== 'string') {
|
|
332
|
+
return self.emit('error', new Error('options.baseUrl must be a string'))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (typeof self.uri !== 'string') {
|
|
336
|
+
return self.emit('error', new Error('options.uri must be a string when using options.baseUrl'))
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (self.uri.indexOf('//') === 0 || self.uri.indexOf('://') !== -1) {
|
|
340
|
+
return self.emit('error', new Error('options.uri must be a path when using options.baseUrl'))
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Handle all cases to make sure that there's only one slash between
|
|
344
|
+
// baseUrl and uri.
|
|
345
|
+
var baseUrlEndsWithSlash = self.baseUrl.lastIndexOf('/') === self.baseUrl.length - 1
|
|
346
|
+
var uriStartsWithSlash = self.uri.indexOf('/') === 0
|
|
347
|
+
|
|
348
|
+
if (baseUrlEndsWithSlash && uriStartsWithSlash) {
|
|
349
|
+
self.uri = self.baseUrl + self.uri.slice(1)
|
|
350
|
+
} else if (baseUrlEndsWithSlash || uriStartsWithSlash) {
|
|
351
|
+
self.uri = self.baseUrl + self.uri
|
|
352
|
+
} else if (self.uri === '') {
|
|
353
|
+
self.uri = self.baseUrl
|
|
354
|
+
} else {
|
|
355
|
+
self.uri = self.baseUrl + '/' + self.uri
|
|
356
|
+
}
|
|
357
|
+
delete self.baseUrl
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// A URI is needed by this point, emit error if we haven't been able to get one
|
|
361
|
+
if (!self.uri) {
|
|
362
|
+
return self.emit('error', new Error('options.uri is a required argument'))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// If a string URI/URL was given, parse it into a URL object
|
|
366
|
+
if (typeof self.uri === 'string') {
|
|
367
|
+
self.uri = self.urlParser.parse(self.uri)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Some URL objects are not from a URL parsed string and need href added
|
|
371
|
+
if (!self.uri.href) {
|
|
372
|
+
self.uri.href = url.format(self.uri)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// DEPRECATED: Warning for users of the old Unix Sockets URL Scheme
|
|
376
|
+
if (self.uri.protocol === 'unix:') {
|
|
377
|
+
return self.emit('error', new Error('`unix://` URL scheme is no longer supported. Please use the format `http://unix:SOCKET:PATH`'))
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Support Unix Sockets
|
|
381
|
+
if (self.uri.host === 'unix' || self.uri.host === 'unix:') {
|
|
382
|
+
self.enableUnixSocket()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (self.strictSSL === false) {
|
|
386
|
+
self.rejectUnauthorized = false
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!self.uri.pathname) { self.uri.pathname = '/' }
|
|
390
|
+
|
|
391
|
+
if (!(self.uri.host || (self.uri.hostname && self.uri.port)) && !self.uri.isUnix) {
|
|
392
|
+
// Invalid URI: it may generate lot of bad errors, like 'TypeError: Cannot call method `indexOf` of undefined' in CookieJar
|
|
393
|
+
// Detect and reject it as soon as possible
|
|
394
|
+
var faultyUri = url.format(self.uri)
|
|
395
|
+
var message = 'Invalid URI "' + faultyUri + '"'
|
|
396
|
+
if (Object.keys(options).length === 0) {
|
|
397
|
+
// No option ? This can be the sign of a redirect
|
|
398
|
+
// As this is a case where the user cannot do anything (they didn't call request directly with this URL)
|
|
399
|
+
// they should be warned that it can be caused by a redirection (can save some hair)
|
|
400
|
+
message += '. This can be caused by a crappy redirection.'
|
|
401
|
+
}
|
|
402
|
+
// This error was fatal
|
|
403
|
+
self.abort()
|
|
404
|
+
return self.emit('error', new Error(message))
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!self.hasOwnProperty('proxy')) {
|
|
408
|
+
self.proxy = getProxyFromURI(self.uri)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (typeof self.proxy === 'string') {
|
|
412
|
+
self.proxy = self.urlParser.parse(self.proxy)
|
|
413
|
+
|
|
414
|
+
if (self.proxy.auth) {
|
|
415
|
+
self.proxy.auth = self._qs.unescape(self.proxy.auth)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
self.tunnel = self._tunnel.isEnabled()
|
|
420
|
+
self.socks = self._socks.isEnabled()
|
|
421
|
+
|
|
422
|
+
if (self.proxy) {
|
|
423
|
+
if (self.socks) {
|
|
424
|
+
self._socks.setup()
|
|
425
|
+
} else {
|
|
426
|
+
self._tunnel.setup(options)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
self._redirect.onRequest(options)
|
|
431
|
+
|
|
432
|
+
// Add `Host` header if not defined already
|
|
433
|
+
self.setHost = (self.setHost === undefined || Boolean(self.setHost))
|
|
434
|
+
if (!self.hasHeader('host') && self.setHost) {
|
|
435
|
+
var hostHeaderName = self.originalHostHeaderName || 'Host'
|
|
436
|
+
self.setHeader(hostHeaderName, self.uri.host)
|
|
437
|
+
// Drop :port suffix from Host header if known protocol.
|
|
438
|
+
if (self.uri.port) {
|
|
439
|
+
if ((self.uri.port === '80' && self.uri.protocol === 'http:') ||
|
|
440
|
+
(self.uri.port === '443' && self.uri.protocol === 'https:')) {
|
|
441
|
+
self.setHeader(hostHeaderName, self.uri.hostname)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (!self.uri.port) {
|
|
447
|
+
if (self.uri.protocol === 'http:') { self.uri.port = 80 } else if (self.uri.protocol === 'https:') { self.uri.port = 443 }
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (self.proxy && !self.tunnel && !self.socks) {
|
|
451
|
+
self.port = self.proxy.port
|
|
452
|
+
self.host = self.proxy.hostname
|
|
453
|
+
} else {
|
|
454
|
+
self.port = self.uri.port
|
|
455
|
+
self.host = self.uri.hostname
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (options.form) {
|
|
459
|
+
self.form(options.form)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (options.formData) {
|
|
463
|
+
var formData = options.formData
|
|
464
|
+
var requestForm = self.form()
|
|
465
|
+
for (var i = 0, ii = formData.length; i < ii; i++) {
|
|
466
|
+
var formParam = formData[i]
|
|
467
|
+
if (!formParam) { continue }
|
|
468
|
+
if (formParam.options) {
|
|
469
|
+
requestForm.append(formParam.key, formParam.value, formParam.options)
|
|
470
|
+
} else {
|
|
471
|
+
requestForm.append(formParam.key, formParam.value)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (options.qs) {
|
|
477
|
+
self.qs(options.qs)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (self.uri.path) {
|
|
481
|
+
self.path = self.uri.path
|
|
482
|
+
} else {
|
|
483
|
+
self.path = self.uri.pathname + (self.uri.search || '')
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (self.path.length === 0) {
|
|
487
|
+
self.path = '/'
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Auth must happen last in case signing is dependent on other headers
|
|
491
|
+
if (options.aws) {
|
|
492
|
+
self.aws(options.aws)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (options.hawk) {
|
|
496
|
+
self.hawk(options.hawk)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (options.httpSignature) {
|
|
500
|
+
self.httpSignature(options.httpSignature)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (options.auth) {
|
|
504
|
+
if (Object.prototype.hasOwnProperty.call(options.auth, 'username')) {
|
|
505
|
+
options.auth.user = options.auth.username
|
|
506
|
+
}
|
|
507
|
+
if (Object.prototype.hasOwnProperty.call(options.auth, 'password')) {
|
|
508
|
+
options.auth.pass = options.auth.password
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
self.auth(
|
|
512
|
+
options.auth.user,
|
|
513
|
+
options.auth.pass,
|
|
514
|
+
options.auth.sendImmediately,
|
|
515
|
+
options.auth.bearer
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (!self.hasHeader('accept-encoding')) {
|
|
520
|
+
var acceptEncoding = ''
|
|
521
|
+
|
|
522
|
+
self.gzip && (acceptEncoding += 'gzip, deflate')
|
|
523
|
+
|
|
524
|
+
if (self.brotli) {
|
|
525
|
+
acceptEncoding && (acceptEncoding += ', ')
|
|
526
|
+
acceptEncoding += 'br'
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
acceptEncoding && self.setHeader('Accept-Encoding', acceptEncoding)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (self.uri.auth && !self.hasHeader('authorization')) {
|
|
533
|
+
var uriAuthPieces = self.uri.auth.split(':').map(function (item) { return self._qs.unescape(item) })
|
|
534
|
+
self.auth(uriAuthPieces[0], uriAuthPieces.slice(1).join(':'), true)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (!self.tunnel && !self.socks && self.proxy && self.proxy.auth && !self.hasHeader('proxy-authorization')) {
|
|
538
|
+
self.setHeader('Proxy-Authorization', 'Basic ' + toBase64(self.proxy.auth))
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (self.proxy && !self.tunnel && !self.socks) {
|
|
542
|
+
self.path = (self.uri.protocol + '//' + self.uri.host + self.path)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (options.json) {
|
|
546
|
+
self.json(options.json)
|
|
547
|
+
}
|
|
548
|
+
if (options.multipart) {
|
|
549
|
+
self.multipart(options.multipart)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// enable timings if verbose is true
|
|
553
|
+
if (options.time || options.verbose) {
|
|
554
|
+
self.timing = true
|
|
555
|
+
|
|
556
|
+
// NOTE: elapsedTime is deprecated in favor of .timings
|
|
557
|
+
self.elapsedTime = self.elapsedTime || 0
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (options.verbose) {
|
|
561
|
+
self.verbose = true
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (typeof options.maxResponseSize === 'number') {
|
|
565
|
+
self.maxResponseSize = options.maxResponseSize
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function setContentLength () {
|
|
569
|
+
if (isTypedArray(self.body)) {
|
|
570
|
+
self.body = Buffer.from(self.body)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (!self.hasHeader('content-length')) {
|
|
574
|
+
var length
|
|
575
|
+
if (typeof self.body === 'string') {
|
|
576
|
+
length = Buffer.byteLength(self.body)
|
|
577
|
+
} else if (Array.isArray(self.body)) {
|
|
578
|
+
length = self.body.reduce(function (a, b) { return a + b.length }, 0)
|
|
579
|
+
} else {
|
|
580
|
+
length = self.body.length
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (length) {
|
|
584
|
+
self.setHeader('Content-Length', length)
|
|
585
|
+
} else {
|
|
586
|
+
self.emit('error', new Error('Argument error, options.body.'))
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (self.body && !isstream(self.body)) {
|
|
592
|
+
setContentLength()
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (options.oauth) {
|
|
596
|
+
self.oauth(options.oauth)
|
|
597
|
+
} else if (self._oauth.params && self.hasHeader('authorization')) {
|
|
598
|
+
self.oauth(self._oauth.params)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
var protocol = (self.proxy && !self.tunnel && !self.socks) ? self.proxy.protocol : self.uri.protocol
|
|
602
|
+
var defaultModules = {'http:': { http2: http, http1: http, auto: http }, 'https:': { http1: https, http2: http2, auto: autohttp2 }}
|
|
603
|
+
var httpModules = self.httpModules || {}
|
|
604
|
+
|
|
605
|
+
// If user defines httpModules, respect if they have different httpModules for different http versions, else use the tls specific http module
|
|
606
|
+
// If the user defines nothing, revert to default modules
|
|
607
|
+
self.httpModule = (httpModules[protocol] && httpModules[protocol][self.protocolVersion]) || httpModules[protocol] || (defaultModules[protocol] && defaultModules[protocol][self.protocolVersion])
|
|
608
|
+
|
|
609
|
+
if (httpModules[protocol] && !(httpModules[protocol][options.protocolVersion])) {
|
|
610
|
+
// If the user is only specifying https/http modules, revert to http1
|
|
611
|
+
self.protocolVersion = 'http1'
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (!self.httpModule) {
|
|
615
|
+
return self.emit('error', new Error('Invalid protocol: ' + protocol))
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (options.ca) {
|
|
619
|
+
self.ca = options.ca
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// prefer common self.agent if exists
|
|
623
|
+
if (self.agents && !self.agent) {
|
|
624
|
+
var agent = protocol === 'http:' ? self.agents.http : self.agents.https
|
|
625
|
+
if (agent) {
|
|
626
|
+
if (agent.agentClass || agent.agentOptions) {
|
|
627
|
+
options.agentClass = agent.agentClass || options.agentClass
|
|
628
|
+
options.agentOptions = agent.agentOptions || options.agentOptions
|
|
629
|
+
} else {
|
|
630
|
+
self.agent = agent
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (!self.agent) {
|
|
636
|
+
if (options.agentOptions) {
|
|
637
|
+
self.agentOptions = options.agentOptions
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (options.agentClass) {
|
|
641
|
+
self.agentClass = options.agentClass
|
|
642
|
+
} else if (options.forever) {
|
|
643
|
+
var v = version()
|
|
644
|
+
// use ForeverAgent in node 0.10- only
|
|
645
|
+
if (v.major === 0 && v.minor <= 10) {
|
|
646
|
+
self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL
|
|
647
|
+
} else {
|
|
648
|
+
self.agentClass = self.httpModule.Agent
|
|
649
|
+
self.agentOptions = self.agentOptions || {}
|
|
650
|
+
self.agentOptions.keepAlive = true
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
self.agentClass = self.httpModule.Agent
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (self.pool === false) {
|
|
658
|
+
self.agent = false
|
|
659
|
+
} else {
|
|
660
|
+
try {
|
|
661
|
+
self.agent = self.agent || self.getNewAgent({agentIdleTimeout: options.agentIdleTimeout})
|
|
662
|
+
} catch (error) {
|
|
663
|
+
// tls.createSecureContext() throws on bad options
|
|
664
|
+
return self.emit('error', error)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
self.on('pipe', function (src) {
|
|
669
|
+
if (self.ntick && self._started) {
|
|
670
|
+
self.emit('error', new Error('You cannot pipe to this stream after the outbound request has started.'))
|
|
671
|
+
}
|
|
672
|
+
self.src = src
|
|
673
|
+
if (isReadStream(src)) {
|
|
674
|
+
if (!self.hasHeader('content-type')) {
|
|
675
|
+
// @note fallback to 'application/octet-stream' if mime.lookup returns `false`
|
|
676
|
+
self.setHeader('Content-Type', mime.lookup(src.path) || 'application/octet-stream')
|
|
677
|
+
}
|
|
678
|
+
} else {
|
|
679
|
+
if (src.headers) {
|
|
680
|
+
for (var i in src.headers) {
|
|
681
|
+
if (!self.hasHeader(i)) {
|
|
682
|
+
self.setHeader(i, src.headers[i])
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (self._json && !self.hasHeader('content-type')) {
|
|
687
|
+
self.setHeader('Content-Type', 'application/json')
|
|
688
|
+
}
|
|
689
|
+
if (src.method && !self.explicitMethod) {
|
|
690
|
+
self.method = src.method
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// self.on('pipe', function () {
|
|
695
|
+
// console.error('You have already piped to this stream. Pipeing twice is likely to break the request.')
|
|
696
|
+
// })
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
defer(function () {
|
|
700
|
+
if (self._aborted) {
|
|
701
|
+
return
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
var end = function () {
|
|
705
|
+
if (self._form) {
|
|
706
|
+
if (!self._auth.hasAuth || (self._auth.hasAuth && self._auth.sentAuth)) {
|
|
707
|
+
try {
|
|
708
|
+
self._form.pipe(self)
|
|
709
|
+
} catch (err) {
|
|
710
|
+
self.abort()
|
|
711
|
+
options.callback && options.callback(err)
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (self._multipart && self._multipart.chunked) {
|
|
717
|
+
self._multipart.body.pipe(self)
|
|
718
|
+
}
|
|
719
|
+
if (self.body) {
|
|
720
|
+
if (isstream(self.body)) {
|
|
721
|
+
if (self.hasHeader('content-length')) {
|
|
722
|
+
self.body.pipe(self)
|
|
723
|
+
} else { // certain servers require content-length to function. we try to pre-detect if possible
|
|
724
|
+
streamLength(self.body, {}, function (err, len) {
|
|
725
|
+
if (!(err || self._started || self.hasHeader('content-length') || len === null || len < 0)) {
|
|
726
|
+
self.setHeader('Content-Length', len)
|
|
727
|
+
}
|
|
728
|
+
self.body.pipe(self)
|
|
729
|
+
})
|
|
730
|
+
}
|
|
731
|
+
} else {
|
|
732
|
+
setContentLength()
|
|
733
|
+
if (Array.isArray(self.body)) {
|
|
734
|
+
self.body.forEach(function (part) {
|
|
735
|
+
self.write(part)
|
|
736
|
+
})
|
|
737
|
+
} else {
|
|
738
|
+
self.write(self.body)
|
|
739
|
+
}
|
|
740
|
+
self.end()
|
|
741
|
+
}
|
|
742
|
+
} else if (self.requestBodyStream) {
|
|
743
|
+
console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.')
|
|
744
|
+
self.requestBodyStream.pipe(self)
|
|
745
|
+
} else if (!self.src) {
|
|
746
|
+
if ((self._auth.hasAuth && !self._auth.sentAuth) || self.hasHeader('content-length')) {
|
|
747
|
+
self.end()
|
|
748
|
+
return
|
|
749
|
+
}
|
|
750
|
+
switch (self.method) {
|
|
751
|
+
case 'GET':
|
|
752
|
+
case 'HEAD':
|
|
753
|
+
case 'TRACE':
|
|
754
|
+
case 'DELETE':
|
|
755
|
+
case 'CONNECT':
|
|
756
|
+
case 'OPTIONS':
|
|
757
|
+
case undefined:
|
|
758
|
+
// @note this behavior is same as Node.js
|
|
759
|
+
break
|
|
760
|
+
default:
|
|
761
|
+
self.setHeader('Content-Length', 0)
|
|
762
|
+
break
|
|
763
|
+
}
|
|
764
|
+
self.end()
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
self.jar(self._jar || options.jar, function () {
|
|
769
|
+
if (self._form && !self.hasHeader('content-length')) {
|
|
770
|
+
// Before ending the request, we had to compute the length of the whole form, asyncly
|
|
771
|
+
self._form.getLength(function (err, length) {
|
|
772
|
+
if (!err && !isNaN(length)) {
|
|
773
|
+
self.setHeader('Content-Length', length)
|
|
774
|
+
}
|
|
775
|
+
end()
|
|
776
|
+
})
|
|
777
|
+
} else {
|
|
778
|
+
end()
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
self.ntick = true
|
|
783
|
+
})
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
Request.prototype.getNewAgent = function ({agentIdleTimeout}) {
|
|
787
|
+
var self = this
|
|
788
|
+
var Agent = self.agentClass
|
|
789
|
+
var options = {}
|
|
790
|
+
if (self.agentOptions) {
|
|
791
|
+
for (var i in self.agentOptions) {
|
|
792
|
+
options[i] = self.agentOptions[i]
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (self.ca) {
|
|
796
|
+
options.ca = self.ca
|
|
797
|
+
}
|
|
798
|
+
if (self.extraCA) {
|
|
799
|
+
options.extraCA = self.extraCA
|
|
800
|
+
}
|
|
801
|
+
if (self.ciphers) {
|
|
802
|
+
options.ciphers = self.ciphers
|
|
803
|
+
}
|
|
804
|
+
if (self.secureProtocol) {
|
|
805
|
+
options.secureProtocol = self.secureProtocol
|
|
806
|
+
}
|
|
807
|
+
if (self.secureOptions) {
|
|
808
|
+
options.secureOptions = self.secureOptions
|
|
809
|
+
}
|
|
810
|
+
if (self.sslKeyLogFile) {
|
|
811
|
+
options.sslKeyLogFile = self.sslKeyLogFile
|
|
812
|
+
}
|
|
813
|
+
if (typeof self.rejectUnauthorized !== 'undefined') {
|
|
814
|
+
options.rejectUnauthorized = self.rejectUnauthorized
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (self.cert && self.key) {
|
|
818
|
+
options.key = self.key
|
|
819
|
+
options.cert = self.cert
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (self.pfx) {
|
|
823
|
+
options.pfx = self.pfx
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (self.passphrase) {
|
|
827
|
+
options.passphrase = self.passphrase
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
var poolKey = ''
|
|
831
|
+
|
|
832
|
+
// different types of agents are in different pools
|
|
833
|
+
if (Agent !== self.httpModule.Agent) {
|
|
834
|
+
poolKey += Agent.name
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// ca option is only relevant if proxy or destination are https
|
|
838
|
+
var proxy = self.proxy
|
|
839
|
+
if (typeof proxy === 'string') {
|
|
840
|
+
proxy = self.urlParser.parse(proxy)
|
|
841
|
+
}
|
|
842
|
+
var isHttps = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:'
|
|
843
|
+
|
|
844
|
+
if (isHttps) {
|
|
845
|
+
if (options.ca) {
|
|
846
|
+
if (poolKey) {
|
|
847
|
+
poolKey += ':'
|
|
848
|
+
}
|
|
849
|
+
poolKey += options.ca
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// only add when NodeExtraCACerts is enabled
|
|
853
|
+
if (options.extraCA) {
|
|
854
|
+
if (poolKey) {
|
|
855
|
+
poolKey += ':'
|
|
856
|
+
}
|
|
857
|
+
poolKey += options.extraCA
|
|
858
|
+
|
|
859
|
+
// Create a new secure context to add the extra CA
|
|
860
|
+
var secureContext = tls.createSecureContext(options)
|
|
861
|
+
secureContext.context.addCACert(options.extraCA)
|
|
862
|
+
options.secureContext = secureContext
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (typeof options.rejectUnauthorized !== 'undefined') {
|
|
866
|
+
if (poolKey) {
|
|
867
|
+
poolKey += ':'
|
|
868
|
+
}
|
|
869
|
+
poolKey += options.rejectUnauthorized
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (options.cert) {
|
|
873
|
+
if (poolKey) {
|
|
874
|
+
poolKey += ':'
|
|
875
|
+
}
|
|
876
|
+
poolKey += options.cert.toString('ascii') + options.key.toString('ascii')
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (options.pfx) {
|
|
880
|
+
if (poolKey) {
|
|
881
|
+
poolKey += ':'
|
|
882
|
+
}
|
|
883
|
+
poolKey += options.pfx.toString('ascii')
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (options.passphrase) {
|
|
887
|
+
if (poolKey) {
|
|
888
|
+
poolKey += ':'
|
|
889
|
+
}
|
|
890
|
+
poolKey += options.passphrase
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (options.ciphers) {
|
|
894
|
+
if (poolKey) {
|
|
895
|
+
poolKey += ':'
|
|
896
|
+
}
|
|
897
|
+
poolKey += options.ciphers
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (options.secureProtocol) {
|
|
901
|
+
if (poolKey) {
|
|
902
|
+
poolKey += ':'
|
|
903
|
+
}
|
|
904
|
+
poolKey += options.secureProtocol
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (options.secureOptions) {
|
|
908
|
+
if (poolKey) {
|
|
909
|
+
poolKey += ':'
|
|
910
|
+
}
|
|
911
|
+
poolKey += options.secureOptions
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (options.sslKeyLogFile) {
|
|
915
|
+
if (poolKey) {
|
|
916
|
+
poolKey += ':'
|
|
917
|
+
}
|
|
918
|
+
poolKey += options.sslKeyLogFile
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (self.pool === globalPool && !poolKey && Object.keys(options).length === 0 && self.httpModule.globalAgent && typeof agentIdleTimeout !== 'number') {
|
|
923
|
+
// not doing anything special. Use the globalAgent
|
|
924
|
+
return self.httpModule.globalAgent
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// we're using a stored agent. Make sure it's protocol-specific
|
|
928
|
+
poolKey = self.protocolVersion + ':' + self.uri.protocol + poolKey
|
|
929
|
+
|
|
930
|
+
let agent = self.pool[poolKey]
|
|
931
|
+
|
|
932
|
+
// generate a new agent for this setting if none yet exists
|
|
933
|
+
if (!agent || (typeof agentIdleTimeout === 'number' && (agent.lastUsedAt || 0) + agentIdleTimeout < Date.now())) {
|
|
934
|
+
agent = self.pool[poolKey] = new Agent(options)
|
|
935
|
+
// properly set maxSockets on new agents
|
|
936
|
+
if (self.pool.maxSockets) {
|
|
937
|
+
self.pool[poolKey].maxSockets = self.pool.maxSockets
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
agent.lastUsedAt = Date.now()
|
|
942
|
+
return agent
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
Request.prototype.start = function () {
|
|
946
|
+
// start() is called once we are ready to send the outgoing HTTP request.
|
|
947
|
+
// this is usually called on the first write(), end() or on nextTick()
|
|
948
|
+
var self = this
|
|
949
|
+
|
|
950
|
+
if (self.timing) {
|
|
951
|
+
// All timings will be relative to this request's startTime. In order to do this,
|
|
952
|
+
// we need to capture the wall-clock start time (via Date), immediately followed
|
|
953
|
+
// by the high-resolution timer (via now()). While these two won't be set
|
|
954
|
+
// at the _exact_ same time, they should be close enough to be able to calculate
|
|
955
|
+
// high-resolution, monotonically non-decreasing timestamps relative to startTime.
|
|
956
|
+
var startTime = new Date().getTime()
|
|
957
|
+
var startTimeNow = now()
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (self._aborted) {
|
|
961
|
+
return
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// postman: emit start event
|
|
965
|
+
self.emit('start')
|
|
966
|
+
|
|
967
|
+
self._started = true
|
|
968
|
+
self.method = self.method || 'GET'
|
|
969
|
+
self.href = self.uri.href
|
|
970
|
+
|
|
971
|
+
if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) {
|
|
972
|
+
self.setHeader('Content-Length', self.src.stat.size)
|
|
973
|
+
}
|
|
974
|
+
if (self._aws) {
|
|
975
|
+
self.aws(self._aws, true)
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
self._reqResInfo.request = {
|
|
979
|
+
method: self.method,
|
|
980
|
+
href: self.uri.href,
|
|
981
|
+
headers: [],
|
|
982
|
+
proxy: (self.proxy && { href: self.proxy.href }) || undefined,
|
|
983
|
+
httpVersion: '1.1'
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// We have a method named auth, which is completely different from the http.request
|
|
987
|
+
// auth option. If we don't remove it, we're gonna have a bad time.
|
|
988
|
+
var reqOptions = copy(self)
|
|
989
|
+
delete reqOptions.auth
|
|
990
|
+
|
|
991
|
+
// Workaround for a bug in Node: https://github.com/nodejs/node/issues/8321
|
|
992
|
+
if (!(self.disableUrlEncoding || self.proxy || self.uri.isUnix)) {
|
|
993
|
+
try {
|
|
994
|
+
extend(reqOptions, urlParse(self.uri.href))
|
|
995
|
+
} catch (e) { } // nothing to do if urlParse fails, "extend" never throws an error.
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
debug('make request', self.uri.href)
|
|
999
|
+
|
|
1000
|
+
// node v6.8.0 now supports a `timeout` value in `http.request()`, but we
|
|
1001
|
+
// should delete it for now since we handle timeouts manually for better
|
|
1002
|
+
// consistency with node versions before v6.8.0
|
|
1003
|
+
delete reqOptions.timeout
|
|
1004
|
+
|
|
1005
|
+
try {
|
|
1006
|
+
self.req = self.httpModule.request(reqOptions)
|
|
1007
|
+
|
|
1008
|
+
// Remove blacklisted headers from the request instance.
|
|
1009
|
+
// @note don't check for `hasHeader` because headers like `connection`,
|
|
1010
|
+
// 'content-length', 'transfer-encoding' etc. are added at the very end
|
|
1011
|
+
// and `removeHeader` updates the Node.js internal state which makes sure
|
|
1012
|
+
// these headers are not added.
|
|
1013
|
+
if (Array.isArray(self.blacklistHeaders) && self.blacklistHeaders.length) {
|
|
1014
|
+
self.blacklistHeaders.forEach(function (header) {
|
|
1015
|
+
self.req.removeHeader(header)
|
|
1016
|
+
// also remove from the `self` for the consistency
|
|
1017
|
+
self.removeHeader(header)
|
|
1018
|
+
})
|
|
1019
|
+
}
|
|
1020
|
+
} catch (err) {
|
|
1021
|
+
self.emit('error', err)
|
|
1022
|
+
return
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (self.timing) {
|
|
1026
|
+
self.startTime = startTime
|
|
1027
|
+
self.startTimeNow = startTimeNow
|
|
1028
|
+
|
|
1029
|
+
// Timing values will all be relative to startTime (by comparing to startTimeNow
|
|
1030
|
+
// so we have an accurate clock)
|
|
1031
|
+
self.timings = {}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
var timeout
|
|
1035
|
+
if (self.timeout && !self.timeoutTimer) {
|
|
1036
|
+
if (self.timeout < 0) {
|
|
1037
|
+
timeout = 0
|
|
1038
|
+
} else if (typeof self.timeout === 'number' && isFinite(self.timeout)) {
|
|
1039
|
+
timeout = self.timeout
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
self.req.on('response', self.onRequestResponse.bind(self))
|
|
1044
|
+
self.req.on('error', self.onRequestError.bind(self))
|
|
1045
|
+
self.req.on('drain', function () {
|
|
1046
|
+
self.emit('drain')
|
|
1047
|
+
})
|
|
1048
|
+
|
|
1049
|
+
self.req.on('socket', function (socket) {
|
|
1050
|
+
if (self.verbose) {
|
|
1051
|
+
// The reused socket holds all the session data which was injected in
|
|
1052
|
+
// during the first connection. This is done because events like
|
|
1053
|
+
// `lookup`, `connect` & `secureConnect` will not be triggered for a
|
|
1054
|
+
// reused socket and debug information will be lost for that request.
|
|
1055
|
+
var reusedSocket = Boolean(socket.__SESSION_ID && socket.__SESSION_DATA)
|
|
1056
|
+
|
|
1057
|
+
if (!reusedSocket) {
|
|
1058
|
+
socket.__SESSION_ID = uuid()
|
|
1059
|
+
socket.__SESSION_DATA = {}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// @note make sure you don't serialize this object to avoid memory leak
|
|
1063
|
+
self._reqResInfo.session = {
|
|
1064
|
+
id: socket.__SESSION_ID,
|
|
1065
|
+
reused: reusedSocket,
|
|
1066
|
+
data: socket.__SESSION_DATA
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Attach event only once on the socket, so that data is not written multiple times
|
|
1071
|
+
// Since the agent key also includes keyLog, we are sure that a socket which is not supposed to be logging the
|
|
1072
|
+
// ssl content will not have a keylog listener set inadvertently, so we don't need to care about removing this listener
|
|
1073
|
+
if (self.sslKeyLogFile && !events.getEventListeners(socket, 'keylog').length) {
|
|
1074
|
+
socket.on('keylog', (line) => {
|
|
1075
|
+
fsPromise.appendFile(self.sslKeyLogFile, line)
|
|
1076
|
+
.catch(() => {
|
|
1077
|
+
debug('Failed to append keylog to file: ' + self.sslKeyLogFile)
|
|
1078
|
+
})
|
|
1079
|
+
})
|
|
1080
|
+
}
|
|
1081
|
+
// `._connecting` was the old property which was made public in node v6.1.0
|
|
1082
|
+
var isConnecting = socket._connecting || socket.connecting
|
|
1083
|
+
if (self.timing) {
|
|
1084
|
+
self.timings.socket = now() - self.startTimeNow
|
|
1085
|
+
|
|
1086
|
+
if (isConnecting) {
|
|
1087
|
+
var onLookupTiming = function () {
|
|
1088
|
+
self.timings.lookup = now() - self.startTimeNow
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
var onConnectTiming = function () {
|
|
1092
|
+
self.timings.connect = now() - self.startTimeNow
|
|
1093
|
+
|
|
1094
|
+
if (self.verbose) {
|
|
1095
|
+
socket.__SESSION_DATA.addresses = {
|
|
1096
|
+
// local address
|
|
1097
|
+
// @note there's no `socket.localFamily` but `.address` method
|
|
1098
|
+
// returns same output as of remote.
|
|
1099
|
+
local: (typeof socket.address === 'function') && socket.address(),
|
|
1100
|
+
|
|
1101
|
+
// remote address
|
|
1102
|
+
remote: {
|
|
1103
|
+
address: socket.remoteAddress,
|
|
1104
|
+
family: socket.remoteFamily,
|
|
1105
|
+
port: socket.remotePort
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
var onSecureConnectTiming = function () {
|
|
1112
|
+
self.timings.secureConnect = now() - self.startTimeNow
|
|
1113
|
+
|
|
1114
|
+
if (self.verbose) {
|
|
1115
|
+
socket.__SESSION_DATA.tls = {
|
|
1116
|
+
// true if the session was reused
|
|
1117
|
+
reused: (typeof socket.isSessionReused === 'function') && socket.isSessionReused(),
|
|
1118
|
+
|
|
1119
|
+
// true if the peer certificate was signed by one of the CAs specified
|
|
1120
|
+
authorized: socket.authorized,
|
|
1121
|
+
|
|
1122
|
+
// reason why the peer's certificate was not been verified
|
|
1123
|
+
authorizationError: socket.authorizationError,
|
|
1124
|
+
|
|
1125
|
+
// negotiated cipher name
|
|
1126
|
+
cipher: (typeof socket.getCipher === 'function') && socket.getCipher(),
|
|
1127
|
+
|
|
1128
|
+
// negotiated SSL/TLS protocol version
|
|
1129
|
+
// @note Node >= v5.7.0
|
|
1130
|
+
protocol: (typeof socket.getProtocol === 'function') && socket.getProtocol(),
|
|
1131
|
+
|
|
1132
|
+
// type, name, and size of parameter of an ephemeral key exchange
|
|
1133
|
+
// @note Node >= v5.0.0
|
|
1134
|
+
ephemeralKeyInfo: (typeof socket.getEphemeralKeyInfo === 'function') && socket.getEphemeralKeyInfo()
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// peer certificate information
|
|
1138
|
+
// @note if session is reused, all certificate information is
|
|
1139
|
+
// stripped from the socket (returns {}).
|
|
1140
|
+
// Refer: https://github.com/nodejs/node/issues/3940
|
|
1141
|
+
var peerCert = (typeof socket.getPeerCertificate === 'function') && (socket.getPeerCertificate() || {})
|
|
1142
|
+
|
|
1143
|
+
socket.__SESSION_DATA.tls.peerCertificate = {
|
|
1144
|
+
subject: peerCert.subject && {
|
|
1145
|
+
country: peerCert.subject.C,
|
|
1146
|
+
stateOrProvince: peerCert.subject.ST,
|
|
1147
|
+
locality: peerCert.subject.L,
|
|
1148
|
+
organization: peerCert.subject.O,
|
|
1149
|
+
organizationalUnit: peerCert.subject.OU,
|
|
1150
|
+
commonName: peerCert.subject.CN,
|
|
1151
|
+
alternativeNames: peerCert.subjectaltname
|
|
1152
|
+
},
|
|
1153
|
+
issuer: peerCert.issuer && {
|
|
1154
|
+
country: peerCert.issuer.C,
|
|
1155
|
+
stateOrProvince: peerCert.issuer.ST,
|
|
1156
|
+
locality: peerCert.issuer.L,
|
|
1157
|
+
organization: peerCert.issuer.O,
|
|
1158
|
+
organizationalUnit: peerCert.issuer.OU,
|
|
1159
|
+
commonName: peerCert.issuer.CN
|
|
1160
|
+
},
|
|
1161
|
+
validFrom: peerCert.valid_from,
|
|
1162
|
+
validTo: peerCert.valid_to,
|
|
1163
|
+
fingerprint: peerCert.fingerprint,
|
|
1164
|
+
serialNumber: peerCert.serialNumber
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
socket.once('lookup', onLookupTiming)
|
|
1170
|
+
socket.once('connect', onConnectTiming)
|
|
1171
|
+
socket.once('secureConnect', onSecureConnectTiming)
|
|
1172
|
+
|
|
1173
|
+
// clean up timing event listeners if needed on error
|
|
1174
|
+
self.req.once('error', function () {
|
|
1175
|
+
// Swallow ERR_HTTP2_SOCKET_UNBOUND error when removing listeners in case of error.
|
|
1176
|
+
// This needs to be done since http2 ClientSession disassociates the underlying socket from the session before emitting the error event
|
|
1177
|
+
try {
|
|
1178
|
+
socket.removeListener('lookup', onLookupTiming)
|
|
1179
|
+
socket.removeListener('connect', onConnectTiming)
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
if (err.code !== 'ERR_HTTP2_SOCKET_UNBOUND') {
|
|
1182
|
+
throw err
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
})
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
var setReqTimeout = function () {
|
|
1190
|
+
// This timeout sets the amount of time to wait *between* bytes sent
|
|
1191
|
+
// from the server once connected.
|
|
1192
|
+
//
|
|
1193
|
+
// In particular, it's useful for erroring if the server fails to send
|
|
1194
|
+
// data halfway through streaming a response.
|
|
1195
|
+
self.req.setTimeout(timeout, function () {
|
|
1196
|
+
if (self.req) {
|
|
1197
|
+
self.abort()
|
|
1198
|
+
var e = new Error('ESOCKETTIMEDOUT')
|
|
1199
|
+
e.code = 'ESOCKETTIMEDOUT'
|
|
1200
|
+
e.connect = false
|
|
1201
|
+
self.emit('error', e)
|
|
1202
|
+
}
|
|
1203
|
+
})
|
|
1204
|
+
}
|
|
1205
|
+
if (timeout !== undefined) {
|
|
1206
|
+
// Only start the connection timer if we're actually connecting a new
|
|
1207
|
+
// socket, otherwise if we're already connected (because this is a
|
|
1208
|
+
// keep-alive connection) do not bother. This is important since we won't
|
|
1209
|
+
// get a 'connect' event for an already connected socket.
|
|
1210
|
+
if (isConnecting) {
|
|
1211
|
+
var onReqSockConnect = function () {
|
|
1212
|
+
socket.removeListener('connect', onReqSockConnect)
|
|
1213
|
+
self.clearTimeout()
|
|
1214
|
+
setReqTimeout()
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
socket.on('connect', onReqSockConnect)
|
|
1218
|
+
|
|
1219
|
+
self.req.on('error', function (err) { // eslint-disable-line handle-callback-err
|
|
1220
|
+
// Swallow ERR_HTTP2_SOCKET_UNBOUND error when removing listeners in case of error.
|
|
1221
|
+
// This needs to be done since http2 ClientSession disassociates the underlying socket from the session before emitting the error event
|
|
1222
|
+
try {
|
|
1223
|
+
socket.removeListener('connect', onReqSockConnect)
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
if (err.code !== 'ERR_HTTP2_SOCKET_UNBOUND') {
|
|
1226
|
+
throw err
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
})
|
|
1230
|
+
|
|
1231
|
+
// Set a timeout in memory - this block will throw if the server takes more
|
|
1232
|
+
// than `timeout` to write the HTTP status and headers (corresponding to
|
|
1233
|
+
// the on('response') event on the client). NB: this measures wall-clock
|
|
1234
|
+
// time, not the time between bytes sent by the server.
|
|
1235
|
+
self.timeoutTimer = setTimeout(function () {
|
|
1236
|
+
socket.removeListener('connect', onReqSockConnect)
|
|
1237
|
+
self.abort()
|
|
1238
|
+
var e = new Error('ETIMEDOUT')
|
|
1239
|
+
e.code = 'ETIMEDOUT'
|
|
1240
|
+
e.connect = true
|
|
1241
|
+
self.emit('error', e)
|
|
1242
|
+
}, timeout)
|
|
1243
|
+
} else {
|
|
1244
|
+
// We're already connected
|
|
1245
|
+
setReqTimeout()
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
self.emit('socket', socket)
|
|
1249
|
+
})
|
|
1250
|
+
|
|
1251
|
+
self.emit('request', self.req)
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
Request.prototype.onRequestError = function (error) {
|
|
1255
|
+
var self = this
|
|
1256
|
+
if (self._aborted) {
|
|
1257
|
+
return
|
|
1258
|
+
}
|
|
1259
|
+
if (self.req && self.req._reusedSocket && error.code === 'ECONNRESET' &&
|
|
1260
|
+
self.agent.addRequestNoreuse) {
|
|
1261
|
+
self.agent = {addRequest: self.agent.addRequestNoreuse.bind(self.agent)}
|
|
1262
|
+
self.start()
|
|
1263
|
+
self.req.end()
|
|
1264
|
+
return
|
|
1265
|
+
}
|
|
1266
|
+
if (error.res && error.res.statusCode && error.res.statusMessage) {
|
|
1267
|
+
// Handle the response to populate all the right properties for the consumer to pick up
|
|
1268
|
+
self.onRequestResponse(error.res)
|
|
1269
|
+
}
|
|
1270
|
+
self.clearTimeout()
|
|
1271
|
+
self.emit('error', error)
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
Request.prototype.onRequestResponse = function (response) {
|
|
1275
|
+
var self = this
|
|
1276
|
+
// De-referencing self.startTimeNow to prevent race condition during redirects
|
|
1277
|
+
// Race-condition:
|
|
1278
|
+
// 30x-url: Request start (self.startTimeNow initialized (self.start()))
|
|
1279
|
+
// Redirect header to 200 request received
|
|
1280
|
+
// 200-url: Request start (self.startTimeNow re-initialized, old value overwritten (redirect.js -> request.init() -> self.start()))
|
|
1281
|
+
// 30x-url: end event received, timing calculated using new self.startTimeNow (incorrect)
|
|
1282
|
+
//
|
|
1283
|
+
// This must've been happening with http/1.1 as well when using keep-alive, but there were no tests to catch this.
|
|
1284
|
+
// Was highlighted with http/2 where connections are reused by default
|
|
1285
|
+
// Does not show up in http/1.x tests due to delays involving socket establishment
|
|
1286
|
+
//
|
|
1287
|
+
// New flow
|
|
1288
|
+
// 30x-url: Request start (self.startTimeNow initialized)
|
|
1289
|
+
// Redirect header to 200 request received
|
|
1290
|
+
// 200-url: Request start (self.startTimeNow re-initialized, old value overwritten)
|
|
1291
|
+
// 30x-url: end event received, timing calculated using requestSegmentStartTime (correct)
|
|
1292
|
+
const requestSegmentStartTime = self.startTimeNow
|
|
1293
|
+
|
|
1294
|
+
if (self.timing) {
|
|
1295
|
+
self.timings.response = now() - requestSegmentStartTime
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
debug('onRequestResponse', self.uri.href, response.statusCode, response.headers)
|
|
1299
|
+
response.on('end', function () {
|
|
1300
|
+
if (self.timing) {
|
|
1301
|
+
self.timings.end = now() - requestSegmentStartTime
|
|
1302
|
+
response.timingStart = self.startTime
|
|
1303
|
+
response.timingStartTimer = requestSegmentStartTime
|
|
1304
|
+
|
|
1305
|
+
// fill in the blanks for any periods that didn't trigger, such as
|
|
1306
|
+
// no lookup or connect due to keep alive
|
|
1307
|
+
if (!self.timings.socket) {
|
|
1308
|
+
self.timings.socket = 0
|
|
1309
|
+
}
|
|
1310
|
+
if (!self.timings.lookup) {
|
|
1311
|
+
self.timings.lookup = self.timings.socket
|
|
1312
|
+
}
|
|
1313
|
+
if (!self.timings.connect) {
|
|
1314
|
+
self.timings.connect = self.timings.lookup
|
|
1315
|
+
}
|
|
1316
|
+
if (!self.timings.secureConnect && self.uri.protocol === 'https:') {
|
|
1317
|
+
self.timings.secureConnect = self.timings.connect
|
|
1318
|
+
}
|
|
1319
|
+
if (!self.timings.response) {
|
|
1320
|
+
self.timings.response = self.timings.connect
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
debug('elapsed time', self.timings.end)
|
|
1324
|
+
|
|
1325
|
+
// elapsedTime includes all redirects
|
|
1326
|
+
self.elapsedTime += Math.round(self.timings.end)
|
|
1327
|
+
|
|
1328
|
+
// NOTE: elapsedTime is deprecated in favor of .timings
|
|
1329
|
+
response.elapsedTime = self.elapsedTime
|
|
1330
|
+
|
|
1331
|
+
// timings is just for the final fetch
|
|
1332
|
+
response.timings = self.timings
|
|
1333
|
+
|
|
1334
|
+
// pre-calculate phase timings as well
|
|
1335
|
+
response.timingPhases = {
|
|
1336
|
+
wait: self.timings.socket,
|
|
1337
|
+
dns: self.timings.lookup - self.timings.socket,
|
|
1338
|
+
tcp: self.timings.connect - self.timings.lookup,
|
|
1339
|
+
firstByte: self.timings.response - self.timings.connect,
|
|
1340
|
+
download: self.timings.end - self.timings.response,
|
|
1341
|
+
total: self.timings.end
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// if secureConnect is present, add secureHandshake and update firstByte
|
|
1345
|
+
if (self.timings.secureConnect) {
|
|
1346
|
+
response.timingPhases.secureHandshake = self.timings.secureConnect - self.timings.connect
|
|
1347
|
+
response.timingPhases.firstByte = self.timings.response - self.timings.secureConnect
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
debug('response end', self.uri.href, response.statusCode, response.headers)
|
|
1352
|
+
})
|
|
1353
|
+
|
|
1354
|
+
if (self._aborted) {
|
|
1355
|
+
debug('aborted', self.uri.href)
|
|
1356
|
+
response.resume()
|
|
1357
|
+
return
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
self._reqResInfo.response = {
|
|
1361
|
+
statusCode: response.statusCode,
|
|
1362
|
+
headers: parseResponseHeaders(response.rawHeaders),
|
|
1363
|
+
httpVersion: response.httpVersion
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// Setting this again since the actual request version that was used is found only after ALPN negotiation in case of protocolVersion: auto
|
|
1367
|
+
self._reqResInfo.request.httpVersion = response.httpVersion
|
|
1368
|
+
|
|
1369
|
+
if (self.timing) {
|
|
1370
|
+
self._reqResInfo.timingStart = self.startTime
|
|
1371
|
+
self._reqResInfo.timingStartTimer = self.startTimeNow
|
|
1372
|
+
self._reqResInfo.timings = self.timings
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
self.response = response
|
|
1376
|
+
response.request = self
|
|
1377
|
+
response.toJSON = responseToJSON
|
|
1378
|
+
|
|
1379
|
+
// XXX This is different on 0.10, because SSL is strict by default
|
|
1380
|
+
if (self.uri.protocol === 'https:' &&
|
|
1381
|
+
self.strictSSL && (!response.hasOwnProperty('socket') ||
|
|
1382
|
+
!response.socket.authorized)) {
|
|
1383
|
+
debug('strict ssl error', self.uri.href)
|
|
1384
|
+
var sslErr = response.hasOwnProperty('socket') ? response.socket.authorizationError : self.uri.href + ' does not support SSL'
|
|
1385
|
+
self.emit('error', new Error('SSL Error: ' + sslErr))
|
|
1386
|
+
return
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// Save the original host before any redirect (if it changes, we need to
|
|
1390
|
+
// remove any authorization headers). Also remember the case of the header
|
|
1391
|
+
// name because lots of broken servers expect Host instead of host and we
|
|
1392
|
+
// want the caller to be able to specify this.
|
|
1393
|
+
self.originalHost = self.getHeader('host')
|
|
1394
|
+
if (!self.originalHostHeaderName) {
|
|
1395
|
+
self.originalHostHeaderName = self.hasHeader('host')
|
|
1396
|
+
}
|
|
1397
|
+
if (self.setHost) {
|
|
1398
|
+
self.removeHeader('host')
|
|
1399
|
+
}
|
|
1400
|
+
self.clearTimeout()
|
|
1401
|
+
|
|
1402
|
+
function responseHandler () {
|
|
1403
|
+
if (self._redirect.onResponse(response)) {
|
|
1404
|
+
return // Ignore the rest of the response
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// Be a good stream and emit end when the response is finished.
|
|
1408
|
+
// Hack to emit end on close because of a core bug that never fires end
|
|
1409
|
+
response.once('close', function () {
|
|
1410
|
+
if (!self._ended) {
|
|
1411
|
+
self.response.emit('end')
|
|
1412
|
+
}
|
|
1413
|
+
})
|
|
1414
|
+
|
|
1415
|
+
response.once('end', function () {
|
|
1416
|
+
self._ended = true
|
|
1417
|
+
})
|
|
1418
|
+
|
|
1419
|
+
var noBody = function (code) {
|
|
1420
|
+
return (
|
|
1421
|
+
self.method === 'HEAD' ||
|
|
1422
|
+
// Informational
|
|
1423
|
+
(code >= 100 && code < 200) ||
|
|
1424
|
+
// No Content
|
|
1425
|
+
code === 204 ||
|
|
1426
|
+
// Not Modified
|
|
1427
|
+
code === 304
|
|
1428
|
+
)
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
var responseContent
|
|
1432
|
+
var downloadSizeTracker = new SizeTrackerStream()
|
|
1433
|
+
|
|
1434
|
+
if ((self.gzip || self.brotli) && !noBody(response.statusCode)) {
|
|
1435
|
+
var contentEncoding = response.headers['content-encoding'] || 'identity'
|
|
1436
|
+
contentEncoding = contentEncoding.trim().toLowerCase()
|
|
1437
|
+
|
|
1438
|
+
// Be more lenient with decoding compressed responses, since (very rarely)
|
|
1439
|
+
// servers send slightly invalid gzip responses that are still accepted
|
|
1440
|
+
// by common browsers.
|
|
1441
|
+
// Always using Z_SYNC_FLUSH is what cURL does.
|
|
1442
|
+
var zlibOptions = {
|
|
1443
|
+
flush: zlib.Z_SYNC_FLUSH,
|
|
1444
|
+
finishFlush: zlib.Z_SYNC_FLUSH
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (self.gzip && contentEncoding === 'gzip') {
|
|
1448
|
+
responseContent = zlib.createGunzip(zlibOptions)
|
|
1449
|
+
response.pipe(downloadSizeTracker).pipe(responseContent)
|
|
1450
|
+
} else if (self.gzip && contentEncoding === 'deflate') {
|
|
1451
|
+
responseContent = inflate.createInflate(zlibOptions)
|
|
1452
|
+
response.pipe(downloadSizeTracker).pipe(responseContent)
|
|
1453
|
+
} else if (self.brotli && contentEncoding === 'br') {
|
|
1454
|
+
responseContent = zlib.createBrotliDecompress()
|
|
1455
|
+
response.pipe(downloadSizeTracker).pipe(responseContent)
|
|
1456
|
+
} else {
|
|
1457
|
+
// Since previous versions didn't check for Content-Encoding header,
|
|
1458
|
+
// ignore any invalid values to preserve backwards-compatibility
|
|
1459
|
+
if (contentEncoding !== 'identity') {
|
|
1460
|
+
debug('ignoring unrecognized Content-Encoding ' + contentEncoding)
|
|
1461
|
+
}
|
|
1462
|
+
responseContent = response.pipe(downloadSizeTracker)
|
|
1463
|
+
}
|
|
1464
|
+
} else {
|
|
1465
|
+
responseContent = response.pipe(downloadSizeTracker)
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
if (self.encoding) {
|
|
1469
|
+
if (self.dests.length !== 0) {
|
|
1470
|
+
console.error('Ignoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.')
|
|
1471
|
+
} else {
|
|
1472
|
+
responseContent.setEncoding(self.encoding)
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Node by default returns the status message with `latin1` character encoding,
|
|
1477
|
+
// which results in characters lying outside the range of `U+0000 to U+00FF` getting truncated
|
|
1478
|
+
// so that they can be mapped in the given range.
|
|
1479
|
+
// Refer: https://nodejs.org/docs/latest-v12.x/api/buffer.html#buffer_buffers_and_character_encodings
|
|
1480
|
+
//
|
|
1481
|
+
// Exposing `statusMessageEncoding` option to make encoding type configurable.
|
|
1482
|
+
// This would help in correctly representing status messages belonging to range outside of `latin1`
|
|
1483
|
+
//
|
|
1484
|
+
// @note: The Regex `[^\w\s-']` is tested to prevent unnecessary computation of creating a Buffer and
|
|
1485
|
+
// then decoding it when the status message consists of common characters,
|
|
1486
|
+
// specifically belonging to the following set: [a-z, A-Z, 0-9, -, _ ', whitespace]
|
|
1487
|
+
// As in that case, no matter what the encoding type is used for decoding the buffer, the result would remain the same.
|
|
1488
|
+
//
|
|
1489
|
+
// @note: Providing a value in this option will result in force re-encoding of the status message
|
|
1490
|
+
// which may not always be intended by the server - specifically in cases where
|
|
1491
|
+
// server returns a status message which when encoded again with a different character encoding
|
|
1492
|
+
// results in some other characters.
|
|
1493
|
+
// For example: If the server intentionally responds with `ð\x9F\x98\x8A` as status message
|
|
1494
|
+
// but if the statusMessageEncoding option is set to `utf8`, then it would get converted to '😊'.
|
|
1495
|
+
var statusMessage = String(response.statusMessage)
|
|
1496
|
+
if (self.statusMessageEncoding && /[^\w\s-']/.test(statusMessage)) {
|
|
1497
|
+
response.statusMessage = Buffer.from(statusMessage, 'latin1').toString(self.statusMessageEncoding)
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
if (self._paused) {
|
|
1501
|
+
responseContent.pause()
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
self.responseContent = responseContent
|
|
1505
|
+
|
|
1506
|
+
self.emit('response', response)
|
|
1507
|
+
|
|
1508
|
+
self.dests.forEach(function (dest) {
|
|
1509
|
+
self.pipeDest(dest)
|
|
1510
|
+
})
|
|
1511
|
+
|
|
1512
|
+
var responseThresholdEnabled = false
|
|
1513
|
+
var responseBytesLeft
|
|
1514
|
+
|
|
1515
|
+
if (typeof self.maxResponseSize === 'number') {
|
|
1516
|
+
responseThresholdEnabled = true
|
|
1517
|
+
responseBytesLeft = self.maxResponseSize
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
responseContent.on('data', function (chunk) {
|
|
1521
|
+
if (self.timing && !self.responseStarted) {
|
|
1522
|
+
self.responseStartTime = (new Date()).getTime()
|
|
1523
|
+
|
|
1524
|
+
// NOTE: responseStartTime is deprecated in favor of .timings
|
|
1525
|
+
response.responseStartTime = self.responseStartTime
|
|
1526
|
+
}
|
|
1527
|
+
// if response threshold is set, update the response bytes left to hit
|
|
1528
|
+
// threshold. If exceeds, abort the request.
|
|
1529
|
+
if (responseThresholdEnabled) {
|
|
1530
|
+
responseBytesLeft -= chunk.length
|
|
1531
|
+
if (responseBytesLeft < 0) {
|
|
1532
|
+
self.emit('error', new Error('Maximum response size reached'))
|
|
1533
|
+
self.destroy()
|
|
1534
|
+
self.abort()
|
|
1535
|
+
return
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
self._destdata = true
|
|
1539
|
+
self.emit('data', chunk)
|
|
1540
|
+
})
|
|
1541
|
+
responseContent.once('end', function (chunk) {
|
|
1542
|
+
self._reqResInfo.response.downloadedBytes = downloadSizeTracker.size
|
|
1543
|
+
self.emit('end', chunk)
|
|
1544
|
+
})
|
|
1545
|
+
responseContent.on('error', function (error) {
|
|
1546
|
+
self.emit('error', error)
|
|
1547
|
+
})
|
|
1548
|
+
responseContent.on('close', function () { self.emit('close') })
|
|
1549
|
+
|
|
1550
|
+
if (self.callback) {
|
|
1551
|
+
self.readResponseBody(response)
|
|
1552
|
+
} else { // if no callback
|
|
1553
|
+
self.on('end', function () {
|
|
1554
|
+
if (self._aborted) {
|
|
1555
|
+
debug('aborted', self.uri.href)
|
|
1556
|
+
return
|
|
1557
|
+
}
|
|
1558
|
+
self.emit('complete', response)
|
|
1559
|
+
})
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function forEachAsync (items, fn, cb) {
|
|
1564
|
+
!cb && (cb = function () { /* (ಠ_ಠ) */ })
|
|
1565
|
+
|
|
1566
|
+
if (!(Array.isArray(items) && fn)) { return cb() }
|
|
1567
|
+
|
|
1568
|
+
var index = 0
|
|
1569
|
+
var totalItems = items.length
|
|
1570
|
+
function next (err) {
|
|
1571
|
+
if (err || index >= totalItems) {
|
|
1572
|
+
return cb(err)
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
try {
|
|
1576
|
+
fn.call(items, items[index++], next)
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
return cb(error)
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
if (!totalItems) { return cb() }
|
|
1583
|
+
|
|
1584
|
+
next()
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
var targetCookieJar = (self._jar && self._jar.setCookie) ? self._jar : globalCookieJar
|
|
1588
|
+
var addCookie = function (cookie, cb) {
|
|
1589
|
+
// set the cookie if it's domain in the URI's domain.
|
|
1590
|
+
targetCookieJar.setCookie(cookie, self.uri, {ignoreError: true}, function () {
|
|
1591
|
+
// swallow the error, don't fail the request because of cookie jar failure
|
|
1592
|
+
cb()
|
|
1593
|
+
})
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
response.caseless = caseless(response.headers)
|
|
1597
|
+
|
|
1598
|
+
if (response.caseless.has('set-cookie') && (!self._disableCookies)) {
|
|
1599
|
+
var headerName = response.caseless.has('set-cookie')
|
|
1600
|
+
if (Array.isArray(response.headers[headerName])) {
|
|
1601
|
+
forEachAsync(response.headers[headerName], addCookie, function (err) {
|
|
1602
|
+
if (err) { return self.emit('error', err) }
|
|
1603
|
+
|
|
1604
|
+
responseHandler()
|
|
1605
|
+
})
|
|
1606
|
+
} else {
|
|
1607
|
+
addCookie(response.headers[headerName], responseHandler)
|
|
1608
|
+
}
|
|
1609
|
+
} else {
|
|
1610
|
+
responseHandler()
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
debug('finish init function', self.uri.href)
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
Request.prototype.readResponseBody = function (response) {
|
|
1617
|
+
var self = this
|
|
1618
|
+
debug('reading response\'s body')
|
|
1619
|
+
var buffers = []
|
|
1620
|
+
var bufferLength = 0
|
|
1621
|
+
var strings = []
|
|
1622
|
+
|
|
1623
|
+
self.on('data', function (chunk) {
|
|
1624
|
+
if (!Buffer.isBuffer(chunk)) {
|
|
1625
|
+
strings.push(chunk)
|
|
1626
|
+
} else if (chunk.length) {
|
|
1627
|
+
bufferLength += chunk.length
|
|
1628
|
+
buffers.push(chunk)
|
|
1629
|
+
}
|
|
1630
|
+
})
|
|
1631
|
+
self.on('end', function () {
|
|
1632
|
+
debug('end event', self.uri.href)
|
|
1633
|
+
if (self._aborted) {
|
|
1634
|
+
debug('aborted', self.uri.href)
|
|
1635
|
+
// `buffer` is defined in the parent scope and used in a closure it exists for the life of the request.
|
|
1636
|
+
// This can lead to leaky behavior if the user retains a reference to the request object.
|
|
1637
|
+
buffers = []
|
|
1638
|
+
bufferLength = 0
|
|
1639
|
+
return
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
if (bufferLength) {
|
|
1643
|
+
debug('has body', self.uri.href, bufferLength)
|
|
1644
|
+
response.body = Buffer.concat(buffers, bufferLength)
|
|
1645
|
+
if (self.encoding !== null) {
|
|
1646
|
+
response.body = response.body.toString(self.encoding)
|
|
1647
|
+
}
|
|
1648
|
+
// `buffer` is defined in the parent scope and used in a closure it exists for the life of the Request.
|
|
1649
|
+
// This can lead to leaky behavior if the user retains a reference to the request object.
|
|
1650
|
+
buffers = []
|
|
1651
|
+
bufferLength = 0
|
|
1652
|
+
} else if (strings.length) {
|
|
1653
|
+
// The UTF8 BOM [0xEF,0xBB,0xBF] is converted to [0xFE,0xFF] in the JS UTC16/UCS2 representation.
|
|
1654
|
+
// Strip this value out when the encoding is set to 'utf8', as upstream consumers won't expect it and it breaks JSON.parse().
|
|
1655
|
+
if (self.encoding === 'utf8' && strings[0].length > 0 && strings[0][0] === '\uFEFF') {
|
|
1656
|
+
strings[0] = strings[0].substring(1)
|
|
1657
|
+
}
|
|
1658
|
+
response.body = strings.join('')
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (self._json) {
|
|
1662
|
+
try {
|
|
1663
|
+
response.body = JSON.parse(response.body, self._jsonReviver)
|
|
1664
|
+
} catch (e) {
|
|
1665
|
+
debug('invalid JSON received', self.uri.href)
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
debug('emitting complete', self.uri.href)
|
|
1669
|
+
if (typeof response.body === 'undefined' && !self._json) {
|
|
1670
|
+
response.body = self.encoding === null ? Buffer.alloc(0) : ''
|
|
1671
|
+
}
|
|
1672
|
+
self.emit('complete', response, response.body)
|
|
1673
|
+
})
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
Request.prototype.abort = function () {
|
|
1677
|
+
var self = this
|
|
1678
|
+
self._aborted = true
|
|
1679
|
+
|
|
1680
|
+
if (self.req) {
|
|
1681
|
+
self.req.abort()
|
|
1682
|
+
} else if (self.response) {
|
|
1683
|
+
self.response.destroy()
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
self.clearTimeout()
|
|
1687
|
+
self.emit('abort')
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
Request.prototype.pipeDest = function (dest) {
|
|
1691
|
+
var self = this
|
|
1692
|
+
var response = self.response
|
|
1693
|
+
// Called after the response is received
|
|
1694
|
+
if (dest.headers && !dest.headersSent) {
|
|
1695
|
+
if (response.caseless.has('content-type')) {
|
|
1696
|
+
var ctname = response.caseless.has('content-type')
|
|
1697
|
+
if (dest.setHeader) {
|
|
1698
|
+
dest.setHeader(ctname, response.headers[ctname])
|
|
1699
|
+
} else {
|
|
1700
|
+
dest.headers[ctname] = response.headers[ctname]
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
if (response.caseless.has('content-length')) {
|
|
1705
|
+
var clname = response.caseless.has('content-length')
|
|
1706
|
+
if (dest.setHeader) {
|
|
1707
|
+
dest.setHeader(clname, response.headers[clname])
|
|
1708
|
+
} else {
|
|
1709
|
+
dest.headers[clname] = response.headers[clname]
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
if (dest.setHeader && !dest.headersSent) {
|
|
1714
|
+
for (var i in response.headers) {
|
|
1715
|
+
if (i.startsWith(':')) {
|
|
1716
|
+
// Don't set HTTP/2 pseudoheaders
|
|
1717
|
+
continue
|
|
1718
|
+
}
|
|
1719
|
+
// If the response content is being decoded, the Content-Encoding header
|
|
1720
|
+
// of the response doesn't represent the piped content, so don't pass it.
|
|
1721
|
+
if (!self.gzip || i !== 'content-encoding') {
|
|
1722
|
+
dest.setHeader(i, response.headers[i])
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
dest.statusCode = response.statusCode
|
|
1726
|
+
}
|
|
1727
|
+
if (self.pipefilter) {
|
|
1728
|
+
self.pipefilter(response, dest)
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
Request.prototype.qs = function (q, clobber) {
|
|
1733
|
+
var self = this
|
|
1734
|
+
var base
|
|
1735
|
+
if (!clobber && self.uri.query) {
|
|
1736
|
+
base = self._qs.parse(self.uri.query)
|
|
1737
|
+
} else {
|
|
1738
|
+
base = {}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
for (var i in q) {
|
|
1742
|
+
base[i] = q[i]
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
var qs = self._qs.stringify(base)
|
|
1746
|
+
|
|
1747
|
+
if (qs === '') {
|
|
1748
|
+
return self
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
self.uri = self.urlParser.parse(self.uri.href.split('?')[0] + '?' + qs)
|
|
1752
|
+
self.url = self.uri
|
|
1753
|
+
self.path = self.uri.path
|
|
1754
|
+
|
|
1755
|
+
if (self.uri.host === 'unix') {
|
|
1756
|
+
self.enableUnixSocket()
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
return self
|
|
1760
|
+
}
|
|
1761
|
+
Request.prototype.form = function (form) {
|
|
1762
|
+
var self = this
|
|
1763
|
+
var contentType = self.getHeader('content-type')
|
|
1764
|
+
var overrideInvalidContentType = contentType ? !self.allowContentTypeOverride : true
|
|
1765
|
+
if (form) {
|
|
1766
|
+
if (overrideInvalidContentType && !/^application\/x-www-form-urlencoded\b/.test(contentType)) {
|
|
1767
|
+
self.setHeader('Content-Type', 'application/x-www-form-urlencoded')
|
|
1768
|
+
}
|
|
1769
|
+
self.body = (typeof form === 'string')
|
|
1770
|
+
? self._qs.rfc3986(form.toString('utf8'))
|
|
1771
|
+
: self._qs.stringify(form).toString('utf8')
|
|
1772
|
+
return self
|
|
1773
|
+
}
|
|
1774
|
+
// form-data
|
|
1775
|
+
var contentTypeMatch = contentType && contentType.match &&
|
|
1776
|
+
contentType.match(/^multipart\/form-data;.*boundary=(?:"([^"]+)"|([^;]+))/)
|
|
1777
|
+
var boundary = contentTypeMatch && (contentTypeMatch[1] || contentTypeMatch[2])
|
|
1778
|
+
// create form-data object
|
|
1779
|
+
// set custom boundary if present in content-type else auto-generate
|
|
1780
|
+
self._form = new FormData({ _boundary: boundary })
|
|
1781
|
+
self._form.on('error', function (err) {
|
|
1782
|
+
err.message = 'form-data: ' + err.message
|
|
1783
|
+
self.emit('error', err)
|
|
1784
|
+
self.abort()
|
|
1785
|
+
})
|
|
1786
|
+
if (overrideInvalidContentType && !contentTypeMatch) {
|
|
1787
|
+
// overrides invalid or missing content-type
|
|
1788
|
+
self.setHeader('Content-Type', 'multipart/form-data; boundary=' + self._form.getBoundary())
|
|
1789
|
+
}
|
|
1790
|
+
return self._form
|
|
1791
|
+
}
|
|
1792
|
+
Request.prototype.multipart = function (multipart) {
|
|
1793
|
+
var self = this
|
|
1794
|
+
|
|
1795
|
+
self._multipart.onRequest(multipart)
|
|
1796
|
+
|
|
1797
|
+
if (!self._multipart.chunked) {
|
|
1798
|
+
self.body = self._multipart.body
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
return self
|
|
1802
|
+
}
|
|
1803
|
+
Request.prototype.json = function (val) {
|
|
1804
|
+
var self = this
|
|
1805
|
+
|
|
1806
|
+
if (!self.hasHeader('accept')) {
|
|
1807
|
+
self.setHeader('Accept', 'application/json')
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
if (typeof self.jsonReplacer === 'function') {
|
|
1811
|
+
self._jsonReplacer = self.jsonReplacer
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
self._json = true
|
|
1815
|
+
if (typeof val === 'boolean') {
|
|
1816
|
+
if (self.body !== undefined) {
|
|
1817
|
+
if (!/^application\/x-www-form-urlencoded\b/.test(self.getHeader('content-type'))) {
|
|
1818
|
+
self.body = safeStringify(self.body, self._jsonReplacer)
|
|
1819
|
+
} else {
|
|
1820
|
+
self.body = self._qs.rfc3986(self.body)
|
|
1821
|
+
}
|
|
1822
|
+
if (!self.hasHeader('content-type')) {
|
|
1823
|
+
self.setHeader('Content-Type', 'application/json')
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
} else {
|
|
1827
|
+
self.body = safeStringify(val, self._jsonReplacer)
|
|
1828
|
+
if (!self.hasHeader('content-type')) {
|
|
1829
|
+
self.setHeader('Content-Type', 'application/json')
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
if (typeof self.jsonReviver === 'function') {
|
|
1834
|
+
self._jsonReviver = self.jsonReviver
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
return self
|
|
1838
|
+
}
|
|
1839
|
+
Request.prototype.getHeader = function (name, headers) {
|
|
1840
|
+
var self = this
|
|
1841
|
+
var result, re, match
|
|
1842
|
+
if (!headers) {
|
|
1843
|
+
headers = self.headers
|
|
1844
|
+
}
|
|
1845
|
+
Object.keys(headers).forEach(function (key) {
|
|
1846
|
+
if (key.length !== name.length) {
|
|
1847
|
+
return
|
|
1848
|
+
}
|
|
1849
|
+
re = new RegExp(name, 'i')
|
|
1850
|
+
match = key.match(re)
|
|
1851
|
+
if (match) {
|
|
1852
|
+
result = headers[key]
|
|
1853
|
+
}
|
|
1854
|
+
})
|
|
1855
|
+
return result
|
|
1856
|
+
}
|
|
1857
|
+
Request.prototype.enableUnixSocket = function () {
|
|
1858
|
+
// Get the socket & request paths from the URL
|
|
1859
|
+
var unixParts = this.uri.path.split(':')
|
|
1860
|
+
var host = unixParts[0]
|
|
1861
|
+
var path = unixParts[1]
|
|
1862
|
+
// Apply unix properties to request
|
|
1863
|
+
this.socketPath = host
|
|
1864
|
+
this.uri.pathname = path
|
|
1865
|
+
this.uri.path = path
|
|
1866
|
+
this.uri.host = host
|
|
1867
|
+
this.uri.hostname = host
|
|
1868
|
+
this.uri.isUnix = true
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
Request.prototype.auth = function (user, pass, sendImmediately, bearer) {
|
|
1872
|
+
var self = this
|
|
1873
|
+
|
|
1874
|
+
self._auth.onRequest(user, pass, sendImmediately, bearer)
|
|
1875
|
+
|
|
1876
|
+
return self
|
|
1877
|
+
}
|
|
1878
|
+
Request.prototype.aws = function (opts, now) {
|
|
1879
|
+
var self = this
|
|
1880
|
+
|
|
1881
|
+
if (!now) {
|
|
1882
|
+
self._aws = opts
|
|
1883
|
+
return self
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
if (opts.sign_version === 4 || opts.sign_version === '4') {
|
|
1887
|
+
// use aws4
|
|
1888
|
+
var options = {
|
|
1889
|
+
host: self.uri.host,
|
|
1890
|
+
path: self.uri.path,
|
|
1891
|
+
method: self.method,
|
|
1892
|
+
headers: self.headers,
|
|
1893
|
+
body: self.body
|
|
1894
|
+
}
|
|
1895
|
+
if (opts.service) {
|
|
1896
|
+
options.service = opts.service
|
|
1897
|
+
}
|
|
1898
|
+
var signRes = aws4.sign(options, {
|
|
1899
|
+
accessKeyId: opts.key,
|
|
1900
|
+
secretAccessKey: opts.secret,
|
|
1901
|
+
sessionToken: opts.session
|
|
1902
|
+
})
|
|
1903
|
+
self.setHeader('Authorization', signRes.headers.Authorization)
|
|
1904
|
+
self.setHeader('X-Amz-Date', signRes.headers['X-Amz-Date'])
|
|
1905
|
+
if (signRes.headers['X-Amz-Security-Token']) {
|
|
1906
|
+
self.setHeader('X-Amz-Security-Token', signRes.headers['X-Amz-Security-Token'])
|
|
1907
|
+
}
|
|
1908
|
+
} else {
|
|
1909
|
+
// default: use aws-sign2
|
|
1910
|
+
var date = new Date()
|
|
1911
|
+
self.setHeader('Date', date.toUTCString())
|
|
1912
|
+
var auth = {
|
|
1913
|
+
key: opts.key,
|
|
1914
|
+
secret: opts.secret,
|
|
1915
|
+
verb: self.method.toUpperCase(),
|
|
1916
|
+
date: date,
|
|
1917
|
+
contentType: self.getHeader('content-type') || '',
|
|
1918
|
+
md5: self.getHeader('content-md5') || '',
|
|
1919
|
+
amazonHeaders: aws2.canonicalizeHeaders(self.headers)
|
|
1920
|
+
}
|
|
1921
|
+
var path = self.uri.path
|
|
1922
|
+
if (opts.bucket && path) {
|
|
1923
|
+
auth.resource = '/' + opts.bucket + path
|
|
1924
|
+
} else if (opts.bucket && !path) {
|
|
1925
|
+
auth.resource = '/' + opts.bucket
|
|
1926
|
+
} else if (!opts.bucket && path) {
|
|
1927
|
+
auth.resource = path
|
|
1928
|
+
} else if (!opts.bucket && !path) {
|
|
1929
|
+
auth.resource = '/'
|
|
1930
|
+
}
|
|
1931
|
+
auth.resource = aws2.canonicalizeResource(auth.resource)
|
|
1932
|
+
self.setHeader('Authorization', aws2.authorization(auth))
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
return self
|
|
1936
|
+
}
|
|
1937
|
+
Request.prototype.httpSignature = function (opts) {
|
|
1938
|
+
var self = this
|
|
1939
|
+
httpSignature.signRequest({
|
|
1940
|
+
getHeader: function (header) {
|
|
1941
|
+
return self.getHeader(header, self.headers)
|
|
1942
|
+
},
|
|
1943
|
+
setHeader: function (header, value) {
|
|
1944
|
+
self.setHeader(header, value)
|
|
1945
|
+
},
|
|
1946
|
+
method: self.method,
|
|
1947
|
+
path: self.path
|
|
1948
|
+
}, opts)
|
|
1949
|
+
debug('httpSignature authorization', self.getHeader('authorization'))
|
|
1950
|
+
|
|
1951
|
+
return self
|
|
1952
|
+
}
|
|
1953
|
+
Request.prototype.hawk = function (opts) {
|
|
1954
|
+
var self = this
|
|
1955
|
+
self.setHeader('Authorization', hawk.header(self.uri, self.method, opts))
|
|
1956
|
+
}
|
|
1957
|
+
Request.prototype.oauth = function (_oauth) {
|
|
1958
|
+
var self = this
|
|
1959
|
+
|
|
1960
|
+
self._oauth.onRequest(_oauth)
|
|
1961
|
+
|
|
1962
|
+
return self
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
Request.prototype.jar = function (jar, cb) {
|
|
1966
|
+
var self = this
|
|
1967
|
+
self._jar = jar
|
|
1968
|
+
|
|
1969
|
+
if (!jar) {
|
|
1970
|
+
// disable cookies
|
|
1971
|
+
self._disableCookies = true
|
|
1972
|
+
return cb()
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
if (self._redirect.redirectsFollowed === 0) {
|
|
1976
|
+
self.originalCookieHeader = self.getHeader('cookie')
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
var targetCookieJar = jar.getCookieString ? jar : globalCookieJar
|
|
1980
|
+
// fetch cookie in the Specified host
|
|
1981
|
+
targetCookieJar.getCookieString(self.uri, function (err, cookies) {
|
|
1982
|
+
if (err) { return cb() }
|
|
1983
|
+
|
|
1984
|
+
// if need cookie and cookie is not empty
|
|
1985
|
+
if (cookies && cookies.length) {
|
|
1986
|
+
if (self.originalCookieHeader) {
|
|
1987
|
+
if (Array.isArray(self.originalCookieHeader)) {
|
|
1988
|
+
self.originalCookieHeader = self.originalCookieHeader.join('; ')
|
|
1989
|
+
}
|
|
1990
|
+
// Don't overwrite existing Cookie header
|
|
1991
|
+
self.setHeader('Cookie', self.originalCookieHeader + '; ' + cookies)
|
|
1992
|
+
} else {
|
|
1993
|
+
self.setHeader('Cookie', cookies)
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
cb()
|
|
1998
|
+
})
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// Stream API
|
|
2002
|
+
Request.prototype.pipe = function (dest, opts) {
|
|
2003
|
+
var self = this
|
|
2004
|
+
|
|
2005
|
+
if (self.response) {
|
|
2006
|
+
if (self._destdata) {
|
|
2007
|
+
self.emit('error', new Error('You cannot pipe after data has been emitted from the response.'))
|
|
2008
|
+
} else if (self._ended) {
|
|
2009
|
+
self.emit('error', new Error('You cannot pipe after the response has been ended.'))
|
|
2010
|
+
} else {
|
|
2011
|
+
stream.Stream.prototype.pipe.call(self, dest, opts)
|
|
2012
|
+
self.pipeDest(dest)
|
|
2013
|
+
return dest
|
|
2014
|
+
}
|
|
2015
|
+
} else {
|
|
2016
|
+
self.dests.push(dest)
|
|
2017
|
+
stream.Stream.prototype.pipe.call(self, dest, opts)
|
|
2018
|
+
return dest
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
Request.prototype.write = function () {
|
|
2022
|
+
var self = this
|
|
2023
|
+
if (self._aborted) { return }
|
|
2024
|
+
|
|
2025
|
+
if (!self._started) {
|
|
2026
|
+
self.start()
|
|
2027
|
+
}
|
|
2028
|
+
if (self.req) {
|
|
2029
|
+
return self.req.write.apply(self.req, arguments)
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
Request.prototype.end = function (chunk) {
|
|
2033
|
+
var self = this
|
|
2034
|
+
if (self._aborted) { return }
|
|
2035
|
+
|
|
2036
|
+
if (chunk) {
|
|
2037
|
+
self.write(chunk)
|
|
2038
|
+
}
|
|
2039
|
+
if (!self._started) {
|
|
2040
|
+
self.start()
|
|
2041
|
+
}
|
|
2042
|
+
if (self.req) {
|
|
2043
|
+
self.req.end()
|
|
2044
|
+
|
|
2045
|
+
// Reference to request, so if _reqResInfo is updated (in case of redirects), we still can update the headers
|
|
2046
|
+
const request = self._reqResInfo.request
|
|
2047
|
+
Promise.resolve(self.req._header).then(function (header) {
|
|
2048
|
+
if (!header) {
|
|
2049
|
+
request.headers = []
|
|
2050
|
+
return
|
|
2051
|
+
}
|
|
2052
|
+
request.headers = parseRequestHeaders(header)
|
|
2053
|
+
})
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
Request.prototype.pause = function () {
|
|
2057
|
+
var self = this
|
|
2058
|
+
if (!self.responseContent) {
|
|
2059
|
+
self._paused = true
|
|
2060
|
+
} else {
|
|
2061
|
+
self.responseContent.pause.apply(self.responseContent, arguments)
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
Request.prototype.resume = function () {
|
|
2065
|
+
var self = this
|
|
2066
|
+
if (!self.responseContent) {
|
|
2067
|
+
self._paused = false
|
|
2068
|
+
} else {
|
|
2069
|
+
self.responseContent.resume.apply(self.responseContent, arguments)
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
Request.prototype.destroy = function () {
|
|
2073
|
+
var self = this
|
|
2074
|
+
this.clearTimeout()
|
|
2075
|
+
if (!self._ended) {
|
|
2076
|
+
self.end()
|
|
2077
|
+
} else if (self.response) {
|
|
2078
|
+
self.response.destroy()
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
Request.prototype.clearTimeout = function () {
|
|
2083
|
+
if (this.timeoutTimer) {
|
|
2084
|
+
clearTimeout(this.timeoutTimer)
|
|
2085
|
+
this.timeoutTimer = null
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
Request.defaultProxyHeaderWhiteList =
|
|
2090
|
+
Tunnel.defaultProxyHeaderWhiteList.slice()
|
|
2091
|
+
|
|
2092
|
+
Request.defaultProxyHeaderExclusiveList =
|
|
2093
|
+
Tunnel.defaultProxyHeaderExclusiveList.slice()
|
|
2094
|
+
|
|
2095
|
+
// Exports
|
|
2096
|
+
|
|
2097
|
+
Request.prototype.toJSON = requestToJSON
|
|
2098
|
+
module.exports = Request
|