a9b23556eb
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
34 lines
1.7 KiB
C#
34 lines
1.7 KiB
C#
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);
|
|
}
|