-
Notifications
You must be signed in to change notification settings - Fork 10.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HeaderPropagation: propagate incoming request headers to outgoing HTT…
…P requests (#7921) * Ported HeaderPropagation from aspnet/Extensions * Introduced Middleware * Refactored middleware logic * Refactored builder extensions * Copyright notice * Test for friendly exception on Builder * Fixed header name selection when no output name specified * Set comparer for the dictionary of headers * Refactored configuration as Dictionary * Renamed state objects * renamed OutboundHeaderName in configuration * Changed DefaultValuesGenerator to ValueFactory * Missing docs * Removed AlwaysAdd and added tests for null entry in configuration * Improved docs * Update src/Middleware/HeaderPropagation/src/DependencyInjection/HeaderPropagationExtensions.cs Co-Authored-By: alefranz <alessio@franceschelli.me> * Moved dependency injection extensions * DI: reused ServiceCollection extension in the HttpClientBuilder one * Moved service registration * Update src/Middleware/HeaderPropagation/src/HeaderPropagationEntry.cs Co-Authored-By: alefranz <alessio@franceschelli.me> * more docs * Improved docs * Update src/Middleware/HeaderPropagation/src/HeaderPropagationValues.cs Co-Authored-By: alefranz <alessio@franceschelli.me> * Fixed build * Update eng/SharedFramework.Local.props Co-Authored-By: alefranz <alessio@franceschelli.me> * Updated tests for null config * Reversed condition on HeaderPropagationMessageHandler as suggested * Added docs for HeaderPropagationMessageHandler * Changed proj to ship package to NuGet
- Loading branch information
Showing
15 changed files
with
938 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
...eaderPropagation/src/DependencyInjection/HeaderPropagationApplicationBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// 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.Net.Http; | ||
using Microsoft.AspNetCore.HeaderPropagation; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace Microsoft.AspNetCore.Builder | ||
{ | ||
public static class HeaderPropagationApplicationBuilderExtensions | ||
{ | ||
private static readonly string _unableToFindServices = string.Format( | ||
"Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to 'ConfigureServices(...)' in the application startup code.", | ||
nameof(IServiceCollection), | ||
nameof(HeaderPropagationServiceCollectionExtensions.AddHeaderPropagation)); | ||
|
||
/// <summary> | ||
/// Adds a middleware that collect headers to be propagated to a <see cref="HttpClient"/>. | ||
/// </summary> | ||
/// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param> | ||
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns> | ||
public static IApplicationBuilder UseHeaderPropagation(this IApplicationBuilder app) | ||
{ | ||
if (app == null) | ||
{ | ||
throw new ArgumentNullException(nameof(app)); | ||
} | ||
|
||
if (app.ApplicationServices.GetService<HeaderPropagationValues>() == null) | ||
{ | ||
throw new InvalidOperationException(_unableToFindServices); | ||
} | ||
|
||
return app.UseMiddleware<HeaderPropagationMiddleware>(); | ||
} | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
...HeaderPropagation/src/DependencyInjection/HeaderPropagationHttpClientBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// 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 Microsoft.AspNetCore.HeaderPropagation; | ||
|
||
namespace Microsoft.Extensions.DependencyInjection | ||
{ | ||
public static class HeaderPropagationHttpClientBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Adds a message handler for propagating headers collected by the <see cref="HeaderPropagationMiddleware"/> to a outgoing request. | ||
/// </summary> | ||
/// <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) | ||
{ | ||
if (builder == null) | ||
{ | ||
throw new ArgumentNullException(nameof(builder)); | ||
} | ||
|
||
builder.Services.AddHeaderPropagation(); | ||
|
||
builder.AddHttpMessageHandler<HeaderPropagationMessageHandler>(); | ||
|
||
return builder; | ||
} | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
...HeaderPropagation/src/DependencyInjection/HeaderPropagationServiceCollectionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// 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.Net.Http; | ||
using Microsoft.AspNetCore.HeaderPropagation; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
|
||
namespace Microsoft.Extensions.DependencyInjection | ||
{ | ||
public static class HeaderPropagationServiceCollectionExtensions | ||
{ | ||
/// <summary> | ||
/// Adds services required for propagating headers to a <see cref="HttpClient"/>. | ||
/// </summary> | ||
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> | ||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> | ||
public static IServiceCollection AddHeaderPropagation(this IServiceCollection services) | ||
{ | ||
if (services == null) | ||
{ | ||
throw new ArgumentNullException(nameof(services)); | ||
} | ||
|
||
services.TryAddSingleton<HeaderPropagationValues>(); | ||
services.TryAddTransient<HeaderPropagationMessageHandler>(); | ||
|
||
return services; | ||
} | ||
|
||
/// <summary> | ||
/// Adds services required for propagating headers to a <see cref="HttpClient"/>. | ||
/// </summary> | ||
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> | ||
/// <param name="configureOptions">A delegate used to configure the <see cref="HeaderPropagationOptions"/>.</param> | ||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> | ||
public static IServiceCollection AddHeaderPropagation(this IServiceCollection services, Action<HeaderPropagationOptions> configureOptions) | ||
{ | ||
if (services == null) | ||
{ | ||
throw new ArgumentNullException(nameof(services)); | ||
} | ||
|
||
if (configureOptions == null) | ||
{ | ||
throw new ArgumentNullException(nameof(configureOptions)); | ||
} | ||
|
||
services.Configure(configureOptions); | ||
services.AddHeaderPropagation(); | ||
|
||
return services; | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/Middleware/HeaderPropagation/src/HeaderPropagationEntry.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// 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 Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
/// <summary> | ||
/// Define the configuration of a header for the <see cref="HeaderPropagationMiddleware"/>. | ||
/// </summary> | ||
public class HeaderPropagationEntry | ||
{ | ||
/// <summary> | ||
/// Gets or sets the name of the header to be used by the <see cref="HeaderPropagationMessageHandler"/> for the | ||
/// outbound http requests. | ||
/// </summary> | ||
/// <remarks> | ||
/// If <see cref="ValueFactory"/> is present, the value of the header in the outbound calls will be the one | ||
/// returned by the factory or, if the factory returns an empty value, the header will be omitted. | ||
/// Otherwise, it will be the value of the header in the incoming request named as the key of this entry in | ||
/// <see cref="HeaderPropagationOptions.Headers"/> or, if missing or empty, the value specified in | ||
/// <see cref="DefaultValue"/> or, if the <see cref="DefaultValue"/> is empty, it will not be | ||
/// added to the outbound calls. | ||
/// </remarks> | ||
public string OutboundHeaderName { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the default value to be used when the header in the incoming request is missing or empty. | ||
/// </summary> | ||
/// <remarks> | ||
/// This value is ignored when <see cref="ValueFactory"/> is set. | ||
/// When it is <see cref="StringValues.Empty"/> it has no effect and, if the header is missing or empty in the | ||
/// incoming request, it will not be added to outbound calls. | ||
/// </remarks> | ||
public StringValues DefaultValue { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the value factory to be used. | ||
/// It gets as input the inbound header name for this entry as defined in | ||
/// <see cref="HeaderPropagationOptions.Headers"/> and the <see cref="HttpContext"/> of the current request. | ||
/// </summary> | ||
/// <remarks> | ||
/// When present, the factory is the only method used to set the value. | ||
/// The factory should return <see cref="StringValues.Empty"/> to not add the header. | ||
/// When not present, the value will be taken from the header in the incoming request named as the key of this | ||
/// entry in <see cref="HeaderPropagationOptions.Headers"/> or, if missing or empty, it will be the values | ||
/// specified in <see cref="DefaultValue"/> or, if the <see cref="DefaultValue"/> is empty, the header will not | ||
/// be added to the outbound calls. | ||
/// Please note the factory is called only once per incoming request and the same value will be used by all the | ||
/// outbound calls. | ||
/// </remarks> | ||
public Func<string, HttpContext, StringValues> ValueFactory { get; set; } | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
src/Middleware/HeaderPropagation/src/HeaderPropagationMessageHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// 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.Net.Http; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
/// <summary> | ||
/// A message handler for propagating headers collected by the <see cref="HeaderPropagationMiddleware"/> to a outgoing request. | ||
/// </summary> | ||
public class HeaderPropagationMessageHandler : DelegatingHandler | ||
{ | ||
private readonly HeaderPropagationValues _values; | ||
private readonly HeaderPropagationOptions _options; | ||
|
||
/// <summary> | ||
/// Creates a new instance of the <see cref="HeaderPropagationMessageHandler"/>. | ||
/// </summary> | ||
/// <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) | ||
{ | ||
if (options == null) | ||
{ | ||
throw new ArgumentNullException(nameof(options)); | ||
} | ||
|
||
_options = options.Value; | ||
|
||
_values = values ?? throw new ArgumentNullException(nameof(values)); | ||
} | ||
|
||
/// <summary> | ||
/// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation, after adding | ||
/// the propagated headers. | ||
/// </summary> | ||
/// <remarks> | ||
/// If an header with the same name is already present in the request, even if empty, the corresponding | ||
/// propagated header will not be added. | ||
/// </remarks> | ||
/// <param name="request">The HTTP request message to send to the server.</param> | ||
/// <param name="cancellationToken">A cancellation token to cancel operation.</param> | ||
/// <returns>The task object representing the asynchronous operation.</returns> | ||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | ||
{ | ||
foreach ((var headerName, var entry) in _options.Headers) | ||
{ | ||
var outputName = string.IsNullOrEmpty(entry?.OutboundHeaderName) ? headerName : entry.OutboundHeaderName; | ||
|
||
if (!request.Headers.Contains(outputName) && | ||
_values.Headers.TryGetValue(headerName, out var values) && | ||
!StringValues.IsNullOrEmpty(values)) | ||
{ | ||
request.Headers.TryAddWithoutValidation(outputName, (string[])values); | ||
} | ||
} | ||
|
||
return base.SendAsync(request, cancellationToken); | ||
} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
src/Middleware/HeaderPropagation/src/HeaderPropagationMiddleware.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// 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.Generic; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
/// <summary> | ||
/// A Middleware for propagating headers to a <see cref="HttpClient"/>. | ||
/// </summary> | ||
public class HeaderPropagationMiddleware | ||
{ | ||
private readonly RequestDelegate _next; | ||
private readonly HeaderPropagationOptions _options; | ||
private readonly HeaderPropagationValues _values; | ||
|
||
public HeaderPropagationMiddleware(RequestDelegate next, IOptions<HeaderPropagationOptions> options, HeaderPropagationValues values) | ||
{ | ||
_next = next ?? throw new ArgumentNullException(nameof(next)); | ||
|
||
if (options == null) | ||
{ | ||
throw new ArgumentNullException(nameof(options)); | ||
} | ||
_options = options.Value; | ||
|
||
_values = values ?? throw new ArgumentNullException(nameof(values)); | ||
} | ||
|
||
public Task Invoke(HttpContext context) | ||
{ | ||
foreach ((var headerName, var entry) in _options.Headers) | ||
{ | ||
var values = GetValues(headerName, entry, context); | ||
|
||
if (!StringValues.IsNullOrEmpty(values)) | ||
{ | ||
_values.Headers.TryAdd(headerName, values); | ||
} | ||
} | ||
|
||
return _next.Invoke(context); | ||
} | ||
|
||
private static StringValues GetValues(string headerName, HeaderPropagationEntry entry, HttpContext context) | ||
{ | ||
if (entry?.ValueFactory != null) | ||
{ | ||
return entry.ValueFactory(headerName, context); | ||
} | ||
|
||
if (context.Request.Headers.TryGetValue(headerName, out var values) | ||
&& !StringValues.IsNullOrEmpty(values)) | ||
{ | ||
return values; | ||
} | ||
|
||
return entry != null ? entry.DefaultValue : StringValues.Empty; | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/Middleware/HeaderPropagation/src/HeaderPropagationOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// 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.Collections.Generic; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
/// <summary> | ||
/// Provides configuration for the <see cref="HeaderPropagationMiddleware"/>. | ||
/// </summary> | ||
public class HeaderPropagationOptions | ||
{ | ||
/// <summary> | ||
/// Gets or sets the headers to be collected by the <see cref="HeaderPropagationMiddleware"/> | ||
/// and to be propagated by the <see cref="HeaderPropagationMessageHandler"/>. | ||
/// </summary> | ||
public IDictionary<string, HeaderPropagationEntry> Headers { get; set; } = new Dictionary<string, HeaderPropagationEntry>(); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/Middleware/HeaderPropagation/src/HeaderPropagationValues.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// 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.Generic; | ||
using System.Threading; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
/// <summary> | ||
/// Contains the headers values for the <see cref="HeaderPropagationMiddleware"/>. | ||
/// </summary> | ||
public class HeaderPropagationValues | ||
{ | ||
private readonly static AsyncLocal<Dictionary<string, StringValues>> _headers = new AsyncLocal<Dictionary<string, StringValues>>(); | ||
|
||
/// <summary> | ||
/// Gets the headers values collected by the <see cref="HeaderPropagationMiddleware"/> from the current request that can be propagated. | ||
/// </summary> | ||
public IDictionary<string, StringValues> Headers | ||
{ | ||
get | ||
{ | ||
return _headers.Value ?? (_headers.Value = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)); | ||
} | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/Middleware/HeaderPropagation/src/Microsoft.AspNetCore.HeaderPropagation.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<Description>ASP.NET Core middleware to propagate HTTP headers from the incoming request to the outgoing HTTP Client requests</Description> | ||
<TargetFramework>netcoreapp3.0</TargetFramework> | ||
<IsShippingPackage>true</IsShippingPackage> | ||
<NoWarn>$(NoWarn);CS1591</NoWarn> | ||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | ||
<PackageTags>aspnetcore;httpclient</PackageTags> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<InternalsVisibleTo Include="Microsoft.AspNetCore.HeaderPropagation.Tests" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Reference Include="Microsoft.AspNetCore.Http" /> | ||
<Reference Include="Microsoft.Extensions.Http" /> | ||
<Reference Include="Microsoft.Extensions.DependencyInjection" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Oops, something went wrong.