in src/connection/UndiciConnection.ts [117:238]
async request (params: ConnectionRequestParams, options: any): Promise<any> {
const maxResponseSize = options.maxResponseSize ?? MAX_STRING_LENGTH
const maxCompressedResponseSize = options.maxCompressedResponseSize ?? MAX_BUFFER_LENGTH
const requestParams = {
method: params.method,
path: params.path + (params.querystring == null || params.querystring === '' ? '' : `?${params.querystring}`),
headers: Object.assign({}, this.headers, params.headers),
body: params.body,
signal: options.signal ?? new AbortController().signal
}
if (requestParams.path[0] !== '/') {
requestParams.path = `/${requestParams.path}`
}
// undici does not support per-request timeouts,
// to address this issue, we default to the constructor
// timeout (which is handled by undici) and create a local
// setTimeout callback if the request-specific timeout
// is different from the constructor timeout.
let timedout = false
let timeoutId
if (options.timeout != null && options.timeout !== this.timeout) {
timeoutId = setTimeout(() => {
timedout = true
requestParams.signal.dispatchEvent(new Event('abort'))
}, options.timeout)
}
// https://github.com/nodejs/node/commit/b961d9fd83
if (INVALID_PATH_REGEX.test(requestParams.path)) {
throw new TypeError(`ERR_UNESCAPED_CHARACTERS: ${requestParams.path}`)
}
debug('Starting a new request', params)
let response
try {
response = await this.pool.request(requestParams)
if (timeoutId != null) clearTimeout(timeoutId)
} catch (err: any) {
if (timeoutId != null) clearTimeout(timeoutId)
switch (err.code) {
case 'UND_ERR_ABORTED':
case DOMException.ABORT_ERR:
throw (timedout ? new TimeoutError('Request timed out') : new RequestAbortedError('Request aborted'))
case 'UND_ERR_HEADERS_TIMEOUT':
throw new TimeoutError('Request timed out')
case 'UND_ERR_SOCKET':
throw new ConnectionError(`${err.message} - Local: ${err.socket?.localAddress ?? 'unknown'}:${err.socket?.localPort ?? 'unknown'}, Remote: ${err.socket?.remoteAddress ?? 'unknown'}:${err.socket?.remotePort ?? 'unknown'}`) // eslint-disable-line
default:
throw new ConnectionError(err.message)
}
}
if (options.asStream === true) {
return {
statusCode: response.statusCode,
headers: response.headers,
body: response.body
}
}
// @ts-expect-error Assume header is not string[] for now.
const contentEncoding = (response.headers['content-encoding'] ?? '').toLowerCase()
const isCompressed = contentEncoding.includes('gzip') || contentEncoding.includes('deflate') // eslint-disable-line
const bodyIsBinary = isBinary(response.headers['content-type'] ?? '')
/* istanbul ignore else */
if (response.headers['content-length'] !== undefined) {
const contentLength = Number(response.headers['content-length'])
if (isCompressed && contentLength > maxCompressedResponseSize) { // eslint-disable-line
response.body.destroy()
throw new RequestAbortedError(`The content length (${contentLength}) is bigger than the maximum allowed buffer (${maxCompressedResponseSize})`)
} else if (contentLength > maxResponseSize) {
response.body.destroy()
throw new RequestAbortedError(`The content length (${contentLength}) is bigger than the maximum allowed string (${maxResponseSize})`)
}
}
this.diagnostic.emit('deserialization', null, options)
try {
if (isCompressed || bodyIsBinary) { // eslint-disable-line
let currentLength = 0
const payload: Buffer[] = []
for await (const chunk of response.body) {
currentLength += Buffer.byteLength(chunk)
if (currentLength > maxCompressedResponseSize) {
response.body.destroy()
throw new RequestAbortedError(`The content length (${currentLength}) is bigger than the maximum allowed buffer (${maxCompressedResponseSize})`)
}
payload.push(chunk)
}
return {
statusCode: response.statusCode,
headers: response.headers,
body: Buffer.concat(payload)
}
} else {
let payload = ''
let currentLength = 0
response.body.setEncoding('utf8')
for await (const chunk of response.body) {
currentLength += Buffer.byteLength(chunk)
if (currentLength > maxResponseSize) {
response.body.destroy()
throw new RequestAbortedError(`The content length (${currentLength}) is bigger than the maximum allowed string (${maxResponseSize})`)
}
payload += chunk as string
}
return {
statusCode: response.statusCode,
headers: response.headers,
body: payload
}
}
} catch (err: any) {
if (err.name === 'RequestAbortedError') {
throw err
}
throw new ConnectionError(err.message)
}
}