New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HeaderPropagation: propagate incoming request headers to outgoing HTTP requests #7921
Merged
Merged
Changes from 2 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
15c0684
Ported HeaderPropagation from aspnet/Extensions
alefranz 3729951
Introduced Middleware
alefranz 91a4970
Refactored middleware logic
alefranz eef7598
Refactored builder extensions
alefranz 27bdc5c
Copyright notice
alefranz 3262f6d
Test for friendly exception on Builder
alefranz d455d66
Fixed header name selection when no output name specified
alefranz cb6e356
Set comparer for the dictionary of headers
alefranz 8a8b005
Merge branch 'master' into header-propagation
alefranz b0b67ef
Refactored configuration as Dictionary
alefranz 3edbc95
Renamed state objects
alefranz 0582f29
renamed OutboundHeaderName in configuration
alefranz f883f68
Changed DefaultValuesGenerator to ValueFactory
alefranz 5af6d22
Missing docs
alefranz 1d4c2f0
Removed AlwaysAdd and added tests for null entry in configuration
alefranz 3e51de0
Improved docs
alefranz 559491f
Merge branch 'master' into header-propagation
alefranz 1619bb4
Update src/Middleware/HeaderPropagation/src/DependencyInjection/Heade…
rynowak 4b414b0
Moved dependency injection extensions
alefranz ae94fe0
DI: reused ServiceCollection extension in the HttpClientBuilder one
alefranz 78d4eec
Moved service registration
alefranz d06b743
Update src/Middleware/HeaderPropagation/src/HeaderPropagationEntry.cs
rynowak adc5962
more docs
alefranz 158faf6
Improved docs
alefranz aeee1fe
Update src/Middleware/HeaderPropagation/src/HeaderPropagationValues.cs
rynowak 9706db1
Fixed build
alefranz c4de7f7
Update eng/SharedFramework.Local.props
analogrelay 6e40837
Updated tests for null config
alefranz 85d67e0
Reversed condition on HeaderPropagationMessageHandler as suggested
alefranz adab2b9
Added docs for HeaderPropagationMessageHandler
alefranz 61df695
Changed proj to ship package to NuGet
alefranz 689f594
Merge branch 'master' into header-propagation
alefranz File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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
src/Middleware/HeaderPropagation/src/DependencyInjection/HeaderPropagationExtensions.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 @@ | ||
using System; | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
public static class HeaderPropagationExtensions | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
public static IServiceCollection AddHeaderPropagation(this IServiceCollection services, Action<HeaderPropagationOptions> configure) | ||
{ | ||
services.TryAddSingleton<HeaderPropagationState>(); | ||
services.Configure(configure); | ||
|
||
return services; | ||
} | ||
|
||
public static IHttpClientBuilder AddHeaderPropagation(this IHttpClientBuilder builder) | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
builder.Services.TryAddSingleton<HeaderPropagationState>(); | ||
builder.Services.TryAddTransient<HeaderPropagationMessageHandler>(); | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
builder.AddHttpMessageHandler(services => | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
var state = services.GetRequiredService<HeaderPropagationState>(); | ||
return new HeaderPropagationMessageHandler(services.GetRequiredService<IOptions<HeaderPropagationOptions>>(), state); | ||
}); | ||
|
||
return builder; | ||
} | ||
|
||
public static IApplicationBuilder UseHeaderPropagation(this IApplicationBuilder builder) | ||
{ | ||
return builder.UseMiddleware<HeaderPropagationMiddleware>(); | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
15 changes: 15 additions & 0 deletions
15
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,15 @@ | ||
using System; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
public class HeaderPropagationEntry | ||
{ | ||
public string InputName { get; set; } | ||
public string OutputName { get; set; } | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public StringValues DefaultValues { get; set; } | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public Func<HttpContext, StringValues> DefaultValuesGenerator { get; set; } | ||
public bool AlwaysAdd { get; set; } | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
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,43 @@ | ||
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 | ||
{ | ||
public class HeaderPropagationMessageHandler : DelegatingHandler | ||
{ | ||
private readonly HeaderPropagationState _state; | ||
private readonly HeaderPropagationOptions _options; | ||
|
||
public HeaderPropagationMessageHandler(IOptions<HeaderPropagationOptions> options, HeaderPropagationState state) | ||
{ | ||
if (options == null) | ||
{ | ||
throw new ArgumentNullException(nameof(options)); | ||
} | ||
|
||
_options = options.Value; | ||
|
||
_state = state ?? throw new ArgumentNullException(nameof(state)); | ||
} | ||
|
||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, | ||
CancellationToken cancellationToken) | ||
{ | ||
foreach (var header in _options.Headers) | ||
{ | ||
if (_state.Headers.TryGetValue(header.OutputName, out var values) && | ||
!StringValues.IsNullOrEmpty(values) && | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(header.AlwaysAdd || !request.Headers.Contains(header.OutputName))) | ||
{ | ||
request.Headers.TryAddWithoutValidation(header.OutputName, (string[]) values); | ||
} | ||
} | ||
|
||
return base.SendAsync(request, cancellationToken); | ||
} | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
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,60 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
public class HeaderPropagationMiddleware | ||
{ | ||
private readonly RequestDelegate _next; | ||
private readonly HeaderPropagationOptions _options; | ||
private readonly HeaderPropagationState _state; | ||
|
||
public HeaderPropagationMiddleware(RequestDelegate next, IOptions<HeaderPropagationOptions> options, HeaderPropagationState state) | ||
{ | ||
_next = next ?? throw new ArgumentNullException(nameof(next)); | ||
|
||
if (options == null) | ||
{ | ||
throw new ArgumentNullException(nameof(options)); | ||
} | ||
_options = options.Value; | ||
|
||
_state = state ?? throw new ArgumentNullException(nameof(state)); | ||
} | ||
|
||
public Task Invoke(HttpContext context) | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
if (context != null) | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
foreach (var header in _options.Headers) | ||
{ | ||
if (!context.Request.Headers.TryGetValue(header.InputName, out var values) | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|| StringValues.IsNullOrEmpty(values)) | ||
{ | ||
if (header.DefaultValuesGenerator != null) | ||
{ | ||
values = header.DefaultValuesGenerator(context); | ||
if (StringValues.IsNullOrEmpty(values)) continue; | ||
} | ||
else if (!StringValues.IsNullOrEmpty(header.DefaultValues)) | ||
{ | ||
values = header.DefaultValues; | ||
} | ||
else | ||
{ | ||
continue; | ||
} | ||
} | ||
|
||
var outputName = !string.IsNullOrEmpty(header.OutputName) ? header.OutputName : header.InputName; | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_state.Headers.TryAdd(outputName, values); | ||
} | ||
} | ||
|
||
return _next.Invoke(context); | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
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,9 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
public class HeaderPropagationOptions | ||
{ | ||
public IList<HeaderPropagationEntry> Headers { get; set; } = new List<HeaderPropagationEntry>(); | ||
} | ||
} | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
19 changes: 19 additions & 0 deletions
19
src/Middleware/HeaderPropagation/src/HeaderPropagationState.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 @@ | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation | ||
{ | ||
public class HeaderPropagationState | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private static AsyncLocal<Dictionary<string, StringValues>> _headers { get; } = new AsyncLocal<Dictionary<string, StringValues>>(); | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public Dictionary<string, StringValues> Headers | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
get | ||
{ | ||
return _headers.Value ?? (_headers.Value = new Dictionary<string, StringValues>()); | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
} |
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> | ||
<IsAspNetCoreApp>true</IsAspNetCoreApp> | ||
<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> |
94 changes: 94 additions & 0 deletions
94
src/Middleware/HeaderPropagation/test/HeaderPropagationIntegrationTest.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,94 @@ | ||
using System; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.TestHost; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Xunit; | ||
|
||
namespace Microsoft.AspNetCore.HeaderPropagation.Tests | ||
{ | ||
public class HeaderPropagationIntegrationTest | ||
{ | ||
[Fact] | ||
public async Task HeaderInRequest_AddCorrectValue() | ||
{ | ||
// Arrange | ||
var handler = new SimpleHandler(); | ||
var builder = CreateBuilder(c => | ||
c.Headers.Add(new HeaderPropagationEntry | ||
{ | ||
InputName = "in", | ||
OutputName = "out", | ||
}), | ||
handler); | ||
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("out")); | ||
Assert.Equal(new[] { "test" }, handler.Headers.GetValues("out")); | ||
} | ||
|
||
private IWebHostBuilder CreateBuilder(Action<HeaderPropagationOptions> configure, HttpMessageHandler primaryHandler) | ||
{ | ||
return new WebHostBuilder() | ||
.Configure(app => | ||
{ | ||
app.UseHeaderPropagation(); | ||
app.Map("", x => x.UseMiddleware<SimpleMiddleware>()); | ||
rynowak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
.ConfigureServices(services => | ||
{ | ||
services.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com")) | ||
.ConfigureHttpMessageHandlerBuilder(b => | ||
{ | ||
b.PrimaryHandler = primaryHandler; | ||
}) | ||
.AddHeaderPropagation(); | ||
services.AddHeaderPropagation(configure); | ||
}); | ||
} | ||
|
||
private class SimpleHandler : DelegatingHandler | ||
{ | ||
public HttpHeaders Headers { get; private set; } | ||
|
||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | ||
{ | ||
Headers = request.Headers; | ||
return Task.FromResult(new HttpResponseMessage()); | ||
} | ||
} | ||
|
||
private class SimpleMiddleware | ||
{ | ||
private readonly RequestDelegate _next; | ||
private readonly IHttpClientFactory _httpClientFactory; | ||
|
||
public SimpleMiddleware(RequestDelegate next, IHttpClientFactory httpClientFactory) | ||
{ | ||
_next = next; | ||
_httpClientFactory = httpClientFactory; | ||
} | ||
|
||
public Task InvokeAsync(HttpContext _) | ||
{ | ||
var client = _httpClientFactory.CreateClient("example.com"); | ||
return client.GetAsync(""); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remove this from the shared framework? We're generally very very cautious about what we put in there since it's very difficult to remove things later on. We're trying to keep it to things that every single app will use (moving forward at least).
We can still ship it as an official package as part of our release, it just wouldn't be included in the shared framework.
@dougbu Do you know if we need to do anything special to make sure this package is in the set of packages that will end up on NuGet? If we remove it from the shared framework, I want to make sure we know it will end up landing on nuget.org
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't remember exactly. Removing the
<AspNetCoreAppReference />
item may be sufficient. If not, there should be a project property you can set…There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed
IsAspNetCoreApp
ans setIsShippingPackage
in 61df695.Looking at https://github.com/aspnet/AspNetCore/blob/master/Directory.Build.targets#L5-L17 this should be the correct approach and executing
dotnet pack
locally creates the package in theShipping
folder.Can you please confirm @dougbu ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to be working fine 😺 And, the CI build logs contain the following which also looks right:
Unfortunately, this is a fork and so artifacts aren't uploaded. Can't double-check that the shared framework bundles lack Microsoft.AspNetCore.HeaderPropagation.dll until this is merged. That's also fine because we can look at what gets published after this is merged.