Skip to content
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 32 commits into from Mar 29, 2019
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 Feb 25, 2019
3729951
Introduced Middleware
alefranz Feb 25, 2019
91a4970
Refactored middleware logic
alefranz Mar 10, 2019
eef7598
Refactored builder extensions
alefranz Mar 10, 2019
27bdc5c
Copyright notice
alefranz Mar 10, 2019
3262f6d
Test for friendly exception on Builder
alefranz Mar 10, 2019
d455d66
Fixed header name selection when no output name specified
alefranz Mar 10, 2019
cb6e356
Set comparer for the dictionary of headers
alefranz Mar 10, 2019
8a8b005
Merge branch 'master' into header-propagation
alefranz Mar 13, 2019
b0b67ef
Refactored configuration as Dictionary
alefranz Mar 13, 2019
3edbc95
Renamed state objects
alefranz Mar 13, 2019
0582f29
renamed OutboundHeaderName in configuration
alefranz Mar 13, 2019
f883f68
Changed DefaultValuesGenerator to ValueFactory
alefranz Mar 14, 2019
5af6d22
Missing docs
alefranz Mar 14, 2019
1d4c2f0
Removed AlwaysAdd and added tests for null entry in configuration
alefranz Mar 14, 2019
3e51de0
Improved docs
alefranz Mar 17, 2019
559491f
Merge branch 'master' into header-propagation
alefranz Mar 27, 2019
1619bb4
Update src/Middleware/HeaderPropagation/src/DependencyInjection/Heade…
rynowak Mar 27, 2019
4b414b0
Moved dependency injection extensions
alefranz Mar 27, 2019
ae94fe0
DI: reused ServiceCollection extension in the HttpClientBuilder one
alefranz Mar 27, 2019
78d4eec
Moved service registration
alefranz Mar 27, 2019
d06b743
Update src/Middleware/HeaderPropagation/src/HeaderPropagationEntry.cs
rynowak Mar 27, 2019
adc5962
more docs
alefranz Mar 27, 2019
158faf6
Improved docs
alefranz Mar 27, 2019
aeee1fe
Update src/Middleware/HeaderPropagation/src/HeaderPropagationValues.cs
rynowak Mar 27, 2019
9706db1
Fixed build
alefranz Mar 27, 2019
c4de7f7
Update eng/SharedFramework.Local.props
anurse Mar 27, 2019
6e40837
Updated tests for null config
alefranz Mar 27, 2019
85d67e0
Reversed condition on HeaderPropagationMessageHandler as suggested
alefranz Mar 27, 2019
adab2b9
Added docs for HeaderPropagationMessageHandler
alefranz Mar 27, 2019
61df695
Changed proj to ship package to NuGet
alefranz Mar 28, 2019
689f594
Merge branch 'master' into header-propagation
alefranz Mar 29, 2019
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -73,6 +73,7 @@
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Diagnostics.Abstractions" ProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics.Abstractions\src\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics.Abstractions\ref\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" ProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics.EntityFrameworkCore\src\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics.EntityFrameworkCore\ref\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Diagnostics" ProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics\src\Microsoft.AspNetCore.Diagnostics.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\Diagnostics\ref\Microsoft.AspNetCore.Diagnostics.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.HeaderPropagation" ProjectPath="$(RepositoryRoot)src\Middleware\HeaderPropagation\src\Microsoft.AspNetCore.HeaderPropagation.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\HeaderPropagation\ref\Microsoft.AspNetCore.HeaderPropagation.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" ProjectPath="$(RepositoryRoot)src\Middleware\HealthChecks.EntityFrameworkCore\src\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\HealthChecks.EntityFrameworkCore\ref\Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" ProjectPath="$(RepositoryRoot)src\Middleware\HealthChecks\src\Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\HealthChecks\ref\Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.HostFiltering" ProjectPath="$(RepositoryRoot)src\Middleware\HostFiltering\src\Microsoft.AspNetCore.HostFiltering.csproj" RefProjectPath="$(RepositoryRoot)src\Middleware\HostFiltering\ref\Microsoft.AspNetCore.HostFiltering.csproj" />
@@ -56,6 +56,7 @@
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Cors" />
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Diagnostics.Abstractions" />
<AspNetCoreAppReference Include="Microsoft.AspNetCore.Diagnostics" />
<AspNetCoreAppReference Include="Microsoft.AspNetCore.HeaderPropagation" />

This comment has been minimized.

