Skip to content

Commit 4882b4a

Browse files
authored
[Core] Documentation for pipeline and policies (Azure#20418)
Packages impacted by this PR None. This PR adds documentation for Pipeline and PipelinePolicy Issues associated with this PR Azure#13561 Describe the problem that is addressed by this PR Add documentation for the Pipeline and Policies and how to customize them.
1 parent 2f2444b commit 4882b4a

File tree

2 files changed

+257
-3
lines changed

2 files changed

+257
-3
lines changed

sdk/core/core-rest-pipeline/documentation/core2.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ In ancient times (~5 years ago), Microsoft provided SDKs for NodeJS developers w
1212

1313
Another package `ms-rest-azure` was created to house Azure-specific components and authetication code that worked with a variety of credentials like Service Principal, Device Token, and Managed Identity. At this point, support for TypeScript came in the form of hand-authored type declaration files.
1414

15-
Later, there emerged a desire for isomorphic packages, packages that worked in both the browser as well as Node. This required a new runtime, code generator and repo. As part of this effort,
15+
Later, there emerged a desire for isomorphic packages, packages that worked in both the browser as well as Node. This required a new runtime, code generator and repo. As part of this effort,
16+
1617
- `ms-rest` became `@azure/ms-rest-js` to provide the same capabilities in both Node.js and browser
1718
- The authentication pieces from `ms-rest-azure` were put into `@azure/ms-rest-nodeauth` and an equivalent package was created for the browser called `@azure/ms-rest-browserauth`.
1819
- All remaining parts of `ms-rest-azure` were placed into `@azure/ms-rest-azure-js`.
1920
- The new code generator (`autorest.typescript`) that generated code in TypeScript and used the newer runtimes mentioned above replaced the existing one (`autorest.nodejs`) that generated JavaScript code.
2021
- The packages thus generated got a new home in the repo `azure-sdk-for-js`. The older packages lived in `azure-sdk-for-node` repo.
2122

22-
A year or so later, the new Azure SDK team was formed in Nov 2018 that then came up with guidelines on how to write an Azure SDK in TypeScript. To follow these guidelines, we needed yet another runtime. This was the new `@azure/core-http` package built over the existing `@azure/ms-rest-js`. At the time we chose to do so as making slight changes and improvements to a tried and tested solution was preferred over investing time in a solution that had to be written from scratch.
23+
A year or so later, the new Azure SDK team was formed in Nov 2018 that then came up with guidelines on how to write an Azure SDK in TypeScript. To follow these guidelines, we needed yet another runtime. This was the new `@azure/core-http` package built over the existing `@azure/ms-rest-js`. At the time we chose to do so as making slight changes and improvements to a tried and tested solution was preferred over investing time in a solution that had to be written from scratch.
2324

2425
The downside is that much of the code had evolved organically over time, experienced incomplete upgrades to various layers, and had dead code that wasn't really needed anymore. We mitigated this during the 1.0 GA timeframe in Nov 2020 by manually declaring cleaner public interfaces (e.g. `OperationOptions` over `RequestOptionsBase`) but many of the internal interfaces were still messy, loosely typed, or confusing.
2526

@@ -46,7 +47,7 @@ The first package, `@azure/core-rest-pipeline` will not be AutoRest-specific and
4647

4748
A major weakness of the existing JS Core was that the request pipeline system was difficult to customize at the library level. This placed an undue burden on authors of libraries and increased support costs. As other languages had better support for customizing the request pipeline, we had a desire to provide a simple and flexible way to add and order policies that could affect both HTTP requests and responses.
4849

49-
The [New Pipeline Design](https://github.com/Azure/azure-sdk-for-js/issues/8461) solves these concerns by providing a new abstraction that is able to reliably apply ordered policies with minimal configuration.
50+
The [New Pipeline Design](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-rest-pipeline/documentation/pipeline.md) solves these concerns by providing a new abstraction that is able to reliably apply ordered policies with minimal configuration.
5051

5152
### Dropping legacy browser support
5253

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
A Pipeline in the Azure SDKs is a collection of policies that can work on the request before sending it to the server and also on the raw responses before handing them to the SDK users. This document describes in detail the structure and functionality of the Pipeline and Policies
2+
3+
# Policies
4+
5+
A pipeline policy manipulates a request as it travels through the pipeline. It is conceptually a middleware that is allowed to modify the request before it is made as well as the response when it is received.
6+
7+
It is important to note that the **first** policy to touch the request will be the **last** policy to work on the response.
8+
9+
[Find out more about the Middleware pattern](https://docs.microsoft.com/aspnet/core/fundamentals/middleware/?tabs=aspnetcore2x&view=aspnetcore-6.0)
10+
11+
```typescript
12+
export type SendRequest = (request: PipelineRequest) => Promise<PipelineResponse>;
13+
14+
export interface PipelinePolicy {
15+
name: string;
16+
sendRequest(request: PipelineRequest, next: SendRequest): Promise<PipelineResponse>;
17+
}
18+
```
19+
20+
# Pipelines
21+
22+
A `Pipeline` is composed of several policies and provides the ability to make a request that will flow through the policies in a specific order determined by their requirements. This allows manipulating each request before it is sent to the server and to work on the response after receiving it.
23+
24+
```typescript
25+
export interface AddPipelineOptions {
26+
afterPhase?: PipelinePhase;
27+
afterPolicies?: string[];
28+
beforePolicies?: string[];
29+
phase?: PipelinePhase;
30+
}
31+
32+
export interface Pipeline {
33+
addPolicy(policy: PipelinePolicy, options?: AddPipelineOptions): void;
34+
clone(): Pipeline;
35+
getOrderedPolicies(): PipelinePolicy[];
36+
removePolicy(options: { name?: string; phase?: PipelinePhase }): PipelinePolicy[];
37+
sendRequest(httpClient: HttpClient, request: PipelineRequest): Promise<PipelineResponse>;
38+
}
39+
```
40+
41+
**Note**: It is important to never rely on policy insert ordering and always express policy execution constraints using `AddPipelineOptions` when policies have interactions. For example, the following code snippets have identical effects:
42+
43+
```typescript
44+
pipeline.addPolicy(barPolicy);
45+
pipeline.addPolicy(fooPolicy, { afterPolicy: "barPolicy" });
46+
47+
// The below is the same as the above.
48+
// Policies don't have to be part of the pipeline in order to be
49+
// referenced by before/afterPolicy.
50+
pipeline.addPolicy(fooPolicy, { afterPolicy: "barPolicy" });
51+
pipeline.addPolicy(barPolicy);
52+
```
53+
54+
Phases are predefined buckets of activity that pre-defined policies inside core will fall into. They are currently a fixed set and not customizable by pipeline consumers.
55+
56+
```typescript
57+
export type PipelinePhase = "Deserialize" | "Serialize" | "Retry" | "Sign";
58+
```
59+
60+
The execution order is:
61+
62+
1. **Serialize Phase** - Policies that assamble the request to be sent through HTTP, i.e. transforming the body object to its JSON string representation.
63+
2. **Policies not in a phase** - Any policies added to the pipeline without a specified phase
64+
3. **Deserialize Phase** - Policies that transform the raw HTTP response into friendlier response shapes. In addition to transforming the body JSON string into a JavaScript object, this can also include things like creating native JS `Date` objects from strings and other object forms not natively supported by JSON.
65+
4. **Retry Phase** - Policies that inspect the raw response, typically the response code and errors returned from the service to decide whether or not to re-try the request.
66+
5. **Sign Phase** - Policies that sign the request for security purposes, for example adding a HMAC signature to prevent tampering during transport.```
67+
68+
# Default Pipeline
69+
70+
`@azure/core-rest-pipeline` exposes `createPipelineFromOptions` which creates pipeline with a predefined set of policies. The resulting pipeline can be configured through `PipelineOptions`
71+
72+
```typescript
73+
/**
74+
* Defines options that are used to configure the HTTP pipeline for
75+
* an SDK client.
76+
*/
77+
export interface PipelineOptions {
78+
/**
79+
* Options that control how to retry failed requests.
80+
*/
81+
retryOptions?: PipelineRetryOptions;
82+
83+
/**
84+
* Options to configure a proxy for outgoing requests.
85+
*/
86+
proxyOptions?: ProxySettings;
87+
88+
/**
89+
* Options for how redirect responses are handled.
90+
*/
91+
redirectOptions?: RedirectPolicyOptions;
92+
93+
/**
94+
* Options for adding user agent details to outgoing requests.
95+
*/
96+
userAgentOptions?: UserAgentPolicyOptions;
97+
}
98+
99+
/**
100+
* Defines options that are used to configure internal options of
101+
* the HTTP pipeline for an SDK client.
102+
*/
103+
export interface InternalPipelineOptions extends PipelineOptions {
104+
/**
105+
* Options to configure request/response logging.
106+
*/
107+
loggingOptions?: LogPolicyOptions;
108+
}
109+
110+
/**
111+
* Create a new pipeline with a default set of customizable policies.
112+
* @param options - Options to configure a custom pipeline.
113+
*/
114+
declare function createPipelineFromOptions(options: InternalPipelineOptions): Pipeline;
115+
```
116+
117+
**Note:** `InternalPipelineOptions` is used internally by SDK client authors to configure the SDK pipeline but it is not exposed to consumers. Consumers can configure the default pipeline through the `PipelineOptions` when instantiating a new client.
118+
119+
### Policies included in a default pipeline
120+
121+
- proxyPolicy (**NodeJS Only**)
122+
- A policy that allows one to apply proxy settings to all requests. If not passed static settings, they will be retrieved from the HTTPS_PROXY or HTTP_PROXY environment variables.
123+
- decompressResponsePolicy (**NodeJS Only**)
124+
- A policy to enable response decompression according to Accept-Encoding header.
125+
- formDataPolicy
126+
- A policy that encodes FormData on the request into the body.
127+
- userAgentPolicy
128+
- A policy that sets the User-Agent header (or equivalent) to reflect the SDK version and device runtime.
129+
- setClientRequestIdPolicy
130+
- A policy that tags each outgoing request with a unique ID for tracing purposes. This can be useful when opening support tickets against Azure services.
131+
- defaultRetryPolicy
132+
- A policy that retries according to three strategies:
133+
- When the server sends a 429 response with a Retry-After header.
134+
- When there are errors in the underlying transport layer (e.g. DNS lookup failures.)
135+
- After considering the above, if the outgoing request fails with a non-final response code, it will retry with an exponentially increasing delay.
136+
- tracingPolicy
137+
- A policy to create [OpenTelemetry](https://opentelemetry.io/) tracing spans for each request made by the pipeline. Requests made without a valid tracing context will not be recorded. SDK client authors are expected to use the appropriate callbacks from `@azure/core-tracing` to set tracing contexts for all client operations.
138+
- redirectPolicy
139+
- A policy to follow Location headers from the server in order to support server-side redirection.
140+
- logPolicy
141+
- A policy that logs all requests and responses using [@azure/logger](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/logger).
142+
143+
There are other policies commonly added to the default pipeline:
144+
145+
- serializationPolicy
146+
- This policy handles assembling the request body and headers using an OperationSpec and OperationArguments on the request.
147+
- deserializationPolicy
148+
- This policy handles parsing out responses according to OperationSpecs on the request.
149+
150+
# Authentication and Signing
151+
152+
Pipeline policies are used to authenticate requests in Azure SDKs. An authentication policy would sign the request before sending it with the required authentication information, these policies run in the `Sign` phase.
153+
154+
For example, the `bearerTokenAuthenticationPolicy` would sign the request by adding the bearer token in the request headers before the request is sent.
155+
156+
# Configuring client pipeline
157+
158+
When instantiating a new client a pipeline can be configured by adding additional policies to the default pipeline, passing a static array of policies.
159+
160+
When configuring additional policies the following execution positions can be specified:
161+
162+
- `perCall` - When a policy is assigned a `perCall` position, it will be run once before sending the request. If the request needs to be retried, the policy will not be called again.
163+
- `perRetry` - When a policy is assigned a `perRetry` position, the policy will be executed again before each new request is sent.
164+
165+
```typescript
166+
type SendRequest = (request: PipelineRequest) => Promise<PipelineResponse>;
167+
168+
const sendRequest = (request: PipelineRequest, next: SendRequest) => next(request);
169+
170+
const policy1: PipelinePolicy = {
171+
name: "policy1",
172+
sendRequest,
173+
};
174+
const policy2: PipelinePolicy = {
175+
name: "policy2",
176+
sendRequest,
177+
};
178+
179+
const client = new ServiceClient({
180+
additionalPolicies: [
181+
{
182+
policy: policy1,
183+
/**
184+
* Determines if this policy be applied before or after retry logic.
185+
* Only use `perRetry` if you need to modify the request again
186+
* each time the operation is retried due to retryable service
187+
* issues.
188+
*/
189+
position: "perRetry",
190+
},
191+
{ policy: policy2, position: "perCall" },
192+
],
193+
});
194+
```
195+
196+
# HttpClient
197+
198+
The `HttpClient` is the last logical piece of the pipeline. The `HttpClient` will send the request over the wire after all policies have run and will hand off the response back to the pipeline once the server responds. There are different default implementations for the `HttpClient` based on if the SDK is running inside a Node or browser runtime.
199+
200+
For browsers, an `HttpClient` implementation based on `Fetch API` will be used. When running in Node, an `HttpClient` based on Node's native `https` module will be used.
201+
202+
A custom `HttpClient` implementation can be provided when constructing an SDK client.
203+
204+
# Examples
205+
206+
## Inserting a new policy at a specific place
207+
208+
```typescript
209+
interface TracingPolicyOptions {
210+
tracingEnabled: boolean;
211+
}
212+
213+
function tracingPolicy(options: TracingPolicyOptions): PipelinePolicy {
214+
return {
215+
name: "TracingPolicy",
216+
sendRequest: (request, next) => {
217+
if (options.tracingEnabled) {
218+
// add some headers
219+
request.headers.set("tracing-id", "123456");
220+
}
221+
222+
return next(request);
223+
},
224+
};
225+
}
226+
227+
// Extend an existing Pipeline
228+
pipeline.addPolicy(tracingPolicy({ tracingEnabled: true }), { beforePhase: "Retry" });
229+
```
230+
231+
## Removing policies
232+
233+
Policies can be removed from a pipeline, consumers can target a specifc policy by name or remove all the policies in a specific phase
234+
235+
```typescript
236+
// Remove a single policy, referencing it by name
237+
pipeline.removePolicy({ name: "customPolicy" });
238+
239+
// Remove all policies from a phase
240+
pipeline.removePolicy({ phase: "Retry" });
241+
```
242+
243+
## List policies
244+
245+
For debugging purposes, consumers can list all the policies in a pipeline calling `getOrderedPolicies()`, which returns the current set of policies in the pipeline in the order in which they will be applied to the request, remember that policies have a bubbling nature, this means that the **first** policy to touch the request will be the **last** policy to work on the response.
246+
247+
```typescript
248+
const policies = pipeline.getOrderedPolicies();
249+
250+
for (const policy of policies) {
251+
console.log(policy.name);
252+
}
253+
```

0 commit comments

Comments
 (0)