160 lines
6.6 KiB
JavaScript
160 lines
6.6 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.NodeHttp2Handler = void 0;
|
|
const protocol_http_1 = require("@smithy/protocol-http");
|
|
const querystring_builder_1 = require("@smithy/querystring-builder");
|
|
const http2_1 = require("http2");
|
|
const get_transformed_headers_1 = require("./get-transformed-headers");
|
|
const node_http2_connection_manager_1 = require("./node-http2-connection-manager");
|
|
const write_request_body_1 = require("./write-request-body");
|
|
class NodeHttp2Handler {
|
|
constructor(options) {
|
|
this.metadata = { handlerProtocol: "h2" };
|
|
this.connectionManager = new node_http2_connection_manager_1.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, _reject) => {
|
|
var _a, _b, _c;
|
|
let fulfilled = false;
|
|
let writeRequestBodyPromise = undefined;
|
|
const resolve = async (arg) => {
|
|
await writeRequestBodyPromise;
|
|
_resolve(arg);
|
|
};
|
|
const reject = async (arg) => {
|
|
await writeRequestBodyPromise;
|
|
_reject(arg);
|
|
};
|
|
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
fulfilled = true;
|
|
const abortError = new Error("Request aborted");
|
|
abortError.name = "AbortError";
|
|
reject(abortError);
|
|
return;
|
|
}
|
|
const { hostname, method, port, protocol, query } = request;
|
|
let auth = "";
|
|
if (request.username != null || request.password != null) {
|
|
const username = (_a = request.username) !== null && _a !== void 0 ? _a : "";
|
|
const password = (_b = request.password) !== null && _b !== void 0 ? _b : "";
|
|
auth = `${username}:${password}@`;
|
|
}
|
|
const authority = `${protocol}//${auth}${hostname}${port ? `:${port}` : ""}`;
|
|
const requestContext = { destination: new URL(authority) };
|
|
const session = this.connectionManager.lease(requestContext, {
|
|
requestTimeout: (_c = this.config) === null || _c === void 0 ? void 0 : _c.sessionTimeout,
|
|
disableConcurrentStreams: disableConcurrentStreams || false,
|
|
});
|
|
const rejectWithDestroy = (err) => {
|
|
if (disableConcurrentStreams) {
|
|
this.destroySession(session);
|
|
}
|
|
fulfilled = true;
|
|
reject(err);
|
|
};
|
|
const queryString = (0, querystring_builder_1.buildQueryString)(query || {});
|
|
let path = request.path;
|
|
if (queryString) {
|
|
path += `?${queryString}`;
|
|
}
|
|
if (request.fragment) {
|
|
path += `#${request.fragment}`;
|
|
}
|
|
const req = session.request({
|
|
...request.headers,
|
|
[http2_1.constants.HTTP2_HEADER_PATH]: path,
|
|
[http2_1.constants.HTTP2_HEADER_METHOD]: method,
|
|
});
|
|
session.ref();
|
|
req.on("response", (headers) => {
|
|
const httpResponse = new protocol_http_1.HttpResponse({
|
|
statusCode: headers[":status"] || -1,
|
|
headers: (0, get_transformed_headers_1.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";
|
|
rejectWithDestroy(timeoutError);
|
|
});
|
|
}
|
|
if (abortSignal) {
|
|
abortSignal.onabort = () => {
|
|
req.close();
|
|
const abortError = new Error("Request aborted");
|
|
abortError.name = "AbortError";
|
|
rejectWithDestroy(abortError);
|
|
};
|
|
}
|
|
req.on("frameError", (type, code, id) => {
|
|
rejectWithDestroy(new Error(`Frame type id ${type} in stream id ${id} has failed with code ${code}.`));
|
|
});
|
|
req.on("error", rejectWithDestroy);
|
|
req.on("aborted", () => {
|
|
rejectWithDestroy(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) {
|
|
rejectWithDestroy(new Error("Unexpected error: http2 request did not get a response"));
|
|
}
|
|
});
|
|
writeRequestBodyPromise = (0, write_request_body_1.writeRequestBody)(req, request, requestTimeout);
|
|
});
|
|
}
|
|
updateHttpClientConfig(key, value) {
|
|
this.config = undefined;
|
|
this.configProvider = this.configProvider.then((config) => {
|
|
return {
|
|
...config,
|
|
[key]: value,
|
|
};
|
|
});
|
|
}
|
|
httpHandlerConfigs() {
|
|
var _a;
|
|
return (_a = this.config) !== null && _a !== void 0 ? _a : {};
|
|
}
|
|
destroySession(session) {
|
|
if (!session.destroyed) {
|
|
session.destroy();
|
|
}
|
|
}
|
|
}
|
|
exports.NodeHttp2Handler = NodeHttp2Handler;
|