Copy link
@anurse

anurse Mar 27, 2019

Member

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

Suggested change
<AspNetCoreAppReference Include="Microsoft.AspNetCore.HeaderPropagation" />

This comment has been minimized.

Copy link
@dougbu

dougbu Mar 27, 2019

Member

I don't remember exactly. Removing the <AspNetCoreAppReference /> item may be sufficient. If not, there should be a project property you can set…

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 28, 2019

Author Contributor

I've removed IsAspNetCoreApp ans set IsShippingPackage 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 the Shipping folder.

Can you please confirm @dougbu ?

This comment has been minimized.

Copy link
@dougbu

dougbu Mar 28, 2019

Member

Seems to be working fine 😺 And, the CI build logs contain the following which also looks right:

  Successfully created package 'F:\workspace\_work\1\s\artifacts\packages\Release\Shipping\Microsoft.AspNetCore.HeaderPropagation.3.0.0-preview4-19177-46.nupkg'.
  Successfully created package 'F:\workspace\_work\1\s\artifacts\packages\Release\Shipping\Microsoft.AspNetCore.HeaderPropagation.3.0.0-preview4-19177-46.symbols.nupkg'.

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.

<AspNetCoreAppReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" />
<AspNetCoreAppReference Include="Microsoft.AspNetCore.HostFiltering" />
<AspNetCoreAppReference Include="Microsoft.AspNetCore.HttpOverrides" />
@@ -0,0 +1,38 @@
using System;
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

All new files will need the copyright notice (copy from other examples).

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
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

We'll want docs on all new public API (once design is finished).

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 21, 2019

Member

Still looking for docs here. Something basic like Provides extension methods for using header propagation. would suffice.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 28, 2019

Author Contributor

I can do this, but it doesn't look like a common practice across the repo.

{
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)
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 12, 2019

Member

Docs here.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 17, 2019

Author Contributor

done

{
builder.Services.TryAddSingleton<HeaderPropagationState>();
builder.Services.TryAddTransient<HeaderPropagationMessageHandler>();
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 21, 2019

Member

I think this could be moved to services.AddHeaderPropagation - it is a service after all, and It can be used without the factory.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 27, 2019

Author Contributor

ok


builder.AddHttpMessageHandler(services =>
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

Is there a reason why this isn't using AddHttpMessageHandler<HeaderPropagationMessageHandler>();?

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

fixed

{
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>();
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

This should check for HeaderPropagationState to be registered in services and throw a friendly error reminding the user to call AddHeaderPropagation. This is something we're starting to do for all middleware to make mistakes more figure-out-able.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

I've copyed the message from

<value>Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.</value>

Is there a better approach?

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 12, 2019

Member

Loooks good

}
}
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 21, 2019

Member

These methods should be split up into separate classes. One reason for this is that they need to go into different namespaces. We want Add and Use methods to be in scope by default in startup code.

I would expect to see the following:

Microsoft.Extensions.DependencyInjection.HeaderPropagationServiceCollectionExtensions -> AddHeaderPropagation
Microsoft.Extensions.DependencyInjection.HeaderPropagationHttpClientBuilderExtensions -> AddHeaderPropagation
Microsoft.AspNetCore.Builder.HeaderPropagationApplicationBuilderExtensions -> UseHeaderPropagation

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 27, 2019

Author Contributor

Makes sense

}
@@ -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; }
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

Suggestion for names InboundHeaderName, OutboundHeaderName

public StringValues DefaultValues { get; set; }
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 21, 2019

Member

I noticied that this is DefaultValueS and the factory is ValueFactory (singular). What would you think about calling this DefaultValue?

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 27, 2019

Author Contributor

yes, it makes sense.

public Func<HttpContext, StringValues> DefaultValuesGenerator { get; set; }
public bool AlwaysAdd { get; set; }
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

Do you have a use case in mind for AlwaysAdd? Usually you have a global default (propagate this header) and allow local overrides (specifying the header explicitly). Why do you think this is needed?

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 12, 2019

Member

I'm tempted to leave this out for now (as it can eventually be added later as a non breaking change) eventually adding some extensibility points to be able to inject a behavior for the specific scenario. What do you think?

Yeah, I'd suggest leaving this out. Users will ask how to do it if it's important.

Another suggestion that I have for this (and other needs) - replace DefaultValuesGenerator with ValueFactory - if it's set, then you just always call it to get the output value. I think that solves all of the cases you were interested in, and also can fill the niche that it currently does.

}
}
@@ -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) &&
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

