Skip to content

Commit

Permalink
fix: support custom agent in node http handler (#489)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
AllanZhengYP authored and trivikr committed Jan 3, 2020
1 parent 8a3bcce commit 9c6cde8
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 90 deletions.
6 changes: 3 additions & 3 deletions clients/client-rds-data/RdsDataServiceClient.ts
Expand Up @@ -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
Expand Down Expand Up @@ -158,7 +158,7 @@ export type RdsDataServiceConfig = RDSDataRuntimeDependencies &
UserAgentInputConfig;

export type RdsDataServiceResolvedConfig = SmithyResolvedConfiguration<
__HttpOptions
__HttpHandlerOptions
> &
Required<RDSDataRuntimeDependencies> &
AwsAuthResolvedConfig &
Expand All @@ -168,7 +168,7 @@ export type RdsDataServiceResolvedConfig = SmithyResolvedConfiguration<
UserAgentResolvedConfig;

export class RdsDataService extends SmithyClient<
__HttpOptions,
__HttpHandlerOptions,
ServiceInputTypes,
ServiceOutputTypes,
RdsDataServiceResolvedConfig
Expand Down
4 changes: 2 additions & 2 deletions 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,
Expand Down Expand Up @@ -32,7 +32,7 @@ export class ExecuteStatementCommand extends Command<
resolveMiddleware(
clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
configuration: RdsDataServiceResolvedConfig,
options?: HttpOptions
options?: __HttpHandlerOptions
): Handler<ExecuteStatementRequest, ExecuteStatementResponse> {
const { requestHandler } = configuration;

Expand Down
4 changes: 2 additions & 2 deletions 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";
Expand All @@ -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.
Expand Down
40 changes: 28 additions & 12 deletions packages/node-http-handler/src/node-http-handler.spec.ts
Expand Up @@ -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);

Expand Down Expand Up @@ -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: {}
},
}),
{}
);
Expand All @@ -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
},
}),
{}
);
Expand Down Expand Up @@ -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
},
}),
{}
);
Expand Down
45 changes: 37 additions & 8 deletions 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 {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
22 changes: 20 additions & 2 deletions 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<string, ClientHttp2Session>;

Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions 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
>;
59 changes: 0 additions & 59 deletions packages/types/src/http.ts
Expand Up @@ -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;
}

0 comments on commit 9c6cde8

Please sign in to comment.