Skip to content

Commit 93d9b38

Browse files
authored
Configurable Healthchecks (#1)
* rename DefaultConfiguration * move HealthChecks to HttpOverrides * Configure HealthChecks from configuration * Update default to /healthz; change IsAzureAppServiceContainer
1 parent ac8e2b2 commit 93d9b38

File tree

6 files changed

+140
-23
lines changed

6 files changed

+140
-23
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ Reference [ForwardedHeadersOptionsSetup.cs](https://github.com/dotnet/aspnetcore
4242

4343
```json
4444
{
45+
"HealthCheck": {
46+
"IsEnabled": true,
47+
"IsAzureAppServiceContainer": false, // will include /healthz and /robots933456.txt
48+
"Path": "/healthz",
49+
"Paths": [ "/healthz", "/robots933456.txt" ],
50+
"Port": null
51+
},
4552
"HttpOverrides": {
4653
"ClearForwardLimit": false,
4754
"ClearKnownProxies": false,
@@ -57,6 +64,6 @@ Reference [ForwardedHeadersOptionsSetup.cs](https://github.com/dotnet/aspnetcore
5764
"OriginalProtoHeaderName": "X-Original-Proto",
5865
"ForwardedHeaders": "", // XForwardedFor,XForwardedHost,XForwardedProto
5966
"AllowedHosts": "" // "*"
60-
}
67+
}
6168
}
6269
```

samples/SampleWebApi/Program.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
builder.Services.AddEndpointsApiExplorer();
2525
builder.Services.AddSwaggerGen();
2626

27-
builder.Services.AddHealthChecks(); // Registers health checks services
28-
2927
builder.AddHttpOverrides();
3028

3129
builder.Services.AddHttpContextAccessor();
@@ -48,8 +46,6 @@
4846
// todo1
4947
}
5048

51-
app.UseHealthChecks("/healthz");
52-
5349
// app.UseHttpsRedirection()
5450

5551
app.UseStatusCodePages();

src/NetLah.Extensions.HttpOverrides/Config.cs renamed to src/NetLah.Extensions.HttpOverrides/DefaultConfiguration.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
namespace NetLah.Extensions.HttpOverrides;
22

3-
public static class Config
3+
public static class DefaultConfiguration
44
{
5+
public const string HealthChecksConst = "/healthz";
6+
public const string HealthChecksAzureAppServiceContainer = "/robots933456.txt";
7+
8+
public const string HealthCheckKey = "HealthCheck";
59
public const string HttpOverridesKey = "HttpOverrides";
610
public const string HttpLoggingKey = "HttpLogging";
711

812
/// <summary>
913
/// Setttings ASPNETCORE_FORWARDEDHEADERS_ENABLED
1014
/// </summary>
1115
public const string AspNetCoreForwardedHeadersEnabledKey = "ForwardedHeaders_Enabled";
12-
16+
1317
public const string ClearForwardLimitKey = "ClearForwardLimit";
1418

1519
public const string KnownNetworksKey = "KnownNetworks";
@@ -21,4 +25,6 @@ public static class Config
2125
public const string HttpLoggingEnabledKey = "HttpLoggingEnabled";
2226
public const string ClearRequestHeadersKey = "ClearRequestHeaders";
2327
public const string ClearResponseHeadersKey = "ClearResponseHeaders";
28+
29+
public static string HealthChecksPath { get; } = HealthChecksConst;
2430
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace NetLah.Extensions.HttpOverrides;
2+
3+
internal class HealthCheckAppOptions
4+
{
5+
public bool IsEnabled { get; set; } = true;
6+
7+
public bool IsAzureAppServiceContainer { get; set; }
8+
9+
public string Path { get; set; } = DefaultConfiguration.HealthChecksPath;
10+
11+
public string[]? Paths { get; set; }
12+
13+
public int? Port { get; set; }
14+
}

src/NetLah.Extensions.HttpOverrides/HttpOverridesExtensions.cs

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
3+
using Microsoft.AspNetCore.Http;
24
using Microsoft.AspNetCore.HttpLogging;
35
using Microsoft.AspNetCore.HttpOverrides;
46
using Microsoft.Extensions.Configuration;
@@ -13,27 +15,53 @@ namespace NetLah.Extensions.HttpOverrides;
1315
public static class HttpOverridesExtensions
1416
{
1517
private static readonly Lazy<ILogger?> _loggerLazy = new(() => AppLogReference.GetAppLogLogger(typeof(HttpOverridesExtensions).Namespace));
18+
private static HealthCheckAppOptions _healthCheckAppOptions = default!;
1619
private static bool _isForwardedHeadersEnabled;
1720
private static bool _isHttpLoggingEnabled;
1821

19-
public static WebApplicationBuilder AddHttpOverrides(this WebApplicationBuilder webApplicationBuilder, string httpOverridesSectionName = Config.HttpOverridesKey, string httpLoggingSectionName = Config.HttpLoggingKey)
22+
public static WebApplicationBuilder AddHttpOverrides(this WebApplicationBuilder webApplicationBuilder,
23+
string httpOverridesSectionName = DefaultConfiguration.HttpOverridesKey,
24+
string httpLoggingSectionName = DefaultConfiguration.HttpLoggingKey,
25+
string healthCheckSectionName = DefaultConfiguration.HealthCheckKey)
2026
{
21-
webApplicationBuilder.Services.AddHttpOverrides(webApplicationBuilder.Configuration, httpOverridesSectionName, httpLoggingSectionName);
27+
webApplicationBuilder.Services.AddHttpOverrides(webApplicationBuilder.Configuration, httpOverridesSectionName, httpLoggingSectionName, healthCheckSectionName);
2228
return webApplicationBuilder;
2329
}
2430

2531
public static IServiceCollection AddHttpOverrides(this IServiceCollection services, IConfiguration configuration,
26-
string httpOverridesSectionName = Config.HttpOverridesKey, string httpLoggingSectionName = Config.HttpLoggingKey)
32+
string httpOverridesSectionName = DefaultConfiguration.HttpOverridesKey,
33+
string httpLoggingSectionName = DefaultConfiguration.HttpLoggingKey,
34+
string healthCheckSectionName = DefaultConfiguration.HealthCheckKey)
2735
{
28-
ILogger? logger =null;
36+
ILogger? logger = null;
2937

3038
void EnsureLogger()
3139
{
3240
logger ??= _loggerLazy.Value;
3341
logger ??= NullLogger.Instance;
3442
}
3543

36-
_isForwardedHeadersEnabled = configuration[Config.AspNetCoreForwardedHeadersEnabledKey].IsTrue();
44+
var healthCheckConfigurationSection = string.IsNullOrEmpty(healthCheckSectionName) ? configuration : configuration.GetSection(healthCheckSectionName);
45+
var healthCheckAppOptions = _healthCheckAppOptions = healthCheckConfigurationSection.Get<HealthCheckAppOptions>() ?? new HealthCheckAppOptions();
46+
if (healthCheckAppOptions.IsEnabled)
47+
{
48+
EnsureLogger();
49+
if (healthCheckConfigurationSection.GetChildren().Any())
50+
{
51+
logger?.LogDebug("Attempt to load HealthCheckOptions from configuration");
52+
services.Configure<HealthCheckOptions>(healthCheckConfigurationSection);
53+
}
54+
else
55+
{
56+
logger?.LogDebug("Add HealthChecks");
57+
}
58+
59+
services.Configure<HealthCheckAppOptions>(healthCheckConfigurationSection);
60+
61+
services.AddHealthChecks(); // Registers health checks services
62+
}
63+
64+
_isForwardedHeadersEnabled = configuration[DefaultConfiguration.AspNetCoreForwardedHeadersEnabledKey].IsTrue();
3765

3866
if (!_isForwardedHeadersEnabled)
3967
{
@@ -45,7 +73,7 @@ void EnsureLogger()
4573

4674
services.Configure<ForwardedHeadersOptions>(options =>
4775
{
48-
if (httpOverridesConfigurationSection[Config.ClearForwardLimitKey].IsTrue())
76+
if (httpOverridesConfigurationSection[DefaultConfiguration.ClearForwardLimitKey].IsTrue())
4977
{
5078
options.ForwardLimit = null;
5179
}
@@ -56,7 +84,7 @@ void EnsureLogger()
5684
});
5785
}
5886

59-
var httpLoggingEnabledKey = string.IsNullOrEmpty(httpLoggingSectionName) ? Config.HttpLoggingEnabledKey : $"{httpLoggingSectionName}:Enabled";
87+
var httpLoggingEnabledKey = string.IsNullOrEmpty(httpLoggingSectionName) ? DefaultConfiguration.HttpLoggingEnabledKey : $"{httpLoggingSectionName}:Enabled";
6088
_isHttpLoggingEnabled = configuration[httpLoggingEnabledKey].IsTrue();
6189

6290
if (_isHttpLoggingEnabled)
@@ -66,8 +94,8 @@ void EnsureLogger()
6694

6795
var httpLoggingConfigurationSection = string.IsNullOrEmpty(httpLoggingSectionName) ? configuration : configuration.GetSection(httpLoggingSectionName);
6896
services.Configure<HttpLoggingOptions>(httpLoggingConfigurationSection);
69-
var isClearRequestHeaders = httpLoggingConfigurationSection[Config.ClearRequestHeadersKey].IsTrue();
70-
var isClearResponseHeaders = httpLoggingConfigurationSection[Config.ClearResponseHeadersKey].IsTrue();
97+
var isClearRequestHeaders = httpLoggingConfigurationSection[DefaultConfiguration.ClearRequestHeadersKey].IsTrue();
98+
var isClearResponseHeaders = httpLoggingConfigurationSection[DefaultConfiguration.ClearResponseHeadersKey].IsTrue();
7199
var httpLoggingConfig = httpLoggingConfigurationSection.Get<HttpLoggingConfig>();
72100
var requestHeaders = httpLoggingConfig?.RequestHeaders.SplitSet() ?? new HashSet<string>();
73101
var responseHeaders = httpLoggingConfig?.ResponseHeaders.SplitSet() ?? new HashSet<string>();
@@ -95,14 +123,62 @@ void EnsureLogger()
95123
return services;
96124
}
97125

98-
public static IApplicationBuilder UseHttpOverrides(this IApplicationBuilder app, ILogger? logger = null)
126+
public static WebApplication UseHttpOverrides(this WebApplication app, ILogger? logger = null)
99127
{
100128
logger ??= _loggerLazy.Value;
101129
logger ??= NullLogger.Instance;
102-
var sp = app.ApplicationServices;
130+
var sp = app.Services;
103131
var optionsForwardedHeadersOptions = sp.GetRequiredService<IOptions<ForwardedHeadersOptions>>();
104132
var fho = optionsForwardedHeadersOptions.Value;
105133

134+
if (_healthCheckAppOptions.IsEnabled)
135+
{
136+
if (_healthCheckAppOptions.IsAzureAppServiceContainer)
137+
{
138+
var mainHealthChecksPath = string.IsNullOrEmpty(_healthCheckAppOptions.Path) ? DefaultConfiguration.HealthChecksPath : _healthCheckAppOptions.Path;
139+
_healthCheckAppOptions.Paths = new[] { mainHealthChecksPath, DefaultConfiguration.HealthChecksAzureAppServiceContainer };
140+
}
141+
142+
var port = _healthCheckAppOptions.Port;
143+
if (_healthCheckAppOptions.Paths is { } pathArrays && pathArrays.Length > 0)
144+
{
145+
var paths = pathArrays.Select(p => (PathString)p).ToArray();
146+
logger.LogDebug("Use HealthChecks Port:{port} {paths}", port, paths);
147+
148+
bool predicate(HttpContext c)
149+
{
150+
if (port == null || c.Connection.LocalPort == port)
151+
{
152+
foreach (var path in paths)
153+
{
154+
if (c.Request.Path.StartsWithSegments(path, out var remaining) &&
155+
string.IsNullOrEmpty(remaining))
156+
{
157+
return true;
158+
}
159+
}
160+
}
161+
162+
return false;
163+
}
164+
165+
app.MapWhen(predicate, b => b.UseMiddleware<HealthCheckMiddleware>(Array.Empty<object>()));
166+
}
167+
else
168+
{
169+
var healthChecksPath = StringHelper.NormalizeNull(_healthCheckAppOptions.Path);
170+
logger.LogDebug("Use HealthChecks Port:{port} {path}", port, healthChecksPath);
171+
if (port.HasValue)
172+
{
173+
app.UseHealthChecks(healthChecksPath, port.Value);
174+
}
175+
else
176+
{
177+
app.UseHealthChecks(healthChecksPath);
178+
}
179+
}
180+
}
181+
106182
var hostFilteringOptions = sp.GetRequiredService<IOptions<Microsoft.AspNetCore.HostFiltering.HostFilteringOptions>>();
107183
if (hostFilteringOptions?.Value is { } hostFiltering)
108184
{
@@ -111,7 +187,7 @@ public static IApplicationBuilder UseHttpOverrides(this IApplicationBuilder app,
111187

112188
if (_isForwardedHeadersEnabled)
113189
{
114-
var bypassNetLahHttpOverridesMessage = $"Bypass HttpOverrides configuration settings because {Config.AspNetCoreForwardedHeadersEnabledKey} is True";
190+
var bypassNetLahHttpOverridesMessage = $"Bypass HttpOverrides configuration settings because {DefaultConfiguration.AspNetCoreForwardedHeadersEnabledKey} is True";
115191
#pragma warning disable CA2254 // Template should be a static expression
116192
logger.LogInformation(bypassNetLahHttpOverridesMessage);
117193
#pragma warning restore CA2254 // Template should be a static expression
@@ -167,8 +243,8 @@ public static IApplicationBuilder UseHttpOverrides(this IApplicationBuilder app,
167243

168244
private static void ProcessKnownNetworks(IConfiguration configuration, ForwardedHeadersOptions options)
169245
{
170-
var knownNetworks = configuration[Config.KnownNetworksKey];
171-
if (knownNetworks != null || configuration[Config.ClearKnownNetworksKey].IsTrue())
246+
var knownNetworks = configuration[DefaultConfiguration.KnownNetworksKey];
247+
if (knownNetworks != null || configuration[DefaultConfiguration.ClearKnownNetworksKey].IsTrue())
172248
{
173249
options.KnownNetworks.Clear();
174250
}
@@ -193,8 +269,8 @@ private static void ProcessKnownNetworks(IConfiguration configuration, Forwarded
193269

194270
private static void ProcessKnownProxies(IConfiguration configuration, ForwardedHeadersOptions options)
195271
{
196-
var knownProxies = configuration[Config.KnownProxiesKey];
197-
if (knownProxies != null || configuration[Config.ClearKnownProxiesKey].IsTrue())
272+
var knownProxies = configuration[DefaultConfiguration.KnownProxiesKey];
273+
if (knownProxies != null || configuration[DefaultConfiguration.ClearKnownProxiesKey].IsTrue())
198274
{
199275
options.KnownProxies.Clear();
200276
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace NetLah.Extensions.HttpOverrides;
2+
3+
internal static class StringHelper
4+
{
5+
public static string GetOrDefault(params string?[] values)
6+
{
7+
foreach (var value in values)
8+
{
9+
if (!string.IsNullOrWhiteSpace(value))
10+
return value;
11+
}
12+
13+
return string.Empty;
14+
}
15+
16+
public static string? NormalizeNull(string? value)
17+
=> string.IsNullOrWhiteSpace(value) ? default : value.Trim();
18+
}

0 commit comments

Comments
 (0)