@Tratcher - can you think of a case where a present but empty header is meaningful? I see the checks for null or empty and I wonder...

This comment has been minimized.

Copy link
@Tratcher

Tratcher Mar 6, 2019

Member

The only case I've seen is the Host header. You're required to have one but it may be empty.

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 12, 2019

Member

OK, well that's one we deffo don't care about propagating.

(header.AlwaysAdd || !request.Headers.Contains(header.OutputName)))
{
request.Headers.TryAddWithoutValidation(header.OutputName, (string[]) values);
}
}

return base.SendAsync(request, cancellationToken);
}
}
}
@@ -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)
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak
{
if (context != null)
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

context won't ever be null here legally, this should be an exception (or just ignored)

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

Removed check

{
foreach (var header in _options.Headers)
{
if (!context.Request.Headers.TryGetValue(header.InputName, out var values)
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

I think this logic would be clearer if you reversed this condition, and didn't nest ifs. It took me a few passes through to be sure that it was doing what you wanted.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

What do you think now? Open for suggestions

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 12, 2019

Member

Looks good

|| 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;
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

It doesn't look like the message handler handles this case. It will try to use OutputName regardless.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

You are right, I fixed it doing a change:
I removed the logic in the middleware and saved in the "state" using the input name, then moved the logic in the handler.
This is because to address the ability to specify the headers to be propagated for each client, there will be a part of config needed for the DelegatingHandler and I believe it will be the right place to also specify the output name (so calls to different services can use different header names).

_state.Headers.TryAdd(outputName, values);
}
}

return _next.Invoke(context);
}
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;

namespace Microsoft.AspNetCore.HeaderPropagation
{
public class HeaderPropagationOptions
{
public IList<HeaderPropagationEntry> Headers { get; set; } = new List<HeaderPropagationEntry>();
}
}
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

The drawback of using a list is that you can express duplicates (same outbound name). I'm not sure that it's really a problem that needs to be solved.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 17, 2019

Author Contributor

I had decided to change it to a dictionary, as overriding arrays in config is always a bit messy. Also the ordering doesn't add much value imo. However I used the input name as key so it doesn't address the potential issue you raised.
The reason why I used the input name is that the input name is the one relevant in the middleware. The output name is only used by the Handler, is optional and I believe it should be moved to be moved to the handler configuration as part as handling the ability to specify which handler to include per client (in a following PR).

@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Threading;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.HeaderPropagation
{
public class HeaderPropagationState
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

Since this is a public type I'd like to consider different names for this. Open to suggestions, my ideas:

HeaderPropagationValues

PropagatedHeaders

PropagatedHeaderValues.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

I was tempted to use somethingHeaders (like PropagatedHeaders you suggested) but then PropagatedHeaders.Headers feel awkward. Renaming the Dictionary to Values is not ideal either as it has a Values property :)

Anyway I don't have a particular preference, what's your pick? and what should be the property name?

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 12, 2019

Member

I'd pick HeaderPropagationValues of those.

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 12, 2019

Member

I think Headers is fine for the name

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 17, 2019

Author Contributor

done

{
private static AsyncLocal<Dictionary<string, StringValues>> _headers { get; } = new AsyncLocal<Dictionary<string, StringValues>>();
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

There's no real need for this to be a property.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

true, corrected! thanks!


public Dictionary<string, StringValues> Headers
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

@Tratcher - is there another type we want to use for this or is this the best collection type for headers?

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

I was thinking about IHeaderDictionary but it's not obvious we want to use that here.

This comment has been minimized.

Copy link
@Tratcher

Tratcher Mar 6, 2019

Member

This is fine.

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

Should this property be internal?

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 12, 2019

Member

No, I think this is fine.

{
get
{
return _headers.Value ?? (_headers.Value = new Dictionary<string, StringValues>());
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

Comparer? @Tratcher do we use ignore case?

This comment has been minimized.

Copy link
@Tratcher

Tratcher Mar 6, 2019

Member

Yes, ordinal ignore case

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

Done

}
}
}
}
@@ -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>
@@ -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>());
This conversation was marked as resolved by rynowak

This comment has been minimized.

Copy link
@rynowak

rynowak Mar 1, 2019

Member

Does map here really do anything?

This comment has been minimized.

Copy link
@alefranz

alefranz Mar 10, 2019

Author Contributor

Removed!

})
.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("");
}
}
}
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.