UNPKG

cdk-amazon-chime-resources

Version:

![Experimental](https://img.shields.io/badge/experimental-important.svg?style=for-the-badge)

120 lines (119 loc) 5.02 kB
import { HttpResponse } from "@aws-sdk/protocol-http"; import { buildQueryString } from "@aws-sdk/querystring-builder"; import { constants } from "http2"; import { getTransformedHeaders } from "./get-transformed-headers"; import { NodeHttp2ConnectionManager } from "./node-http2-connection-manager"; import { writeRequestBody } from "./write-request-body"; export class NodeHttp2Handler { constructor(options) { this.metadata = { handlerProtocol: "h2" }; this.connectionManager = new NodeHttp2ConnectionManager({}); this.configProvider = new Promise((resolve, reject) => { if (typeof options === "function") { options() .then((opts) => { resolve(opts || {}); }) .catch(reject); } else { resolve(options || {}); } }); } destroy() { this.connectionManager.destroy(); } async handle(request, { abortSignal } = {}) { if (!this.config) { this.config = await this.configProvider; this.connectionManager.setDisableConcurrentStreams(this.config.disableConcurrentStreams || false); if (this.config.maxConcurrentStreams) { this.connectionManager.setMaxConcurrentStreams(this.config.maxConcurrentStreams); } } const { requestTimeout, disableConcurrentStreams } = this.config; return new Promise((resolve, rejectOriginal) => { let fulfilled = false; if (abortSignal?.aborted) { fulfilled = true; const abortError = new Error("Request aborted"); abortError.name = "AbortError"; rejectOriginal(abortError); return; } const { hostname, method, port, protocol, path, query } = request; const authority = `${protocol}//${hostname}${port ? `:${port}` : ""}`; const requestContext = { destination: new URL(authority) }; const session = this.connectionManager.lease(requestContext, { requestTimeout: this.config?.sessionTimeout, disableConcurrentStreams: disableConcurrentStreams || false, }); const reject = (err) => { if (disableConcurrentStreams) { this.destroySession(session); } fulfilled = true; rejectOriginal(err); }; const queryString = buildQueryString(query || {}); const req = session.request({ ...request.headers, [constants.HTTP2_HEADER_PATH]: queryString ? `${path}?${queryString}` : path, [constants.HTTP2_HEADER_METHOD]: method, }); session.ref(); req.on("response", (headers) => { const httpResponse = new HttpResponse({ statusCode: headers[":status"] || -1, headers: getTransformedHeaders(headers), body: req, }); fulfilled = true; resolve({ response: httpResponse }); if (disableConcurrentStreams) { session.close(); this.connectionManager.deleteSession(authority, session); } }); if (requestTimeout) { req.setTimeout(requestTimeout, () => { req.close(); const timeoutError = new Error(`Stream timed out because of no activity for ${requestTimeout} ms`); timeoutError.name = "TimeoutError"; reject(timeoutError); }); } if (abortSignal) { abortSignal.onabort = () => { req.close(); const abortError = new Error("Request aborted"); abortError.name = "AbortError"; reject(abortError); }; } req.on("frameError", (type, code, id) => { reject(new Error(`Frame type id ${type} in stream id ${id} has failed with code ${code}.`)); }); req.on("error", reject); req.on("aborted", () => { reject(new Error(`HTTP/2 stream is abnormally aborted in mid-communication with result code ${req.rstCode}.`)); }); req.on("close", () => { session.unref(); if (disableConcurrentStreams) { session.destroy(); } if (!fulfilled) { reject(new Error("Unexpected error: http2 request did not get a response")); } }); writeRequestBody(req, request); }); } destroySession(session) { if (!session.destroyed) { session.destroy(); } } }