Skip to content

Commit b61bc69

Browse files
authored
HttpPipelineBuilder proposal (Azure#43504)
HttpPipelineBuilder proposal
1 parent c181059 commit b61bc69

File tree

19 files changed

+471
-136
lines changed

19 files changed

+471
-136
lines changed

sdk/clientcore/core/spotbugs-exclude.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<Or>
1616
<Class name="io.clientcore.core.http.models.HttpRequest" />
1717
<Class name="io.clientcore.core.http.models.RequestOptions" />
18+
<Class name="io.clientcore.core.http.pipeline.HttpPipelineBuilder" />
1819
<Class name="io.clientcore.core.http.pipeline.HttpPipelineNextPolicy" />
1920
<Class name="io.clientcore.core.http.pipeline.HttpRetryPolicy" />
2021
<Class name="io.clientcore.core.implementation.ReflectionUtilsMethodHandle" />
@@ -91,13 +92,17 @@
9192
</Match>
9293
<Match>
9394
<Bug pattern="DMI_RANDOM_USED_ONLY_ONCE" />
94-
<Class name="io.clientcore.core.util.TestUtils" />
95+
<Or>
96+
<Class name="io.clientcore.core.http.pipeline.HttpPipelinePolicyTests$SyncPolicy" />
97+
<Class name="io.clientcore.core.util.TestUtils" />
98+
</Or>
9599
</Match>
96100
<Match>
97101
<Bug pattern="DM_CONVERT_CASE" />
98102
<Or>
99103
<Class name="io.clientcore.core.http.exception.HttpExceptionType" />
100104
<Class name="io.clientcore.core.http.models.HttpHeaderName" />
105+
<Class name="io.clientcore.core.http.pipeline.HttpPipelineBuilder" />
101106
<Class name="io.clientcore.core.util.ServerSentEventUtils" />
102107
<Class name="io.clientcore.core.util.auth.AuthUtils" />
103108
</Or>
@@ -108,6 +113,7 @@
108113
<Class name="io.clientcore.core.http.RestProxyTests" />
109114
<Class name="io.clientcore.core.http.client.DefaultHttpClientIT" />
110115
<Class name="io.clientcore.core.http.client.SimpleBasicAuthHttpProxyServer" />
116+
<Class name="io.clientcore.core.http.pipeline.HttpPipelinePolicyTests$SyncPolicy" />
111117
<Class name="io.clientcore.core.implementation.http.rest.RestProxyImplTests" />
112118
<Class name="io.clientcore.core.implementation.serializer.AdditionalPropertiesSerializerTests" />
113119
<Class name="io.clientcore.core.implementation.serializer.BinaryDataSerializationTests" />
@@ -340,6 +346,7 @@
340346
<Match>
341347
<Bug pattern="SIC_INNER_SHOULD_BE_STATIC_ANON" />
342348
<Or>
349+
<Class name="io.clientcore.core.http.pipeline.HttpPipelinePolicyTests" />
343350
<Class name="io.clientcore.core.http.pipeline.HttpPipelineTests" />
344351
<Class name="io.clientcore.core.http.pipeline.RedirectPolicyTest" />
345352
<Class name="io.clientcore.core.http.pipeline.RetryPolicyTests" />
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package io.clientcore.core.http.pipeline;
4+
5+
/**
6+
* This class is exactly the same as {@link HttpPipelinePolicy} but exists to provide a standard parent class for all
7+
* credential policies and to differentiate them from other forms of {@link HttpPipelinePolicy}s.
8+
*/
9+
public abstract class HttpCredentialPolicy implements HttpPipelinePolicy {
10+
/**
11+
* Creates an instance of {@link HttpCredentialPolicy}.
12+
*/
13+
public HttpCredentialPolicy() {
14+
}
15+
16+
@Override
17+
public final HttpPipelineOrder getOrder() {
18+
return HttpPipelineOrder.AUTHENTICATION;
19+
}
20+
}

sdk/clientcore/core/src/main/java/io/clientcore/core/http/pipeline/HttpInstrumentationPolicy.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@
6868
* <p>
6969
* It propagates context to the downstream service following <a href="https://www.w3.org/TR/trace-context-1/">W3C Trace Context</a> specification.
7070
* <p>
71-
* The {@link HttpInstrumentationPolicy} should be added to the HTTP pipeline by client libraries. It should be added after
72-
* {@link HttpRetryPolicy} and {@link HttpRedirectPolicy} so that it's executed on each try or redirect and logging happens
73-
* in the scope of the span.
71+
* The {@link HttpInstrumentationPolicy} should be added to the HTTP pipeline by client libraries. It should be added
72+
* after {@link HttpRetryPolicy} and {@link HttpRedirectPolicy} so that it's executed on each try or redirect and
73+
* logging happens in the scope of the span.
7474
* <p>
7575
* The policy supports basic customizations using {@link HttpInstrumentationOptions}.
7676
* <p>
@@ -644,4 +644,9 @@ public void close() throws IOException {
644644
originalBody.close();
645645
}
646646
}
647+
648+
@Override
649+
public HttpPipelineOrder getOrder() {
650+
return HttpPipelineOrder.INSTRUMENTATION;
651+
}
647652
}

