UNPKG

@aws/cloudfront-hosting-toolkit

Version:

CloudFront Hosting Toolkit offers the convenience of a managed frontend hosting service while retaining full control over the hosting and deployment infrastructure to make it your own.

147 lines (146 loc) 5.83 kB
import { HttpResponse } from "@smithy/protocol-http"; import { buildQueryString } from "@smithy/querystring-builder"; import { Agent as hAgent, request as hRequest } from "http"; import { Agent as hsAgent, request as hsRequest } from "https"; import { NODEJS_TIMEOUT_ERROR_CODES } from "./constants"; import { getTransformedHeaders } from "./get-transformed-headers"; import { setConnectionTimeout } from "./set-connection-timeout"; import { setSocketKeepAlive } from "./set-socket-keep-alive"; import { setSocketTimeout } from "./set-socket-timeout"; import { writeRequestBody } from "./write-request-body"; export const DEFAULT_REQUEST_TIMEOUT = 0; export class NodeHttpHandler { static create(instanceOrOptions) { if (typeof instanceOrOptions?.handle === "function") { return instanceOrOptions; } return new NodeHttpHandler(instanceOrOptions); } constructor(options) { this.metadata = { handlerProtocol: "http/1.1" }; this.configProvider = new Promise((resolve, reject) => { if (typeof options === "function") { options() .then((_options) => { resolve(this.resolveDefaultConfig(_options)); }) .catch(reject); } else { resolve(this.resolveDefaultConfig(options)); } }); } resolveDefaultConfig(options) { const { requestTimeout, connectionTimeout, socketTimeout, httpAgent, httpsAgent } = options || {}; const keepAlive = true; const maxSockets = 50; return { connectionTimeout, requestTimeout: requestTimeout ?? socketTimeout, httpAgent: httpAgent || new hAgent({ keepAlive, maxSockets }), httpsAgent: httpsAgent || new hsAgent({ keepAlive, maxSockets }), }; } destroy() { this.config?.httpAgent?.destroy(); this.config?.httpsAgent?.destroy(); } async handle(request, { abortSignal } = {}) { if (!this.config) { this.config = await this.configProvider; } return new Promise((_resolve, _reject) => { let writeRequestBodyPromise = undefined; const resolve = async (arg) => { await writeRequestBodyPromise; _resolve(arg); }; const reject = async (arg) => { await writeRequestBodyPromise; _reject(arg); }; if (!this.config) { throw new Error("Node HTTP request handler config is not resolved"); } if (abortSignal?.aborted) { const abortError = new Error("Request aborted"); abortError.name = "AbortError"; reject(abortError); return; } const isSSL = request.protocol === "https:"; const queryString = buildQueryString(request.query || {}); let auth = undefined; if (request.username != null || request.password != null) { const username = request.username ?? ""; const password = request.password ?? ""; auth = `${username}:${password}`; } let path = request.path; if (queryString) { path += `?${queryString}`; } if (request.fragment) { path += `#${request.fragment}`; } const nodeHttpsOptions = { headers: request.headers, host: request.hostname, method: request.method, path, port: request.port, agent: isSSL ? this.config.httpsAgent : this.config.httpAgent, auth, }; const requestFunc = isSSL ? hsRequest : hRequest; const req = requestFunc(nodeHttpsOptions, (res) => { const httpResponse = new HttpResponse({ statusCode: res.statusCode || -1, reason: res.statusMessage, headers: getTransformedHeaders(res.headers), body: res, }); resolve({ response: httpResponse }); }); req.on("error", (err) => { if (NODEJS_TIMEOUT_ERROR_CODES.includes(err.code)) { reject(Object.assign(err, { name: "TimeoutError" })); } else { reject(err); } }); setConnectionTimeout(req, reject, this.config.connectionTimeout); setSocketTimeout(req, reject, this.config.requestTimeout); if (abortSignal) { abortSignal.onabort = () => { req.abort(); const abortError = new Error("Request aborted"); abortError.name = "AbortError"; reject(abortError); }; } const httpAgent = nodeHttpsOptions.agent; if (typeof httpAgent === "object" && "keepAlive" in httpAgent) { setSocketKeepAlive(req, { keepAlive: httpAgent.keepAlive, keepAliveMsecs: httpAgent.keepAliveMsecs, }); } writeRequestBodyPromise = writeRequestBody(req, request, this.config.requestTimeout).catch(_reject); }); } updateHttpClientConfig(key, value) { this.config = undefined; this.configProvider = this.configProvider.then((config) => { return { ...config, [key]: value, }; }); } httpHandlerConfigs() { return this.config ?? {}; } }