Skip to content

Commit 1d5eb56

Browse files
authored
Add RequestOptions to Core.Experimental (Azure#25678)
* intial implementation * rework * update api listing * pr fb
1 parent df729e1 commit 1d5eb56

File tree

5 files changed

+318
-0
lines changed

5 files changed

+318
-0
lines changed

sdk/core/Azure.Core.Experimental/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Added `RequestOptions` to enable per-invocation control of the request pipeline.
8+
79
### Breaking Changes
810

911
- The following types were removed:

sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
namespace Azure
2+
{
3+
public partial class RequestOptions
4+
{
5+
public RequestOptions() { }
6+
public Azure.ErrorOptions ErrorOptions { get { throw null; } set { } }
7+
public void AddPolicy(Azure.Core.Pipeline.HttpPipelinePolicy policy, Azure.Core.HttpPipelinePosition position) { }
8+
}
9+
}
110
namespace Azure.Core
211
{
312
[System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")]
@@ -86,6 +95,13 @@ public void Set(string propertyName, string? value) { }
8695
public System.Threading.Tasks.Task<long> WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken) { throw null; }
8796
}
8897
}
98+
namespace Azure.Core.Pipeline
99+
{
100+
public static partial class HttpPipelineExtensions
101+
{
102+
public static Azure.Core.HttpMessage CreateMessage(this Azure.Core.Pipeline.HttpPipeline pipeline, Azure.RequestOptions? options) { throw null; }
103+
}
104+
}
89105
namespace Azure.Messaging
90106
{
91107
public abstract partial class MessageWithMetadata
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
8+
namespace Azure.Core.Pipeline
9+
{
10+
/// <summary>
11+
/// Extensions to HttpPipeline to support RequestOptions.
12+
/// </summary>
13+
public static class HttpPipelineExtensions
14+
{
15+
/// <summary>
16+
/// Creates a new <see cref="HttpMessage"/> instance.
17+
/// </summary>
18+
/// <param name="pipeline"></param>
19+
/// <param name="options">The message options.</param>
20+
/// <returns>The message.</returns>
21+
public static HttpMessage CreateMessage(this HttpPipeline pipeline, RequestOptions? options)
22+
{
23+
// TODO: This method will be added as a method on HttpPipeline directly
24+
// when RequestOptions moves to core. At that time, we expect RequestContext
25+
// to inherit from RequestOptions, so copying RequestOptions to
26+
// RequestContext can be removed.
27+
28+
if (options == null)
29+
{
30+
return pipeline.CreateMessage();
31+
}
32+
33+
RequestContext context = new RequestContext();
34+
35+
context.ErrorOptions = options.ErrorOptions;
36+
37+
if (options.Policies != null)
38+
{
39+
foreach (var policy in options.Policies)
40+
{
41+
context.AddPolicy(policy.Policy, policy.Position);
42+
}
43+
}
44+
45+
return pipeline.CreateMessage(context);
46+
}
47+
}
48+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
using Azure.Core;
7+
using Azure.Core.Pipeline;
8+
9+
namespace Azure
10+
{
11+
/// <summary>
12+
/// Options which can be used to control the behavior of a request sent by a client.
13+
/// </summary>
14+
public class RequestOptions
15+
{
16+
internal List<(HttpPipelinePosition Position, HttpPipelinePolicy Policy)>? Policies { get; private set; }
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="RequestOptions"/> class.
20+
/// </summary>
21+
public RequestOptions()
22+
{
23+
}
24+
25+
/// <summary>
26+
/// Controls under what conditions the operation raises an exception if the underlying response indicates a failure.
27+
/// </summary>
28+
public ErrorOptions ErrorOptions { get; set; } = ErrorOptions.Default;
29+
30+
/// <summary>
31+
/// Adds an <see cref="HttpPipelinePolicy"/> into the pipeline for the duration of this request.
32+
/// The position of policy in the pipeline is controlled by <paramref name="position"/> parameter.
33+
/// If you want the policy to execute once per client request use <see cref="HttpPipelinePosition.PerCall"/>
34+
/// otherwise use <see cref="HttpPipelinePosition.PerRetry"/> to run the policy for every retry.
35+
/// </summary>
36+
/// <param name="policy">The <see cref="HttpPipelinePolicy"/> instance to be added to the pipeline.</param>
37+
/// <param name="position">The position of the policy in the pipeline.</param>
38+
public void AddPolicy(HttpPipelinePolicy policy, HttpPipelinePosition position)
39+
{
40+
Policies ??= new();
41+
Policies.Add((position, policy));
42+
}
43+
}
44+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using Azure.Core.Pipeline;
7+
using Azure.Core.TestFramework;
8+
using NUnit.Framework;
9+
10+
namespace Azure.Core.Experimental.Tests
11+
{
12+
public class RequestOptionsTests
13+
{
14+
[Test]
15+
public void CanSetErrorOptions()
16+
{
17+
RequestOptions options = new RequestOptions { ErrorOptions = ErrorOptions.NoThrow };
18+
19+
Assert.IsTrue(options.ErrorOptions == ErrorOptions.NoThrow);
20+
}
21+
22+
[Test]
23+
public async Task CanAddPolicy_PerCall()
24+
{
25+
var mockTransport = new MockTransport(new MockResponse(200));
26+
var clientOptions = new TestOptions()
27+
{
28+
Transport = mockTransport,
29+
};
30+
var pipeline = HttpPipelineBuilder.Build(clientOptions);
31+
32+
var options = new RequestOptions();
33+
options.AddPolicy(new AddHeaderPolicy("PerCallHeader", "Value"), HttpPipelinePosition.PerCall);
34+
35+
var message = pipeline.CreateMessage(options);
36+
await pipeline.SendAsync(message, message.CancellationToken);
37+
38+
Request request = mockTransport.Requests[0];
39+
Assert.IsTrue(request.Headers.TryGetValues("PerCallHeader", out var values));
40+
Assert.AreEqual(1, values.Count());
41+
Assert.AreEqual("Value", values.ElementAt(0));
42+
}
43+
44+
[Test]
45+
public async Task CanAddPolicy_PerRetry()
46+
{
47+
var retryResponse = new MockResponse(408); // Request Timeout
48+
var mockTransport = new MockTransport(retryResponse, retryResponse, new MockResponse(200));
49+
50+
var clientOptions = new TestOptions()
51+
{
52+
Transport = mockTransport,
53+
};
54+
var pipeline = HttpPipelineBuilder.Build(clientOptions);
55+
56+
var options = new RequestOptions();
57+
options.AddPolicy(new AddHeaderPolicy("PerRetryHeader", "Value"), HttpPipelinePosition.PerRetry);
58+
59+
var message = pipeline.CreateMessage(options);
60+
await pipeline.SendAsync(message, message.CancellationToken);
61+
62+
Request request = mockTransport.Requests[0];
63+
Assert.IsTrue(request.Headers.TryGetValues("PerRetryHeader", out var values));
64+
Assert.AreEqual(3, values.Count());
65+
Assert.AreEqual("Value", values.ElementAt(0));
66+
Assert.AreEqual("Value", values.ElementAt(1));
67+
Assert.AreEqual("Value", values.ElementAt(2));
68+
}
69+
70+
[Test]
71+
public async Task CanAddPolicy_BeforeTransport()
72+
{
73+
var retryResponse = new MockResponse(408); // Request Timeout
74+
75+
// retry twice
76+
var mockTransport = new MockTransport(retryResponse, retryResponse, new MockResponse(200));
77+
var clientOptions = new TestOptions()
78+
{
79+
Transport = mockTransport,
80+
};
81+
var pipeline = HttpPipelineBuilder.Build(clientOptions);
82+
83+
var options = new RequestOptions();
84+
options.AddPolicy(new AddHeaderPolicy("BeforeTransportHeader", "Value"), HttpPipelinePosition.BeforeTransport);
85+
86+
var message = pipeline.CreateMessage(options);
87+
await pipeline.SendAsync(message, message.CancellationToken);
88+
89+
Request request = mockTransport.Requests[0];
90+
91+
Assert.IsTrue(request.Headers.TryGetValues("BeforeTransportHeader", out var values));
92+
Assert.AreEqual(3, values.Count());
93+
Assert.AreEqual("Value", values.ElementAt(0));
94+
Assert.AreEqual("Value", values.ElementAt(1));
95+
Assert.AreEqual("Value", values.ElementAt(2));
96+
}
97+
98+
[Test]
99+
public async Task CanAddRequestPolicies_AllPositions()
100+
{
101+
var retryResponse = new MockResponse(408); // Request Timeout
102+
103+
// retry twice -- this will add the header three times.
104+
var mockTransport = new MockTransport(retryResponse, retryResponse, new MockResponse(200));
105+
var clientOptions = new TestOptions()
106+
{
107+
Transport = mockTransport,
108+
};
109+
var pipeline = HttpPipelineBuilder.Build(clientOptions);
110+
111+
var options = new RequestOptions();
112+
options.AddPolicy(new AddHeaderPolicy("PerCallHeader1", "PerCall1"), HttpPipelinePosition.PerCall);
113+
options.AddPolicy(new AddHeaderPolicy("PerCallHeader2", "PerCall2"), HttpPipelinePosition.PerCall);
114+
options.AddPolicy(new AddHeaderPolicy("PerRetryHeader", "PerRetry"), HttpPipelinePosition.PerRetry);
115+
options.AddPolicy(new AddHeaderPolicy("BeforeTransportHeader", "BeforeTransport"), HttpPipelinePosition.BeforeTransport);
116+
117+
var message = pipeline.CreateMessage(options);
118+
await pipeline.SendAsync(message, message.CancellationToken);
119+
120+
Request request = mockTransport.Requests[0];
121+
122+
Assert.IsTrue(request.Headers.TryGetValues("PerCallHeader1", out var perCall1Values));
123+
Assert.AreEqual(1, perCall1Values.Count());
124+
Assert.AreEqual("PerCall1", perCall1Values.ElementAt(0));
125+
126+
Assert.IsTrue(request.Headers.TryGetValues("PerCallHeader2", out var perCall2Values));
127+
Assert.AreEqual(1, perCall2Values.Count());
128+
Assert.AreEqual("PerCall2", perCall2Values.ElementAt(0));
129+
130+
Assert.IsTrue(request.Headers.TryGetValues("PerRetryHeader", out var perRetryValues));
131+
Assert.AreEqual("PerRetry", perRetryValues.ElementAt(0));
132+
Assert.AreEqual("PerRetry", perRetryValues.ElementAt(1));
133+
Assert.AreEqual("PerRetry", perRetryValues.ElementAt(2));
134+
135+
Assert.IsTrue(request.Headers.TryGetValues("BeforeTransportHeader", out var beforeTransportValues));
136+
Assert.AreEqual("BeforeTransport", beforeTransportValues.ElementAt(0));
137+
Assert.AreEqual("BeforeTransport", beforeTransportValues.ElementAt(1));
138+
Assert.AreEqual("BeforeTransport", beforeTransportValues.ElementAt(2));
139+
}
140+
141+
[Test]
142+
public async Task CanAddPolicies_ThreeWays()
143+
{
144+
var mockTransport = new MockTransport(new MockResponse(200));
145+
146+
var clientOptions = new TestOptions()
147+
{
148+
Transport = mockTransport,
149+
};
150+
var perCallPolicies = new HttpPipelinePolicy[] { new AddHeaderPolicy("PerCall", "Builder") };
151+
var perRetryPolicies = new HttpPipelinePolicy[] { new AddHeaderPolicy("PerRetry", "Builder") };
152+
153+
clientOptions.AddPolicy(new AddHeaderPolicy("BeforeTransport", "ClientOptions"), HttpPipelinePosition.BeforeTransport);
154+
clientOptions.AddPolicy(new AddHeaderPolicy("PerRetry", "ClientOptions"), HttpPipelinePosition.PerRetry);
155+
clientOptions.AddPolicy(new AddHeaderPolicy("PerCall", "ClientOptions"), HttpPipelinePosition.PerCall);
156+
157+
var pipeline = HttpPipelineBuilder.Build(clientOptions, perCallPolicies, perRetryPolicies, null);
158+
159+
var options = new RequestOptions();
160+
options.AddPolicy(new AddHeaderPolicy("PerRetry", "RequestContext"), HttpPipelinePosition.PerRetry);
161+
options.AddPolicy(new AddHeaderPolicy("PerCall", "RequestContext"), HttpPipelinePosition.PerCall);
162+
options.AddPolicy(new AddHeaderPolicy("BeforeTransport", "RequestContext"), HttpPipelinePosition.BeforeTransport);
163+
164+
var message = pipeline.CreateMessage(options);
165+
await pipeline.SendAsync(message, message.CancellationToken);
166+
167+
Request request = mockTransport.Requests[0];
168+
169+
Assert.IsTrue(request.Headers.TryGetValues("PerCall", out var perCallValues));
170+
Assert.AreEqual(3, perCallValues.Count());
171+
Assert.AreEqual("Builder", perCallValues.ElementAt(0));
172+
Assert.AreEqual("ClientOptions", perCallValues.ElementAt(1));
173+
Assert.AreEqual("RequestContext", perCallValues.ElementAt(2));
174+
175+
Assert.IsTrue(request.Headers.TryGetValues("PerRetry", out var perRetryValues));
176+
Assert.AreEqual(3, perRetryValues.Count());
177+
Assert.AreEqual("Builder", perRetryValues.ElementAt(0));
178+
Assert.AreEqual("ClientOptions", perRetryValues.ElementAt(1));
179+
Assert.AreEqual("RequestContext", perRetryValues.ElementAt(2));
180+
181+
Assert.IsTrue(request.Headers.TryGetValues("BeforeTransport", out var beforeTransportValues));
182+
Assert.AreEqual(2, beforeTransportValues.Count());
183+
Assert.AreEqual("ClientOptions", beforeTransportValues.ElementAt(0));
184+
Assert.AreEqual("RequestContext", beforeTransportValues.ElementAt(1));
185+
}
186+
187+
public class AddHeaderPolicy : HttpPipelineSynchronousPolicy
188+
{
189+
private string _headerName;
190+
private string _headerVaue;
191+
192+
public AddHeaderPolicy(string headerName, string headerValue) : base()
193+
{
194+
_headerName = headerName;
195+
_headerVaue = headerValue;
196+
}
197+
198+
public override void OnSendingRequest(HttpMessage message)
199+
{
200+
message.Request.Headers.Add(_headerName, _headerVaue);
201+
}
202+
}
203+
204+
private class TestOptions : ClientOptions
205+
{
206+
}
207+
}
208+
}

0 commit comments

Comments
 (0)