Skip to content

Commit

Permalink
[#172124428] Adds option to enable keepalive in fetch client (#628)
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudify authored Apr 2, 2020
1 parent 8c20453 commit 4ef97f0
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 49 deletions.
79 changes: 44 additions & 35 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,42 +231,48 @@ export const API_URL = getRequiredENVVar("API_URL");
// see https://blog.botframework.com/2018/03/05/fix-snat-exhaustion-node-js-bots/
const isFetchKeepaliveEnabled = process.env.FETCH_KEEPALIVE_ENABLED === "true";

export const keepAliveAgentOptions = isFetchKeepaliveEnabled
? {
freeSocketTimeout:
process.env.FETCH_KEEPALIVE_FREE_SOCKET_TIMEOUT === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_FREE_SOCKET_TIMEOUT, 10),
keepAlive: true,
keepAliveMsecs:
process.env.FETCH_KEEPALIVE_KEEPALIVE_MSECS === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_KEEPALIVE_MSECS, 10),
maxFreeSockets:
process.env.FETCH_KEEPALIVE_MAX_FREE_SOCKETS === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_MAX_FREE_SOCKETS, 10),
maxSockets:
process.env.FETCH_KEEPALIVE_MAX_SOCKETS === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_MAX_SOCKETS, 10),
socketActiveTTL:
process.env.FETCH_KEEPALIVE_SOCKET_ACTIVE_TTL === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_SOCKET_ACTIVE_TTL, 10),
timeout:
process.env.FETCH_KEEPALIVE_TIMEOUT === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_TIMEOUT, 10)
}
: undefined;

// Returns a fetch instance backed by a keepalive-enabled HTTP agent
const getKeepaliveFetch: () => typeof fetch = () => {
const getKeepaliveHttpFetch: (
_: agentkeepalive.HttpOptions
) => typeof fetch = httpOptions => {
// custom HTTP agent that will reuse sockets
// see https://github.com/node-modules/agentkeepalive#new-agentoptions
const keepaliveAgent = new agentkeepalive.default({
freeSocketTimeout:
process.env.FETCH_KEEPALIVE_FREE_SOCKET_TIMEOUT === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_FREE_SOCKET_TIMEOUT, 10),
keepAlive: true,
keepAliveMsecs:
process.env.FETCH_KEEPALIVE_KEEPALIVE_MSECS === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_KEEPALIVE_MSECS, 10),
maxFreeSockets:
process.env.FETCH_KEEPALIVE_MAX_FREE_SOCKETS === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_MAX_FREE_SOCKETS, 10),
maxSockets:
process.env.FETCH_KEEPALIVE_MAX_SOCKETS === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_MAX_SOCKETS, 10),
socketActiveTTL:
process.env.FETCH_KEEPALIVE_SOCKET_ACTIVE_TTL === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_SOCKET_ACTIVE_TTL, 10),
timeout:
process.env.FETCH_KEEPALIVE_TIMEOUT === undefined
? undefined
: parseInt(process.env.FETCH_KEEPALIVE_TIMEOUT, 10)
});
const httpAgent = new agentkeepalive.default(httpOptions);

return (input, init) => {
const initWithKeepalive = {
...(init === undefined ? {} : init),
agent: keepaliveAgent
agent: httpAgent
};
// need to cast to any since node-fetch has a slightly different type
// signature that DOM's fetch
Expand All @@ -276,12 +282,14 @@ const getKeepaliveFetch: () => typeof fetch = () => {
};
};

const apiFetch: typeof fetch = isFetchKeepaliveEnabled
? getKeepaliveFetch()
: // tslint:disable-next-line: no-any
(nodeFetch as any);
// HTTP-only fetch with keepalive agent
const httpApiFetch: typeof fetch =
keepAliveAgentOptions !== undefined
? getKeepaliveHttpFetch(keepAliveAgentOptions)
: // tslint:disable-next-line: no-any
(nodeFetch as any);

export const API_CLIENT = new ApiClientFactory(API_KEY, API_URL, apiFetch);
export const API_CLIENT = new ApiClientFactory(API_KEY, API_URL, httpApiFetch);

// Set default session duration to 30 days
const DEFAULT_TOKEN_DURATION_IN_SECONDS = 3600 * 24 * 30;
Expand All @@ -295,7 +303,8 @@ const pagoPAApiUrlProd = getRequiredENVVar("PAGOPA_API_URL_PROD");
const pagoPAApiUrlTest = getRequiredENVVar("PAGOPA_API_URL_TEST");
export const PAGOPA_CLIENT = new PagoPAClientFactory(
pagoPAApiUrlProd,
pagoPAApiUrlTest
pagoPAApiUrlTest,
httpApiFetch
);

