Skip to content

Commit 28ba9d9

Browse files
committed
Handle multivalue X-Forwarded-Proto header
Closes #5198
1 parent 52a204e commit 28ba9d9

File tree

6 files changed

+57
-11
lines changed

6 files changed

+57
-11
lines changed

src/WebJobs.Script.WebHost/Middleware/AppServiceHeaderFixupMiddleware.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System.Linq;
45
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.Http;
6-
using Microsoft.Azure.WebJobs.Script.Extensions;
77
using Microsoft.Extensions.Primitives;
88

99
namespace Microsoft.Azure.WebJobs.Script.WebHost.Middleware
1010
{
1111
public class AppServiceHeaderFixupMiddleware
1212
{
13-
private const string DisguisedHostHeader = "DISGUISED-HOST";
14-
private const string HostHeader = "HOST";
15-
private const string ForwardedProtocolHeader = "X-Forwarded-Proto";
13+
internal const string DisguisedHostHeader = "DISGUISED-HOST";
14+
internal const string HostHeader = "HOST";
15+
internal const string ForwardedProtocolHeader = "X-Forwarded-Proto";
1616
private readonly RequestDelegate _next;
1717

1818
public AppServiceHeaderFixupMiddleware(RequestDelegate next)
@@ -29,7 +29,11 @@ public async Task Invoke(HttpContext httpContext)
2929

3030
if (httpContext.Request.Headers.TryGetValue(ForwardedProtocolHeader, out value))
3131
{
32-
httpContext.Request.Scheme = value;
32+
string scheme = value.FirstOrDefault();
33+
if (!string.IsNullOrEmpty(scheme))
34+
{
35+
httpContext.Request.Scheme = scheme;
36+
}
3337
}
3438

3539
await _next(httpContext);

src/WebJobs.Script.WebHost/WebJobsApplicationBuilderExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static IApplicationBuilder UseWebJobsScriptHost(this IApplicationBuilder
4646
// JobHost/ScriptHost scoped services.
4747
builder.UseMiddleware<ScriptHostRequestServiceProviderMiddleware>();
4848

49-
if (!environment.IsAppService())
49+
if (environment.IsLinuxConsumption())
5050
{
5151
builder.UseMiddleware<AppServiceHeaderFixupMiddleware>();
5252
}

test/WebJobs.Script.Tests.Integration/TestFunctionHost.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
using Microsoft.Extensions.Options;
2929
using Microsoft.WebJobs.Script.Tests;
3030
using Newtonsoft.Json.Linq;
31+
using Microsoft.AspNetCore.Builder;
32+
using Microsoft.Azure.WebJobs.Script.WebHost.Middleware;
3133

3234
namespace Microsoft.Azure.WebJobs.Script.Tests
3335
{
@@ -185,15 +187,15 @@ public static void WriteNugetPackageSources(string appRoot, params string[] sour
185187
}
186188

187189
/// <summary>
188-
/// The functions host has two logger providers -- one at the WebHost level and one at the ScriptHost level.
190+
/// The functions host has two logger providers -- one at the WebHost level and one at the ScriptHost level.
189191
/// These providers use different LoggerProviders, so it's important to know which one is receiving the logs.
190192
/// </summary>
191193
/// <returns>The messages from the ScriptHost LoggerProvider</returns>
192194
public IList<LogMessage> GetScriptHostLogMessages() => _scriptHostLoggerProvider.GetAllLogMessages();
193195
public IEnumerable<LogMessage> GetScriptHostLogMessages(string category) => GetScriptHostLogMessages().Where(p => p.Category == category);
194196

195197
/// <summary>
196-
/// The functions host has two logger providers -- one at the WebHost level and one at the ScriptHost level.
198+
/// The functions host has two logger providers -- one at the WebHost level and one at the ScriptHost level.
197199
/// These providers use different LoggerProviders, so it's important to know which one is receiving the logs.
198200
/// </summary>
199201
/// <returns>The messages from the WebHost LoggerProvider</returns>
@@ -292,6 +294,10 @@ public void ConfigureServices(IServiceCollection services)
292294

293295
public void Configure(AspNetCore.Builder.IApplicationBuilder app, AspNetCore.Hosting.IApplicationLifetime applicationLifetime, AspNetCore.Hosting.IHostingEnvironment env, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory)
294296
{
297+
// This middleware is only added when env.IsLinuxConsumption()
298+
// It should be a no-op for most tests
299+
app.UseMiddleware<AppServiceHeaderFixupMiddleware>();
300+
295301
_startup.Configure(app, applicationLifetime, env, loggerFactory);
296302
}
297303
}

test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/CSharpEndToEndTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ await TestHelpers.Await(() =>
110110

111111
logs.Single(p => p.EndsWith($"From TraceWriter: {guid1}"));
112112
logs.Single(p => p.EndsWith($"From ILogger: {guid2}"));
113-
113+
114114
// Make sure we get a metric logged from both ILogger and TraceWriter
115-
var key = MetricsEventManager.GetAggregateKey(MetricEventNames.FunctionUserLog, "Scenarios");
115+
var key = MetricsEventManager.GetAggregateKey(MetricEventNames.FunctionUserLog, "Scenarios");
116116
Assert.Equal(2, Fixture.MetricsLogger.LoggedEvents.Where(p => p == key).Count());
117117

118118
// Make sure we've gotten a log from the aggregator
@@ -137,14 +137,15 @@ public async Task VerifyHostHeader()
137137
const string actualHost = "actual-host";
138138
const string actualProtocol = "https";
139139
const string path = "api/httptrigger-scenarios";
140+
var protocolHeaders = new[] { "https", "http" };
140141
var request = new HttpRequestMessage
141142
{
142143
RequestUri = new Uri(string.Format($"http://localhost/{path}")),
143144
Method = HttpMethod.Post
144145
};
145146

146147
request.Headers.TryAddWithoutValidation("DISGUISED-HOST", actualHost);
147-
request.Headers.TryAddWithoutValidation("X-Forwarded-Proto", actualProtocol);
148+
request.Headers.Add("X-Forwarded-Proto", protocolHeaders);
148149

149150
var input = new JObject
150151
{
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Threading.Tasks;
5+
using FluentAssertions;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Azure.WebJobs.Script.WebHost.Middleware;
8+
using Microsoft.Extensions.Primitives;
9+
using Xunit;
10+
11+
namespace Microsoft.Azure.WebJobs.Script.Tests.Middleware
12+
{
13+
public class AppServiceHeaderFixupMiddlewareTests
14+
{
15+
[Theory]
16+
[InlineData(new[] { "http" }, "http")]
17+
[InlineData(new[] { "https" }, "https")]
18+
[InlineData(new[] { "https", "http" }, "https")]
19+
[InlineData(new[] { "http", "https" }, "http")]
20+
public async Task AppServiceFixupMiddleware_Handles_Multivalue_Header(string[] headerValues, string expectedScheme)
21+
{
22+
var ctx = new DefaultHttpContext();
23+
ctx.Request.Headers.Add(AppServiceHeaderFixupMiddleware.ForwardedProtocolHeader, new StringValues(headerValues));
24+
25+
var middleware = new AppServiceHeaderFixupMiddleware(nextCtx =>
26+
{
27+
nextCtx.Request.Scheme.Should().Be(expectedScheme);
28+
return Task.CompletedTask;
29+
});
30+
31+
await middleware.Invoke(ctx);
32+
}
33+
}
34+
}

test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
<ItemGroup>
5151
<PackageReference Include="appinsights.testlogger" Version="1.0.0" />
52+
<PackageReference Include="FluentAssertions" Version="5.9.0" />
5253
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.2.0" />
5354
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
5455
<PackageReference Include="Moq" Version="4.9.0" />

0 commit comments

Comments
 (0)