diff --git a/Changelog.md b/Changelog.md index 3e26f28..b9d8183 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,8 @@ # Changelog +## 2.1.0 - 2020-10-08 +- Add support for custom http/https agent (PR [#403](https://github.com/Azure/ms-rest-js/pull/403)) +- Fix WebResource clone to include extra settings (Issue [#405](https://github.com/Azure/ms-rest-js/issue/403)) + ## 2.0.8 - 2020-07-23 - [BugFix] - Fixed loading of proxyPolicy.browser.js in the HTML files.(PR [#397](https://github.com/Azure/ms-rest-js/pull/397)) @@ -7,7 +11,7 @@ - Replace public usage of `RequestPolicyOptions` to an interface `RequestPolicyOptionsLike` to avoid compatibility issues with private members. - Fix issue with null/undefined values in array and tabs/space delimiter arrays during sendOperationRequest. [PR #390](https://github.com/Azure/ms-rest-js/pull/390) - Fix in flattenResponse when expecting an array, checking for parsedBody to be an array before proceeding with flattening. (PR [#385](https://github.com/Azure/ms-rest-js/pull/385)) - + ## 2.0.6 - 2020-04-15 - A new interface `WebResourceLike` was introduced to avoid a direct dependency on the class `WebResource` in public interfaces. `HttpHeadersLike` was also added to replace references to `HttpHeaders`. This change was added to improve compatibility between `@azure/core-http` and `@azure/ms-rest-nodeauth`. diff --git a/lib/msRest.ts b/lib/msRest.ts index 37477bf..41c4712 100644 --- a/lib/msRest.ts +++ b/lib/msRest.ts @@ -22,6 +22,7 @@ export { generateClientRequestIdPolicy } from "./policies/generateClientRequestI export { exponentialRetryPolicy } from "./policies/exponentialRetryPolicy"; export { systemErrorRetryPolicy } from "./policies/systemErrorRetryPolicy"; export { throttlingRetryPolicy } from "./policies/throttlingRetryPolicy"; +export { agentPolicy } from "./policies/agentPolicy"; export { getDefaultProxySettings, proxyPolicy } from "./policies/proxyPolicy"; export { redirectPolicy } from "./policies/redirectPolicy"; export { signingPolicy } from "./policies/signingPolicy"; diff --git a/lib/nodeFetchHttpClient.ts b/lib/nodeFetchHttpClient.ts index 51a74ef..56f3508 100644 --- a/lib/nodeFetchHttpClient.ts +++ b/lib/nodeFetchHttpClient.ts @@ -46,7 +46,14 @@ export class NodeFetchHttpClient extends FetchHttpClient { httpRequest.headers.set("Cookie", cookieString); } - if (httpRequest.proxySettings) { + if (httpRequest.agentSettings) { + const {http: httpAgent, https: httpsAgent} = httpRequest.agentSettings; + if (httpsAgent && httpRequest.url.startsWith("https")) { + requestInit.agent = httpsAgent; + } else if (httpAgent) { + requestInit.agent = httpAgent; + } + } else if (httpRequest.proxySettings) { const tunnel: ProxyAgent = createProxyAgent(httpRequest.url, httpRequest.proxySettings, httpRequest.headers); requestInit.agent = tunnel.agent; } diff --git a/lib/policies/agentPolicy.browser.ts b/lib/policies/agentPolicy.browser.ts new file mode 100644 index 0000000..840cdcb --- /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, RequestPolicyOptionsLike } from "./requestPolicy"; +import { HttpOperationResponse } from "../httpOperationResponse"; +import { WebResourceLike } from "../webResource"; + +const agentNotSupportedInBrowser = new Error("AgentPolicy is not supported in browser environment"); + +export function agentPolicy(_agentSettings?: AgentSettings): RequestPolicyFactory { + return { + create: (_nextPolicy: RequestPolicy, _options: RequestPolicyOptionsLike) => { + throw agentNotSupportedInBrowser; + } + }; +} + +export class AgentPolicy extends BaseRequestPolicy { + constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) { + super(nextPolicy, options); + throw agentNotSupportedInBrowser; + } + + public sendRequest(_request: WebResourceLike): Promise { + throw agentNotSupportedInBrowser; + } +} diff --git a/lib/policies/agentPolicy.ts b/lib/policies/agentPolicy.ts new file mode 100644 index 0000000..46d399b --- /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, RequestPolicyOptionsLike } from "./requestPolicy"; +import { HttpOperationResponse } from "../httpOperationResponse"; +import { WebResourceLike } from "../webResource"; + +export function agentPolicy(agentSettings?: AgentSettings): RequestPolicyFactory { + return { + create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { + return new AgentPolicy(nextPolicy, options, agentSettings!); + } + }; +} + +export class AgentPolicy extends BaseRequestPolicy { + agentSettings: AgentSettings; + + constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike, agentSettings: AgentSettings) { + super(nextPolicy, options); + this.agentSettings = agentSettings; + } + + public sendRequest(request: WebResourceLike): Promise { + if (!request.agentSettings) { + request.agentSettings = this.agentSettings; + } + return this._nextPolicy.sendRequest(request); + } +} diff --git a/lib/serviceClient.ts b/lib/serviceClient.ts index d433eb9..1ff6f13 100644 --- a/lib/serviceClient.ts +++ b/lib/serviceClient.ts @@ -26,8 +26,10 @@ import { stringifyXML } from "./util/xml"; import { RequestOptionsBase, RequestPrepareOptions, WebResourceLike, isWebResourceLike, WebResource } from "./webResource"; import { OperationResponse } from "./operationResponse"; import { ServiceCallback } from "./util/utils"; +import { agentPolicy } from "./policies/agentPolicy"; import { proxyPolicy, getDefaultProxySettings } from "./policies/proxyPolicy"; import { throttlingRetryPolicy } from "./policies/throttlingRetryPolicy"; +import { Agent } from "http"; /** @@ -40,6 +42,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 +109,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; } /** @@ -434,6 +448,10 @@ function createDefaultRequestPolicyFactories(credentials: ServiceClientCredentia factories.push(proxyPolicy(proxySettings)); } + if (options.agentSettings) { + factories.push(agentPolicy(options.agentSettings)); + } + return factories; } diff --git a/lib/util/constants.ts b/lib/util/constants.ts index a7ac58b..c8363d5 100644 --- a/lib/util/constants.ts +++ b/lib/util/constants.ts @@ -7,7 +7,7 @@ export const Constants = { * @const * @type {string} */ - msRestVersion: "2.0.8", + msRestVersion: "2.1.0", /** * Specifies HTTP. diff --git a/lib/webResource.ts b/lib/webResource.ts index 05a279b..8ce5ff8 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); @@ -94,6 +94,10 @@ export interface WebResourceLike { * Proxy configuration. */ proxySettings?: ProxySettings; + /** + * HTTP(S) agent configuration. + */ + agentSettings?: AgentSettings; /** * If the connection should be reused. */ @@ -182,6 +186,7 @@ export class WebResource { timeout: number; proxySettings?: ProxySettings; keepAlive?: boolean; + agentSettings?: AgentSettings; abortSignal?: AbortSignalLike; @@ -204,7 +209,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 || ""; @@ -220,6 +226,7 @@ export class WebResource { this.onDownloadProgress = onDownloadProgress; this.proxySettings = proxySettings; this.keepAlive = keepAlive; + this.agentSettings = agentSettings; } /** @@ -427,7 +434,10 @@ export class WebResource { this.abortSignal, this.timeout, this.onUploadProgress, - this.onDownloadProgress); + this.onDownloadProgress, + this.proxySettings, + this.keepAlive, + this.agentSettings); if (this.formData) { result.formData = this.formData; diff --git a/lib/xhrHttpClient.ts b/lib/xhrHttpClient.ts index 3d95355..245deba 100644 --- a/lib/xhrHttpClient.ts +++ b/lib/xhrHttpClient.ts @@ -14,6 +14,10 @@ export class XhrHttpClient implements HttpClient { public sendRequest(request: WebResourceLike): 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.json b/package.json index 1278787..0f551d0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "email": "azsdkteam@microsoft.com", "url": "https://github.com/Azure/ms-rest-js" }, - "version": "2.0.8", + "version": "2.1.0", "description": "Isomorphic client Runtime for Typescript/node.js/browser javascript client libraries generated using AutoRest", "tags": [ "isomorphic", @@ -42,6 +42,7 @@ ], "browser": { "./es/lib/policies/msRestUserAgentPolicy.js": "./es/lib/policies/msRestUserAgentPolicy.browser.js", + "./es/lib/policies/agentPolicy.js": "./es/lib/policies/agentPolicy.browser.js", "./es/lib/policies/proxyPolicy.js": "./es/lib/policies/proxyPolicy.browser.js", "./es/lib/util/base64.js": "./es/lib/util/base64.browser.js", "./es/lib/util/xml.js": "./es/lib/util/xml.browser.js", diff --git a/test/policies/agentPolicyTests.ts b/test/policies/agentPolicyTests.ts new file mode 100644 index 0000000..093608d --- /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, WebResourceLike } 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: (_: WebResourceLike) => + 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 af58fc6..49f3111 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: {