Skip to content

Commit

Permalink
[core-https] Implement HttpsClient interface for node and browser (#…
Browse files Browse the repository at this point in the history
…9082)

Adds new clients for actually making requests over a transport layer. 

The XhrClient was relatively unchanged from before, but the Node client now directly uses Node's `https` module, which should provide us with more flexibility in the future for customizing how requests are made.
  • Loading branch information
xirzec authored Jun 2, 2020
1 parent 252ccfa commit 00dad89
Show file tree
Hide file tree
Showing 25 changed files with 1,769 additions and 42 deletions.
2 changes: 0 additions & 2 deletions sdk/core/core-http/review/core-http.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,10 +572,8 @@ export function proxyPolicy(proxySettings?: ProxySettings): RequestPolicyFactory

// @public
export interface ProxySettings {
// (undocumented)
host: string;
password?: string;
// (undocumented)
port: number;
username?: string;
}
Expand Down
4 changes: 2 additions & 2 deletions sdk/core/core-http/src/serviceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ import { disableResponseDecompressionPolicy } from "./policies/disableResponseDe
* Options to configure a proxy for outgoing requests (Node.js only).
*/
export interface ProxySettings {
/*
/**
* The proxy's host address.
*/
host: string;

/*
/**
* The proxy host's port.
*/
port: number;
Expand Down
37 changes: 25 additions & 12 deletions sdk/core/core-https/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
"main": "dist/index.js",
"module": "dist-esm/src/index.js",
"browser": {
"stream": "./node_modules/stream-browserify/index.js",
"./dist-esm/src/print.js": "./dist-esm/src/print.browser.js"
"./dist-esm/src/defaultHttpsClient.js": "./dist-esm/src/defaultHttpsClient.browser.js",
"./dist-esm/src/util/inspect.js": "./dist-esm/src/util/inspect.browser.js",
"./dist-esm/src/util/url.js": "./dist-esm/src/util/url.browser.js"
},
"types": "types/latest/coreHttps.d.ts",
"typesVersions": {
Expand All @@ -20,16 +21,22 @@
},
"scripts": {
"audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit",
"build:browser": "tsc -p . && cross-env ONLY_BROWSER=true rollup -c 2>&1",
"build:node": "tsc -p . && cross-env ONLY_NODE=true rollup -c 2>&1",
"build:browser": "npm run build:ts && cross-env ONLY_BROWSER=true rollup -c 2>&1",
"build:node": "npm run build:ts && cross-env ONLY_NODE=true rollup -c 2>&1",
"build:samples": "cd samples && tsc -p .",
"build:test": "tsc -p . && rollup -c rollup.test.config.js 2>&1",
"build:test": "npm run build:ts && npm run bundle:test",
"build:test:browser": "npm run build:ts && npm run bundle:test:browser",
"build:test:node": "npm run build:ts && npm run bundle:test:node",
"build:ts": "tsc -p .",
"build:types": "downlevel-dts types/latest/ types/3.1/",
"build": "tsc -p . && rollup -c 2>&1 && api-extractor run --local && npm run build:types",
"build": "npm run build:ts && rollup -c 2>&1 && api-extractor run --local && npm run build:types",
"bundle:test": "rollup -c rollup.test.config.js 2>&1",
"bundle:test:browser": "cross-env ONLY_BROWSER=true rollup -c rollup.test.config.js 2>&1",
"bundle:test:node": "cross-env ONLY_NODE=true rollup -c rollup.test.config.js 2>&1",
"check-format": "prettier --list-different \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
"clean": "rimraf dist dist-* types *.tgz *.log",
"execute:samples": "echo skipped",
"extract-api": "tsc -p . && api-extractor run --local",
"extract-api": "npm run build:ts && api-extractor run --local",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
"integration-test:browser": "echo skipped",
"integration-test:node": "echo skipped",
Expand All @@ -38,11 +45,11 @@
"lint": "eslint package.json tsconfig.json api-extractor.json src test --ext .ts -f html -o coreHttps-lintReport.html || exit 0",
"pack": "npm pack 2>&1",
"prebuild": "npm run clean",
"test:browser": "npm run build:test && npm run unit-test:browser && npm run integration-test:browser",
"test:node": "npm run build:test && npm run unit-test:node && npm run integration-test:node",
"test": "npm run build:test && npm run unit-test && npm run integration-test",
"test:browser": "npm run build:test:browser && npm run unit-test:browser && npm run integration-test:browser",
"test:node": "npm run build:test:node && npm run unit-test:node && npm run integration-test:node",
"test": "npm run clean && npm run build:ts && npm run bundle:test:node && npm run unit-test:node && npm run bundle:test:browser && npm run unit-test:browser && npm run integration-test:node && npm run integration-test:browser",
"unit-test:browser": "karma start --single-run",
"unit-test:node": "mocha --reporter ../../../common/tools/mocha-multi-reporter.js dist-test/index.node.js",
"unit-test:node": "mocha --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js dist-test/index.node.js",
"unit-test": "npm run unit-test:node && npm run unit-test:browser"
},
"files": [
Expand Down Expand Up @@ -70,8 +77,11 @@
"sideEffects": false,
"prettier": "@azure/eslint-plugin-azure-sdk/prettier.json",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@opentelemetry/api": "^0.6.1",
"tslib": "^1.10.0"
"form-data": "^3.0.0",
"tslib": "^1.10.0",
"https-proxy-agent": "^3.0.1"
},
"devDependencies": {
"@microsoft/api-extractor": "7.7.11",
Expand All @@ -83,6 +93,7 @@
"@types/chai": "^4.1.6",
"@types/mocha": "^7.0.2",
"@types/node": "^8.0.0",
"@types/sinon": "^9.0.4",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
Expand Down Expand Up @@ -114,6 +125,8 @@
"rollup-plugin-sourcemaps": "^0.4.2",
"rollup-plugin-terser": "^5.1.1",
"rollup-plugin-visualizer": "^3.1.1",
"sinon": "^9.0.2",
"source-map-support": "^0.5.9",
"typescript": "~3.9.3",
"util": "^0.12.1"
}
Expand Down
113 changes: 113 additions & 0 deletions sdk/core/core-https/review/core-https.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
```ts

import { AbortSignalLike } from '@azure/abort-controller';

// @public
export interface AddPipelineOptions {
afterPhase?: PipelinePhase;
Expand All @@ -12,6 +14,41 @@ export interface AddPipelineOptions {
phase?: PipelinePhase;
}

// @public
export function createEmptyPipeline(): Pipeline;

// @public
export function createHttpHeaders(rawHeaders?: RawHttpHeaders): HttpHeaders;

// @public
export function createPipelineRequest(options: PipelineRequestOptions): PipelineRequest;

// @public
export class DefaultHttpsClient implements HttpsClient {
sendRequest(request: PipelineRequest): Promise<PipelineResponse>;
}

// @public
export type FormDataMap = {
[key: string]: FormDataValue | FormDataValue[];
};

// @public
export type FormDataValue = string | Blob;

// @public
export interface HttpHeaders extends Iterable<[string, string]> {
clone(): HttpHeaders;
delete(name: string): void;
get(name: string): string | undefined;
has(name: string): boolean;
set(name: string, value: string | number): void;
toJSON(): RawHttpHeaders;
}

// @public
export type HttpMethods = "GET" | "PUT" | "POST" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS" | "TRACE";

// @public
export interface HttpsClient {
sendRequest: SendRequest;
Expand Down Expand Up @@ -40,18 +77,94 @@ export interface PipelinePolicy {

// @public
export interface PipelineRequest {
abortSignal?: AbortSignalLike;
body?: RequestBodyType;
formData?: FormDataMap;
headers: HttpHeaders;
keepAlive?: boolean;
method: HttpMethods;
onDownloadProgress?: (progress: TransferProgressEvent) => void;
onUploadProgress?: (progress: TransferProgressEvent) => void;
proxySettings?: ProxySettings;
skipDecompressResponse?: boolean;
streamResponseBody?: boolean;
timeout: number;
url: string;
withCredentials: boolean;
}

// @public
export interface PipelineRequestOptions {
abortSignal?: AbortSignalLike;
body?: RequestBodyType;
formData?: FormDataMap;
headers?: HttpHeaders;
keepAlive?: boolean;
method?: HttpMethods;
onDownloadProgress?: (progress: TransferProgressEvent) => void;
onUploadProgress?: (progress: TransferProgressEvent) => void;
proxySettings?: ProxySettings;
skipDecompressResponse?: boolean;
streamResponseBody?: boolean;
timeout?: number;
url: string;
withCredentials?: boolean;
}

// @public
export interface PipelineResponse {
blobBody?: Promise<Blob>;
bodyAsText?: string | null;
headers: HttpHeaders;
readableStreamBody?: NodeJS.ReadableStream;
request: PipelineRequest;
status: number;
}

// @public
export interface ProxySettings {
host: string;
password?: string;
port: number;
username?: string;
}

// @public
export type RawHttpHeaders = {
[headerName: string]: string;
};

// @public
export type RequestBodyType = NodeJS.ReadableStream | Blob | ArrayBuffer | ArrayBufferView | FormData | string | null;

// @public
export class RestError extends Error {
constructor(message: string, options?: RestErrorOptions);
code?: string;
details?: unknown;
static readonly PARSE_ERROR: string;
request?: PipelineRequest;
static readonly REQUEST_SEND_ERROR: string;
response?: PipelineResponse;
statusCode?: number;
}

// @public
export interface RestErrorOptions {
code?: string;
request?: PipelineRequest;
response?: PipelineResponse;
statusCode?: number;
}

// @public
export type SendRequest = (request: PipelineRequest) => Promise<PipelineResponse>;

// @public
export type TransferProgressEvent = {
loadedBytes: number;
};


// (No @packageDocumentation comment for this package)

Expand Down
2 changes: 1 addition & 1 deletion sdk/core/core-https/rollup.base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const input = "dist-esm/src/index.js";
const production = process.env.NODE_ENV === "production";

export function nodeConfig(test = false) {
const externalNodeBuiltins = [];
const externalNodeBuiltins = ["stream", "https", "zlib", "url", "util"];
const baseConfig = {
input: input,
external: depNames.concat(externalNodeBuiltins),
Expand Down
12 changes: 11 additions & 1 deletion sdk/core/core-https/rollup.test.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import * as base from "./rollup.base.config";

export default [base.nodeConfig(true), base.browserConfig(true)];
const inputs = [];

if (!process.env.ONLY_BROWSER) {
inputs.push(base.nodeConfig(true));
}

if (!process.env.ONLY_NODE) {
inputs.push(base.browserConfig(true));
}

export default inputs;
4 changes: 4 additions & 0 deletions sdk/core/core-https/src/defaultHttpsClient.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

export { XhrHttpsClient as DefaultHttpsClient } from "./xhrHttpsClient";
4 changes: 4 additions & 0 deletions sdk/core/core-https/src/defaultHttpsClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

export { NodeHttpsClient as DefaultHttpsClient } from "./nodeHttpsClient";
96 changes: 96 additions & 0 deletions sdk/core/core-https/src/httpHeaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { HttpHeaders, RawHttpHeaders } from "./interfaces";

function normalizeName(name: string): string {
return name.toLowerCase();
}

class HttpHeadersImpl implements HttpHeaders {
private readonly _headersMap: Map<string, string>;

constructor(rawHeaders?: RawHttpHeaders) {
this._headersMap = new Map<string, string>();
if (rawHeaders) {
for (const headerName of Object.keys(rawHeaders)) {
this.set(headerName, rawHeaders[headerName]);
}
}
}

/**
* Set a header in this collection with the provided name and value. The name is
* case-insensitive.
* @param name The name of the header to set. This value is case-insensitive.
* @param value The value of the header to set.
*/
public set(name: string, value: string | number): void {
this._headersMap.set(normalizeName(name), String(value));
}

/**
* Get the header value for the provided header name, or undefined if no header exists in this
* collection with the provided name.
* @param name The name of the header. This value is case-insensitive.
*/
public get(name: string): string | undefined {
return this._headersMap.get(normalizeName(name));
}

/**
* Get whether or not this header collection contains a header entry for the provided header name.
* @param name The name of the header to set. This value is case-insensitive.
*/
public has(name: string): boolean {
return this._headersMap.has(normalizeName(name));
}

/**
* Remove the header with the provided headerName.
* @param name The name of the header to remove.
*/
public delete(name: string): void {
this._headersMap.delete(normalizeName(name));
}

/**
* Get the JSON object representation of this HTTP header collection.
*/
public toJSON(): RawHttpHeaders {
const result: RawHttpHeaders = {};
for (const [key, value] of this._headersMap) {
result[key] = value;
}
return result;
}

/**
* Get the string representation of this HTTP header collection.
*/
public toString(): string {
return JSON.stringify(this.toJSON());
}

/**
* Create a deep clone/copy of this HttpHeaders collection.
*/
public clone(): HttpHeaders {
return new HttpHeadersImpl(this.toJSON());
}

/**
* Iterate over tuples of header [name, value] pairs.
*/
[Symbol.iterator](): Iterator<[string, string]> {
return this._headersMap.entries();
}
}

/**
* Creates an object that satisfies the `HttpHeaders` interface.
* @param rawHeaders A simple object representing initial headers
*/
export function createHttpHeaders(rawHeaders?: RawHttpHeaders): HttpHeaders {
return new HttpHeadersImpl(rawHeaders);
}
Loading

0 comments on commit 00dad89

Please sign in to comment.