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
This commit is contained in:
parent
c43d713fd4
commit
320fadb756
|
|
@ -21,9 +21,9 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
}
|
||||
public partial class HeaderPropagationEntry
|
||||
{
|
||||
public HeaderPropagationEntry(string inboundHeaderName, string outboundHeaderName, System.Func<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationContext, Microsoft.Extensions.Primitives.StringValues> valueFilter) { }
|
||||
public HeaderPropagationEntry(string inboundHeaderName, string capturedHeaderName, System.Func<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationContext, Microsoft.Extensions.Primitives.StringValues> valueFilter) { }
|
||||
public string CapturedHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string InboundHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string OutboundHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public System.Func<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationContext, Microsoft.Extensions.Primitives.StringValues> ValueFilter { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
public sealed partial class HeaderPropagationEntryCollection : System.Collections.ObjectModel.Collection<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationEntry>
|
||||
|
|
@ -36,9 +36,26 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
}
|
||||
public partial class HeaderPropagationMessageHandler : System.Net.Http.DelegatingHandler
|
||||
{
|
||||
public HeaderPropagationMessageHandler(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationOptions> options, Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationValues values) { }
|
||||
public HeaderPropagationMessageHandler(Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMessageHandlerOptions options, Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationValues values) { }
|
||||
protected override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; }
|
||||
}
|
||||
public partial class HeaderPropagationMessageHandlerEntry
|
||||
{
|
||||
public HeaderPropagationMessageHandlerEntry(string capturedHeaderName, string outboundHeaderName) { }
|
||||
public string CapturedHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string OutboundHeaderName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
public sealed partial class HeaderPropagationMessageHandlerEntryCollection : System.Collections.ObjectModel.Collection<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMessageHandlerEntry>
|
||||
{
|
||||
public HeaderPropagationMessageHandlerEntryCollection() { }
|
||||
public void Add(string headerName) { }
|
||||
public void Add(string capturedHeaderName, string outboundHeaderName) { }
|
||||
}
|
||||
public partial class HeaderPropagationMessageHandlerOptions
|
||||
{
|
||||
public HeaderPropagationMessageHandlerOptions() { }
|
||||
public Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMessageHandlerEntryCollection Headers { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
}
|
||||
public partial class HeaderPropagationMiddleware
|
||||
{
|
||||
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
|
|||
public static partial class HeaderPropagationHttpClientBuilderExtensions
|
||||
{
|
||||
public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHeaderPropagation(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder) { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHttpClientBuilder AddHeaderPropagation(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action<Microsoft.AspNetCore.HeaderPropagation.HeaderPropagationMessageHandlerOptions> configure) { throw null; }
|
||||
}
|
||||
public static partial class HeaderPropagationServiceCollectionExtensions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Diagnostics" />
|
||||
<Reference Include="Microsoft.AspNetCore.HeaderPropagation" />
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
<Reference Include="Microsoft.AspNetCore.StaticFiles" />
|
||||
<Reference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace HeaderPropagationSample
|
|||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHost(webBuilder =>
|
||||
{
|
||||
webBuilder.UseKestrel();
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ namespace HeaderPropagationSample
|
|||
.AddHttpClient("test")
|
||||
.AddHeaderPropagation();
|
||||
|
||||
services
|
||||
.AddHttpClient("another")
|
||||
.AddHeaderPropagation(options => options.Headers.Add("X-BetaFeatures", "X-Experiments"));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpClientFactory clientFactory)
|
||||
|
|
@ -71,19 +74,23 @@ namespace HeaderPropagationSample
|
|||
await context.Response.WriteAsync($"'/' Got Header '{header.Key}': {string.Join(", ", header.Value)}\r\n");
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync("Sending request to /forwarded\r\n");
|
||||
|
||||
var uri = UriHelper.BuildAbsolute(context.Request.Scheme, context.Request.Host, context.Request.PathBase, "/forwarded");
|
||||
var client = clientFactory.CreateClient("test");
|
||||
var response = await client.GetAsync(uri);
|
||||
|
||||
foreach (var header in response.RequestMessage.Headers)
|
||||
var clientNames = new[] { "test", "another" };
|
||||
foreach (var clientName in clientNames)
|
||||
{
|
||||
await context.Response.WriteAsync($"Sent Header '{header.Key}': {string.Join(", ", header.Value)}\r\n");
|
||||
}
|
||||
await context.Response.WriteAsync("Sending request to /forwarded\r\n");
|
||||
|
||||
await context.Response.WriteAsync("Got response\r\n");
|
||||
await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
|
||||
var uri = UriHelper.BuildAbsolute(context.Request.Scheme, context.Request.Host, context.Request.PathBase, "/forwarded");
|
||||
var client = clientFactory.CreateClient(clientName);
|
||||
var response = await client.GetAsync(uri);
|
||||
|
||||
foreach (var header in response.RequestMessage.Headers)
|
||||
{
|
||||
await context.Response.WriteAsync($"Sent Header '{header.Key}': {string.Join(", ", header.Value)}\r\n");
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync("Got response\r\n");
|
||||
await context.Response.WriteAsync(await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
});
|
||||
|
||||
endpoints.MapGet("/forwarded", async context =>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.HeaderPropagation;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
|
|
@ -11,6 +12,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
/// <summary>
|
||||
/// Adds a message handler for propagating headers collected by the <see cref="HeaderPropagationMiddleware"/> to a outgoing request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When using this method, all the configured headers will be applied to the outgoing HTTP requests.
|
||||
/// </remarks>
|
||||
/// <param name="builder">The <see cref="IHttpClientBuilder"/> to add the message handler to.</param>
|
||||
/// <returns>The <see cref="IHttpClientBuilder"/> so that additional calls can be chained.</returns>
|
||||
public static IHttpClientBuilder AddHeaderPropagation(this IHttpClientBuilder builder)
|
||||
|
|
@ -22,7 +26,49 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
builder.Services.AddHeaderPropagation();
|
||||
|
||||
builder.AddHttpMessageHandler<HeaderPropagationMessageHandler>();
|
||||
builder.AddHttpMessageHandler(services =>
|
||||
{
|
||||
var options = new HeaderPropagationMessageHandlerOptions();
|
||||
var middlewareOptions = services.GetRequiredService<IOptions<HeaderPropagationOptions>>();
|
||||
for (var i = 0; i < middlewareOptions.Value.Headers.Count; i++)
|
||||
{
|
||||
var header = middlewareOptions.Value.Headers[i];
|
||||
options.Headers.Add(header.CapturedHeaderName, header.CapturedHeaderName);
|
||||
}
|
||||
return new HeaderPropagationMessageHandler(options, services.GetRequiredService<HeaderPropagationValues>());
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a message handler for propagating headers collected by the <see cref="HeaderPropagationMiddleware"/> to a outgoing request,
|
||||
/// explicitly specifying which headers to propagate.
|
||||
/// </summary>
|
||||
/// <remarks>This also allows to redefine the name to use for a header in the outgoing request.</remarks>
|
||||
/// <param name="builder">The <see cref="IHttpClientBuilder"/> to add the message handler to.</param>
|
||||
/// <param name="configure">A delegate used to configure the <see cref="HeaderPropagationMessageHandlerOptions"/>.</param>
|
||||
/// <returns>The <see cref="IHttpClientBuilder"/> so that additional calls can be chained.</returns>
|
||||
public static IHttpClientBuilder AddHeaderPropagation(this IHttpClientBuilder builder, Action<HeaderPropagationMessageHandlerOptions> configure)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (configure == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
|
||||
builder.Services.AddHeaderPropagation();
|
||||
|
||||
builder.AddHttpMessageHandler(services =>
|
||||
{
|
||||
var options = new HeaderPropagationMessageHandlerOptions();
|
||||
configure(options);
|
||||
return new HeaderPropagationMessageHandler(options, services.GetRequiredService<HeaderPropagationValues>());
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HeaderPropagationEntry"/> with the provided <paramref name="inboundHeaderName"/>,
|
||||
/// <paramref name="outboundHeaderName"/>, and
|
||||
/// <paramref name="capturedHeaderName"/> and <paramref name="valueFilter"/>.
|
||||
/// </summary>
|
||||
/// <param name="inboundHeaderName">
|
||||
/// The name of the header to be captured by <see cref="HeaderPropagationMiddleware"/>.
|
||||
/// </param>
|
||||
/// <param name="outboundHeaderName">
|
||||
/// <param name="capturedHeaderName">
|
||||
/// The name of the header to be added by <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </param>
|
||||
/// <param name="valueFilter">
|
||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
/// </param>
|
||||
public HeaderPropagationEntry(
|
||||
string inboundHeaderName,
|
||||
string outboundHeaderName,
|
||||
string capturedHeaderName,
|
||||
Func<HeaderPropagationContext, StringValues> valueFilter)
|
||||
{
|
||||
if (inboundHeaderName == null)
|
||||
|
|
@ -34,13 +34,13 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
throw new ArgumentNullException(nameof(inboundHeaderName));
|
||||
}
|
||||
|
||||
if (outboundHeaderName == null)
|
||||
if (capturedHeaderName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(outboundHeaderName));
|
||||
throw new ArgumentNullException(nameof(capturedHeaderName));
|
||||
}
|
||||
|
||||
InboundHeaderName = inboundHeaderName;
|
||||
OutboundHeaderName = outboundHeaderName;
|
||||
CapturedHeaderName = capturedHeaderName;
|
||||
ValueFilter = valueFilter; // May be null
|
||||
}
|
||||
|
||||
|
|
@ -50,10 +50,10 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
public string InboundHeaderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the header to be used by the <see cref="HeaderPropagationMessageHandler"/> for the
|
||||
/// Gets the name of the header to be used by default by the <see cref="HeaderPropagationMessageHandler"/> for the
|
||||
/// outbound http requests.
|
||||
/// </summary>
|
||||
public string OutboundHeaderName { get; }
|
||||
public string CapturedHeaderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a filter delegate that can be used to transform the header value.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
/// <summary>
|
||||
/// Adds an <see cref="HeaderPropagationEntry"/> that will use <paramref name="headerName"/> as
|
||||
/// the value of <see cref="HeaderPropagationEntry.InboundHeaderName"/> and
|
||||
/// <see cref="HeaderPropagationEntry.OutboundHeaderName"/>.
|
||||
/// <see cref="HeaderPropagationEntry.CapturedHeaderName"/>.
|
||||
/// </summary>
|
||||
/// <param name="headerName">The header name to be propagated.</param>
|
||||
public void Add(string headerName)
|
||||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
/// <summary>
|
||||
/// Adds an <see cref="HeaderPropagationEntry"/> that will use <paramref name="headerName"/> as
|
||||
/// the value of <see cref="HeaderPropagationEntry.InboundHeaderName"/> and
|
||||
/// <see cref="HeaderPropagationEntry.OutboundHeaderName"/>.
|
||||
/// <see cref="HeaderPropagationEntry.CapturedHeaderName"/>.
|
||||
/// </summary>
|
||||
/// <param name="headerName">The header name to be propagated.</param>
|
||||
/// <param name="valueFilter">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Net.Http;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
|
|
@ -17,7 +16,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
public class HeaderPropagationMessageHandler : DelegatingHandler
|
||||
{
|
||||
private readonly HeaderPropagationValues _values;
|
||||
private readonly HeaderPropagationOptions _options;
|
||||
private readonly HeaderPropagationMessageHandlerOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
|
|
@ -25,15 +24,9 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
/// <param name="options">The options that define which headers are propagated.</param>
|
||||
/// <param name="values">The values of the headers to be propagated populated by the
|
||||
/// <see cref="HeaderPropagationMiddleware"/>.</param>
|
||||
public HeaderPropagationMessageHandler(IOptions<HeaderPropagationOptions> options, HeaderPropagationValues values)
|
||||
public HeaderPropagationMessageHandler(HeaderPropagationMessageHandlerOptions options, HeaderPropagationValues values)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_values = values ?? throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +64,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
if (!request.Headers.TryGetValues(entry.OutboundHeaderName, out var _) &&
|
||||
!(hasContent && request.Content.Headers.TryGetValues(entry.OutboundHeaderName, out var _)))
|
||||
{
|
||||
if (captured.TryGetValue(entry.OutboundHeaderName, out var stringValues) &&
|
||||
if (captured.TryGetValue(entry.CapturedHeaderName, out var stringValues) &&
|
||||
!StringValues.IsNullOrEmpty(stringValues))
|
||||
{
|
||||
if (stringValues.Count == 1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// Define the configuration of an header for the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </summary>
|
||||
public class HeaderPropagationMessageHandlerEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HeaderPropagationMessageHandlerEntry"/> with the provided <paramref name="capturedHeaderName"/>
|
||||
/// and <paramref name="outboundHeaderName"/>.
|
||||
/// </summary>
|
||||
/// <param name="capturedHeaderName">
|
||||
/// The name of the header to be used to lookup the headers captured by the <see cref="HeaderPropagationMiddleware"/>.
|
||||
/// </param>
|
||||
/// <param name="outboundHeaderName">
|
||||
/// The name of the header to be added to the outgoing http requests by the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </param>
|
||||
public HeaderPropagationMessageHandlerEntry(
|
||||
string capturedHeaderName,
|
||||
string outboundHeaderName)
|
||||
{
|
||||
if (capturedHeaderName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(capturedHeaderName));
|
||||
}
|
||||
|
||||
if (outboundHeaderName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(outboundHeaderName));
|
||||
}
|
||||
|
||||
CapturedHeaderName = capturedHeaderName;
|
||||
OutboundHeaderName = outboundHeaderName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the header to be used to lookup the headers captured by the <see cref="HeaderPropagationMiddleware"/>.
|
||||
/// </summary>
|
||||
public string CapturedHeaderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the header to be added to the outgoing http requests by the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </summary>
|
||||
public string OutboundHeaderName { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of <see cref="HeaderPropagationMessageHandlerEntry"/> items.
|
||||
/// </summary>
|
||||
public sealed class HeaderPropagationMessageHandlerEntryCollection : Collection<HeaderPropagationMessageHandlerEntry>
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds an <see cref="HeaderPropagationMessageHandlerEntry"/> that will use <paramref name="headerName"/> as
|
||||
/// the value of <see cref="HeaderPropagationMessageHandlerEntry.CapturedHeaderName"/> and
|
||||
/// <see cref="HeaderPropagationMessageHandlerEntry.OutboundHeaderName"/>.
|
||||
/// </summary>
|
||||
/// <param name="headerName">
|
||||
/// The name of the header to be added by the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </param>
|
||||
public void Add(string headerName)
|
||||
{
|
||||
if (headerName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(headerName));
|
||||
}
|
||||
|
||||
Add(new HeaderPropagationMessageHandlerEntry(headerName, headerName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="HeaderPropagationMessageHandlerEntry"/> that will use the provided <paramref name="capturedHeaderName"/>
|
||||
/// and <paramref name="outboundHeaderName"/>.
|
||||
/// </summary>
|
||||
/// <param name="capturedHeaderName">
|
||||
/// The name of the header captured by the <see cref="HeaderPropagationMiddleware"/>.
|
||||
/// </param>
|
||||
/// <param name="outboundHeaderName">
|
||||
/// The name of the header to be added by the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </param>
|
||||
public void Add(string capturedHeaderName, string outboundHeaderName)
|
||||
{
|
||||
if (capturedHeaderName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(capturedHeaderName));
|
||||
}
|
||||
|
||||
if (outboundHeaderName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(outboundHeaderName));
|
||||
}
|
||||
|
||||
Add(new HeaderPropagationMessageHandlerEntry(capturedHeaderName, outboundHeaderName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides configuration for the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </summary>
|
||||
public class HeaderPropagationMessageHandlerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the headers to be propagated by the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="Headers"/> is empty, all the headers captured by the <see cref="HeaderPropagationMiddleware"/> are propagated.
|
||||
/// Entries in <see cref="Headers"/> are processed in order while adding headers inside
|
||||
/// <see cref="HeaderPropagationMessageHandler"/>. This can cause an earlier entry to take precedence
|
||||
/// over a later entry if they have the same <see cref="HeaderPropagationMessageHandlerEntry.OutboundHeaderName"/>.
|
||||
/// </remarks>
|
||||
public HeaderPropagationMessageHandlerEntryCollection Headers { get; set; } = new HeaderPropagationMessageHandlerEntryCollection();
|
||||
}
|
||||
}
|
||||
|
|
@ -46,12 +46,12 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
|
||||
// We intentionally process entries in order, and allow earlier entries to
|
||||
// take precedence over later entries when they have the same output name.
|
||||
if (!headers.ContainsKey(entry.OutboundHeaderName))
|
||||
if (!headers.ContainsKey(entry.CapturedHeaderName))
|
||||
{
|
||||
var value = GetValue(context, entry);
|
||||
if (!StringValues.IsNullOrEmpty(value))
|
||||
{
|
||||
headers.Add(entry.OutboundHeaderName, value);
|
||||
headers.Add(entry.CapturedHeaderName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
/// and to be propagated by the <see cref="HeaderPropagationMessageHandler"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Entries in <see cref="Headers"/> are processes in order while capturing headers inside
|
||||
/// Entries in <see cref="Headers"/> are processed in order while capturing headers inside
|
||||
/// <see cref="HeaderPropagationMiddleware"/>. This can cause an earlier entry to take precedence
|
||||
/// over a later entry if they have the same <see cref="HeaderPropagationEntry.OutboundHeaderName"/>.
|
||||
/// over a later entry if they have the same <see cref="HeaderPropagationEntry.CapturedHeaderName"/>.
|
||||
/// </remarks>
|
||||
public HeaderPropagationEntryCollection Headers { get; set; } = new HeaderPropagationEntryCollection();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation
|
|||
/// that can be propagated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The keys of <see cref="Headers"/> correspond to <see cref="HeaderPropagationEntry.OutboundHeaderName"/>.
|
||||
/// The keys of <see cref="Headers"/> correspond to <see cref="HeaderPropagationEntry.CapturedHeaderName"/>.
|
||||
/// </remarks>
|
||||
public IDictionary<string, StringValues> Headers
|
||||
{
|
||||
|
|
|
|||
|
|
@ -91,6 +91,35 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
Assert.Equal(new[] { "test" }, handler.Headers.GetValues("out"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleHeaders_HeadersInRequest_AddAllHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var handler = new SimpleHandler();
|
||||
var builder = CreateBuilder(c =>
|
||||
{
|
||||
c.Headers.Add("first");
|
||||
c.Headers.Add("second");
|
||||
},
|
||||
handler);
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Add("first", "value");
|
||||
request.Headers.Add("second", "other");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.True(handler.Headers.Contains("first"));
|
||||
Assert.Equal(new[] { "value" }, handler.Headers.GetValues("first"));
|
||||
Assert.True(handler.Headers.Contains("second"));
|
||||
Assert.Equal(new[] { "other" }, handler.Headers.GetValues("second"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_UseHeaderPropagation_Without_AddHeaderPropagation_Throws()
|
||||
{
|
||||
|
|
@ -106,7 +135,31 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
exception.Message);
|
||||
}
|
||||
|
||||
private IWebHostBuilder CreateBuilder(Action<HeaderPropagationOptions> configure, HttpMessageHandler primaryHandler)
|
||||
[Fact]
|
||||
public async Task HeaderInRequest_OverrideHeaderPerClient_AddCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var handler = new SimpleHandler();
|
||||
var builder = CreateBuilder(
|
||||
c => c.Headers.Add("in", "out"),
|
||||
handler,
|
||||
c => c.Headers.Add("out", "different"));
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage();
|
||||
request.Headers.Add("in", "test");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.True(handler.Headers.Contains("different"));
|
||||
Assert.Equal(new[] { "test" }, handler.Headers.GetValues("different"));
|
||||
}
|
||||
|
||||
private IWebHostBuilder CreateBuilder(Action<HeaderPropagationOptions> configure, HttpMessageHandler primaryHandler, Action<HeaderPropagationMessageHandlerOptions> configureClient = null)
|
||||
{
|
||||
return new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
|
|
@ -116,13 +169,21 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
|
||||
services.AddHeaderPropagation(configure);
|
||||
var client = services.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
|
||||
.ConfigureHttpMessageHandlerBuilder(b =>
|
||||
{
|
||||
b.PrimaryHandler = primaryHandler;
|
||||
})
|
||||
.AddHeaderPropagation();
|
||||
services.AddHeaderPropagation(configure);
|
||||
});
|
||||
|
||||
if (configureClient != null)
|
||||
{
|
||||
client.AddHeaderPropagation(configureClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.AddHeaderPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
||||
{
|
||||
public class HeaderPropagationMessageHandlerEntryCollectionTest
|
||||
{
|
||||
[Fact]
|
||||
public void Add_SingleValue_UseValueForBothProperties()
|
||||
{
|
||||
var collection = new HeaderPropagationMessageHandlerEntryCollection();
|
||||
collection.Add("foo");
|
||||
|
||||
Assert.Single(collection);
|
||||
var entry = collection[0];
|
||||
Assert.Equal("foo", entry.CapturedHeaderName);
|
||||
Assert.Equal("foo", entry.OutboundHeaderName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_BothValues_UseCorrectValues()
|
||||
{
|
||||
var collection = new HeaderPropagationMessageHandlerEntryCollection();
|
||||
collection.Add("foo", "bar");
|
||||
|
||||
Assert.Single(collection);
|
||||
var entry = collection[0];
|
||||
Assert.Equal("foo", entry.CapturedHeaderName);
|
||||
Assert.Equal("bar", entry.OutboundHeaderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ using System.Net.Http;
|
|||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -22,10 +21,10 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
State = new HeaderPropagationValues();
|
||||
State.Headers = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
Configuration = new HeaderPropagationOptions();
|
||||
Configuration = new HeaderPropagationMessageHandlerOptions();
|
||||
|
||||
var headerPropagationMessageHandler =
|
||||
new HeaderPropagationMessageHandler(Options.Create(Configuration), State)
|
||||
new HeaderPropagationMessageHandler(Configuration, State)
|
||||
{
|
||||
InnerHandler = Handler
|
||||
};
|
||||
|
|
@ -38,14 +37,14 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
|
||||
private SimpleHandler Handler { get; }
|
||||
public HeaderPropagationValues State { get; set; }
|
||||
public HeaderPropagationOptions Configuration { get; set; }
|
||||
public HeaderPropagationMessageHandlerOptions Configuration { get; set; }
|
||||
public HttpClient Client { get; set; }
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInState_AddCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", "out");
|
||||
Configuration.Headers.Add("out");
|
||||
State.Headers.Add("out", "test");
|
||||
|
||||
// Act
|
||||
|
|
@ -60,7 +59,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
public async Task HeaderInState_WithMultipleValues_AddAllValues()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in", "out");
|
||||
Configuration.Headers.Add("out");
|
||||
State.Headers.Add("out", new[] { "one", "two" });
|
||||
|
||||
// Act
|
||||
|
|
@ -74,8 +73,8 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
[Fact]
|
||||
public async Task HeaderInState_RequestWithContent_ContentHeaderPresent_DoesNotAddIt()
|
||||
{
|
||||
Configuration.Headers.Add("in", "Content-Type");
|
||||
State.Headers.Add("in", "test");
|
||||
Configuration.Headers.Add("Content-Type");
|
||||
State.Headers.Add("Content-Type", "test");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage() { Content = new StringContent("test") });
|
||||
|
|
@ -88,7 +87,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
[Fact]
|
||||
public async Task HeaderInState_RequestWithContent_ContentHeaderNotPresent_AddValue()
|
||||
{
|
||||
Configuration.Headers.Add("in", "Content-Language");
|
||||
Configuration.Headers.Add("Content-Language");
|
||||
State.Headers.Add("Content-Language", "test");
|
||||
|
||||
// Act
|
||||
|
|
@ -102,7 +101,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
[Fact]
|
||||
public async Task HeaderInState_WithMultipleValues_RequestWithContent_ContentHeaderNotPresent_AddAllValues()
|
||||
{
|
||||
Configuration.Headers.Add("in", "Content-Language");
|
||||
Configuration.Headers.Add("Content-Language");
|
||||
State.Headers.Add("Content-Language", new[] { "one", "two" });
|
||||
|
||||
// Act
|
||||
|
|
@ -114,18 +113,18 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInState_NoOutputName_UseInputName()
|
||||
public async Task HeaderInState_WithOutboundName_UseOutboundName()
|
||||
{
|
||||
// Arrange
|
||||
Configuration.Headers.Add("in");
|
||||
State.Headers.Add("in", "test");
|
||||
Configuration.Headers.Add("state", "out");
|
||||
State.Headers.Add("state", "test");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("in"));
|
||||
Assert.Equal(new[] { "test" }, Handler.Headers.GetValues("in"));
|
||||
Assert.True(Handler.Headers.Contains("out"));
|
||||
Assert.Equal(new[] { "test" }, Handler.Headers.GetValues("out"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -193,7 +192,7 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
[InlineData("", new[] { "" })]
|
||||
[InlineData(null, new[] { "" })]
|
||||
[InlineData("42", new[] { "42" })]
|
||||
public async Task HeaderInState_HeaderAlreadyInOutgoingRequest(string outgoingValue,
|
||||
public async Task HeaderInState_HeaderAlreadyInOutgoingRequest_DoesNotOverrideIt(string outgoingValue,
|
||||
string[] expectedValues)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -211,6 +210,57 @@ namespace Microsoft.AspNetCore.HeaderPropagation.Tests
|
|||
Assert.Equal(expectedValues, Handler.Headers.GetValues("inout"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInState_HeaderTwiceInOptions_DoesNotAddItTwice()
|
||||
{
|
||||
// Arrange
|
||||
State.Headers.Add("name", "value");
|
||||
Configuration.Headers.Add("name");
|
||||
Configuration.Headers.Add("name");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("name"));
|
||||
Assert.Equal(new[] { "value" }, Handler.Headers.GetValues("name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HeaderInState_HeaderTwiceInOptionsWithDifferentNames_AddsBoth()
|
||||
{
|
||||
// Arrange
|
||||
State.Headers.Add("name", "value");
|
||||
Configuration.Headers.Add("name");
|
||||
Configuration.Headers.Add("name", "other");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("name"));
|
||||
Assert.Equal(new[] { "value" }, Handler.Headers.GetValues("name"));
|
||||
Assert.True(Handler.Headers.Contains("other"));
|
||||
Assert.Equal(new[] { "value" }, Handler.Headers.GetValues("name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TwoHeadersInState_BothHeadersInOptionsWithSameName_AddsFirst()
|
||||
{
|
||||
// Arrange
|
||||
State.Headers.Add("name", "value");
|
||||
State.Headers.Add("other", "override");
|
||||
Configuration.Headers.Add("name");
|
||||
Configuration.Headers.Add("other", "name");
|
||||
|
||||
// Act
|
||||
await Client.SendAsync(new HttpRequestMessage());
|
||||
|
||||
// Assert
|
||||
Assert.True(Handler.Headers.Contains("name"));
|
||||
Assert.Equal(new[] { "value" }, Handler.Headers.GetValues("name"));
|
||||
}
|
||||
|
||||
private class SimpleHandler : DelegatingHandler
|
||||
{
|
||||
public HttpHeaders Headers { get; private set; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue