gobridge

HTTP Transport

Part of the Transport Configuration Reference.

Transport name: http Factory: httptransport.NewFactory(opts...) Capabilities: http_endpoint OpenAPI spec: spec/http-adapter/http-api.yaml

The HTTP transport exposes receivers as POST endpoints and senders as Server-Sent Events (SSE) GET endpoints. All endpoints are mounted on an internal http.ServeMux accessible via factory.Handler().

flowchart LR
    Client1["HTTP Client"] -->|"POST /receivers/{id}/messages"| Recv["Receiver Handler"]
    Recv --> Bridge["Bridge Pipeline"]
    Bridge --> SSE["SSE Sender"]
    SSE -->|"GET /senders/{id}/events"| Client2["SSE Client"]

Authentication

Both receivers and senders support optional per-endpoint API key authentication. When api_key is configured, requests must include the key via X-API-Key header or Authorization: Bearer token. Keys are compared using SHA-256 constant-time comparison to prevent timing and length-based information leaks. A 401 always carries an RFC 7235 WWW-Authenticate: Bearer challenge.

Minimum key length (breaking). An inline api_key shorter than 16 characters is rejected at decode time (minAPIKeyLength). A short key an earlier build accepted must be lengthened to >= 16 characters. Credential-resolved keys (via credentials_uri) are validated at the credential layer and not re-checked here.

The api_key and the cluster forward token MUST be distinct secrets. Every client presents api_key on each request; reusing it as the forward token would let any authenticated caller spoof X-Bridge-Forwarded. Provision two independent secrets.

YAML Example

receivers:
  - id: webhook-receiver
    transport: http
    options:
      path: "/transport/http/receivers/webhook-receiver/messages"
      api_key: "recv-secret-min-16ch"
      max_body_size: 1048576

senders:
  - id: event-stream
    transport: http
    options:
      mode: "sse"
      path: "/transport/http/senders/event-stream/events"
      heartbeat_interval: "30s"
      api_key: "sse-secret-min-16ch"
      max_clients: 100

Receiver Options Reference

Key Type Default Description
path string /transport/http/receivers/{id}/messages POST endpoint path (literal mount point; no ServeMux {} metacharacters)
api_key string Per-receiver API key (constant-time comparison; inline keys >= 16 chars)
max_body_size int 1048576 (1 MiB) Maximum request body in bytes; a breach returns 413
dedup_window int 4096 Size of the node-local ingress idempotency LRU (remembered Idempotency-Key / X-Dedup-Id values)
max_dispatch_duration duration 5m Hard bound on the detached dispatch: the delivery is emitted on a context.WithoutCancel copy of the request context, and this cap always cancels it so a wedged downstream cannot leak a goroutine + in-memory delivery per stuck request. Released early when the delivery settles (Ack/Retry). Independent of any fronting http.TimeoutHandler.
credentials_uri string URI resolved by the bridge credential store at build time (populates api_key when empty)

Receiver Request Format

The receiver accepts a single JSON POST value (trailing tokens are rejected with 400) with the following fields:

Field Type Required Description
subject string yes Logical event subject. Mapped 1:1 to Envelope.Subject. Not a topic or routing key.
payload any JSON no Message content (stored as raw bytes)
id string no Caller-provided message ID (auto-assigned as http-<instance-entropy>-<unixnano>-<counter> when omitted – 8 crypto/rand bytes hex; NOT a UUID)
headers object no Custom metadata (reserved x-bridge.* keys stripped at ingress)
expires_at RFC 3339 no Message TTL (drives on_expired policy)

First-class propagation headers. The idempotency, dedup, and ordering keys are accepted only through their dedicated non-reserved HTTP request headers and re-stamped on the trusted side; a client cannot inject them via the reserved x-bridge.* namespace (stripped at ingress):

Header Purpose
Idempotency-Key Cross-hop identity/dedup key; feeds the ingress idempotency window and rides forwards
X-Dedup-Id Alternative dedup key remembered by the ingress window
X-Ordering-Key Propagated as envelope metadata for ordered targets (FIFO queues); HTTP ingress itself never orders

