mappersmith
Version:
It is a lightweight rest client for node.js and the browser
200 lines (160 loc) • 4.95 kB
text/typescript
import url from 'url'
import http from 'http'
import https from 'https'
import { assign } from '../utils/index'
import { Gateway } from './gateway'
import type { Method, HTTPGatewayConfiguration, HTTPRequestParams } from './types'
import Response from '../response'
import { createTimeoutError } from './timeout-error'
import type { Primitive } from '../types'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Chunk = any
export class HTTP extends Gateway {
private canceled = false
get() {
this.performRequest('get')
}
head() {
this.performRequest('head')
}
post() {
this.performRequest('post')
}
put() {
this.performRequest('put')
}
patch() {
this.performRequest('patch')
}
delete() {
this.performRequest('delete')
}
performRequest(requestMethod: Method) {
const headers: Record<string, Primitive> = {}
// FIXME: Deprecated API
// eslint-disable-next-line n/no-deprecated-api
const defaults = url.parse(this.request.url())
const method = this.shouldEmulateHTTP() ? 'post' : requestMethod
const body = this.prepareBody(requestMethod, headers)
const timeout = this.request.timeout()
const signal = this.request.signal()
this.canceled = false
if (
body &&
typeof body !== 'boolean' &&
typeof body !== 'number' &&
typeof body.length === 'number'
) {
headers['content-length'] = Buffer.byteLength(body)
}
const handler = defaults.protocol === 'https:' ? https : http
const requestParams: http.RequestOptions = assign(defaults, {
method,
headers: assign(headers, this.request.headers() as http.OutgoingHttpHeaders),
})
const auth = this.request.auth()
if (auth) {
const username = auth.username || ''
const password = auth.password || ''
requestParams['auth'] = `${username}:${password}`
}
const httpOptions = this.options().HTTP
if (httpOptions.useSocketConnectionTimeout) {
requestParams['timeout'] = timeout
}
if (httpOptions.configure) {
assign(requestParams, httpOptions.configure(requestParams))
}
if (httpOptions.onRequestWillStart) {
httpOptions.onRequestWillStart(requestParams)
}
if (signal) {
requestParams.signal = signal
}
const httpRequest = handler.request(requestParams, (httpResponse) =>
this.onResponse(httpResponse, httpOptions, requestParams)
)
httpRequest.on('socket', (socket) => {
if (httpOptions.onRequestSocketAssigned) {
httpOptions.onRequestSocketAssigned(requestParams)
}
if (httpRequest.reusedSocket) {
return
}
if (httpOptions.onSocketLookup) {
socket.on('lookup', () => {
httpOptions.onSocketLookup?.(requestParams)
})
}
if (httpOptions.onSocketConnect) {
socket.on('connect', () => {
httpOptions.onSocketConnect?.(requestParams)
})
}
if (httpOptions.onSocketSecureConnect) {
socket.on('secureConnect', () => {
httpOptions.onSocketSecureConnect?.(requestParams)
})
}
})
httpRequest.on('error', (e) => this.onError(e))
body && httpRequest.write(body)
if (timeout) {
if (!httpOptions.useSocketConnectionTimeout) {
httpRequest.setTimeout(timeout)
}
httpRequest.on('timeout', () => {
this.canceled = true
httpRequest.abort()
const error = createTimeoutError(`Timeout (${timeout}ms)`)
this.dispatchClientError(error.message, error)
})
}
httpRequest.end()
}
onResponse(
httpResponse: http.IncomingMessage,
httpOptions: Partial<HTTPGatewayConfiguration>,
requestParams: HTTPRequestParams
) {
const rawData: Chunk[] = []
if (!this.request.isBinary()) {
httpResponse.setEncoding('utf8')
}
httpResponse.once('readable', () => {
if (httpOptions.onResponseReadable) {
httpOptions.onResponseReadable(requestParams)
}
})
httpResponse
.on('data', (chunk) => rawData.push(chunk))
.on('end', () => {
if (this.canceled) {
return
}
this.dispatchResponse(this.createResponse(httpResponse, rawData))
})
httpResponse.on('end', () => {
if (httpOptions.onResponseEnd) {
httpOptions.onResponseEnd(requestParams)
}
})
}
onError(e: Error) {
if (this.canceled) {
return
}
this.dispatchClientError(e.message, e)
}
createResponse(httpResponse: http.IncomingMessage, rawData: Chunk) {
const responseData = this.request.isBinary() ? Buffer.concat(rawData) : rawData.join('')
return new Response(
this.request,
httpResponse.statusCode as number,
responseData,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
httpResponse.headers as any
)
}
}
export default HTTP