// Azure Notification Hub credentials.
Expand Down
11 changes: 10 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Main entry point for the Digital Citizenship proxy.
*/

import * as agentkeepalive from "agentkeepalive";
import * as appInsights from "applicationinsights";
import { fromNullable } from "fp-ts/lib/Option";
import * as http from "http";
Expand All @@ -14,6 +15,7 @@ import {
API_BASE_PATH,
AUTHENTICATION_BASE_PATH,
ENV,
keepAliveAgentOptions,
PAGOPA_BASE_PATH,
SAML_CERT,
SAML_KEY,
Expand Down Expand Up @@ -50,7 +52,14 @@ let server: http.Server | https.Server;
*/
const maybeAppInsightsClient = fromNullable(
process.env.APPINSIGHTS_INSTRUMENTATIONKEY
).map(initAppInsights);
).map(k =>
keepAliveAgentOptions !== undefined
? initAppInsights(k)
: initAppInsights(k, {
httpAgent: new agentkeepalive.default(keepAliveAgentOptions),
httpsAgent: new agentkeepalive.HttpsAgent(keepAliveAgentOptions)
})
);

newApp(
ENV,
Expand Down
8 changes: 4 additions & 4 deletions src/services/apiClientFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ export default class ApiClientFactory implements IApiClientFactoryInterface {
private apiClient!: ReturnType<APIClient>;

constructor(
private readonly apiKey: string,
private readonly apiUrl: string,
apiKey: string,
apiUrl: string,
// tslint:disable-next-line: no-any
private readonly fetchApi: typeof fetch = nodeFetch as any
fetchApi: typeof fetch = nodeFetch as any
) {
this.apiClient = APIClient(this.apiUrl, this.apiKey, this.fetchApi);
this.apiClient = APIClient(apiUrl, apiKey, fetchApi);
}

/**
Expand Down
25 changes: 17 additions & 8 deletions src/services/pagoPAClientFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,37 @@
* This service builds API clients.
*/

import nodeFetch from "node-fetch";

import {
IPagoPAClientFactoryInterface,
PagoPAEnvironment
} from "./IPagoPAClientFactory";

import { PagoPAClient } from "../clients/pagopa";

// TODO: this class is actually useless as PagoPAClient is immutable, it can be removed
export default class PagoPAClientFactory
implements IPagoPAClientFactoryInterface {
private prodApiClient!: ReturnType<PagoPAClient>;
private testApiClient!: ReturnType<PagoPAClient>;

constructor(
public readonly pagoPAApiUrlProd: string,
public readonly pagoPAApiUrlTest: string
) {}
pagoPAApiUrlProd: string,
pagoPAApiUrlTest: string,
// tslint:disable-next-line:no-any
fetchApi: typeof fetch = (nodeFetch as any) as typeof fetch
) {
this.prodApiClient = PagoPAClient(pagoPAApiUrlProd, fetchApi);
this.testApiClient = PagoPAClient(pagoPAApiUrlTest, fetchApi);
}

/**
* {@inheritDoc}
*/
public getClient(environment: PagoPAEnvironment): ReturnType<PagoPAClient> {
return PagoPAClient(
environment === PagoPAEnvironment.TEST
? this.pagoPAApiUrlTest
: this.pagoPAApiUrlProd
);
return environment === PagoPAEnvironment.TEST
? this.testApiClient
: this.prodApiClient;
}
}
15 changes: 14 additions & 1 deletion src/utils/appinsights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ interface IInsightsRequestData {
* - Realtime API metrics
*/
export function initAppInsights(
instrumentationKey: string
instrumentationKey: string,
config: Partial<
Pick<appInsights.TelemetryClient["config"], "httpAgent" | "httpsAgent">
> = {}
): appInsights.TelemetryClient {
appInsights
.setup(instrumentationKey)
Expand Down Expand Up @@ -54,6 +57,16 @@ export function initAppInsights(
appInsights.defaultClient.context.keys.cloudRole
] = getValueFromPackageJson("name");

if (config.httpAgent !== undefined) {
// tslint:disable-next-line: no-object-mutation
appInsights.defaultClient.config.httpAgent = config.httpAgent;
}

if (config.httpsAgent !== undefined) {
// tslint:disable-next-line: no-object-mutation
appInsights.defaultClient.config.httpAgent = config.httpsAgent;
}

return appInsights.defaultClient;
}

Expand Down

0 comments on commit 4ef97f0

Please sign in to comment.