2020-04-10 13:50:06 +00:00
import http = require ( 'http' )
import https = require ( 'https' )
import ifm = require ( './interfaces' )
import pm = require ( './proxy' )
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
let tunnel : any
2020-01-09 23:32:59 +00:00
export enum HttpCodes {
2020-04-10 13:50:06 +00:00
OK = 200 ,
MultipleChoices = 300 ,
MovedPermanently = 301 ,
ResourceMoved = 302 ,
SeeOther = 303 ,
NotModified = 304 ,
UseProxy = 305 ,
SwitchProxy = 306 ,
TemporaryRedirect = 307 ,
PermanentRedirect = 308 ,
BadRequest = 400 ,
Unauthorized = 401 ,
PaymentRequired = 402 ,
Forbidden = 403 ,
NotFound = 404 ,
MethodNotAllowed = 405 ,
NotAcceptable = 406 ,
ProxyAuthenticationRequired = 407 ,
RequestTimeout = 408 ,
Conflict = 409 ,
Gone = 410 ,
TooManyRequests = 429 ,
InternalServerError = 500 ,
NotImplemented = 501 ,
BadGateway = 502 ,
ServiceUnavailable = 503 ,
GatewayTimeout = 504
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
export enum Headers {
Accept = 'accept' ,
ContentType = 'content-type'
2020-02-04 19:19:46 +00:00
}
export enum MediaTypes {
2020-04-10 13:50:06 +00:00
ApplicationJson = 'application/json'
2020-02-04 19:19:46 +00:00
}
2020-01-23 04:09:35 +00:00
/ * *
* Returns the proxy URL , depending upon the supplied url and proxy environment variables .
* @param serverUrl The server URL where the request will be sent . For example , https : //api.github.com
* /
export function getProxyUrl ( serverUrl : string ) : string {
2020-04-11 15:04:55 +00:00
let proxyUrl = pm . getProxyUrl ( new URL ( serverUrl ) )
2020-04-10 13:50:06 +00:00
return proxyUrl ? proxyUrl . href : ''
2020-01-23 04:09:35 +00:00
}
2020-04-10 13:50:06 +00:00
const HttpRedirectCodes : number [ ] = [
HttpCodes . MovedPermanently ,
HttpCodes . ResourceMoved ,
HttpCodes . SeeOther ,
HttpCodes . TemporaryRedirect ,
HttpCodes . PermanentRedirect
]
const HttpResponseRetryCodes : number [ ] = [
HttpCodes . BadGateway ,
HttpCodes . ServiceUnavailable ,
HttpCodes . GatewayTimeout
]
const RetryableHttpVerbs : string [ ] = [ 'OPTIONS' , 'GET' , 'DELETE' , 'HEAD' ]
const ExponentialBackoffCeiling = 10
const ExponentialBackoffTimeSlice = 5
2020-01-09 23:32:59 +00:00
2020-08-18 02:16:30 +00:00
export class HttpClientError extends Error {
constructor ( message : string , statusCode : number ) {
super ( message )
this . name = 'HttpClientError'
this . statusCode = statusCode
Object . setPrototypeOf ( this , HttpClientError . prototype )
}
public statusCode : number
public result? : any
}
2020-01-09 23:32:59 +00:00
export class HttpClientResponse implements ifm . IHttpClientResponse {
2020-04-10 13:50:06 +00:00
constructor ( message : http.IncomingMessage ) {
this . message = message
}
public message : http.IncomingMessage
readBody ( ) : Promise < string > {
return new Promise < string > ( async ( resolve , reject ) = > {
let output = Buffer . alloc ( 0 )
this . message . on ( 'data' , ( chunk : Buffer ) = > {
output = Buffer . concat ( [ output , chunk ] )
} )
this . message . on ( 'end' , ( ) = > {
resolve ( output . toString ( ) )
} )
} )
}
2020-01-09 23:32:59 +00:00
}
export function isHttps ( requestUrl : string ) {
2020-04-11 15:04:55 +00:00
let parsedUrl : URL = new URL ( requestUrl )
2020-04-10 13:50:06 +00:00
return parsedUrl . protocol === 'https:'
2020-01-09 23:32:59 +00:00
}
export class HttpClient {
2020-04-10 13:50:06 +00:00
userAgent : string | undefined
handlers : ifm.IRequestHandler [ ]
requestOptions : ifm.IRequestOptions
private _ignoreSslError : boolean = false
private _socketTimeout : number
private _allowRedirects : boolean = true
private _allowRedirectDowngrade : boolean = false
private _maxRedirects : number = 50
private _allowRetries : boolean = false
private _maxRetries : number = 1
private _agent
private _proxyAgent
private _keepAlive : boolean = false
private _disposed : boolean = false
constructor (
userAgent? : string ,
handlers? : ifm.IRequestHandler [ ] ,
requestOptions? : ifm.IRequestOptions
) {
this . userAgent = userAgent
this . handlers = handlers || [ ]
this . requestOptions = requestOptions
if ( requestOptions ) {
if ( requestOptions . ignoreSslError != null ) {
this . _ignoreSslError = requestOptions . ignoreSslError
}
this . _socketTimeout = requestOptions . socketTimeout
if ( requestOptions . allowRedirects != null ) {
this . _allowRedirects = requestOptions . allowRedirects
}
if ( requestOptions . allowRedirectDowngrade != null ) {
this . _allowRedirectDowngrade = requestOptions . allowRedirectDowngrade
}
if ( requestOptions . maxRedirects != null ) {
this . _maxRedirects = Math . max ( requestOptions . maxRedirects , 0 )
}
if ( requestOptions . keepAlive != null ) {
this . _keepAlive = requestOptions . keepAlive
}
if ( requestOptions . allowRetries != null ) {
this . _allowRetries = requestOptions . allowRetries
}
if ( requestOptions . maxRetries != null ) {
this . _maxRetries = requestOptions . maxRetries
}
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
}
public options (
requestUrl : string ,
additionalHeaders? : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
return this . request ( 'OPTIONS' , requestUrl , null , additionalHeaders || { } )
}
public get (
requestUrl : string ,
additionalHeaders? : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
return this . request ( 'GET' , requestUrl , null , additionalHeaders || { } )
}
public del (
requestUrl : string ,
additionalHeaders? : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
return this . request ( 'DELETE' , requestUrl , null , additionalHeaders || { } )
}
public post (
requestUrl : string ,
data : string ,
additionalHeaders? : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
return this . request ( 'POST' , requestUrl , data , additionalHeaders || { } )
}
public patch (
requestUrl : string ,
data : string ,
additionalHeaders? : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
return this . request ( 'PATCH' , requestUrl , data , additionalHeaders || { } )
}
public put (
requestUrl : string ,
data : string ,
additionalHeaders? : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
return this . request ( 'PUT' , requestUrl , data , additionalHeaders || { } )
}
public head (
requestUrl : string ,
additionalHeaders? : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
return this . request ( 'HEAD' , requestUrl , null , additionalHeaders || { } )
}
public sendStream (
verb : string ,
requestUrl : string ,
stream : NodeJS.ReadableStream ,
additionalHeaders? : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
return this . request ( verb , requestUrl , stream , additionalHeaders )
}
/ * *
* Gets a typed object from an endpoint
* Be aware that not found returns a null . Other errors ( 4 xx , 5 xx ) reject the promise
* /
public async getJson < T > (
requestUrl : string ,
additionalHeaders : ifm.IHeaders = { }
) : Promise < ifm.ITypedResponse < T > > {
additionalHeaders [ Headers . Accept ] = this . _getExistingOrDefaultHeader (
additionalHeaders ,
Headers . Accept ,
MediaTypes . ApplicationJson
)
let res : ifm.IHttpClientResponse = await this . get (
requestUrl ,
additionalHeaders
)
return this . _processResponse < T > ( res , this . requestOptions )
}
public async postJson < T > (
requestUrl : string ,
obj : any ,
additionalHeaders : ifm.IHeaders = { }
) : Promise < ifm.ITypedResponse < T > > {
let data : string = JSON . stringify ( obj , null , 2 )
additionalHeaders [ Headers . Accept ] = this . _getExistingOrDefaultHeader (
additionalHeaders ,
Headers . Accept ,
MediaTypes . ApplicationJson
)
additionalHeaders [ Headers . ContentType ] = this . _getExistingOrDefaultHeader (
additionalHeaders ,
Headers . ContentType ,
MediaTypes . ApplicationJson
)
let res : ifm.IHttpClientResponse = await this . post (
requestUrl ,
data ,
additionalHeaders
)
return this . _processResponse < T > ( res , this . requestOptions )
}
public async putJson < T > (
requestUrl : string ,
obj : any ,
additionalHeaders : ifm.IHeaders = { }
) : Promise < ifm.ITypedResponse < T > > {
let data : string = JSON . stringify ( obj , null , 2 )
additionalHeaders [ Headers . Accept ] = this . _getExistingOrDefaultHeader (
additionalHeaders ,
Headers . Accept ,
MediaTypes . ApplicationJson
)
additionalHeaders [ Headers . ContentType ] = this . _getExistingOrDefaultHeader (
additionalHeaders ,
Headers . ContentType ,
MediaTypes . ApplicationJson
)
let res : ifm.IHttpClientResponse = await this . put (
requestUrl ,
data ,
additionalHeaders
)
return this . _processResponse < T > ( res , this . requestOptions )
}
public async patchJson < T > (
requestUrl : string ,
obj : any ,
additionalHeaders : ifm.IHeaders = { }
) : Promise < ifm.ITypedResponse < T > > {
let data : string = JSON . stringify ( obj , null , 2 )
additionalHeaders [ Headers . Accept ] = this . _getExistingOrDefaultHeader (
additionalHeaders ,
Headers . Accept ,
MediaTypes . ApplicationJson
)
additionalHeaders [ Headers . ContentType ] = this . _getExistingOrDefaultHeader (
additionalHeaders ,
Headers . ContentType ,
MediaTypes . ApplicationJson
)
let res : ifm.IHttpClientResponse = await this . patch (
requestUrl ,
data ,
additionalHeaders
)
return this . _processResponse < T > ( res , this . requestOptions )
}
/ * *
* Makes a raw http request .
* All other methods such as get , post , patch , and request ultimately call this .
* Prefer get , del , post and patch
* /
public async request (
verb : string ,
requestUrl : string ,
data : string | NodeJS . ReadableStream ,
headers : ifm.IHeaders
) : Promise < ifm.IHttpClientResponse > {
if ( this . _disposed ) {
throw new Error ( 'Client has already been disposed.' )
2020-01-09 23:32:59 +00:00
}
2020-04-11 15:04:55 +00:00
let parsedUrl = new URL ( requestUrl )
2020-04-10 13:50:06 +00:00
let info : ifm.IRequestInfo = this . _prepareRequest ( verb , parsedUrl , headers )
// Only perform retries on reads since writes may not be idempotent.
let maxTries : number =
this . _allowRetries && RetryableHttpVerbs . indexOf ( verb ) != - 1
? this . _maxRetries + 1
: 1
let numTries : number = 0
let response : HttpClientResponse
while ( numTries < maxTries ) {
response = await this . requestRaw ( info , data )
// Check if it's an authentication challenge
if (
response &&
response . message &&
response . message . statusCode === HttpCodes . Unauthorized
) {
let authenticationHandler : ifm.IRequestHandler
for ( let i = 0 ; i < this . handlers . length ; i ++ ) {
if ( this . handlers [ i ] . canHandleAuthentication ( response ) ) {
authenticationHandler = this . handlers [ i ]
break
}
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
if ( authenticationHandler ) {
return authenticationHandler . handleAuthentication ( this , info , data )
} else {
// We have received an unauthorized response but have no handlers to handle it.
// Let the response return to the caller.
return response
}
}
let redirectsRemaining : number = this . _maxRedirects
while (
HttpRedirectCodes . indexOf ( response . message . statusCode ) != - 1 &&
this . _allowRedirects &&
redirectsRemaining > 0
) {
const redirectUrl : string | null = response . message . headers [ 'location' ]
if ( ! redirectUrl ) {
// if there's no location to redirect to, we won't
break
}
2020-04-11 15:04:55 +00:00
let parsedRedirectUrl = new URL ( redirectUrl )
2020-04-10 13:50:06 +00:00
if (
parsedUrl . protocol == 'https:' &&
parsedUrl . protocol != parsedRedirectUrl . protocol &&
! this . _allowRedirectDowngrade
) {
throw new Error (
'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.'
)
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
// we need to finish reading the response before reassigning response
// which will leak the open socket.
await response . readBody ( )
2020-01-09 23:32:59 +00:00
2020-04-23 20:26:28 +00:00
// strip authorization header if redirected to a different hostname
if ( parsedRedirectUrl . hostname !== parsedUrl . hostname ) {
2020-04-23 20:33:34 +00:00
for ( let header in headers ) {
2020-04-23 20:26:28 +00:00
// header names are case insensitive
2020-04-23 20:33:34 +00:00
if ( header . toLowerCase ( ) === 'authorization' ) {
delete headers [ header ]
2020-04-23 20:26:28 +00:00
}
}
}
2020-04-10 13:50:06 +00:00
// let's make the request with the new redirectUrl
info = this . _prepareRequest ( verb , parsedRedirectUrl , headers )
response = await this . requestRaw ( info , data )
redirectsRemaining --
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
if ( HttpResponseRetryCodes . indexOf ( response . message . statusCode ) == - 1 ) {
// If not a retry code, return immediately instead of retrying
return response
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
numTries += 1
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
if ( numTries < maxTries ) {
await response . readBody ( )
await this . _performExponentialBackoff ( numTries )
}
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
return response
}
2020-02-01 23:39:08 +00:00
2020-04-10 13:50:06 +00:00
/ * *
* Needs to be called if keepAlive is set to true in request options .
* /
public dispose() {
if ( this . _agent ) {
this . _agent . destroy ( )
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
this . _disposed = true
}
/ * *
* Raw request .
* @param info
* @param data
* /
public requestRaw (
info : ifm.IRequestInfo ,
data : string | NodeJS . ReadableStream
) : Promise < ifm.IHttpClientResponse > {
return new Promise < ifm.IHttpClientResponse > ( ( resolve , reject ) = > {
let callbackForResult = function (
err : any ,
res : ifm.IHttpClientResponse
) {
if ( err ) {
reject ( err )
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
resolve ( res )
}
this . requestRawWithCallback ( info , data , callbackForResult )
} )
}
/ * *
* Raw request with callback .
* @param info
* @param data
* @param onResult
* /
public requestRawWithCallback (
info : ifm.IRequestInfo ,
data : string | NodeJS . ReadableStream ,
onResult : ( err : any , res : ifm.IHttpClientResponse ) = > void
) : void {
let socket
if ( typeof data === 'string' ) {
info . options . headers [ 'Content-Length' ] = Buffer . byteLength ( data , 'utf8' )
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
let callbackCalled : boolean = false
let handleResult = ( err : any , res : HttpClientResponse ) = > {
if ( ! callbackCalled ) {
callbackCalled = true
onResult ( err , res )
}
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
let req : http.ClientRequest = info . httpModule . request (
info . options ,
( msg : http.IncomingMessage ) = > {
let res : HttpClientResponse = new HttpClientResponse ( msg )
handleResult ( null , res )
}
)
req . on ( 'socket' , sock = > {
socket = sock
} )
// If we ever get disconnected, we want the socket to timeout eventually
req . setTimeout ( this . _socketTimeout || 3 * 60000 , ( ) = > {
if ( socket ) {
socket . end ( )
}
handleResult ( new Error ( 'Request timeout: ' + info . options . path ) , null )
} )
req . on ( 'error' , function ( err ) {
// err has statusCode property
// res should have headers
handleResult ( err , null )
} )
if ( data && typeof data === 'string' ) {
req . write ( data , 'utf8' )
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
if ( data && typeof data !== 'string' ) {
data . on ( 'close' , function ( ) {
req . end ( )
} )
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
data . pipe ( req )
} else {
req . end ( )
}
}
/ * *
* Gets an http agent . This function is useful when you need an http agent that handles
* routing through a proxy server - depending upon the url and proxy environment variables .
* @param serverUrl The server URL where the request will be sent . For example , https : //api.github.com
* /
public getAgent ( serverUrl : string ) : http . Agent {
2020-04-11 15:04:55 +00:00
let parsedUrl = new URL ( serverUrl )
2020-04-10 13:50:06 +00:00
return this . _getAgent ( parsedUrl )
}
private _prepareRequest (
method : string ,
2020-04-11 15:04:55 +00:00
requestUrl : URL ,
2020-04-10 13:50:06 +00:00
headers : ifm.IHeaders
) : ifm . IRequestInfo {
const info : ifm.IRequestInfo = < ifm.IRequestInfo > { }
info . parsedUrl = requestUrl
const usingSsl : boolean = info . parsedUrl . protocol === 'https:'
info . httpModule = usingSsl ? https : http
const defaultPort : number = usingSsl ? 443 : 80
info . options = < http.RequestOptions > { }
info . options . host = info . parsedUrl . hostname
info . options . port = info . parsedUrl . port
? parseInt ( info . parsedUrl . port )
: defaultPort
info . options . path =
( info . parsedUrl . pathname || '' ) + ( info . parsedUrl . search || '' )
info . options . method = method
info . options . headers = this . _mergeHeaders ( headers )
if ( this . userAgent != null ) {
info . options . headers [ 'user-agent' ] = this . userAgent
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
info . options . agent = this . _getAgent ( info . parsedUrl )
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
// gives handlers an opportunity to participate
if ( this . handlers ) {
this . handlers . forEach ( handler = > {
handler . prepareRequest ( info . options )
} )
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
return info
}
2020-01-17 04:49:18 +00:00
2020-04-10 13:50:06 +00:00
private _mergeHeaders ( headers : ifm.IHeaders ) : ifm . IHeaders {
const lowercaseKeys = obj = >
Object . keys ( obj ) . reduce ( ( c , k ) = > ( ( c [ k . toLowerCase ( ) ] = obj [ k ] ) , c ) , { } )
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
if ( this . requestOptions && this . requestOptions . headers ) {
return Object . assign (
{ } ,
lowercaseKeys ( this . requestOptions . headers ) ,
lowercaseKeys ( headers )
)
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
return lowercaseKeys ( headers || { } )
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
private _getExistingOrDefaultHeader (
additionalHeaders : ifm.IHeaders ,
header : string ,
_default : string
) {
const lowercaseKeys = obj = >
Object . keys ( obj ) . reduce ( ( c , k ) = > ( ( c [ k . toLowerCase ( ) ] = obj [ k ] ) , c ) , { } )
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
let clientHeader : string
if ( this . requestOptions && this . requestOptions . headers ) {
clientHeader = lowercaseKeys ( this . requestOptions . headers ) [ header ]
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
return additionalHeaders [ header ] || clientHeader || _default
}
2020-01-09 23:32:59 +00:00
2020-04-11 15:04:55 +00:00
private _getAgent ( parsedUrl : URL ) : http . Agent {
2020-04-10 13:50:06 +00:00
let agent
2020-04-11 15:04:55 +00:00
let proxyUrl : URL = pm . getProxyUrl ( parsedUrl )
2020-04-10 13:50:06 +00:00
let useProxy = proxyUrl && proxyUrl . hostname
2020-02-04 20:27:30 +00:00
2020-04-10 13:50:06 +00:00
if ( this . _keepAlive && useProxy ) {
agent = this . _proxyAgent
2020-02-04 19:19:46 +00:00
}
2020-04-10 13:50:06 +00:00
if ( this . _keepAlive && ! useProxy ) {
agent = this . _agent
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
// if agent is already assigned use that agent.
if ( ! ! agent ) {
return agent
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
const usingSsl = parsedUrl . protocol === 'https:'
let maxSockets = 100
if ( ! ! this . requestOptions ) {
maxSockets = this . requestOptions . maxSockets || http . globalAgent . maxSockets
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
if ( useProxy ) {
// If using proxy, need tunnel
if ( ! tunnel ) {
tunnel = require ( 'tunnel' )
}
const agentOptions = {
maxSockets : maxSockets ,
keepAlive : this._keepAlive ,
proxy : {
2021-03-24 21:36:53 +00:00
. . . ( ( proxyUrl . username || proxyUrl . password ) && {
proxyAuth : ` ${ proxyUrl . username } : ${ proxyUrl . password } `
} ) ,
2020-04-10 13:50:06 +00:00
host : proxyUrl.hostname ,
port : proxyUrl.port
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
}
let tunnelAgent : Function
const overHttps = proxyUrl . protocol === 'https:'
if ( usingSsl ) {
tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp
} else {
tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp
}
agent = tunnelAgent ( agentOptions )
this . _proxyAgent = agent
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
// if reusing agent across request and tunneling agent isn't assigned create a new agent
if ( this . _keepAlive && ! agent ) {
const options = { keepAlive : this._keepAlive , maxSockets : maxSockets }
agent = usingSsl ? new https . Agent ( options ) : new http . Agent ( options )
this . _agent = agent
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
// if not using private agent and tunnel agent isn't setup then use global agent
if ( ! agent ) {
agent = usingSsl ? https.globalAgent : http.globalAgent
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
if ( usingSsl && this . _ignoreSslError ) {
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
// we have to cast it to any and change it directly
agent . options = Object . assign ( agent . options || { } , {
rejectUnauthorized : false
} )
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
return agent
}
private _performExponentialBackoff ( retryNumber : number ) : Promise < void > {
retryNumber = Math . min ( ExponentialBackoffCeiling , retryNumber )
const ms : number = ExponentialBackoffTimeSlice * Math . pow ( 2 , retryNumber )
return new Promise ( resolve = > setTimeout ( ( ) = > resolve ( ) , ms ) )
}
private static dateTimeDeserializer ( key : any , value : any ) : any {
if ( typeof value === 'string' ) {
let a = new Date ( value )
if ( ! isNaN ( a . valueOf ( ) ) ) {
return a
}
}
2020-01-09 23:32:59 +00:00
2020-04-10 13:50:06 +00:00
return value
}
private async _processResponse < T > (
res : ifm.IHttpClientResponse ,
options : ifm.IRequestOptions
) : Promise < ifm.ITypedResponse < T > > {
return new Promise < ifm.ITypedResponse < T > > ( async ( resolve , reject ) = > {
const statusCode : number = res . message . statusCode
const response : ifm.ITypedResponse < T > = {
statusCode : statusCode ,
result : null ,
headers : { }
}
// not found leads to null obj returned
if ( statusCode == HttpCodes . NotFound ) {
resolve ( response )
}
let obj : any
let contents : string
// get the result from the body
try {
contents = await res . readBody ( )
if ( contents && contents . length > 0 ) {
if ( options && options . deserializeDates ) {
obj = JSON . parse ( contents , HttpClient . dateTimeDeserializer )
} else {
obj = JSON . parse ( contents )
}
response . result = obj
2020-01-09 23:32:59 +00:00
}
2020-04-10 13:50:06 +00:00
response . headers = res . message . headers
} catch ( err ) {
// Invalid resource (contents not json); leaving result obj null
}
// note that 3xx redirects are handled by the http layer.
if ( statusCode > 299 ) {
let msg : string
// if exception/error in body, attempt to get better error
if ( obj && obj . message ) {
msg = obj . message
} else if ( contents && contents . length > 0 ) {
// it may be the case that the exception is in the body message as string
msg = contents
} else {
msg = 'Failed request: (' + statusCode + ')'
}
2020-01-09 23:32:59 +00:00
2020-08-18 02:16:30 +00:00
let err = new HttpClientError ( msg , statusCode )
err . result = response . result
2020-02-01 23:39:08 +00:00
2020-04-10 13:50:06 +00:00
reject ( err )
} else {
resolve ( response )
}
} )
}
2020-01-09 23:32:59 +00:00
}