Skip to content

Commit

Permalink
[core-client-rest] Handle Binary and FormData in Rest Clients (#18753)
Browse files Browse the repository at this point in the history
This PR adds better handling for Binary and FormData for RestClients the main changes are:

- Move injecting api-version qsp to a pipeline policy to be more robust
-  Logic to identify binary response content based on well known content-types
- Add a request option to explicitly tell core-client-rest to handle response a binary regardless of the content-type. This is useful for non-common content-types.
- Pack binary responses into Uint8Array
- Allow users providing Binary content for request body as Uint8Array or string
- Support form-data using the same body property. Internally we detect `content-type: multipart/form-data` and pass it to core-rest-pipeline as `request.formData`
  • Loading branch information
joheredi authored Nov 29, 2021
1 parent 0680cea commit c07af44
Show file tree
Hide file tree
Showing 20 changed files with 803 additions and 32 deletions.
25 changes: 22 additions & 3 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"autoPublish": false,
"dependencies": {
"@azure/core-auth": "^1.3.0",
"@azure-rest/core-client": "1.0.0-beta.7",
"@azure-rest/core-client": "1.0.0-beta.8",
"@azure/core-rest-pipeline": "^1.1.0",
"@azure/logger": "^1.0.0",
"tslib": "^2.2.0"
Expand Down
2 changes: 2 additions & 0 deletions sdk/core/core-client-rest/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Handle Binary and FormData content. [#18753](https://github.com/Azure/azure-sdk-for-js/pull/18753)

### Breaking Changes

### Bugs Fixed
Expand Down
15 changes: 8 additions & 7 deletions sdk/core/core-client-rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,35 +63,36 @@
"tslib": "^2.2.0"
},
"devDependencies": {
"@azure/dev-tool": "^1.0.0",
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
"@microsoft/api-extractor": "^7.18.11",
"@types/chai": "^4.1.6",
"@types/mocha": "^7.0.2",
"@types/node": "^12.0.0",
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
"@azure/dev-tool": "^1.0.0",
"@types/sinon": "^9.0.4",
"chai": "^4.2.0",
"cross-env": "^7.0.2",
"eslint": "^7.15.0",
"inherits": "^2.0.3",
"karma": "^6.2.0",
"karma-chrome-launcher": "^3.0.0",
"karma-coverage": "^2.0.0",
"karma-edge-launcher": "^0.4.2",
"karma-env-preprocessor": "^0.1.1",
"karma-firefox-launcher": "^1.1.0",
"karma-ie-launcher": "^1.0.0",
"karma-junit-reporter": "^2.0.1",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-mocha": "^2.0.1",
"karma-sourcemap-loader": "^0.3.8",
"mocha": "^7.1.1",
"karma": "^6.2.0",
"mocha-junit-reporter": "^1.18.0",
"mocha": "^7.1.1",
"prettier": "2.2.1",
"rimraf": "^3.0.0",
"rollup": "^1.16.3",
"sinon": "^9.0.2",
"typedoc": "0.15.2",
"typescript": "~4.2.0",
"util": "^0.12.1",
"typedoc": "0.15.2"
"util": "^0.12.1"
}
}
1 change: 1 addition & 0 deletions sdk/core/core-client-rest/review/core-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export type RequestParameters = {
contentType?: string;
allowInsecureConnection?: boolean;
skipUrlEncoding?: boolean;
binaryResponse?: boolean;
};

// @public
Expand Down
28 changes: 28 additions & 0 deletions sdk/core/core-client-rest/src/apiVersionPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { PipelinePolicy } from "@azure/core-rest-pipeline";
import { ClientOptions } from "./common";
import { URL } from "./url";

export const apiVersionPolicyName = "ApiVersionPolicy";

/**
* Creates a policy that sets the apiVersion as a query parameter on every request
* @param options - Client options
* @returns Pipeline policy that sets the apiVersion as a query parameter on every request
*/
export function apiVersionPolicy(options: ClientOptions): PipelinePolicy {
return {
name: apiVersionPolicyName,
sendRequest: (req, next) => {
if (options.apiVersion) {
const url = new URL(req.url);
url.searchParams.append("api-version", options.apiVersion);
req.url = url.toString();
}

return next(req);
},
};
}
3 changes: 3 additions & 0 deletions sdk/core/core-client-rest/src/clientHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { isNode } from "@azure/core-util";
import { TokenCredential, KeyCredential, isTokenCredential } from "@azure/core-auth";
import { ClientOptions } from "./common";
import { keyCredentialAuthenticationPolicy } from "./keyCredentialAuthenticationPolicy";
import { apiVersionPolicy } from "./apiVersionPolicy";

let cachedHttpClient: HttpClient | undefined;

Expand Down Expand Up @@ -49,6 +50,8 @@ export function createDefaultPipeline(
pipeline.addPolicy(redirectPolicy(options.redirectOptions), { afterPhase: "Retry" });
pipeline.addPolicy(logPolicy(), { afterPhase: "Retry" });

pipeline.addPolicy(apiVersionPolicy(options));

if (credential) {
if (isTokenCredential(credential)) {
const tokenPolicy = bearerTokenAuthenticationPolicy({
Expand Down
5 changes: 5 additions & 0 deletions sdk/core/core-client-rest/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export type RequestParameters = {
allowInsecureConnection?: boolean;
/** Set to true if you want to skip encoding the path parameters */
skipUrlEncoding?: boolean;
/**
* With this property set to true, the response body will be returned
* as a binary array UInt8Array
*/
binaryResponse?: boolean;
};

/**
Expand Down
17 changes: 0 additions & 17 deletions sdk/core/core-client-rest/src/getClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export function getClient(
get: (options: RequestParameters = {}): Promise<HttpResponse> => {
return buildSendRequest(
"GET",
clientOptions,
baseUrl,
path,
pipeline,
Expand All @@ -58,7 +57,6 @@ export function getClient(
post: (options: RequestParameters = {}): Promise<HttpResponse> => {
return buildSendRequest(
"POST",
clientOptions,
baseUrl,
path,
pipeline,
Expand All @@ -69,7 +67,6 @@ export function getClient(
put: (options: RequestParameters = {}): Promise<HttpResponse> => {
return buildSendRequest(
"PUT",
clientOptions,
baseUrl,
path,
pipeline,
Expand All @@ -80,7 +77,6 @@ export function getClient(
patch: (options: RequestParameters = {}): Promise<HttpResponse> => {
return buildSendRequest(
"PATCH",
clientOptions,
baseUrl,
path,
pipeline,
Expand All @@ -91,7 +87,6 @@ export function getClient(
delete: (options: RequestParameters = {}): Promise<HttpResponse> => {
return buildSendRequest(
"DELETE",
clientOptions,
baseUrl,
path,
pipeline,
Expand All @@ -102,7 +97,6 @@ export function getClient(
head: (options: RequestParameters = {}): Promise<HttpResponse> => {
return buildSendRequest(
"HEAD",
clientOptions,
baseUrl,
path,
pipeline,
Expand All @@ -113,7 +107,6 @@ export function getClient(
options: (options: RequestParameters = {}): Promise<HttpResponse> => {
return buildSendRequest(
"OPTIONS",
clientOptions,
baseUrl,
path,
pipeline,
Expand All @@ -124,7 +117,6 @@ export function getClient(
trace: (options: RequestParameters = {}): Promise<HttpResponse> => {
return buildSendRequest(
"TRACE",
clientOptions,
baseUrl,
path,
pipeline,
Expand All @@ -144,22 +136,13 @@ export function getClient(

function buildSendRequest(
method: HttpMethods,
clientOptions: ClientOptions,
baseUrl: string,
path: string,
pipeline: Pipeline,
requestOptions: RequestParameters = {},
args: string[] = []
): Promise<HttpResponse> {
// If the client has an api-version and the request doesn't specify one, inject the one in the client options
if (!requestOptions.queryParameters?.["api-version"] && clientOptions.apiVersion) {
if (!requestOptions.queryParameters) {
requestOptions.queryParameters = {};
}

requestOptions.queryParameters["api-version"] = clientOptions.apiVersion;
}

const url = buildRequestUrl(baseUrl, path, args, requestOptions);
return sendRequest(method, url, pipeline, requestOptions);
}
Expand Down
26 changes: 26 additions & 0 deletions sdk/core/core-client-rest/src/helpers/getBinaryBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/**
* Converts a string representing binary content into a Uint8Array
*/
export function stringToBinaryArray(content: string): Uint8Array {
const arr = new Uint8Array(content.length);
for (let i = 0; i < content.length; i++) {
arr[i] = content.charCodeAt(i);
}

return arr;
}

/**
* Converts binary content to its string representation
*/
export function binaryArrayToString(content: Uint8Array): string {
let decodedBody = "";
for (const element of content) {
decodedBody += String.fromCharCode(element);
}

return decodedBody;
}
Loading

0 comments on commit c07af44

Please sign in to comment.