Add filtering by port
This adds UseHealthChecks overloads that configure the health checks middleware to listen on a preconfigured port. This is sugar for MapWhen, but it's important because a significant set of users will want to use Health Checks in this way.
This commit is contained in:
parent
0973a723fe
commit
2c792ce116
|
|
@ -0,0 +1,47 @@
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace HealthChecksSample
|
||||||
|
{
|
||||||
|
// Pass in `--scenario port` at the command line to run this sample.
|
||||||
|
public class ManagementPortStartup
|
||||||
|
{
|
||||||
|
public ManagementPortStartup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// Registers required services for health checks
|
||||||
|
services.AddHealthChecks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
// This will register the health checks middleware at the URL /health but only on the specified port.
|
||||||
|
//
|
||||||
|
// By default health checks will return a 200 with 'Healthy'.
|
||||||
|
// - No health checks are registered by default, the app is healthy if it is reachable
|
||||||
|
// - The default response writer writes the HealthCheckStatus as text/plain content
|
||||||
|
//
|
||||||
|
// Use UseHealthChecks with a port will only process health checks requests on connection
|
||||||
|
// to the specified port. This is typically used in a container environment where you can expose
|
||||||
|
// a port for monitoring services to have access to the service.
|
||||||
|
// - In this case the management is configured in the launchSettings.json and passed through
|
||||||
|
// and environment variable
|
||||||
|
// - Additionally, the server is also configured to listen to requests on the management port.
|
||||||
|
app.UseHealthChecks("/health", port: Configuration["ManagementPort"]);
|
||||||
|
|
||||||
|
app.Run(async (context) =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync($"Go to http://localhost:{Configuration["ManagementPort"]}/health to see the health status");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ namespace HealthChecksSample
|
||||||
{ "basic", typeof(BasicStartup) },
|
{ "basic", typeof(BasicStartup) },
|
||||||
{ "writer", typeof(CustomWriterStartup) },
|
{ "writer", typeof(CustomWriterStartup) },
|
||||||
{ "liveness", typeof(LivenessProbeStartup) },
|
{ "liveness", typeof(LivenessProbeStartup) },
|
||||||
|
{ "port", typeof(ManagementPortStartup) },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"HealthChecksSample": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"ASPNETCORE_URLS": "http://localhost:5000/;http://localhost:5001/",
|
||||||
|
"ASPNETCORE_MANAGEMENTPORT": "5001"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://localhost:5000/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,7 +40,8 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
throw new ArgumentException("A URL path must be provided", nameof(path));
|
throw new ArgumentException("A URL path must be provided", nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.Map(path, b => b.UseMiddleware<HealthCheckMiddleware>());
|
UseHealthChecksCore(app, path, port: null, Array.Empty<object>());
|
||||||
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -73,7 +74,176 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
throw new ArgumentNullException(nameof(options));
|
throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.Map(path, b => b.UseMiddleware<HealthCheckMiddleware>(Options.Create(options)));
|
UseHealthChecksCore(app, path, port: null, new[] { Options.Create(options), });
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a middleware that provides health check status.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||||
|
/// <param name="path">The path on which to provide health check status.</param>
|
||||||
|
/// <param name="port">The port to listen on. Must be a local port on which the server is listening.</param>
|
||||||
|
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This method will use <see cref="MapWhenExtensions.MapWhen(IApplicationBuilder, Func{HttpContext, bool}, Action{IApplicationBuilder})"/> to
|
||||||
|
/// listen to health checks requests on the specified URL path and port.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The health check middleware will use default settings from <see cref="IOptions{HealthCheckOptions}"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path, int port)
|
||||||
|
{
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("A URL path must be provided", nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
UseHealthChecksCore(app, path, port, Array.Empty<object>());
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a middleware that provides health check status.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||||
|
/// <param name="path">The path on which to provide health check status.</param>
|
||||||
|
/// <param name="port">The port to listen on. Must be a local port on which the server is listening.</param>
|
||||||
|
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This method will use <see cref="MapWhenExtensions.MapWhen(IApplicationBuilder, Func{HttpContext, bool}, Action{IApplicationBuilder})"/> to
|
||||||
|
/// listen to health checks requests on the specified URL path and port.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The health check middleware will use default settings from <see cref="IOptions{HealthCheckOptions}"/>.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path, string port)
|
||||||
|
{
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("A URL path must be provided", nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(port, out var portAsInt))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("The port must be a valid integer.", nameof(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
UseHealthChecksCore(app, path, portAsInt, Array.Empty<object>());
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a middleware that provides health check status.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||||
|
/// <param name="path">The path on which to provide health check status.</param>
|
||||||
|
/// <param name="port">The port to listen on. Must be a local port on which the server is listening.</param>
|
||||||
|
/// <param name="options">A <see cref="HealthCheckOptions"/> used to configure the middleware.</param>
|
||||||
|
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This method will use <see cref="MapExtensions.Map(IApplicationBuilder, PathString, Action{IApplicationBuilder})"/> to
|
||||||
|
/// listen to health checks requests on the specified URL path.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path, int port, HealthCheckOptions options)
|
||||||
|
{
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("A URL path must be provided", nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
UseHealthChecksCore(app, path, port, new[] { Options.Create(options), });
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a middleware that provides health check status.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||||
|
/// <param name="path">The path on which to provide health check status.</param>
|
||||||
|
/// <param name="port">The port to listen on. Must be a local port on which the server is listening.</param>
|
||||||
|
/// <param name="options">A <see cref="HealthCheckOptions"/> used to configure the middleware.</param>
|
||||||
|
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This method will use <see cref="MapExtensions.Map(IApplicationBuilder, PathString, Action{IApplicationBuilder})"/> to
|
||||||
|
/// listen to health checks requests on the specified URL path.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path, string port, HealthCheckOptions options)
|
||||||
|
{
|
||||||
|
if (app == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("A URL path must be provided", nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(port, out var portAsInt))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("The port must be a valid integer.", nameof(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
UseHealthChecksCore(app, path, portAsInt, new[] { Options.Create(options), });
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UseHealthChecksCore(IApplicationBuilder app, PathString path, int? port, object[] args)
|
||||||
|
{
|
||||||
|
if (port == null)
|
||||||
|
{
|
||||||
|
app.Map(path, b => b.UseMiddleware<HealthCheckMiddleware>(args));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.MapWhen(
|
||||||
|
c => c.Connection.LocalPort == port && c.Request.Path.StartsWithSegments(path),
|
||||||
|
b => b.UseMiddleware<HealthCheckMiddleware>(args));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -355,5 +355,65 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
||||||
"The following health checks were not found: 'Bazzzzzz'. Registered health checks: 'Foo, Bar, Baz'.",
|
"The following health checks were not found: 'Bazzzzzz'. Registered health checks: 'Foo, Bar, Baz'.",
|
||||||
ex.Message);
|
ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanListenOnPort_AcceptsRequest_OnSpecifiedPort()
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Use(next => async (context) =>
|
||||||
|
{
|
||||||
|
// Need to fake setting the connection info. TestServer doesn't
|
||||||
|
// do that, because it doesn't have a connection.
|
||||||
|
context.Connection.LocalPort = context.Request.Host.Port.Value;
|
||||||
|
await next(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseHealthChecks("/health", port: 5001);
|
||||||
|
})
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHealthChecks();
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var response = await client.GetAsync("http://localhost:5001/health");
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
|
||||||
|
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanListenOnPort_RejectsRequest_OnOtherPort()
|
||||||
|
{
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Use(next => async (context) =>
|
||||||
|
{
|
||||||
|
// Need to fake setting the connection info. TestServer doesn't
|
||||||
|
// do that, because it doesn't have a connection.
|
||||||
|
context.Connection.LocalPort = context.Request.Host.Port.Value;
|
||||||
|
await next(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseHealthChecks("/health", port: 5001);
|
||||||
|
})
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHealthChecks();
|
||||||
|
});
|
||||||
|
|
||||||
|
var server = new TestServer(builder);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var response = await client.GetAsync("http://localhost:5000/health");
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue