From 9c6cde855fce0eb012f64a305fcc38c0d16011b9 Mon Sep 17 00:00:00 2001 From: AllanFly120 Date: Wed, 4 Dec 2019 16:22:07 -0800 Subject: [PATCH] fix: support custom agent in node http handler (#489) * feat(node-http-handler): support custom agent * fix: supply httpHandlerOptions with client.send() instead of httpOptions * feat: remove Browser&Node handler options interface from types package Because these interfaces are constructor interface for individual http handler, they only need to be exposed from its own http handler package --- .../client-rds-data/RdsDataServiceClient.ts | 6 +- .../commands/ExecuteStatementCommand.ts | 4 +- .../src/fetch-http-handler.ts | 4 +- .../src/node-http-handler.spec.ts | 40 +++++++++---- .../src/node-http-handler.ts | 45 +++++++++++--- .../src/node-http2-handler.ts | 22 ++++++- packages/protocol-http/src/httpHandler.ts | 4 +- packages/types/src/http.ts | 59 ------------------- 8 files changed, 94 insertions(+), 90 deletions(-) diff --git a/clients/client-rds-data/RdsDataServiceClient.ts b/clients/client-rds-data/RdsDataServiceClient.ts index 6935a7f1ad82..8ed6878d89aa 100644 --- a/clients/client-rds-data/RdsDataServiceClient.ts +++ b/clients/client-rds-data/RdsDataServiceClient.ts @@ -55,7 +55,7 @@ import { Client as SmithyClient, SmithyResolvedConfiguration } from "@aws-sdk/smithy-client"; -import { HttpOptions as __HttpOptions } from "@aws-sdk/types"; +import { HttpHandlerOptions as __HttpHandlerOptions } from "@aws-sdk/types"; export type ServiceInputTypes = | RollbackTransactionRequest @@ -158,7 +158,7 @@ export type RdsDataServiceConfig = RDSDataRuntimeDependencies & UserAgentInputConfig; export type RdsDataServiceResolvedConfig = SmithyResolvedConfiguration< - __HttpOptions + __HttpHandlerOptions > & Required & AwsAuthResolvedConfig & @@ -168,7 +168,7 @@ export type RdsDataServiceResolvedConfig = SmithyResolvedConfiguration< UserAgentResolvedConfig; export class RdsDataService extends SmithyClient< - __HttpOptions, + __HttpHandlerOptions, ServiceInputTypes, ServiceOutputTypes, RdsDataServiceResolvedConfig diff --git a/clients/client-rds-data/commands/ExecuteStatementCommand.ts b/clients/client-rds-data/commands/ExecuteStatementCommand.ts index 8ee83794f612..e024bdb93f00 100644 --- a/clients/client-rds-data/commands/ExecuteStatementCommand.ts +++ b/clients/client-rds-data/commands/ExecuteStatementCommand.ts @@ -1,7 +1,7 @@ import { Command } from "@aws-sdk/smithy-client"; import { getSerdePlugin } from "@aws-sdk/middleware-serde"; import { - HttpOptions, + HttpHandlerOptions as __HttpHandlerOptions, Handler, HandlerExecutionContext, FinalizeHandlerArguments, @@ -32,7 +32,7 @@ export class ExecuteStatementCommand extends Command< resolveMiddleware( clientStack: MiddlewareStack, configuration: RdsDataServiceResolvedConfig, - options?: HttpOptions + options?: __HttpHandlerOptions ): Handler { const { requestHandler } = configuration; diff --git a/packages/fetch-http-handler/src/fetch-http-handler.ts b/packages/fetch-http-handler/src/fetch-http-handler.ts index faebc2f11bdd..3bd13a0f6839 100644 --- a/packages/fetch-http-handler/src/fetch-http-handler.ts +++ b/packages/fetch-http-handler/src/fetch-http-handler.ts @@ -1,4 +1,4 @@ -import { HttpOptions, HeaderBag, HttpHandlerOptions } from "@aws-sdk/types"; +import { HeaderBag, HttpHandlerOptions } from "@aws-sdk/types"; import { HttpHandler, HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; import { requestTimeout } from "./request-timeout"; import { buildQueryString } from "@aws-sdk/querystring-builder"; @@ -8,7 +8,7 @@ declare var AbortController: any; /** * Represents the http options that can be passed to a browser http client. */ -export interface BrowserHttpOptions extends HttpOptions { +export interface BrowserHttpOptions { /** * The number of milliseconds a request can take before being automatically * terminated. diff --git a/packages/node-http-handler/src/node-http-handler.spec.ts b/packages/node-http-handler/src/node-http-handler.spec.ts index 37983b9d9c1a..9f11af14e3ae 100644 --- a/packages/node-http-handler/src/node-http-handler.spec.ts +++ b/packages/node-http-handler/src/node-http-handler.spec.ts @@ -14,6 +14,22 @@ import { import { AddressInfo } from "net"; describe("NodeHttpHandler", () => { + describe("constructor", () => { + it("can set httpAgent and httpsAgent", () => { + let maxSockets = Math.round(Math.random() * 50); + let nodeHttpHandler = new NodeHttpHandler({ + httpAgent: new http.Agent({ maxSockets }) + }); + expect((nodeHttpHandler as any).httpAgent.maxSockets).toEqual(maxSockets); + maxSockets = Math.round(Math.random() * 50); + nodeHttpHandler = new NodeHttpHandler({ + httpsAgent: new https.Agent({ maxSockets }) + }); + expect((nodeHttpHandler as any).httpsAgent.maxSockets).toEqual( + maxSockets + ); + }); + }); describe("http", () => { const mockHttpServer: HttpServer = createMockHttpServer().listen(54321); @@ -89,15 +105,15 @@ describe("NodeHttpHandler", () => { ); const nodeHttpHandler = new NodeHttpHandler(); - let response = await nodeHttpHandler.handle( - { + let { response } = await nodeHttpHandler.handle( + new HttpRequest({ hostname: "localhost", method: "GET", - port: (mockHttpServer.address() as AddressInfo).port, + port: (mockHttpsServer.address() as AddressInfo).port, protocol: "https:", path: "/", headers: {} - }, + }), {} ); @@ -124,16 +140,16 @@ describe("NodeHttpHandler", () => { }); const nodeHttpHandler = new NodeHttpHandler(); - let response = await nodeHttpHandler.handle( - { + let { response } = await nodeHttpHandler.handle( + new HttpRequest({ hostname: "localhost", method: "PUT", - port: (mockHttpServer.address() as AddressInfo).port, + port: (mockHttpsServer.address() as AddressInfo).port, protocol: "https:", path: "/", headers: {}, body - }, + }), {} ); @@ -214,16 +230,16 @@ describe("NodeHttpHandler", () => { ); const nodeHttpHandler = new NodeHttpHandler(); - let response = await nodeHttpHandler.handle( - { + let { response } = await nodeHttpHandler.handle( + new HttpRequest({ hostname: "localhost", method: "PUT", - port: (mockHttpServer.address() as AddressInfo).port, + port: (mockHttpsServer.address() as AddressInfo).port, protocol: "https:", path: "/", headers: {}, body - }, + }), {} ); diff --git a/packages/node-http-handler/src/node-http-handler.ts b/packages/node-http-handler/src/node-http-handler.ts index d16d4702c707..cac49daed570 100644 --- a/packages/node-http-handler/src/node-http-handler.ts +++ b/packages/node-http-handler/src/node-http-handler.ts @@ -1,21 +1,50 @@ import * as https from "https"; import * as http from "http"; import { buildQueryString } from "@aws-sdk/querystring-builder"; -import { HttpOptions, NodeHttpOptions } from "@aws-sdk/types"; +import { HttpHandlerOptions } from "@aws-sdk/types"; import { HttpHandler, HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; import { setConnectionTimeout } from "./set-connection-timeout"; import { setSocketTimeout } from "./set-socket-timeout"; import { writeRequestBody } from "./write-request-body"; import { getTransformedHeaders } from "./get-transformed-headers"; +/** + * Represents the http options that can be passed to a node http client. + */ +export interface NodeHttpOptions { + /** + * The maximum time in milliseconds that the connection phase of a request + * may take before the connection attempt is abandoned. + */ + connectionTimeout?: number; + + /** + * The maximum time in milliseconds that a socket may remain idle before it + * is closed. + */ + socketTimeout?: number; + + httpAgent?: http.Agent; + httpsAgent?: https.Agent; +} + export class NodeHttpHandler implements HttpHandler { private readonly httpAgent: http.Agent; private readonly httpsAgent: https.Agent; + private readonly connectionTimeout?: number; + private readonly socketTimeout?: number; - constructor(private readonly httpOptions: NodeHttpOptions = {}) { - const { keepAlive = true } = httpOptions; - this.httpAgent = new http.Agent({ keepAlive }); - this.httpsAgent = new https.Agent({ keepAlive }); + constructor({ + connectionTimeout, + socketTimeout, + httpAgent, + httpsAgent + }: NodeHttpOptions = {}) { + this.connectionTimeout = connectionTimeout; + this.socketTimeout = socketTimeout; + const keepAlive = true; + this.httpAgent = httpAgent || new http.Agent({ keepAlive }); + this.httpsAgent = httpsAgent || new https.Agent({ keepAlive }); } destroy(): void { @@ -25,7 +54,7 @@ export class NodeHttpHandler implements HttpHandler { handle( request: HttpRequest, - { abortSignal }: HttpOptions + { abortSignal }: HttpHandlerOptions ): Promise<{ response: HttpResponse }> { return new Promise((resolve, reject) => { // if the request was already aborted, prevent doing extra work @@ -61,8 +90,8 @@ export class NodeHttpHandler implements HttpHandler { req.on("error", reject); // wire-up any timeout logic - setConnectionTimeout(req, reject, this.httpOptions.connectionTimeout); - setSocketTimeout(req, reject, this.httpOptions.socketTimeout); + setConnectionTimeout(req, reject, this.connectionTimeout); + setSocketTimeout(req, reject, this.socketTimeout); // wire-up abort logic if (abortSignal) { diff --git a/packages/node-http-handler/src/node-http2-handler.ts b/packages/node-http-handler/src/node-http2-handler.ts index 726b338a428a..42db5b2bba51 100644 --- a/packages/node-http-handler/src/node-http2-handler.ts +++ b/packages/node-http-handler/src/node-http2-handler.ts @@ -1,12 +1,30 @@ import { connect, constants, ClientHttp2Session } from "http2"; import { buildQueryString } from "@aws-sdk/querystring-builder"; -import { HttpOptions, NodeHttp2Options } from "@aws-sdk/types"; +import { HttpHandlerOptions } from "@aws-sdk/types"; import { HttpHandler, HttpRequest, HttpResponse } from "@aws-sdk/protocol-http"; import { writeRequestBody } from "./write-request-body"; import { getTransformedHeaders } from "./get-transformed-headers"; +/** + * Represents the http2 options that can be passed to a node http2 client. + */ +export interface NodeHttp2Options { + /** + * The maximum time in milliseconds that a stream may remain idle before it + * is closed. + */ + requestTimeout?: number; + + /** + * The maximum time in milliseconds that a session or socket may remain idle + * before it is closed. + * https://nodejs.org/docs/latest-v12.x/api/http2.html#http2_http2session_and_sockets + */ + sessionTimeout?: number; +} + export class NodeHttp2Handler implements HttpHandler { private readonly connectionPool: Map; @@ -23,7 +41,7 @@ export class NodeHttp2Handler implements HttpHandler { handle( request: HttpRequest, - { abortSignal }: HttpOptions + { abortSignal }: HttpHandlerOptions ): Promise<{ response: HttpResponse }> { return new Promise((resolve, reject) => { // if the request was already aborted, prevent doing extra work diff --git a/packages/protocol-http/src/httpHandler.ts b/packages/protocol-http/src/httpHandler.ts index dfbe69b7b6d0..999cdeabb64e 100644 --- a/packages/protocol-http/src/httpHandler.ts +++ b/packages/protocol-http/src/httpHandler.ts @@ -1,9 +1,9 @@ import { HttpRequest } from "./httpRequest"; import { HttpResponse } from "./httpResponse"; -import { RequestHandler, HttpOptions } from "@aws-sdk/types"; +import { RequestHandler, HttpHandlerOptions } from "@aws-sdk/types"; export type HttpHandler = RequestHandler< HttpRequest, HttpResponse, - HttpOptions + HttpHandlerOptions >; diff --git a/packages/types/src/http.ts b/packages/types/src/http.ts index a31c5a5442c6..38d4c2e5efb1 100644 --- a/packages/types/src/http.ts +++ b/packages/types/src/http.ts @@ -100,62 +100,3 @@ export interface ResolvedHttpResponse extends HttpResponse { export interface HttpHandlerOptions { abortSignal?: AbortSignal; } - -/** - * Represents the http options that can be shared across environments. - */ -export type HttpOptions = BrowserHttpOptions & - NodeHttpOptions & { abortSignal?: AbortSignal }; - -/** - * Represents the http options that can be passed to a browser http client. - */ -export interface BrowserHttpOptions { - /** - * The number of milliseconds a request can take before being automatically - * terminated. - */ - requestTimeout?: number; -} - -/** - * Represents the http options that can be passed to a node http client. - */ -export interface NodeHttpOptions { - /** - * The maximum time in milliseconds that the connection phase of a request - * may take before the connection attempt is abandoned. - */ - connectionTimeout?: number; - - /** - * Whether sockets should be kept open even when there are no outstanding - * requests so that future requests can forgo having to reestablish a TCP or - * TLS connection. - */ - keepAlive?: boolean; - - /** - * The maximum time in milliseconds that a socket may remain idle before it - * is closed. - */ - socketTimeout?: number; -} - -/** - * Represents the http2 options that can be passed to a node http2 client. - */ -export interface NodeHttp2Options extends HttpOptions { - /** - * The maximum time in milliseconds that a stream may remain idle before it - * is closed. - */ - requestTimeout?: number; - - /** - * The maximum time in milliseconds that a session or socket may remain idle - * before it is closed. - * https://nodejs.org/docs/latest-v12.x/api/http2.html#http2_http2session_and_sockets - */ - sessionTimeout?: number; -}