Skip to content

Commit bb5b80e

Browse files
committed
Added global HTTP response headers configuration. Resolves #3788.
1 parent 2789644 commit bb5b80e

File tree

16 files changed

+524
-0
lines changed

16 files changed

+524
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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.Collections.Generic;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Configuration
7+
{
8+
/// <summary>
9+
/// Gets or sets the list of headers to add to every HTTP response.
10+
/// </summary>
11+
public class CustomHttpHeadersOptions : Dictionary<string, string>
12+
{
13+
}
14+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 Microsoft.Azure.WebJobs.Script.Configuration;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.Options;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Configuration
9+
{
10+
internal class CustomHttpHeadersOptionsSetup : IConfigureOptions<CustomHttpHeadersOptions>
11+
{
12+
private readonly IConfiguration _configuration;
13+
14+
public CustomHttpHeadersOptionsSetup(IConfiguration configuration)
15+
{
16+
_configuration = configuration;
17+
}
18+
19+
public void Configure(CustomHttpHeadersOptions options)
20+
{
21+
IConfigurationSection jobHostSection = _configuration.GetSection(ConfigurationSectionNames.JobHost);
22+
var httpGlobalSection = jobHostSection.GetSection(ConfigurationSectionNames.CustomHttpHeaders);
23+
httpGlobalSection.Bind(options);
24+
}
25+
}
26+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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.Collections.Generic;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Azure.WebJobs.Script.Middleware;
8+
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration;
9+
using Microsoft.Extensions.Options;
10+
11+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Middleware
12+
{
13+
public class CustomHttpHeadersMiddleware : IJobHostHttpMiddleware
14+
{
15+
private readonly CustomHttpHeadersOptions _hostOptions;
16+
17+
public CustomHttpHeadersMiddleware(IOptions<CustomHttpHeadersOptions> hostOptions)
18+
{
19+
_hostOptions = hostOptions.Value;
20+
}
21+
22+
public async Task Invoke(HttpContext context, RequestDelegate next)
23+
{
24+
await next(context);
25+
26+
foreach (var header in _hostOptions)
27+
{
28+
context.Response.Headers.TryAdd(header.Key, header.Value);
29+
}
30+
}
31+
}
32+
}

src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection;
1313
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics;
1414
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
15+
using Microsoft.Azure.WebJobs.Script.WebHost.Middleware;
1516
using Microsoft.Extensions.DependencyInjection;
1617
using Microsoft.Extensions.DependencyInjection.Extensions;
1718
using Microsoft.Extensions.Hosting;
@@ -33,6 +34,7 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP
3334
// register default configuration
3435
// must happen before the script host is added below
3536
services.ConfigureOptions<HttpOptionsSetup>();
37+
services.ConfigureOptions<CustomHttpHeadersOptionsSetup>();
3638
services.ConfigureOptions<HostHstsOptionsSetup>();
3739

3840
// Add logging service early.
@@ -79,6 +81,7 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP
7981
services.TryAddSingleton<IScriptWebHookProvider>(p => p.GetService<DefaultScriptWebHookProvider>());
8082
services.TryAddSingleton<IWebHookProvider>(p => p.GetService<DefaultScriptWebHookProvider>());
8183
services.TryAddSingleton<IJobHostMiddlewarePipeline, DefaultMiddlewarePipeline>();
84+
services.TryAddSingleton<IJobHostHttpMiddleware, CustomHttpHeadersMiddleware>();
8285
services.TryAddSingleton<IJobHostHttpMiddleware, HstsConfigurationMiddleware>();
8386

8487
// Make sure the registered IHostIdProvider is used

src/WebJobs.Script/Config/ConfigurationSectionNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ public static class ConfigurationSectionNames
1515
public const string ManagedDependency = "managedDependency";
1616
public const string Http = "http";
1717
public const string Hsts = Http + ":hsts";
18+
public const string CustomHttpHeaders = Http + ":customHeaders";
1819
}
1920
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 Microsoft.Azure.WebJobs.Script.Rpc;
6+
using Xunit;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.Tests.Middleware
9+
{
10+
public class CustomHeadersMiddlewareCSharpEndToEndTests :
11+
CustomHeadersMiddlewareEndToEndTestsBase<CustomHeadersMiddlewareCSharpEndToEndTests.TestFixture>
12+
{
13+
public CustomHeadersMiddlewareCSharpEndToEndTests(TestFixture fixture) : base(fixture)
14+
{
15+
}
16+
17+
[Fact]
18+
public Task CustomHeadersMiddlewareRootUrl()
19+
{
20+
return CustomHeadersMiddlewareRootUrlTest();
21+
}
22+
23+
[Fact]
24+
public Task CustomHeadersMiddlewareAdminUrl()
25+
{
26+
return CustomHeadersMiddlewareAdminUrlTest();
27+
}
28+
29+
[Fact]
30+
public Task CustomHeadersMiddlewareHttpTriggerUrl()
31+
{
32+
return CustomHeadersMiddlewareHttpTriggerUrlTest();
33+
}
34+
35+
[Fact]
36+
public Task CustomHeadersMiddlewareExtensionWebhookUrl()
37+
{
38+
return CustomHeadersMiddlewareExtensionWebhookUrlTest();
39+
}
40+
41+
public class TestFixture : CustomHeadersMiddlewareTestFixture
42+
{
43+
private const string ScriptRoot = @"TestScripts\CustomHeadersMiddleware\CSharp";
44+
45+
public TestFixture() : base(ScriptRoot, "csharp", LanguageWorkerConstants.DotNetLanguageWorkerName)
46+
{
47+
}
48+
}
49+
}
50+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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.Collections.Generic;
5+
using System.Linq;
6+
using System.Net;
7+
using System.Net.Http;
8+
using System.Threading.Tasks;
9+
using Microsoft.Azure.WebJobs.Script.Models;
10+
using Xunit;
11+
12+
namespace Microsoft.Azure.WebJobs.Script.Tests.Middleware
13+
{
14+
public abstract class CustomHeadersMiddlewareEndToEndTestsBase<TTestFixture> :
15+
EndToEndTestsBase<TTestFixture> where TTestFixture : CustomHeadersMiddlewareTestFixture, new()
16+
{
17+
public CustomHeadersMiddlewareEndToEndTestsBase(TTestFixture fixture) : base(fixture)
18+
{
19+
}
20+
21+
protected async Task CustomHeadersMiddlewareRootUrlTest()
22+
{
23+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, string.Empty);
24+
25+
HttpResponseMessage response = await Fixture.Host.HttpClient.SendAsync(request);
26+
response.EnsureSuccessStatusCode();
27+
28+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
29+
30+
IEnumerable<string> values;
31+
Assert.True(response.Headers.TryGetValues("X-Content-Type-Options", out values));
32+
Assert.Equal("nosniff", values.FirstOrDefault());
33+
}
34+
35+
protected async Task CustomHeadersMiddlewareAdminUrlTest()
36+
{
37+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "admin/host/ping");
38+
39+
HttpResponseMessage response = await Fixture.Host.HttpClient.SendAsync(request);
40+
response.EnsureSuccessStatusCode();
41+
42+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
43+
44+
IEnumerable<string> values;
45+
Assert.True(response.Headers.TryGetValues("X-Content-Type-Options", out values));
46+
Assert.Equal("nosniff", values.FirstOrDefault());
47+
}
48+
49+
protected async Task CustomHeadersMiddlewareHttpTriggerUrlTest()
50+
{
51+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "api/HttpTrigger");
52+
53+
HttpResponseMessage response = await Fixture.Host.HttpClient.SendAsync(request);
54+
response.EnsureSuccessStatusCode();
55+
56+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
57+
58+
IEnumerable<string> values;
59+
Assert.True(response.Headers.TryGetValues("X-Content-Type-Options", out values));
60+
Assert.Equal("nosniff", values.FirstOrDefault());
61+
}
62+
63+
protected async Task CustomHeadersMiddlewareExtensionWebhookUrlTest()
64+
{
65+
var secrets = await Fixture.Host.SecretManager.GetHostSecretsAsync();
66+
var url = $"/runtime/webhooks/durableTask/instances?taskHub=MiddlewareTestHub&code={secrets.MasterKey}";
67+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
68+
69+
HttpResponseMessage response = await Fixture.Host.HttpClient.SendAsync(request);
70+
response.EnsureSuccessStatusCode();
71+
72+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
73+
74+
IEnumerable<string> values;
75+
Assert.True(response.Headers.TryGetValues("X-Content-Type-Options", out values));
76+
Assert.Equal("nosniff", values.FirstOrDefault());
77+
}
78+
}
79+
80+
public abstract class CustomHeadersMiddlewareTestFixture: EndToEndTestFixture
81+
{
82+
protected override ExtensionPackageReference[] GetExtensionsToInstall()
83+
{
84+
return new ExtensionPackageReference[]
85+
{
86+
new ExtensionPackageReference
87+
{
88+
Id = "Microsoft.Azure.WebJobs.Extensions.DurableTask",
89+
Version = "1.8.2"
90+
}
91+
};
92+
}
93+
94+
protected CustomHeadersMiddlewareTestFixture(string rootPath, string testId, string language) :
95+
base(rootPath, testId, language)
96+
{
97+
}
98+
}
99+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 Microsoft.Azure.WebJobs.Script.Rpc;
6+
using Xunit;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.Tests.Middleware
9+
{
10+
public class CustomHeadersMiddlewareNodeEndToEndTests :
11+
CustomHeadersMiddlewareEndToEndTestsBase<CustomHeadersMiddlewareNodeEndToEndTests.TestFixture>
12+
{
13+
public CustomHeadersMiddlewareNodeEndToEndTests(TestFixture fixture) : base(fixture)
14+
{
15+
}
16+
17+
[Fact]
18+
public Task CustomHeadersMiddlewareRootUrl()
19+
{
20+
return CustomHeadersMiddlewareRootUrlTest();
21+
}
22+
23+
[Fact]
24+
public Task CustomHeadersMiddlewareAdminUrl()
25+
{
26+
return CustomHeadersMiddlewareAdminUrlTest();
27+
}
28+
29+
[Fact]
30+
public Task CustomHeadersMiddlewareHttpTriggerUrl()
31+
{
32+
return CustomHeadersMiddlewareHttpTriggerUrlTest();
33+
}
34+
35+
[Fact]
36+
public Task CustomHeadersMiddlewareExtensionWebhookUrl()
37+
{
38+
return CustomHeadersMiddlewareExtensionWebhookUrlTest();
39+
}
40+
41+
public class TestFixture : CustomHeadersMiddlewareTestFixture
42+
{
43+
private const string ScriptRoot = @"TestScripts\CustomHeadersMiddleware\Node";
44+
45+
public TestFixture() : base(ScriptRoot, "node", LanguageWorkerConstants.NodeLanguageWorkerName)
46+
{
47+
}
48+
}
49+
}
50+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"bindings": [
3+
{
4+
"type": "httpTrigger",
5+
"name": "req",
6+
"direction": "in",
7+
"methods": [ "get" ],
8+
"authLevel": "anonymous"
9+
},
10+
{
11+
"type": "http",
12+
"name": "$return",
13+
"direction": "out"
14+
}
15+
]
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Net;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Primitives;
4+
5+
public static IActionResult Run(HttpRequest req, TraceWriter log)
6+
{
7+
log.Info("C# HTTP trigger function processed a request.");
8+
9+
return new OkObjectResult("Success");
10+
}

0 commit comments

Comments
 (0)