Skip to content

Commit 320fadb

Browse files
alefranzrynowak
authored andcommitted
HeaderPropagation Middleware: configuration per client (#10096)
* HeaderPropagation Middleware: configuration per client * Renamed fields * Renamed fields * Fix sample * Addressed feedback, cleaned up and added tests * Addressed feedback on HeaderPropagationHttpClientBuilderExtensions * Updated reference assemblies
1 parent c43d713 commit 320fadb

17 files changed

+402
-61
lines changed

src/Middleware/HeaderPropagation/ref/Microsoft.AspNetCore.HeaderPropagation.netcoreapp3.0.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public readonly partial struct HeaderPropagationContext
2121
}
2222
public partial class HeaderPropagationEntry
2323
{
24-
public HeaderPropagationEntry(string inboundHeaderName, string outboundHeaderName, System.Func<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationContext, Microsoft.Extensions.Primitives.StringValues> valueFilter) { }
24+
public HeaderPropagationEntry(string inboundHeaderName, string capturedHeaderName, System.Func<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationContext, Microsoft.Extensions.Primitives.StringValues> valueFilter) { }
25+
public string CapturedHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
2526
public string InboundHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
26-
public string OutboundHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
2727
public System.Func<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationContext, Microsoft.Extensions.Primitives.StringValues> ValueFilter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
2828
}
2929
public sealed partial class HeaderPropagationEntryCollection : System.Collections.ObjectModel.Collection<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationEntry>
@@ -36,9 +36,26 @@ public void Add(string inboundHeaderName, string outboundHeaderName, System.Func
3636
}
3737
public partial class HeaderPropagationMessageHandler : System.Net.Http.DelegatingHandler
3838
{
39-
public HeaderPropagationMessageHandler(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationOptions> options, Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationValues values) { }
39+
public HeaderPropagationMessageHandler(Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMessageHandlerOptions options, Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationValues values) { }
4040
protected override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; }
4141
}
42+
public partial class HeaderPropagationMessageHandlerEntry
43+
{
44+
public HeaderPropagationMessageHandlerEntry(string capturedHeaderName, string outboundHeaderName) { }
45+
public string CapturedHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
46+
public string OutboundHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
47+
}
48+
public sealed partial class HeaderPropagationMessageHandlerEntryCollection : System.Collections.ObjectModel.Collection<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMessageHandlerEntry>
49+
{
50+
public HeaderPropagationMessageHandlerEntryCollection() { }
51+
public void Add(string headerName) { }
52+
public void Add(string capturedHeaderName, string outboundHeaderName) { }
53+
}
54+
public partial class HeaderPropagationMessageHandlerOptions
55+
{
56+
public HeaderPropagationMessageHandlerOptions() { }
57+
public Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMessageHandlerEntryCollection Headers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
58+
}
4259
public partial class HeaderPropagationMiddleware
4360
{
4461
public HeaderPropagationMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationOptions> options, Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationValues values) { }
@@ -60,6 +77,7 @@ namespace Microsoft.Extensions.DependencyInjection
6077
public static partial class HeaderPropagationHttpClientBuilderExtensions
6178
{
6279
public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHeaderPropagation(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder) { throw null; }
80+
public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHeaderPropagation(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMessageHandlerOptions> configure) { throw null; }
6381
}
6482
public static partial class HeaderPropagationServiceCollectionExtensions
6583
{

src/Middleware/HeaderPropagation/samples/HeaderPropagationSample/HeaderPropagationSample.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<Reference Include="Microsoft.AspNetCore.Diagnostics" />
99
<Reference Include="Microsoft.AspNetCore.HeaderPropagation" />
1010
<Reference Include="Microsoft.AspNetCore.Hosting" />
11+
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
1112
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
1213
<Reference Include="Microsoft.Extensions.Hosting" />
1314
</ItemGroup>

src/Middleware/HeaderPropagation/samples/HeaderPropagationSample/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
1515
Host.CreateDefaultBuilder(args)
1616
.ConfigureWebHost(webBuilder =>
1717
{
18+
webBuilder.UseKestrel();
1819
webBuilder.UseStartup<Startup>();
1920
});
2021
}

src/Middleware/HeaderPropagation/samples/HeaderPropagationSample/Startup.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public void ConfigureServices(IServiceCollection services)
4949
.AddHttpClient("test")
5050
.AddHeaderPropagation();
5151

52+
services
53+
.AddHttpClient("another")
54+
.AddHeaderPropagation(options => options.Headers.Add("X-BetaFeatures", "X-Experiments"));
5255
}
5356

