Reputation: 2353
Net Core 2.2 has a support to execute health checks for published services. I would like to cache a response of checking.
I see that I can use the HealthCheckOptions and set true
value for the AllowCachingResponses property.
app.UseHealthChecks("/api/services/healthCheck",
new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions()
{
AllowCachingResponses = true
});
But I don't understand how I can set the amount time caching. What is the best place to set corresponding HTTP headers(Cache-Control
, Expires
, etc.) and how?
My service is published by IIS.
Upvotes: 2
Views: 3572
Reputation: 3056
I have a scheduled job inside my APIs which invokes HealthCheckService.CheckHealthAsync()
and stores the HealthReport
result. I then just create regular API endpoints which returns this value. Far simpler and doesn't require an artificial wrapper health check.
Edit: As requested, hopefully enough supporting code to demonstrate.
Controller method:
/// <summary>
/// Returns the last global health check result.
/// </summary>
[AllowAnonymous]
[Route("api/health"), HttpGet]
public HealthReport Health() => HealthJob.LastReport;
And bits of the scheduled job. You will need to register the BackgroundService
using AddHostedService<HealthJob>()
on IServiceCollection
:
public class HealthJob : BackgroundService
{
private static readonly TimeSpan CheckInterval = TimeSpan.FromSeconds(10);
// [snip]
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
LastReport = await _healthCheckService.CheckHealthAsync(stoppingToken);
LastExecution = DateTime.Now;
}
catch (TaskCanceledException)
{
// Ignored
}
catch (Exception e)
{
// [snip]
//
// TODO assign LastReport to error values
}
await Task.Delay(CheckInterval, stoppingToken);
}
}
}
Upvotes: 1
Reputation: 22883
The AllowCachingResponses
option you mention relates only to whether HTTP headers are set by the HealthCheckMiddleware. Typically, intermediate servers, proxies, etc. may cache the result of a GET request and those headers indicate that the server should re-fetch them every time.
However, if your load balancer is using these checks to indicate whether the service should receive more traffic, it is likely not caching the results anyway.
To accomplish what you're looking for, you would need to write additional logic. One approach would be to write a type of HealthCheckCacher
like the following:
public class HealthCheckCacher : IHealthCheck
{
private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);
private readonly IHealthCheck _healthCheck;
private readonly TimeSpan _timeToLive;
private HealthCheckResult _result;
private DateTime _lastCheck;
public static readonly TimeSpan DefaultTimeToLive = TimeSpan.FromSeconds(30);
/// <summary>
/// Creates a new HealthCheckCacher which will cache the result for the amount of time specified.
/// </summary>
/// <param name="healthCheck">The underlying health check to perform.</param>
/// <param name="timeToLive">The amount of time for which the health check should be cached. Defaults to 30 seconds.</param>
public HealthCheckCacher(IHealthCheck healthCheck, TimeSpan? timeToLive = null)
{
_healthCheck = healthCheck;
_timeToLive = timeToLive ?? DefaultTimeToLive;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
// you could improve thread concurrency by separating the read/write logic but this would require additional thread safety checks.
// will throw OperationCanceledException if the token is canceled while we're waiting.
await _mutex.WaitAsync(cancellationToken);
try
{
// previous check is cached & not yet expired; just return it
if (_lastCheck > DateTime.MinValue && DateTime.Now - _lastCheck < _timeToLive)
return _result;
// check has not been performed or is expired; run it now & cache the result
_result = await _healthCheck.CheckHealthAsync(context, cancellationToken);
_lastCheck = DateTime.Now;
return _result;
}
finally
{
_mutex.Release();
}
}
}
Upvotes: 1