sdk/clientcore/core/src/main/java/io/clientcore/core/http/pipeline/HttpPipelineBuilder.java

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
package io.clientcore.core.http.pipeline;
55

66
import io.clientcore.core.http.client.HttpClient;
7+
import io.clientcore.core.instrumentation.logging.ClientLogger;
78
import io.clientcore.core.util.configuration.Configuration;
89

910
import java.util.ArrayList;
10-
import java.util.Arrays;
11+
import java.util.LinkedList;
1112
import java.util.List;
13+
import java.util.Objects;
1214

1315
/**
1416
* This class provides a fluent builder API to help aid the configuration and instantiation of the {@link HttpPipeline},
@@ -28,7 +30,7 @@
2830
* <!-- end io.clientcore.core.http.HttpPipelineBuilder.noConfiguration -->
2931
*
3032
* <p>Create a pipeline using the default HTTP client and a retry policy</p>
31-
*
33+
*
3234
* <!-- src_embed io.clientcore.core.http.HttpPipelineBuilder.defaultHttpClientWithRetryPolicy -->
3335
* <pre>
3436
* HttpPipeline pipeline = new HttpPipelineBuilder&#40;&#41;
@@ -41,8 +43,19 @@
4143
* @see HttpPipeline
4244
*/
4345
public class HttpPipelineBuilder {
46+
private static final ClientLogger LOGGER = new ClientLogger(HttpPipelineBuilder.class);
47+
4448
private HttpClient httpClient;
45-
private List<HttpPipelinePolicy> pipelinePolicies;
49+
50+
private final LinkedList<HttpPipelinePolicy> beforeRedirect = new LinkedList<>();
51+
private HttpRedirectPolicy redirectPolicy;
52+
private final LinkedList<HttpPipelinePolicy> betweenRedirectAndRetry = new LinkedList<>();
53+
private HttpRetryPolicy retryPolicy;
54+
private final LinkedList<HttpPipelinePolicy> betweenRetryAndAuthentication = new LinkedList<>();
55+
private HttpCredentialPolicy credentialPolicy;
56+
private final LinkedList<HttpPipelinePolicy> betweenAuthenticationAndInstrumentation = new LinkedList<>();
57+
private HttpInstrumentationPolicy instrumentationPolicy;
58+
private final LinkedList<HttpPipelinePolicy> afterInstrumentation = new LinkedList<>();
4659

4760
/**
4861
* Creates a new instance of HttpPipelineBuilder that can configure options for the {@link HttpPipeline} before
@@ -60,7 +73,32 @@ public HttpPipelineBuilder() {
6073
* @return A HttpPipeline with the options set from the builder.
6174
*/
6275
public HttpPipeline build() {
63-
List<HttpPipelinePolicy> policies = (pipelinePolicies == null) ? new ArrayList<>() : pipelinePolicies;
76+
List<HttpPipelinePolicy> policies = new ArrayList<>(beforeRedirect);
77+
78+
if (redirectPolicy != null) {
79+
policies.add(redirectPolicy);
80+
}
81+
82+
policies.addAll(betweenRedirectAndRetry);
83+
84+
if (retryPolicy != null) {
85+
policies.add(retryPolicy);
86+
}
87+
88+
policies.addAll(betweenRetryAndAuthentication);
89+
90+
if (credentialPolicy != null) {
91+
policies.add(credentialPolicy);
92+
}
93+
94+
policies.addAll(betweenAuthenticationAndInstrumentation);
95+
96+
if (instrumentationPolicy != null) {
97+
policies.add(instrumentationPolicy);
98+
}
99+
100+
policies.addAll(afterInstrumentation);
101+
64102
HttpClient client;
65103

66104
if (httpClient != null) {
@@ -80,7 +118,6 @@ public HttpPipeline build() {
80118
* Sets the HttpClient that the pipeline will use to send requests.
81119
*
82120
* @param httpClient The HttpClient the pipeline will use when sending requests.
83-
*
84121
* @return The updated HttpPipelineBuilder object.
85122
*/
86123
public HttpPipelineBuilder httpClient(HttpClient httpClient) {
@@ -90,20 +127,82 @@ public HttpPipelineBuilder httpClient(HttpClient httpClient) {
90127
}
91128

92129
/**
93-
* Adds {@link HttpPipelinePolicy policies} to the set of policies that the pipeline will use when sending
94-
* requests.
95-
*
96-
* @param policies Policies to add to the policy set.
130+
* Adds an {@link HttpPipelinePolicy} to the builder.
131+
* <p>
132+
* The {@code policy} passed will be positioned based on {@link HttpPipelinePolicy#getOrder()}. If the
133+
* {@link HttpPipelineOrder} is null an {@link IllegalArgumentException} will be thrown.
134+
* <p>
135+
* If the {@code policy} is one of the pillar policies ({@link HttpRedirectPolicy}, {@link HttpRetryPolicy},
136+
* {@link HttpCredentialPolicy}, or {@link HttpInstrumentationPolicy}) the {@link HttpPipelineOrder} will be ignored
137+
* as those policies are positioned in a specific location within the pipeline. If a duplicate pillar policy is
138+
* added (for example two {@link HttpRetryPolicy}) the last one added will be used and a message will be logged.
97139
*
140+
* @param policy The policy to add to the pipeline.
98141
* @return The updated HttpPipelineBuilder object.
99142
*/
100-
public HttpPipelineBuilder policies(HttpPipelinePolicy... policies) {
101-
if (pipelinePolicies == null) {
102-
pipelinePolicies = new ArrayList<>();
143+
public HttpPipelineBuilder addPolicy(HttpPipelinePolicy policy) {
144+
Objects.requireNonNull(policy, "'policy' cannot be null.");
145+
146+
if (tryAddPillar(policy)) {
147+
return this;
103148
}
104149

105-
this.pipelinePolicies.addAll(Arrays.asList(policies));
150+
HttpPipelineOrder order = policy.getOrder();
151+
if (order == null) {
152+
throw LOGGER.atError()
153+
.addKeyValue("policyType", policy.getClass())
154+
.log("Policy order cannot be null.", new IllegalArgumentException("Policy order cannot be null."));
155+
}
156+
157+
if (order == HttpPipelineOrder.BEFORE_REDIRECT) {
158+
beforeRedirect.add(policy);
159+
} else if (order == HttpPipelineOrder.BETWEEN_REDIRECT_AND_RETRY) {
160+
betweenRedirectAndRetry.add(policy);
161+
} else if (order == HttpPipelineOrder.BETWEEN_RETRY_AND_AUTHENTICATION) {
162+
betweenRetryAndAuthentication.add(policy);
163+
} else if (order == HttpPipelineOrder.BETWEEN_AUTHENTICATION_AND_INSTRUMENTATION) {
164+
betweenAuthenticationAndInstrumentation.add(policy);
165+
} else if (order == HttpPipelineOrder.AFTER_INSTRUMENTATION) {
166+
afterInstrumentation.add(policy);
167+
} else {
168+
throw LOGGER.atError()
169+
.addKeyValue("policyType", policy.getClass())
170+
.addKeyValue("order", order)
171+
.log("Unknown policy order.", new IllegalArgumentException("Unknown policy order."));
172+
}
106173

107174
return this;
108175
}
176+
177+
private boolean tryAddPillar(HttpPipelinePolicy policy) {
178+
HttpPipelinePolicy previous = null;
179+
boolean added = false;
180+
181+
HttpPipelineOrder order = policy.getOrder();
182+
if (order == HttpPipelineOrder.REDIRECT) {
183+
previous = redirectPolicy;
184+
redirectPolicy = (HttpRedirectPolicy) policy;
185+
added = true;
186+
} else if (order == HttpPipelineOrder.RETRY) {
187+
previous = retryPolicy;
188+
retryPolicy = (HttpRetryPolicy) policy;
189+
added = true;
190+
} else if (order == HttpPipelineOrder.AUTHENTICATION) {
191+
previous = credentialPolicy;
192+
credentialPolicy = (HttpCredentialPolicy) policy;
193+
added = true;
194+
} else if (order == HttpPipelineOrder.INSTRUMENTATION) {
195+
previous = instrumentationPolicy;
196+
instrumentationPolicy = (HttpInstrumentationPolicy) policy;
197+
added = true;
198+
}
199+
200+
if (previous != null) {
201+
LOGGER.atWarning()
202+
.addKeyValue("policyType", previous.getClass().getSimpleName())
203+
.log("A pillar policy was replaced in the pipeline.");
204+
}
205+
206+
return added;
207+
}
109208
}

0 commit comments

Comments
 (0)