Smoke-test /health for the reachable (200) and unreachable (503) cases
This commit is contained in:
1 parent
c67c71ea28
commit
666614a3b2
4 files changed
+110
-28
No files matched your search
+31
-21
@@ -6,52 +6,50 @@ using Microsoft.AspNetCore.HttpOverrides;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents();
|
||||
|
||||
// The typed client reads content from PocketBase over its REST API. The base URL
|
||||
// comes from configuration: appsettings for local dev, the PocketBase__BaseUrl
|
||||
// env var on the server (see compose.yaml).
|
||||
// comes from config: appsettings for local dev, the PocketBase__BaseUrl env var
|
||||
// on the server (see compose.yaml).
|
||||
var pocketBaseUrl = builder.Configuration["PocketBase:BaseUrl"]
|
||||
?? throw new InvalidOperationException("PocketBase:BaseUrl is not configured.");
|
||||
builder.Services.AddHttpClient<PocketBaseClient>(client =>
|
||||
client.BaseAddress = new Uri(pocketBaseUrl));
|
||||
{
|
||||
client.BaseAddress = new Uri(pocketBaseUrl);
|
||||
// Every page waits for PocketBase. The default wait is 100 seconds, so if
|
||||
// PocketBase is down the page would hang that long. 5 seconds fails fast instead.
|
||||
client.Timeout = TimeSpan.FromSeconds(5);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// NPM terminates TLS and is the only way to reach the container (no port
|
||||
// mapping in production). Without UseForwardedHeaders the app sees every request
|
||||
// as HTTP and with the proxy IP instead of the client IP.
|
||||
// KnownNetworks/KnownProxies are deliberately empty because only NPM reaches
|
||||
// the container.
|
||||
// NPM sits in front of the app and handles HTTPS. Without this line the app would
|
||||
// think every request is plain HTTP and would see NPM's IP, not the visitor's.
|
||||
// KnownNetworks/KnownProxies are left empty on purpose: only NPM reaches the app.
|
||||
app.UseForwardedHeaders(
|
||||
new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
||||
});
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment() is false)
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
// No UseHsts() and no UseHttpsRedirection(): NPM sets HSTS and terminates
|
||||
// TLS. Both here would create a redirect loop behind the proxy.
|
||||
// No UseHsts() and no UseHttpsRedirection() here: NPM already handles HTTPS.
|
||||
// Adding them behind NPM would send the browser into a redirect loop.
|
||||
}
|
||||
|
||||
// No UseStatusCodePagesWithReExecute: the Router renders the NotFoundPage
|
||||
// (Pages.NotFound) in process for both an unmatched route and an explicit
|
||||
// NavigationManager.NotFound() from a component (see Routes.razor). Re-executing
|
||||
// every error code to /not-found would also turn a 503 (content source down) into
|
||||
// a "not found" page; leaving it out lets those components keep their own
|
||||
// "temporarily unavailable" markup and 503 status.
|
||||
// Don't add UseStatusCodePagesWithReExecute here. Blazor already shows its own
|
||||
// not-found page. That middleware would catch *every* error code, so when PocketBase
|
||||
// is down (a 503) it would wrongly show "not found" instead of our own message.
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>();
|
||||
|
||||
// Subscribable calendar feed of all events. A minimal API endpoint rather than
|
||||
// a Razor page because it returns text/calendar, not HTML. Reads from PocketBase
|
||||
// per request; an unreachable source yields an empty calendar, not a 500.
|
||||
// Calendar feed people can subscribe to. It returns calendar data, not a web page,
|
||||
// so it is a plain endpoint, not a Razor page. If PocketBase is down it returns an
|
||||
// empty calendar rather than an error.
|
||||
app.MapGet("/events.ics", async (PocketBaseClient pocketBase, CancellationToken token) =>
|
||||
{
|
||||
IReadOnlyList<Event> events;
|
||||
@@ -67,4 +65,16 @@ app.MapGet("/events.ics", async (PocketBaseClient pocketBase, CancellationToken
|
||||
return Results.Text(IcsCalendar.Build(events), "text/calendar; charset=utf-8");
|
||||
});
|
||||
|
||||
// Status page for an outside monitor (Uptime Kuma) to check. One check tells all
|
||||
// three states apart: no reply = the app is down, 503 = the app runs but PocketBase
|
||||
// is down, 200 = both are fine. It talks to PocketBase directly, not through the
|
||||
// page-rendering code, so it can still report when that code is the thing failing.
|
||||
app.MapGet("/health", async (PocketBaseClient pocketBase, CancellationToken token) =>
|
||||
await pocketBase.IsHealthyAsync(token)
|
||||
? Results.Text("healthy", "text/plain; charset=utf-8")
|
||||
: Results.Text(
|
||||
"PocketBase unreachable",
|
||||
"text/plain; charset=utf-8",
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable));
|
||||
|
||||
app.Run();
|
||||
Reference in new issue
Block a user