@actions/http-client 1.0.10 → 1.0.11
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/RELEASES.md +4 -0
- package/auth.d.ts +23 -0
- package/auth.js +58 -0
- package/index.d.ts +124 -0
- package/index.js +537 -0
- package/interfaces.d.ts +49 -0
- package/interfaces.js +2 -0
- package/package.json +1 -1
- package/proxy.d.ts +2 -0
- package/proxy.js +57 -0
- package/.github/workflows/test.yml +0 -51
- package/.prettierignore +0 -2
- package/.prettierrc.json +0 -11
- package/__tests__/auth.test.ts +0 -61
- package/__tests__/basics.test.ts +0 -375
- package/__tests__/headers.test.ts +0 -115
- package/__tests__/keepalive.test.ts +0 -79
- package/__tests__/proxy.test.ts +0 -228
- package/auth.ts +0 -86
- package/index.ts +0 -768
- package/interfaces.ts +0 -98
- package/jest.config.js +0 -10
- package/proxy.ts +0 -60
- package/tsconfig.json +0 -15
package/index.ts
DELETED
|
@@ -1,768 +0,0 @@
|
|
|
1
|
-
import http = require('http')
|
|
2
|
-
import https = require('https')
|
|
3
|
-
import ifm = require('./interfaces')
|
|
4
|
-
import pm = require('./proxy')
|
|
5
|
-
|
|
6
|
-
let tunnel: any
|
|
7
|
-
|
|
8
|
-
export enum HttpCodes {
|
|
9
|
-
OK = 200,
|
|
10
|
-
MultipleChoices = 300,
|
|
11
|
-
MovedPermanently = 301,
|
|
12
|
-
ResourceMoved = 302,
|
|
13
|
-
SeeOther = 303,
|
|
14
|
-
NotModified = 304,
|
|
15
|
-
UseProxy = 305,
|
|
16
|
-
SwitchProxy = 306,
|
|
17
|
-
TemporaryRedirect = 307,
|
|
18
|
-
PermanentRedirect = 308,
|
|
19
|
-
BadRequest = 400,
|
|
20
|
-
Unauthorized = 401,
|
|
21
|
-
PaymentRequired = 402,
|
|
22
|
-
Forbidden = 403,
|
|
23
|
-
NotFound = 404,
|
|
24
|
-
MethodNotAllowed = 405,
|
|
25
|
-
NotAcceptable = 406,
|
|
26
|
-
ProxyAuthenticationRequired = 407,
|
|
27
|
-
RequestTimeout = 408,
|
|
28
|
-
Conflict = 409,
|
|
29
|
-
Gone = 410,
|
|
30
|
-
TooManyRequests = 429,
|
|
31
|
-
InternalServerError = 500,
|
|
32
|
-
NotImplemented = 501,
|
|
33
|
-
BadGateway = 502,
|
|
34
|
-
ServiceUnavailable = 503,
|
|
35
|
-
GatewayTimeout = 504
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export enum Headers {
|
|
39
|
-
Accept = 'accept',
|
|
40
|
-
ContentType = 'content-type'
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export enum MediaTypes {
|
|
44
|
-
ApplicationJson = 'application/json'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Returns the proxy URL, depending upon the supplied url and proxy environment variables.
|
|
49
|
-
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
|
50
|
-
*/
|
|
51
|
-
export function getProxyUrl(serverUrl: string): string {
|
|
52
|
-
let proxyUrl = pm.getProxyUrl(new URL(serverUrl))
|
|
53
|
-
return proxyUrl ? proxyUrl.href : ''
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const HttpRedirectCodes: number[] = [
|
|
57
|
-
HttpCodes.MovedPermanently,
|
|
58
|
-
HttpCodes.ResourceMoved,
|
|
59
|
-
HttpCodes.SeeOther,
|
|
60
|
-
HttpCodes.TemporaryRedirect,
|
|
61
|
-
HttpCodes.PermanentRedirect
|
|
62
|
-
]
|
|
63
|
-
const HttpResponseRetryCodes: number[] = [
|
|
64
|
-
HttpCodes.BadGateway,
|
|
65
|
-
HttpCodes.ServiceUnavailable,
|
|
66
|
-
HttpCodes.GatewayTimeout
|
|
67
|
-
]
|
|
68
|
-
const RetryableHttpVerbs: string[] = ['OPTIONS', 'GET', 'DELETE', 'HEAD']
|
|
69
|
-
const ExponentialBackoffCeiling = 10
|
|
70
|
-
const ExponentialBackoffTimeSlice = 5
|
|
71
|
-
|
|
72
|
-
export class HttpClientError extends Error {
|
|
73
|
-
constructor(message: string, statusCode: number) {
|
|
74
|
-
super(message)
|
|
75
|
-
this.name = 'HttpClientError'
|
|
76
|
-
this.statusCode = statusCode
|
|
77
|
-
Object.setPrototypeOf(this, HttpClientError.prototype)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
public statusCode: number
|
|
81
|
-
public result?: any
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export class HttpClientResponse implements ifm.IHttpClientResponse {
|
|
85
|
-
constructor(message: http.IncomingMessage) {
|
|
86
|
-
this.message = message
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public message: http.IncomingMessage
|
|
90
|
-
readBody(): Promise<string> {
|
|
91
|
-
return new Promise<string>(async (resolve, reject) => {
|
|
92
|
-
let output = Buffer.alloc(0)
|
|
93
|
-
|
|
94
|
-
this.message.on('data', (chunk: Buffer) => {
|
|
95
|
-
output = Buffer.concat([output, chunk])
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
this.message.on('end', () => {
|
|
99
|
-
resolve(output.toString())
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function isHttps(requestUrl: string) {
|
|
106
|
-
let parsedUrl: URL = new URL(requestUrl)
|
|
107
|
-
return parsedUrl.protocol === 'https:'
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export class HttpClient {
|
|
111
|
-
userAgent: string | undefined
|
|
112
|
-
handlers: ifm.IRequestHandler[]
|
|
113
|
-
requestOptions: ifm.IRequestOptions
|
|
114
|
-
|
|
115
|
-
private _ignoreSslError: boolean = false
|
|
116
|
-
private _socketTimeout: number
|
|
117
|
-
private _allowRedirects: boolean = true
|
|
118
|
-
private _allowRedirectDowngrade: boolean = false
|
|
119
|
-
private _maxRedirects: number = 50
|
|
120
|
-
private _allowRetries: boolean = false
|
|
121
|
-
private _maxRetries: number = 1
|
|
122
|
-
private _agent
|
|
123
|
-
private _proxyAgent
|
|
124
|
-
private _keepAlive: boolean = false
|
|
125
|
-
private _disposed: boolean = false
|
|
126
|
-
|
|
127
|
-
constructor(
|
|
128
|
-
userAgent?: string,
|
|
129
|
-
handlers?: ifm.IRequestHandler[],
|
|
130
|
-
requestOptions?: ifm.IRequestOptions
|
|
131
|
-
) {
|
|
132
|
-
this.userAgent = userAgent
|
|
133
|
-
this.handlers = handlers || []
|
|
134
|
-
this.requestOptions = requestOptions
|
|
135
|
-
if (requestOptions) {
|
|
136
|
-
if (requestOptions.ignoreSslError != null) {
|
|
137
|
-
this._ignoreSslError = requestOptions.ignoreSslError
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this._socketTimeout = requestOptions.socketTimeout
|
|
141
|
-
|
|
142
|
-
if (requestOptions.allowRedirects != null) {
|
|
143
|
-
this._allowRedirects = requestOptions.allowRedirects
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (requestOptions.allowRedirectDowngrade != null) {
|
|
147
|
-
this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (requestOptions.maxRedirects != null) {
|
|
151
|
-
this._maxRedirects = Math.max(requestOptions.maxRedirects, 0)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (requestOptions.keepAlive != null) {
|
|
155
|
-
this._keepAlive = requestOptions.keepAlive
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (requestOptions.allowRetries != null) {
|
|
159
|
-
this._allowRetries = requestOptions.allowRetries
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (requestOptions.maxRetries != null) {
|
|
163
|
-
this._maxRetries = requestOptions.maxRetries
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
public options(
|
|
169
|
-
requestUrl: string,
|
|
170
|
-
additionalHeaders?: ifm.IHeaders
|
|
171
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
172
|
-
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {})
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
public get(
|
|
176
|
-
requestUrl: string,
|
|
177
|
-
additionalHeaders?: ifm.IHeaders
|
|
178
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
179
|
-
return this.request('GET', requestUrl, null, additionalHeaders || {})
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
public del(
|
|
183
|
-
requestUrl: string,
|
|
184
|
-
additionalHeaders?: ifm.IHeaders
|
|
185
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
186
|
-
return this.request('DELETE', requestUrl, null, additionalHeaders || {})
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
public post(
|
|
190
|
-
requestUrl: string,
|
|
191
|
-
data: string,
|
|
192
|
-
additionalHeaders?: ifm.IHeaders
|
|
193
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
194
|
-
return this.request('POST', requestUrl, data, additionalHeaders || {})
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
public patch(
|
|
198
|
-
requestUrl: string,
|
|
199
|
-
data: string,
|
|
200
|
-
additionalHeaders?: ifm.IHeaders
|
|
201
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
202
|
-
return this.request('PATCH', requestUrl, data, additionalHeaders || {})
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
public put(
|
|
206
|
-
requestUrl: string,
|
|
207
|
-
data: string,
|
|
208
|
-
additionalHeaders?: ifm.IHeaders
|
|
209
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
210
|
-
return this.request('PUT', requestUrl, data, additionalHeaders || {})
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
public head(
|
|
214
|
-
requestUrl: string,
|
|
215
|
-
additionalHeaders?: ifm.IHeaders
|
|
216
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
217
|
-
return this.request('HEAD', requestUrl, null, additionalHeaders || {})
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
public sendStream(
|
|
221
|
-
verb: string,
|
|
222
|
-
requestUrl: string,
|
|
223
|
-
stream: NodeJS.ReadableStream,
|
|
224
|
-
additionalHeaders?: ifm.IHeaders
|
|
225
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
226
|
-
return this.request(verb, requestUrl, stream, additionalHeaders)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Gets a typed object from an endpoint
|
|
231
|
-
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
|
|
232
|
-
*/
|
|
233
|
-
public async getJson<T>(
|
|
234
|
-
requestUrl: string,
|
|
235
|
-
additionalHeaders: ifm.IHeaders = {}
|
|
236
|
-
): Promise<ifm.ITypedResponse<T>> {
|
|
237
|
-
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
|
238
|
-
additionalHeaders,
|
|
239
|
-
Headers.Accept,
|
|
240
|
-
MediaTypes.ApplicationJson
|
|
241
|
-
)
|
|
242
|
-
let res: ifm.IHttpClientResponse = await this.get(
|
|
243
|
-
requestUrl,
|
|
244
|
-
additionalHeaders
|
|
245
|
-
)
|
|
246
|
-
return this._processResponse<T>(res, this.requestOptions)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
public async postJson<T>(
|
|
250
|
-
requestUrl: string,
|
|
251
|
-
obj: any,
|
|
252
|
-
additionalHeaders: ifm.IHeaders = {}
|
|
253
|
-
): Promise<ifm.ITypedResponse<T>> {
|
|
254
|
-
let data: string = JSON.stringify(obj, null, 2)
|
|
255
|
-
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
|
256
|
-
additionalHeaders,
|
|
257
|
-
Headers.Accept,
|
|
258
|
-
MediaTypes.ApplicationJson
|
|
259
|
-
)
|
|
260
|
-
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
|
261
|
-
additionalHeaders,
|
|
262
|
-
Headers.ContentType,
|
|
263
|
-
MediaTypes.ApplicationJson
|
|
264
|
-
)
|
|
265
|
-
let res: ifm.IHttpClientResponse = await this.post(
|
|
266
|
-
requestUrl,
|
|
267
|
-
data,
|
|
268
|
-
additionalHeaders
|
|
269
|
-
)
|
|
270
|
-
return this._processResponse<T>(res, this.requestOptions)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
public async putJson<T>(
|
|
274
|
-
requestUrl: string,
|
|
275
|
-
obj: any,
|
|
276
|
-
additionalHeaders: ifm.IHeaders = {}
|
|
277
|
-
): Promise<ifm.ITypedResponse<T>> {
|
|
278
|
-
let data: string = JSON.stringify(obj, null, 2)
|
|
279
|
-
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
|
280
|
-
additionalHeaders,
|
|
281
|
-
Headers.Accept,
|
|
282
|
-
MediaTypes.ApplicationJson
|
|
283
|
-
)
|
|
284
|
-
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
|
285
|
-
additionalHeaders,
|
|
286
|
-
Headers.ContentType,
|
|
287
|
-
MediaTypes.ApplicationJson
|
|
288
|
-
)
|
|
289
|
-
let res: ifm.IHttpClientResponse = await this.put(
|
|
290
|
-
requestUrl,
|
|
291
|
-
data,
|
|
292
|
-
additionalHeaders
|
|
293
|
-
)
|
|
294
|
-
return this._processResponse<T>(res, this.requestOptions)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
public async patchJson<T>(
|
|
298
|
-
requestUrl: string,
|
|
299
|
-
obj: any,
|
|
300
|
-
additionalHeaders: ifm.IHeaders = {}
|
|
301
|
-
): Promise<ifm.ITypedResponse<T>> {
|
|
302
|
-
let data: string = JSON.stringify(obj, null, 2)
|
|
303
|
-
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
|
304
|
-
additionalHeaders,
|
|
305
|
-
Headers.Accept,
|
|
306
|
-
MediaTypes.ApplicationJson
|
|
307
|
-
)
|
|
308
|
-
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
|
309
|
-
additionalHeaders,
|
|
310
|
-
Headers.ContentType,
|
|
311
|
-
MediaTypes.ApplicationJson
|
|
312
|
-
)
|
|
313
|
-
let res: ifm.IHttpClientResponse = await this.patch(
|
|
314
|
-
requestUrl,
|
|
315
|
-
data,
|
|
316
|
-
additionalHeaders
|
|
317
|
-
)
|
|
318
|
-
return this._processResponse<T>(res, this.requestOptions)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Makes a raw http request.
|
|
323
|
-
* All other methods such as get, post, patch, and request ultimately call this.
|
|
324
|
-
* Prefer get, del, post and patch
|
|
325
|
-
*/
|
|
326
|
-
public async request(
|
|
327
|
-
verb: string,
|
|
328
|
-
requestUrl: string,
|
|
329
|
-
data: string | NodeJS.ReadableStream,
|
|
330
|
-
headers: ifm.IHeaders
|
|
331
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
332
|
-
if (this._disposed) {
|
|
333
|
-
throw new Error('Client has already been disposed.')
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
let parsedUrl = new URL(requestUrl)
|
|
337
|
-
let info: ifm.IRequestInfo = this._prepareRequest(verb, parsedUrl, headers)
|
|
338
|
-
|
|
339
|
-
// Only perform retries on reads since writes may not be idempotent.
|
|
340
|
-
let maxTries: number =
|
|
341
|
-
this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
|
|
342
|
-
? this._maxRetries + 1
|
|
343
|
-
: 1
|
|
344
|
-
let numTries: number = 0
|
|
345
|
-
|
|
346
|
-
let response: HttpClientResponse
|
|
347
|
-
while (numTries < maxTries) {
|
|
348
|
-
response = await this.requestRaw(info, data)
|
|
349
|
-
|
|
350
|
-
// Check if it's an authentication challenge
|
|
351
|
-
if (
|
|
352
|
-
response &&
|
|
353
|
-
response.message &&
|
|
354
|
-
response.message.statusCode === HttpCodes.Unauthorized
|
|
355
|
-
) {
|
|
356
|
-
let authenticationHandler: ifm.IRequestHandler
|
|
357
|
-
|
|
358
|
-
for (let i = 0; i < this.handlers.length; i++) {
|
|
359
|
-
if (this.handlers[i].canHandleAuthentication(response)) {
|
|
360
|
-
authenticationHandler = this.handlers[i]
|
|
361
|
-
break
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (authenticationHandler) {
|
|
366
|
-
return authenticationHandler.handleAuthentication(this, info, data)
|
|
367
|
-
} else {
|
|
368
|
-
// We have received an unauthorized response but have no handlers to handle it.
|
|
369
|
-
// Let the response return to the caller.
|
|
370
|
-
return response
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
let redirectsRemaining: number = this._maxRedirects
|
|
375
|
-
while (
|
|
376
|
-
HttpRedirectCodes.indexOf(response.message.statusCode) != -1 &&
|
|
377
|
-
this._allowRedirects &&
|
|
378
|
-
redirectsRemaining > 0
|
|
379
|
-
) {
|
|
380
|
-
const redirectUrl: string | null = response.message.headers['location']
|
|
381
|
-
if (!redirectUrl) {
|
|
382
|
-
// if there's no location to redirect to, we won't
|
|
383
|
-
break
|
|
384
|
-
}
|
|
385
|
-
let parsedRedirectUrl = new URL(redirectUrl)
|
|
386
|
-
if (
|
|
387
|
-
parsedUrl.protocol == 'https:' &&
|
|
388
|
-
parsedUrl.protocol != parsedRedirectUrl.protocol &&
|
|
389
|
-
!this._allowRedirectDowngrade
|
|
390
|
-
) {
|
|
391
|
-
throw new Error(
|
|
392
|
-
'Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'
|
|
393
|
-
)
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// we need to finish reading the response before reassigning response
|
|
397
|
-
// which will leak the open socket.
|
|
398
|
-
await response.readBody()
|
|
399
|
-
|
|
400
|
-
// strip authorization header if redirected to a different hostname
|
|
401
|
-
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
|
|
402
|
-
for (let header in headers) {
|
|
403
|
-
// header names are case insensitive
|
|
404
|
-
if (header.toLowerCase() === 'authorization') {
|
|
405
|
-
delete headers[header]
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// let's make the request with the new redirectUrl
|
|
411
|
-
info = this._prepareRequest(verb, parsedRedirectUrl, headers)
|
|
412
|
-
response = await this.requestRaw(info, data)
|
|
413
|
-
redirectsRemaining--
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) {
|
|
417
|
-
// If not a retry code, return immediately instead of retrying
|
|
418
|
-
return response
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
numTries += 1
|
|
422
|
-
|
|
423
|
-
if (numTries < maxTries) {
|
|
424
|
-
await response.readBody()
|
|
425
|
-
await this._performExponentialBackoff(numTries)
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return response
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Needs to be called if keepAlive is set to true in request options.
|
|
434
|
-
*/
|
|
435
|
-
public dispose() {
|
|
436
|
-
if (this._agent) {
|
|
437
|
-
this._agent.destroy()
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
this._disposed = true
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Raw request.
|
|
445
|
-
* @param info
|
|
446
|
-
* @param data
|
|
447
|
-
*/
|
|
448
|
-
public requestRaw(
|
|
449
|
-
info: ifm.IRequestInfo,
|
|
450
|
-
data: string | NodeJS.ReadableStream
|
|
451
|
-
): Promise<ifm.IHttpClientResponse> {
|
|
452
|
-
return new Promise<ifm.IHttpClientResponse>((resolve, reject) => {
|
|
453
|
-
let callbackForResult = function (
|
|
454
|
-
err: any,
|
|
455
|
-
res: ifm.IHttpClientResponse
|
|
456
|
-
) {
|
|
457
|
-
if (err) {
|
|
458
|
-
reject(err)
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
resolve(res)
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
this.requestRawWithCallback(info, data, callbackForResult)
|
|
465
|
-
})
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Raw request with callback.
|
|
470
|
-
* @param info
|
|
471
|
-
* @param data
|
|
472
|
-
* @param onResult
|
|
473
|
-
*/
|
|
474
|
-
public requestRawWithCallback(
|
|
475
|
-
info: ifm.IRequestInfo,
|
|
476
|
-
data: string | NodeJS.ReadableStream,
|
|
477
|
-
onResult: (err: any, res: ifm.IHttpClientResponse) => void
|
|
478
|
-
): void {
|
|
479
|
-
let socket
|
|
480
|
-
|
|
481
|
-
if (typeof data === 'string') {
|
|
482
|
-
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8')
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
let callbackCalled: boolean = false
|
|
486
|
-
let handleResult = (err: any, res: HttpClientResponse) => {
|
|
487
|
-
if (!callbackCalled) {
|
|
488
|
-
callbackCalled = true
|
|
489
|
-
onResult(err, res)
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
let req: http.ClientRequest = info.httpModule.request(
|
|
494
|
-
info.options,
|
|
495
|
-
(msg: http.IncomingMessage) => {
|
|
496
|
-
let res: HttpClientResponse = new HttpClientResponse(msg)
|
|
497
|
-
handleResult(null, res)
|
|
498
|
-
}
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
req.on('socket', sock => {
|
|
502
|
-
socket = sock
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
// If we ever get disconnected, we want the socket to timeout eventually
|
|
506
|
-
req.setTimeout(this._socketTimeout || 3 * 60000, () => {
|
|
507
|
-
if (socket) {
|
|
508
|
-
socket.end()
|
|
509
|
-
}
|
|
510
|
-
handleResult(new Error('Request timeout: ' + info.options.path), null)
|
|
511
|
-
})
|
|
512
|
-
|
|
513
|
-
req.on('error', function (err) {
|
|
514
|
-
// err has statusCode property
|
|
515
|
-
// res should have headers
|
|
516
|
-
handleResult(err, null)
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
if (data && typeof data === 'string') {
|
|
520
|
-
req.write(data, 'utf8')
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (data && typeof data !== 'string') {
|
|
524
|
-
data.on('close', function () {
|
|
525
|
-
req.end()
|
|
526
|
-
})
|
|
527
|
-
|
|
528
|
-
data.pipe(req)
|
|
529
|
-
} else {
|
|
530
|
-
req.end()
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Gets an http agent. This function is useful when you need an http agent that handles
|
|
536
|
-
* routing through a proxy server - depending upon the url and proxy environment variables.
|
|
537
|
-
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
|
538
|
-
*/
|
|
539
|
-
public getAgent(serverUrl: string): http.Agent {
|
|
540
|
-
let parsedUrl = new URL(serverUrl)
|
|
541
|
-
return this._getAgent(parsedUrl)
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
private _prepareRequest(
|
|
545
|
-
method: string,
|
|
546
|
-
requestUrl: URL,
|
|
547
|
-
headers: ifm.IHeaders
|
|
548
|
-
): ifm.IRequestInfo {
|
|
549
|
-
const info: ifm.IRequestInfo = <ifm.IRequestInfo>{}
|
|
550
|
-
|
|
551
|
-
info.parsedUrl = requestUrl
|
|
552
|
-
const usingSsl: boolean = info.parsedUrl.protocol === 'https:'
|
|
553
|
-
info.httpModule = usingSsl ? https : http
|
|
554
|
-
const defaultPort: number = usingSsl ? 443 : 80
|
|
555
|
-
|
|
556
|
-
info.options = <http.RequestOptions>{}
|
|
557
|
-
info.options.host = info.parsedUrl.hostname
|
|
558
|
-
info.options.port = info.parsedUrl.port
|
|
559
|
-
? parseInt(info.parsedUrl.port)
|
|
560
|
-
: defaultPort
|
|
561
|
-
info.options.path =
|
|
562
|
-
(info.parsedUrl.pathname || '') + (info.parsedUrl.search || '')
|
|
563
|
-
info.options.method = method
|
|
564
|
-
info.options.headers = this._mergeHeaders(headers)
|
|
565
|
-
if (this.userAgent != null) {
|
|
566
|
-
info.options.headers['user-agent'] = this.userAgent
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
info.options.agent = this._getAgent(info.parsedUrl)
|
|
570
|
-
|
|
571
|
-
// gives handlers an opportunity to participate
|
|
572
|
-
if (this.handlers) {
|
|
573
|
-
this.handlers.forEach(handler => {
|
|
574
|
-
handler.prepareRequest(info.options)
|
|
575
|
-
})
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
return info
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
private _mergeHeaders(headers: ifm.IHeaders): ifm.IHeaders {
|
|
582
|
-
const lowercaseKeys = obj =>
|
|
583
|
-
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
|
|
584
|
-
|
|
585
|
-
if (this.requestOptions && this.requestOptions.headers) {
|
|
586
|
-
return Object.assign(
|
|
587
|
-
{},
|
|
588
|
-
lowercaseKeys(this.requestOptions.headers),
|
|
589
|
-
lowercaseKeys(headers)
|
|
590
|
-
)
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
return lowercaseKeys(headers || {})
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
private _getExistingOrDefaultHeader(
|
|
597
|
-
additionalHeaders: ifm.IHeaders,
|
|
598
|
-
header: string,
|
|
599
|
-
_default: string
|
|
600
|
-
) {
|
|
601
|
-
const lowercaseKeys = obj =>
|
|
602
|
-
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
|
|
603
|
-
|
|
604
|
-
let clientHeader: string
|
|
605
|
-
if (this.requestOptions && this.requestOptions.headers) {
|
|
606
|
-
clientHeader = lowercaseKeys(this.requestOptions.headers)[header]
|
|
607
|
-
}
|
|
608
|
-
return additionalHeaders[header] || clientHeader || _default
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
private _getAgent(parsedUrl: URL): http.Agent {
|
|
612
|
-
let agent
|
|
613
|
-
let proxyUrl: URL = pm.getProxyUrl(parsedUrl)
|
|
614
|
-
let useProxy = proxyUrl && proxyUrl.hostname
|
|
615
|
-
|
|
616
|
-
if (this._keepAlive && useProxy) {
|
|
617
|
-
agent = this._proxyAgent
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (this._keepAlive && !useProxy) {
|
|
621
|
-
agent = this._agent
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// if agent is already assigned use that agent.
|
|
625
|
-
if (!!agent) {
|
|
626
|
-
return agent
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
const usingSsl = parsedUrl.protocol === 'https:'
|
|
630
|
-
let maxSockets = 100
|
|
631
|
-
if (!!this.requestOptions) {
|
|
632
|
-
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
if (useProxy) {
|
|
636
|
-
// If using proxy, need tunnel
|
|
637
|
-
if (!tunnel) {
|
|
638
|
-
tunnel = require('tunnel')
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const agentOptions = {
|
|
642
|
-
maxSockets: maxSockets,
|
|
643
|
-
keepAlive: this._keepAlive,
|
|
644
|
-
proxy: {
|
|
645
|
-
...((proxyUrl.username || proxyUrl.password) && {
|
|
646
|
-
proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
|
|
647
|
-
}),
|
|
648
|
-
host: proxyUrl.hostname,
|
|
649
|
-
port: proxyUrl.port
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
let tunnelAgent: Function
|
|
654
|
-
const overHttps = proxyUrl.protocol === 'https:'
|
|
655
|
-
if (usingSsl) {
|
|
656
|
-
tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp
|
|
657
|
-
} else {
|
|
658
|
-
tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
agent = tunnelAgent(agentOptions)
|
|
662
|
-
this._proxyAgent = agent
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// if reusing agent across request and tunneling agent isn't assigned create a new agent
|
|
666
|
-
if (this._keepAlive && !agent) {
|
|
667
|
-
const options = {keepAlive: this._keepAlive, maxSockets: maxSockets}
|
|
668
|
-
agent = usingSsl ? new https.Agent(options) : new http.Agent(options)
|
|
669
|
-
this._agent = agent
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// if not using private agent and tunnel agent isn't setup then use global agent
|
|
673
|
-
if (!agent) {
|
|
674
|
-
agent = usingSsl ? https.globalAgent : http.globalAgent
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
if (usingSsl && this._ignoreSslError) {
|
|
678
|
-
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
|
|
679
|
-
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
|
|
680
|
-
// we have to cast it to any and change it directly
|
|
681
|
-
agent.options = Object.assign(agent.options || {}, {
|
|
682
|
-
rejectUnauthorized: false
|
|
683
|
-
})
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
return agent
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
private _performExponentialBackoff(retryNumber: number): Promise<void> {
|
|
690
|
-
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber)
|
|
691
|
-
const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber)
|
|
692
|
-
return new Promise(resolve => setTimeout(() => resolve(), ms))
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
private static dateTimeDeserializer(key: any, value: any): any {
|
|
696
|
-
if (typeof value === 'string') {
|
|
697
|
-
let a = new Date(value)
|
|
698
|
-
if (!isNaN(a.valueOf())) {
|
|
699
|
-
return a
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
return value
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
private async _processResponse<T>(
|
|
707
|
-
res: ifm.IHttpClientResponse,
|
|
708
|
-
options: ifm.IRequestOptions
|
|
709
|
-
): Promise<ifm.ITypedResponse<T>> {
|
|
710
|
-
return new Promise<ifm.ITypedResponse<T>>(async (resolve, reject) => {
|
|
711
|
-
const statusCode: number = res.message.statusCode
|
|
712
|
-
|
|
713
|
-
const response: ifm.ITypedResponse<T> = {
|
|
714
|
-
statusCode: statusCode,
|
|
715
|
-
result: null,
|
|
716
|
-
headers: {}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// not found leads to null obj returned
|
|
720
|
-
if (statusCode == HttpCodes.NotFound) {
|
|
721
|
-
resolve(response)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
let obj: any
|
|
725
|
-
let contents: string
|
|
726
|
-
|
|
727
|
-
// get the result from the body
|
|
728
|
-
try {
|
|
729
|
-
contents = await res.readBody()
|
|
730
|
-
if (contents && contents.length > 0) {
|
|
731
|
-
if (options && options.deserializeDates) {
|
|
732
|
-
obj = JSON.parse(contents, HttpClient.dateTimeDeserializer)
|
|
733
|
-
} else {
|
|
734
|
-
obj = JSON.parse(contents)
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
response.result = obj
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
response.headers = res.message.headers
|
|
741
|
-
} catch (err) {
|
|
742
|
-
// Invalid resource (contents not json); leaving result obj null
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// note that 3xx redirects are handled by the http layer.
|
|
746
|
-
if (statusCode > 299) {
|
|
747
|
-
let msg: string
|
|
748
|
-
|
|
749
|
-
// if exception/error in body, attempt to get better error
|
|
750
|
-
if (obj && obj.message) {
|
|
751
|
-
msg = obj.message
|
|
752
|
-
} else if (contents && contents.length > 0) {
|
|
753
|
-
// it may be the case that the exception is in the body message as string
|
|
754
|
-
msg = contents
|
|
755
|
-
} else {
|
|
756
|
-
msg = 'Failed request: (' + statusCode + ')'
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
let err = new HttpClientError(msg, statusCode)
|
|
760
|
-
err.result = response.result
|
|
761
|
-
|
|
762
|
-
reject(err)
|
|
763
|
-
} else {
|
|
764
|
-
resolve(response)
|
|
765
|
-
}
|
|
766
|
-
})
|
|
767
|
-
}
|
|
768
|
-
}
|