ecluse:ecluse-runtime
Safe HaskellNone
LanguageGHC2021

Ecluse.Runtime.Server.Internal

Description

The dispatch, the request perimeter and the listener behind Ecluse.Runtime.Server, which documents the front door and re-exports the curated surface. Importing this module opts out of that stability promise, the convention text and bytestring use, so production code imports the public one.

Synopsis

The WAI application

data ServerConfig Source #

The settings the web layer needs to serve that the composition-root Env does not carry. Not the request-body cap: the publish route bounds its own body as a value.

Constructors

ServerConfig 

Fields

  • scPort :: Int

    The TCP port warp listens on.

  • scMounts :: [MountBinding]

    The mounts served. The first whose prefix matches the request's leading segments wins, and a path under no mount is the neutral 404.

  • scDrain :: DrainSignal

    The shutdown-drain flag the front door observes. Once raised, the readiness probe fails and every response carries Connection: close. Defaults to neverDraining.

  • scDrainTimeout :: ShutdownDrainTimeout

    How long the graceful drain waits for in-flight requests and in-progress artifact streams to finish before the process exits (defaultShutdownDrainTimeout).

  • scCheckReady :: IO Readiness

    The readiness verdict the composition root installs, which /readyz renders and the drain check overrides. Each mount's flip is one way, so readiness never flaps a pod out of rotation.

  • scCheckLive :: IO Liveness

    The liveness check /livez answers from, beyond the listener itself. A worker heartbeat is wired here only when a worker runs, so a serve-only deployment stays live.

  • scOnException :: Maybe Request -> SomeException -> IO ()

    warp's exception hook, for a post-commit escape the request perimeter rethrew or a fault in warp's own connection handling. The mkServerConfig default is inert.

mkServerConfig :: [MountBinding] -> ServerConfig Source #

Build a ServerConfig over the mount bindings on defaultPort. There is no built-in mount: the web layer serves only the ecosystems the composition root binds here.

defaultPort :: Int Source #

The conventional npm proxy listen port (4873), the mkServerConfig default.

data MountBinding #

Constructors

MountBinding 

Fields

application :: ServerConfig -> Env -> Application Source #

The proxy's WAI Application: the request dispatch under the cross-cutting middleware stack (serverMiddleware).

tracedApplication :: ServerConfig -> Env -> IO Application Source #

application with the OpenTelemetry server-span middleware wrapped outermost, so one server span covers the whole request. The wrapper is id when telemetry is off.

Running the server

runWarp :: LogEnv -> Text -> ServerConfig -> (ServerConfig -> IO Application) -> IO () Source #

Serve the front door over one live DrainSignal, which the probe, the going-away header, and the shutdown handler share. warp drains under scDrainTimeout, and a TTY adds Ctrl-D.

serveBound :: LogEnv -> Text -> Settings -> Application -> IO () Source #

Bind as runSettings does, log the bound port, then serve.

proxyListener :: Text Source #

The proxy's listener name, which starts the line it logs once bound.

listeningPrefix :: Text -> Text Source #

The start of the line a named listener logs once bound, followed by the port.

raceServerAgainstLoop :: MonadUnliftIO m => m () -> m () -> m () Source #

Race a server arm against a never-returning background loop, the single-process shutdown shape. race_ is the invariant: concurrently_ would wait forever, brackets un-unwound.

probeApplication :: DrainSignal -> IO Readiness -> IO Liveness -> Application Source #

The control-plane health probes, answered above any mount: /livez from the injected liveness check, /readyz from the drain signal and startup gate. Any other path is a 404.

probeOnlyApplication :: ServerConfig -> IO Application Source #

The WAI Application of a role that serves the health probes and nothing else. Every path outside /livez and /readyz is the neutral 404.

The typed request perimeter

perimeterGuard Source #

Arguments

:: (RequestFault -> IO ())

Observe a classified pre-commit fault (the metric and the audit line).

-> (response -> IO ResponseReceived)

The route-scoped response continuation.

-> response

The route's declared neutral pre-commit fallback.

-> ((response -> IO ResponseReceived) -> IO ResponseReceived)

The route's handler, discharged to IO, awaiting the tracked respond.

-> IO ResponseReceived 

Run one route's handler behind a commit-tracking respond, catching synchronous escapes only. Pre-commit one answers the neutral fallback with no detail, post-commit it rethrows.

Graceful shutdown

data DrainSignal Source #

The shutdown-drain flag the front door observes during a graceful rollover. Nothing lowers it again. The readiness probe and the going-away middleware read it per request.

newDrainSignal :: IO DrainSignal Source #

Allocate a live drain signal, lowered. runWarp allocates one per launch and raises it from the shutdown signal handler.

neverDraining :: DrainSignal Source #

The inert drain signal: permanently lowered, and raising it does nothing. It is the mkServerConfig default, so a socket-free test reports ready and stamps no going-away header.

beginDrain :: DrainSignal -> IO () Source #

Raise a drain signal: the one-way transition into draining. Idempotent.

isDraining :: DrainSignal -> IO Bool Source #

Read whether a drain signal is raised.

newtype ShutdownDrainTimeout Source #

The bound on the graceful drain, in seconds. The server stops accepting connections, waits this long for in-flight requests and artifact streams, then exits regardless.

defaultShutdownDrainTimeout :: ShutdownDrainTimeout Source #

The default graceful-drain bound: 30 seconds. It covers an in-flight metadata fetch or a moderate artifact stream, and stops a stuck request pinning the old instance.

Local-dev immediate halt

data InteractiveHalt Source #

The local-development immediate-halt wiring, as injection points a test drives without a terminal. Ctrl-D exits the process at once and aborts the drain. It is inert on a non-TTY.

Constructors

InteractiveHalt 

Fields

  • haltOnInteractive :: IO Bool

    Whether to arm the halt. A non-interactive process never installs the watcher.

  • awaitHaltSignal :: IO ()

    Block until the dev's halt signal. The real wiring reads standard input until end-of-input (Ctrl-D). It returns when the watcher should fire.

  • halt :: IO ()

    Stop the process immediately, bypassing the drain wait. The real wiring is a direct _exit (exitImmediately).

defaultInteractiveHalt :: InteractiveHalt Source #

The real local-dev halt: armed only on a terminal and fired by end-of-input, exiting at once without a drain. Status 130 is the conventional "terminated from the terminal" code.

withInteractiveHalt :: InteractiveHalt -> IO a -> IO a Source #

Run an action with the immediate-halt watcher armed only when haltOnInteractive is True. The watcher lives exactly as long as the action, so it never outlives it.

Middleware

serverMiddleware :: ServerConfig -> Middleware Source #

The cross-cutting stack around the proxy Application. The body cap is not a middleware: it would throw across the request perimeter, and Autohead and Gzip fight streaming.