5457
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpClientFactory clientFactory)
@@ -71,19 +74,23 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpCli
7174
await context.Response.WriteAsync($"'/' Got Header '{header.Key}': {string.Join(", ", header.Value)}\r\n");
7275
}
7376

74-
await context.Response.WriteAsync("Sending request to /forwarded\r\n");
77+
var clientNames = new[] { "test", "another" };
78+
foreach (var clientName in clientNames)
79+
{
80+
await context.Response.WriteAsync("Sending request to /forwarded\r\n");
7581

76-
var uri = UriHelper.BuildAbsolute(context.Request.Scheme, context.Request.Host, context.Request.PathBase, "/forwarded");
77-
var client = clientFactory.CreateClient("test");
78-
var response = await client.GetAsync(uri);
82+
var uri = UriHelper.BuildAbsolute(context.Request.Scheme, context.Request.Host, context.Request.PathBase, "/forwarded");
83+
var client = clientFactory.CreateClient(clientName);
84+
var response = await client.GetAsync(uri);
7985

80-
foreach (var header in response.RequestMessage.Headers)
81-
{
82-
await context.Response.WriteAsync($"Sent Header '{header.Key}': {string.Join(", ", header.Value)}\r\n");
83-
}
86+
foreach (var header in response.RequestMessage.Headers)
87+
{
88+
await context.Response.WriteAsync($"Sent Header '{header.Key}': {string.Join(", ", header.Value)}\r\n");
89+
}
8490

85-
await context.Response.WriteAsync("Got response\r\n");
86-
await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
91+
await context.Response.WriteAsync("Got response\r\n");
92+
await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
93+
}
8794
});
8895

