From 00513a6484e611e54dfea94c1aeccbd21c1056af Mon Sep 17 00:00:00 2001 From: Josh Gummersall Date: Fri, 2 Oct 2020 12:13:29 -0700 Subject: [PATCH] Port HTTP agent ServiceClientOption to 1.x Ports functionality to 1.x from: https://github.com/Azure/ms-rest-js/pull/403/files --- lib/axiosHttpClient.ts | 27 ++++++-- lib/policies/agentPolicy.browser.ts | 28 +++++++++ lib/policies/agentPolicy.ts | 31 ++++++++++ lib/serviceClient.ts | 13 ++++ lib/util/constants.ts | 2 +- lib/webResource.ts | 7 ++- lib/xhrHttpClient.ts | 4 ++ package-lock.json | 2 +- package.json | 6 +- test/policies/agentPolicyTests.ts | 95 +++++++++++++++++++++++++++++ test/xhrTests.browser.ts | 11 +++- webpack.testconfig.ts | 1 + 12 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 lib/policies/agentPolicy.browser.ts create mode 100644 lib/policies/agentPolicy.ts create mode 100644 test/policies/agentPolicyTests.ts diff --git a/lib/axiosHttpClient.ts b/lib/axiosHttpClient.ts index b0374a8..b1d4da5 100644 --- a/lib/axiosHttpClient.ts +++ b/lib/axiosHttpClient.ts @@ -154,16 +154,35 @@ export class AxiosHttpClient implements HttpClient { proxy: false }; - if (httpRequest.proxySettings) { + if (httpRequest.agentSettings) { + const {http: httpAgent, https: httpsAgent} = httpRequest.agentSettings; + if (httpsAgent) { + config.httpsAgent = httpsAgent; + } + if (httpAgent) { + config.httpAgent = httpAgent; + } + } else if (httpRequest.proxySettings) { const agent = createProxyAgent(httpRequest.url, httpRequest.proxySettings, httpRequest.headers); if (agent.isHttps) { config.httpsAgent = agent.agent; } else { config.httpAgent = agent.agent; } - } else if (httpRequest.keepAlive) { - config.httpAgent = keepaliveAgents.http; - config.httpsAgent = keepaliveAgents.https; + } + + if (httpRequest.keepAlive === true) { + if (config.httpAgent) { + config.httpAgent.keepAlive = true; + } else { + config.httpAgent = keepaliveAgents.http; + } + + if (config.httpsAgent) { + config.httpsAgent.keepAlive = true; + } else { + config.httpsAgent = keepaliveAgents.https; + } } res = await axiosInstance.request(config); diff --git a/lib/policies/agentPolicy.browser.ts b/lib/policies/agentPolicy.browser.ts new file mode 100644 index 0000000..5412325 --- /dev/null +++ b/lib/policies/agentPolicy.browser.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +import { AgentSettings } from "../serviceClient"; +import { BaseRequestPolicy, RequestPolicy, RequestPolicyFactory, RequestPolicyOptions } from "./requestPolicy"; +import { HttpOperationResponse } from "../httpOperationResponse"; +import { WebResource } from "../webResource"; + +const agentNotSupportedInBrowser = new Error("AgentPolicy is not supported in browser environment"); + +export function agentPolicy(_agentSettings?: AgentSettings): RequestPolicyFactory { + return { + create: (_nextPolicy: RequestPolicy, _options: RequestPolicyOptions) => { + throw agentNotSupportedInBrowser; + } + }; +} + +export class AgentPolicy extends BaseRequestPolicy { + constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptions) { + super(nextPolicy, options); + throw agentNotSupportedInBrowser; + } + + public sendRequest(_request: WebResource): Promise { + throw agentNotSupportedInBrowser; + } +} diff --git a/lib/policies/agentPolicy.ts b/lib/policies/agentPolicy.ts new file mode 100644 index 0000000..b823231 --- /dev/null +++ b/lib/policies/agentPolicy.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +import { AgentSettings } from "../serviceClient"; +import { BaseRequestPolicy, RequestPolicy, RequestPolicyFactory, RequestPolicyOptions } from "./requestPolicy"; +import { HttpOperationResponse } from "../httpOperationResponse"; +import { WebResource } from "../webResource"; + +export function agentPolicy(agentSettings?: AgentSettings): RequestPolicyFactory { + return { + create: (nextPolicy: RequestPolicy, options: RequestPolicyOptions) => { + return new AgentPolicy(nextPolicy, options, agentSettings!); + } + }; +} + +export class AgentPolicy extends BaseRequestPolicy { + agentSettings: AgentSettings; + + constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptions, agentSettings: AgentSettings) { + super(nextPolicy, options); + this.agentSettings = agentSettings; + } + + public sendRequest(request: WebResource): Promise { + if (!request.agentSettings) { + request.agentSettings = this.agentSettings; + } + return this._nextPolicy.sendRequest(request); + } +} diff --git a/lib/serviceClient.ts b/lib/serviceClient.ts index 49e247c..b73e789 100644 --- a/lib/serviceClient.ts +++ b/lib/serviceClient.ts @@ -28,6 +28,7 @@ import { OperationResponse } from "./operationResponse"; import { ServiceCallback } from "./util/utils"; import { proxyPolicy, getDefaultProxySettings } from "./policies/proxyPolicy"; import { throttlingRetryPolicy } from "./policies/throttlingRetryPolicy"; +import { Agent } from "http"; /** @@ -40,6 +41,14 @@ export interface ProxySettings { password?: string; } +/** + * HTTP and HTTPS agents (Node.js only) + */ +export interface AgentSettings { + http: Agent; + https: Agent; +} + /** * Options to be provided while creating the client. */ @@ -99,6 +108,10 @@ export interface ServiceClientOptions { * Proxy settings which will be used for every HTTP request (Node.js only). */ proxySettings?: ProxySettings; + /** + * HTTP and HTTPS agents which will be used for every HTTP request (Node.js only). + */ + agentSettings?: AgentSettings; } /** diff --git a/lib/util/constants.ts b/lib/util/constants.ts index 1410c6d..3504655 100644 --- a/lib/util/constants.ts +++ b/lib/util/constants.ts @@ -7,7 +7,7 @@ export const Constants = { * @const * @type {string} */ - msRestVersion: "1.8.16", + msRestVersion: "1.8.17", /** * Specifies HTTP. diff --git a/lib/webResource.ts b/lib/webResource.ts index e4d7065..c1b77ac 100644 --- a/lib/webResource.ts +++ b/lib/webResource.ts @@ -7,7 +7,7 @@ import { Mapper, Serializer } from "./serializer"; import { generateUuid } from "./util/utils"; import { HttpOperationResponse } from "./httpOperationResponse"; import { OperationResponse } from "./operationResponse"; -import { ProxySettings } from "./serviceClient"; +import { AgentSettings, ProxySettings } from "./serviceClient"; export type HttpMethods = "GET" | "PUT" | "POST" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS" | "TRACE"; export type HttpRequestBody = Blob | string | ArrayBuffer | ArrayBufferView | (() => NodeJS.ReadableStream); @@ -67,6 +67,7 @@ export class WebResource { timeout: number; proxySettings?: ProxySettings; keepAlive?: boolean; + agentSettings?: AgentSettings; abortSignal?: AbortSignalLike; @@ -89,7 +90,8 @@ export class WebResource { onUploadProgress?: (progress: TransferProgressEvent) => void, onDownloadProgress?: (progress: TransferProgressEvent) => void, proxySettings?: ProxySettings, - keepAlive?: boolean) { + keepAlive?: boolean, + agentSettings?: AgentSettings) { this.streamResponseBody = streamResponseBody; this.url = url || ""; @@ -105,6 +107,7 @@ export class WebResource { this.onDownloadProgress = onDownloadProgress; this.proxySettings = proxySettings; this.keepAlive = keepAlive; + this.agentSettings = agentSettings; } /** diff --git a/lib/xhrHttpClient.ts b/lib/xhrHttpClient.ts index 6ed86ee..f3fe5b7 100644 --- a/lib/xhrHttpClient.ts +++ b/lib/xhrHttpClient.ts @@ -14,6 +14,10 @@ export class XhrHttpClient implements HttpClient { public sendRequest(request: WebResource): Promise { const xhr = new XMLHttpRequest(); + if (request.agentSettings) { + throw new Error("HTTP agent settings not supported in browser environment"); + } + if (request.proxySettings) { throw new Error("HTTP proxy is not supported in browser environment"); } diff --git a/package-lock.json b/package-lock.json index a4e359e..b3e0bcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@azure/ms-rest-js", - "version": "1.8.15", + "version": "1.8.17", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e6cbdca..9b1a027 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "email": "azsdkteam@microsoft.com", "url": "https://github.com/Azure/ms-rest-js" }, - "version": "1.8.16", + "version": "1.8.17", "description": "Isomorphic client Runtime for Typescript/node.js/browser javascript client libraries generated using AutoRest", "tags": [ "isomorphic", @@ -44,7 +44,9 @@ "./es/lib/policies/msRestUserAgentPolicy.js": "./es/lib/policies/msRestUserAgentPolicy.browser.js", "./es/lib/util/base64.js": "./es/lib/util/base64.browser.js", "./es/lib/util/xml.js": "./es/lib/util/xml.browser.js", - "./es/lib/defaultHttpClient.js": "./es/lib/defaultHttpClient.browser.js" + "./es/lib/defaultHttpClient.js": "./es/lib/defaultHttpClient.browser.js", + "./es/lib/policies/agentPolicy.js": "./es/lib/policies/agentPolicy.browser.js", + "./es/lib/policies/proxyPolicy.js": "./es/lib/policies/proxyPolicy.browser.js" }, "license": "MIT", "dependencies": { diff --git a/test/policies/agentPolicyTests.ts b/test/policies/agentPolicyTests.ts new file mode 100644 index 0000000..3f46d78 --- /dev/null +++ b/test/policies/agentPolicyTests.ts @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +import "chai/register-should"; +import { AgentSettings } from "../../lib/serviceClient"; +import { RequestPolicyOptions } from "../../lib/policies/requestPolicy"; +import { WebResource } from "../../lib/webResource"; +import { HttpHeaders } from "../../lib/httpHeaders"; +import { agentPolicy, AgentPolicy } from "../../lib/policies/agentPolicy"; +import { nodeDescribe, browserDescribe } from "../msAssert"; + +describe("AgentPolicy", function () { + const emptyRequestPolicy = { + sendRequest: (_: WebResource) => + Promise.resolve({ + request: new WebResource(), + status: 404, + headers: new HttpHeaders(undefined), + }), + }; + + const emptyPolicyOptions = new RequestPolicyOptions(); + + nodeDescribe("for Node.js", function () { + const http = require("http"); + const https = require("https"); + + const agentSettings: AgentSettings = { + http: new http.Agent(), + https: new https.Agent(), + }; + + it("factory passes correct agent settings", function () { + const factory = agentPolicy(agentSettings); + + const policy = factory.create( + emptyRequestPolicy, + emptyPolicyOptions + ) as AgentPolicy; + + policy.agentSettings.should.be.deep.equal(agentSettings); + }); + + it("sets correct agent settings through constructor", function () { + const policy = new AgentPolicy( + emptyRequestPolicy, + emptyPolicyOptions, + agentSettings + ); + + policy.agentSettings.should.be.deep.equal(agentSettings); + }); + + it("should assign agent settings to the web request", async function () { + const policy = new AgentPolicy( + emptyRequestPolicy, + emptyPolicyOptions, + agentSettings + ); + const request = new WebResource(); + + await policy.sendRequest(request); + + request.agentSettings!.should.be.deep.equal(agentSettings); + }); + + it("should not override agent settings to the web request", async function () { + const policy = new AgentPolicy( + emptyRequestPolicy, + emptyPolicyOptions, + agentSettings + ); + + const request = new WebResource(); + const requestSpecificAgentSettings = { + http: new http.Agent({keepAlive: true}), + https: new http.Agent({keepAlive: true}), + }; + request.agentSettings = requestSpecificAgentSettings; + + await policy.sendRequest(request); + + request.agentSettings!.should.be.deep.equal(requestSpecificAgentSettings); + }); + }); + + browserDescribe("for browser", () => { + it("should throw an Error while constructing object", () => { + const agentSettings = {} as AgentSettings; + const construct = () => + new AgentPolicy(emptyRequestPolicy, emptyPolicyOptions, agentSettings); + construct.should.throw(); + }); + }); +}); diff --git a/test/xhrTests.browser.ts b/test/xhrTests.browser.ts index 20b2b62..7a709e2 100644 --- a/test/xhrTests.browser.ts +++ b/test/xhrTests.browser.ts @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +import { AgentSettings } from "../lib/serviceClient"; +import { WebResource } from "../lib/webResource"; import { assert } from "chai"; import { parseHeaders, XhrHttpClient } from "../lib/xhrHttpClient"; -import { WebResource } from "../lib/webResource"; describe("XhrHttpClient", function() { it("parses headers", function() { @@ -38,4 +39,12 @@ describe("XhrHttpClient", function() { const client = new XhrHttpClient(); assert.throws(() => { client.sendRequest(request); }, Error); }); + + it("throws when agent settings are passed", function() { + const request = new WebResource(); + request.agentSettings = {} as AgentSettings; + + const client = new XhrHttpClient(); + assert.throws(() => { client.sendRequest(request); }, Error); + }); }); diff --git a/webpack.testconfig.ts b/webpack.testconfig.ts index 70c6c1a..7377606 100644 --- a/webpack.testconfig.ts +++ b/webpack.testconfig.ts @@ -31,6 +31,7 @@ const config: webpack.Configuration = { new webpack.NormalModuleReplacementPlugin(/(\.).+util\/xml/, path.resolve(__dirname, "./lib/util/xml.browser.ts")), new webpack.NormalModuleReplacementPlugin(/(\.).+defaultHttpClient/, path.resolve(__dirname, "./lib/defaultHttpClient.browser.ts")), new webpack.NormalModuleReplacementPlugin(/(\.).+msRestUserAgentPolicy/, path.resolve(__dirname, "./lib/policies/msRestUserAgentPolicy.browser.ts")), + new webpack.NormalModuleReplacementPlugin(/(\.).+agentPolicy/, path.resolve(__dirname, "./lib/policies/agentPolicy.browser.ts")), new webpack.NormalModuleReplacementPlugin(/(\.).+proxyPolicy/, path.resolve(__dirname, "./lib/policies/proxyPolicy.browser.ts")) ], module: {