UNPKG

6.29 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.NodeHttp2Handler = void 0;
4const protocol_http_1 = require("@aws-sdk/protocol-http");
5const querystring_builder_1 = require("@aws-sdk/querystring-builder");
6const http2_1 = require("http2");
7const get_transformed_headers_1 = require("./get-transformed-headers");
8const write_request_body_1 = require("./write-request-body");
9class NodeHttp2Handler {
10 constructor(options) {
11 this.metadata = { handlerProtocol: "h2" };
12 this.configProvider = new Promise((resolve, reject) => {
13 if (typeof options === "function") {
14 options()
15 .then((opts) => {
16 resolve(opts || {});
17 })
18 .catch(reject);
19 }
20 else {
21 resolve(options || {});
22 }
23 });
24 this.sessionCache = new Map();
25 }
26 destroy() {
27 for (const sessions of this.sessionCache.values()) {
28 sessions.forEach((session) => this.destroySession(session));
29 }
30 this.sessionCache.clear();
31 }
32 async handle(request, { abortSignal } = {}) {
33 if (!this.config) {
34 this.config = await this.configProvider;
35 }
36 const { requestTimeout, disableConcurrentStreams } = this.config;
37 return new Promise((resolve, rejectOriginal) => {
38 let fulfilled = false;
39 if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
40 fulfilled = true;
41 const abortError = new Error("Request aborted");
42 abortError.name = "AbortError";
43 rejectOriginal(abortError);
44 return;
45 }
46 const { hostname, method, port, protocol, path, query } = request;
47 const authority = `${protocol}//${hostname}${port ? `:${port}` : ""}`;
48 const session = this.getSession(authority, disableConcurrentStreams || false);
49 const reject = (err) => {
50 if (disableConcurrentStreams) {
51 this.destroySession(session);
52 }
53 fulfilled = true;
54 rejectOriginal(err);
55 };
56 const queryString = (0, querystring_builder_1.buildQueryString)(query || {});
57 const req = session.request({
58 ...request.headers,
59 [http2_1.constants.HTTP2_HEADER_PATH]: queryString ? `${path}?${queryString}` : path,
60 [http2_1.constants.HTTP2_HEADER_METHOD]: method,
61 });
62 session.ref();
63 req.on("response", (headers) => {
64 const httpResponse = new protocol_http_1.HttpResponse({
65 statusCode: headers[":status"] || -1,
66 headers: (0, get_transformed_headers_1.getTransformedHeaders)(headers),
67 body: req,
68 });
69 fulfilled = true;
70 resolve({ response: httpResponse });
71 if (disableConcurrentStreams) {
72 session.close();
73 this.deleteSessionFromCache(authority, session);
74 }
75 });
76 if (requestTimeout) {
77 req.setTimeout(requestTimeout, () => {
78 req.close();
79 const timeoutError = new Error(`Stream timed out because of no activity for ${requestTimeout} ms`);
80 timeoutError.name = "TimeoutError";
81 reject(timeoutError);
82 });
83 }
84 if (abortSignal) {
85 abortSignal.onabort = () => {
86 req.close();
87 const abortError = new Error("Request aborted");
88 abortError.name = "AbortError";
89 reject(abortError);
90 };
91 }
92 req.on("frameError", (type, code, id) => {
93 reject(new Error(`Frame type id ${type} in stream id ${id} has failed with code ${code}.`));
94 });
95 req.on("error", reject);
96 req.on("aborted", () => {
97 reject(new Error(`HTTP/2 stream is abnormally aborted in mid-communication with result code ${req.rstCode}.`));
98 });
99 req.on("close", () => {
100 session.unref();
101 if (disableConcurrentStreams) {
102 session.destroy();
103 }
104 if (!fulfilled) {
105 reject(new Error("Unexpected error: http2 request did not get a response"));
106 }
107 });
108 (0, write_request_body_1.writeRequestBody)(req, request);
109 });
110 }
111 getSession(authority, disableConcurrentStreams) {
112 var _a;
113 const sessionCache = this.sessionCache;
114 const existingSessions = sessionCache.get(authority) || [];
115 if (existingSessions.length > 0 && !disableConcurrentStreams)
116 return existingSessions[0];
117 const newSession = (0, http2_1.connect)(authority);
118 newSession.unref();
119 const destroySessionCb = () => {
120 this.destroySession(newSession);
121 this.deleteSessionFromCache(authority, newSession);
122 };
123 newSession.on("goaway", destroySessionCb);
124 newSession.on("error", destroySessionCb);
125 newSession.on("frameError", destroySessionCb);
126 newSession.on("close", () => this.deleteSessionFromCache(authority, newSession));
127 if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.sessionTimeout) {
128 newSession.setTimeout(this.config.sessionTimeout, destroySessionCb);
129 }
130 existingSessions.push(newSession);
131 sessionCache.set(authority, existingSessions);
132 return newSession;
133 }
134 destroySession(session) {
135 if (!session.destroyed) {
136 session.destroy();
137 }
138 }
139 deleteSessionFromCache(authority, session) {
140 const existingSessions = this.sessionCache.get(authority) || [];
141 if (!existingSessions.includes(session)) {
142 return;
143 }
144 this.sessionCache.set(authority, existingSessions.filter((s) => s !== session));
145 }
146}
147exports.NodeHttp2Handler = NodeHttp2Handler;