feat(inbound-api): standby central node relays POST /api/* to the active peer instead of 503
A caller addressing a standby central node directly (or hitting Traefik inside its /health/active probe window after a failover) used to get 503 STANDBY_NODE. The standby now relays the request verbatim to the active peer over HTTP (IStandbyRequestForwarder / HttpStandbyRequestForwarder) and replays the response; method scripts and Route.To() still execute only on the active node. - ScadaBridge:InboundApi:StandbyForwardPeerEndpoints (static list of the OTHER central node's HTTP base URL; empty = old 503 behaviour) + StandbyForwardTimeout (60s), both startup-validated. - Peers tried in order: peer 503 stamped X-ScadaBridge-Standby, connection failure or timeout → next peer; all fail → 503 ACTIVE_NODE_UNREACHABLE. A 503 without the marker is the active node's own answer and is replayed. - One-hop loop guard via X-ScadaBridge-Forwarded; 413 body cap now runs BEFORE the active-node check so a standby never buffers/relays an oversized body. - AuditWriteMiddleware skips its own ApiInbound row for a relayed request so a call is audited once, on the node that executed it. - Rig + env2 central nodes configured; spec (Component-InboundAPI.md → Standby forwarding), Component-Host.md options row, CLAUDE.md updated. - 20 new tests (filter decision + relay mechanics + validator). Claude-Session: https://claude.ai/code/session_019ZxXT5sxZs5XrtdwYzJDPV
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.InboundAPI;
|
||||
|
||||
/// <summary>
|
||||
/// Standby-node forwarding seam for <c>POST /api/{methodName}</c>. When a request
|
||||
/// lands on a standby central node, <see cref="InboundApiEndpointFilter"/> hands it
|
||||
/// here instead of returning HTTP 503, and the implementation relays it verbatim to
|
||||
/// the active central node over HTTP so callers that address a central node directly
|
||||
/// (or that hit the load balancer inside its health-probe window after a failover)
|
||||
/// still get a served response. Method scripts and <c>Route.To()</c> calls still run
|
||||
/// ONLY on the active node — the standby is a pure relay.
|
||||
/// </summary>
|
||||
public interface IStandbyRequestForwarder
|
||||
{
|
||||
/// <summary>
|
||||
/// <c>true</c> when at least one peer endpoint is configured
|
||||
/// (<see cref="InboundApiOptions.StandbyForwardPeerEndpoints"/>). When <c>false</c>
|
||||
/// the filter falls back to the pre-forwarding behaviour: HTTP 503 <c>STANDBY_NODE</c>.
|
||||
/// </summary>
|
||||
bool IsConfigured { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Relays the current request to the first peer that serves it and returns a
|
||||
/// result that replays the peer's status, content type and body to the caller.
|
||||
/// Never throws for peer failures: when no peer served the request the result is
|
||||
/// HTTP 503 <c>ACTIVE_NODE_UNREACHABLE</c>.
|
||||
/// </summary>
|
||||
/// <param name="context">The inbound HTTP context on the standby node.</param>
|
||||
/// <param name="cancellationToken">Cancelled when the caller aborts the request.</param>
|
||||
/// <returns>A result replaying the peer's response, or a 503 when no peer served it.</returns>
|
||||
Task<IResult> ForwardAsync(HttpContext context, CancellationToken cancellationToken);
|
||||
}
|
||||
Reference in New Issue
Block a user