8996
endpoints.MapGet("/forwarded", async context =>

src/Middleware/HeaderPropagation/src/DependencyInjection/HeaderPropagationHttpClientBuilderExtensions.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using Microsoft.AspNetCore.HeaderPropagation;
6+
using Microsoft.Extensions.Options;
67

78
namespace Microsoft.Extensions.DependencyInjection
89
{
@@ -11,6 +12,9 @@ public static class HeaderPropagationHttpClientBuilderExtensions
1112
/// <summary>
1213
/// Adds a message handler for propagating headers collected by the <see cref="HeaderPropagationMiddleware"/> to a outgoing request.
1314
/// </summary>
15+
/// <remarks>
16+
/// When using this method, all the configured headers will be applied to the outgoing HTTP requests.
17+
/// </remarks>
1418
/// <param name="builder">The <see cref="IHttpClientBuilder"/> to add the message handler to.</param>
1519
/// <returns>The <see cref="IHttpClientBuilder"/> so that additional calls can be chained.</returns>
1620
public static IHttpClientBuilder AddHeaderPropagation(this IHttpClientBuilder builder)
@@ -22,7 +26,49 @@ public static IHttpClientBuilder AddHeaderPropagation(this IHttpClientBuilder bu
2226

2327
builder.Services.AddHeaderPropagation();
2428

25-
builder.AddHttpMessageHandler<HeaderPropagationMessageHandler>();
29+
builder.AddHttpMessageHandler(services =>
30+
{
31+
var options = new HeaderPropagationMessageHandlerOptions();
32+
var middlewareOptions = services.GetRequiredService<IOptions<HeaderPropagationOptions>>();
33+
for (var i = 0; i < middlewareOptions.Value.Headers.Count; i++)
34+
{
35+
var header = middlewareOptions.Value.Headers[i];
36+
options.Headers.Add(header.CapturedHeaderName, header.CapturedHeaderName);
37+
}
38+
return new HeaderPropagationMessageHandler(options, services.GetRequiredService<HeaderPropagationValues>());
39+
});
40+
41+
return builder;
42+
}
43+
44+
/// <summary>
45+
/// Adds a message handler for propagating headers collected by the <see cref="HeaderPropagationMiddleware"/> to a outgoing request,
46+
/// explicitly specifying which headers to propagate.
47+
/// </summary>
48+
/// <remarks>This also allows to redefine the name to use for a header in the outgoing request.</remarks>
49+
/// <param name="builder">The <see cref="IHttpClientBuilder"/> to add the message handler to.</param>
50+
/// <param name="configure">A delegate used to configure the <see cref="HeaderPropagationMessageHandlerOptions"/>.</param>
51+
/// <returns>The <see cref="IHttpClientBuilder"/> so that additional calls can be chained.</returns>
52+
public static IHttpClientBuilder AddHeaderPropagation(this IHttpClientBuilder builder, Action<HeaderPropagationMessageHandlerOptions> configure)
53+
{
54+
if (builder == null)
55+
{
56+
throw new ArgumentNullException(nameof(builder));
57+
}
58+
59+
if (configure == null)
60+
{
61+
throw new ArgumentNullException(nameof(configure));
62+
}
63+
64+
builder.Services.AddHeaderPropagation();
65+
66+
builder.AddHttpMessageHandler(services =>
67+
{
68+
var options = new HeaderPropagationMessageHandlerOptions();
69+
configure(options);
70+
return new HeaderPropagationMessageHandler(options, services.GetRequiredService<HeaderPropagationValues>());
71+
});
2672

2773
return builder;
2874
}

src/Middleware/HeaderPropagation/src/HeaderPropagationEntry.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,34 @@ public class HeaderPropagationEntry
1313
{
1414
/// <summary>
1515
/// Creates a new <see cref="HeaderPropagationEntry"/> with the provided <paramref name="inboundHeaderName"/>,
16-
/// <paramref name="outboundHeaderName"/>, and
16+
/// <paramref name="capturedHeaderName"/> and <paramref name="valueFilter"/>.
1717
/// </summary>
1818
/// <param name="inboundHeaderName">
1919
/// The name of the header to be captured by <see cref="HeaderPropagationMiddleware"/>.
2020
/// </param>
21-
/// <param name="outboundHeaderName">
21+
/// <param name="capturedHeaderName">
2222
/// The name of the header to be added by <see cref="HeaderPropagationMessageHandler"/>.
2323
/// </param>
2424
/// <param name="valueFilter">
2525
/// A filter delegate that can be used to transform the header value. May be null.
2626
/// </param>
2727
public HeaderPropagationEntry(
2828
string inboundHeaderName,
29-
string outboundHeaderName,
29+
string capturedHeaderName,
3030
Func<HeaderPropagationContext, StringValues> valueFilter)
3131
{
3232
if (inboundHeaderName == null)
3333
{
3434
throw new ArgumentNullException(nameof(inboundHeaderName));
3535
}
3636

37-
if (outboundHeaderName == null)
37+
if (capturedHeaderName == null)
3838
{
39-
throw new ArgumentNullException(nameof(outboundHeaderName));
39+
throw new ArgumentNullException(nameof(capturedHeaderName));
4040
}
4141

4242
InboundHeaderName = inboundHeaderName;
43-
OutboundHeaderName = outboundHeaderName;
43+
CapturedHeaderName = capturedHeaderName;
4444
ValueFilter = valueFilter; // May be null
4545
}
4646

@@ -50,10 +50,10 @@ public HeaderPropagationEntry(
5050
public string InboundHeaderName { get; }
5151

5252
/// <summary>
53-
/// Gets the name of the header to be used by the <see cref="HeaderPropagationMessageHandler"/> for the
53+
/// Gets the name of the header to be used by default by the <see cref="HeaderPropagationMessageHandler"/> for the
5454
/// outbound http requests.
5555
/// </summary>
56-
public string OutboundHeaderName { get; }
56+
public string CapturedHeaderName { get; }
5757

5858
/// <summary>
5959
/// Gets or sets a filter delegate that can be used to transform the header value.

src/Middleware/HeaderPropagation/src/HeaderPropagationEntryCollection.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public sealed class HeaderPropagationEntryCollection : Collection<HeaderPropagat
1515
/// <summary>
1616
/// Adds an <see cref="HeaderPropagationEntry"/> that will use <paramref name="headerName"/> as
1717
/// the value of <see cref="HeaderPropagationEntry.InboundHeaderName"/> and
18-
/// <see cref="HeaderPropagationEntry.OutboundHeaderName"/>.
18+
/// <see cref="HeaderPropagationEntry.CapturedHeaderName"/>.
1919
/// </summary>
2020
/// <param name="headerName">The header name to be propagated.</param>
2121
public void Add(string headerName)
@@ -31,7 +31,7 @@ public void Add(string headerName)
3131
/// <summary>
3232
/// Adds an <see cref="HeaderPropagationEntry"/> that will use <paramref name="headerName"/> as
3333
/// the value of <see cref="HeaderPropagationEntry.InboundHeaderName"/> and
34-
/// <see cref="HeaderPropagationEntry.OutboundHeaderName"/>.
34+
/// <see cref="HeaderPropagationEntry.CapturedHeaderName"/>.
3535
/// </summary>
3636
/// <param name="headerName">The header name to be propagated.</param>
3737
/// <param name="valueFilter">

src/Middleware/HeaderPropagation/src/HeaderPropagationMessageHandler.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Threading;
77
using System.Threading.Tasks;
88
using Microsoft.AspNetCore.Builder;
9-
using Microsoft.Extensions.Options;
109
using Microsoft.Extensions.Primitives;
1110

1211
namespace Microsoft.AspNetCore.HeaderPropagation
@@ -17,23 +16,17 @@ namespace Microsoft.AspNetCore.HeaderPropagation
1716
public class HeaderPropagationMessageHandler : DelegatingHandler
1817
{
1918
private readonly HeaderPropagationValues _values;
20-
private readonly HeaderPropagationOptions _options;
19+
private readonly HeaderPropagationMessageHandlerOptions _options;
2120

2221
/// <summary>
2322
/// Creates a new instance of the <see cref="HeaderPropagationMessageHandler"/>.
2423
/// </summary>
2524
/// <param name="options">The options that define which headers are propagated.</param>
2625
/// <param name="values">The values of the headers to be propagated populated by the
2726
/// <see cref="HeaderPropagationMiddleware"/>.</param>
28-
public HeaderPropagationMessageHandler(IOptions<HeaderPropagationOptions> options, HeaderPropagationValues values)
27+
public HeaderPropagationMessageHandler(HeaderPropagationMessageHandlerOptions options, HeaderPropagationValues values)
2928
{
30-
if (options == null)
31-
{
32-
throw new ArgumentNullException(nameof(options));
33-
}
34-
35-
_options = options.Value;
36-
29+
_options = options ?? throw new ArgumentNullException(nameof(options));
3730
_values = values ?? throw new ArgumentNullException(nameof(values));
3831
}
3932

@@ -71,7 +64,7 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
7164
if (!request.Headers.TryGetValues(entry.OutboundHeaderName, out var _) &&
7265
!(hasContent && request.Content.Headers.TryGetValues(entry.OutboundHeaderName, out var _)))
7366
{
74-
if (captured.TryGetValue(entry.OutboundHeaderName, out var stringValues) &&
67+
if (captured.TryGetValue(entry.CapturedHeaderName, out var stringValues) &&
7568
!StringValues.IsNullOrEmpty(stringValues))
7669
{
7770
if (stringValues.Count == 1)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNetCore.HeaderPropagation
7+
{
8+
/// <summary>
9+
/// Define the configuration of an header for the <see cref="HeaderPropagationMessageHandler"/>.
10+
/// </summary>
11+
public class HeaderPropagationMessageHandlerEntry
12+
{
13+
/// <summary>
14+
/// Creates a new <see cref="HeaderPropagationMessageHandlerEntry"/> with the provided <paramref name="capturedHeaderName"/>
15+
/// and <paramref name="outboundHeaderName"/>.
16+
/// </summary>
17+
/// <param name="capturedHeaderName">
18+
/// The name of the header to be used to lookup the headers captured by the <see cref="HeaderPropagationMiddleware"/>.
19+
/// </param>
20+
/// <param name="outboundHeaderName">
21+
/// The name of the header to be added to the outgoing http requests by the <see cref="HeaderPropagationMessageHandler"/>.
22+
/// </param>
23+
public HeaderPropagationMessageHandlerEntry(
24+
string capturedHeaderName,
25+
string outboundHeaderName)
26+
{
27+
if (capturedHeaderName == null)
28+
{
29+
throw new ArgumentNullException(nameof(capturedHeaderName));
30+
}
31+
32+
if (outboundHeaderName == null)
33+
{
34+
throw new ArgumentNullException(nameof(outboundHeaderName));
35+
}
36+
37+
CapturedHeaderName = capturedHeaderName;
38+
OutboundHeaderName = outboundHeaderName;
39+
}
40+
41+
/// <summary>
42+
/// Gets the name of the header to be used to lookup the headers captured by the <see cref="HeaderPropagationMiddleware"/>.
43+
/// </summary>
44+
public string CapturedHeaderName { get; }
45+
46+
/// <summary>
47+
/// Gets the name of the header to be added to the outgoing http requests by the <see cref="HeaderPropagationMessageHandler"/>.
48+
/// </summary>
49+
public string OutboundHeaderName { get; }
50+
}
51+
}

0 commit comments

Comments
 (0)