Sender Options Reference

Key Type Default Description
mode string sse Sender mode (only sse supported)
path string /transport/http/senders/{id}/events GET endpoint path
heartbeat_interval duration 30s SSE keep-alive heartbeat interval
write_timeout duration 15s Per-frame SSE write deadline (re-armed before every frame; overrides a fronting server’s global WriteTimeout and evicts a stalled subscriber)
api_key string Per-sender API key (constant-time comparison; inline keys >= 16 chars)
max_clients int 10000 Maximum concurrent SSE connections (no uncapped mode)
client_buffer_size int 256 Per-subscriber event-queue depth. A full queue drops the event for that subscriber (SSEDroppedEvents) instead of blocking healthy subscribers. Raise it to tolerate bursty producers / briefly slow consumers. There is deliberately no slow-consumer disconnect policy keyed on this depth — a persistently slow subscriber is evicted by write_timeout.
fail_on_zero_delivery bool false Legacy / deprecated. Retained for backward compatibility only. Zero-delivery now fails transient by default (see at_most_once_accept_loss), so true is a no-op equal to the default and false no longer means “ack the loss”. Mutually exclusive with at_most_once_accept_loss — configuring both is rejected at load time. New config should use at_most_once_accept_loss to opt out of the safe default.
at_most_once_accept_loss bool false Safe default (false): a broadcast that reaches zero subscribers (none connected, or every buffer full) makes Send return a transient (Unavailable-class) error so the route runner does not ack a delivery that reached nobody. A durable source (SQS/ASB/AMQP) retries then DLQs; an HTTP-ingress (webhook→SSE) source surfaces HTTP 500 to the producer. Set true to restore classic fire-and-forget at-most-once (ack even when delivery reached nobody). Not a replay buffer — durable fan-out still needs a shared_outbox policy.
redirect_endpoint string – (disabled) PeerInfo.Endpoints key used to build a 307 redirect for a remote-owned route. Empty disables redirect (remote route → 503) so an internal peer endpoint is never leaked to an SSE client.
credentials_uri string URI resolved by the bridge credential store at build time

Resilience & Delivery Semantics

Forwarder Configuration (ForwarderConfig)

Cluster forwarding is configured by the composition root, not the YAML options: block. Defaults (DefaultForwarderConfig):

Field Default Description
Timeout 30s Per-forward request timeout
IdleConnTimeout 90s Transport idle connection timeout
MaxRetries 2 Forward retry attempts
RetryInitialDelay 100ms First retry backoff
RetryMaxDelay 200ms Retry backoff ceiling
MaxIdleConnsPerHost 32 Transport idle conns per peer
MaxConnsPerHost 64 Transport max conns per peer
ForwardToken Shared secret sent as X-Bridge-Forward-Token (must match receiver WithForwardToken)
ReceiverAPIKeys Per-receiver-ID API keys used when forwarding to protected peers
Breaker Optional ports.CircuitBreaker gating each forward
TLSClientConfig – (Go defaults) Optional *tls.Config applied to the forward http.Transport so a peer reachable only over HTTPS with a private CA, or requiring mTLS, can be forwarded to. Nil keeps system roots and no client certificate.
Metrics no-op Receives HTTPForwardBreakerOpen

Cluster-Aware Routing

When a RouteLocator is configured, endpoints become cluster-aware:

Factory Options

The HTTP factory accepts functional options at registration time:

Option Description
WithPathPrefix(prefix) Override URL prefix (default /transport/http)
WithRouteLocator(l) Set cluster-aware route locator
WithMessageForwarder(fw) Set cluster message forwarder
WithForwardToken(token) Shared secret receivers require in X-Bridge-Forward-Token before trusting an X-Bridge-Forwarded marker; must match ForwarderConfig.ForwardToken
WithFactoryMetrics(m) Set metrics exporter
WithFactoryLogger(l) Set structured logger