| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Core.Server.Pipeline
Description
The proxy's data-plane entry point for package and artifact routes.
This module re-exports the top-level handlers for packument merges (GET /{pkg}),
artifact relays (GET /{pkg}/-/{file}.tgz), and first-party publishes (PUT /{pkg}).
Ecosystem coupling
This is the npm packument pipeline: it reaches for the npm registry client, projection, and structural filter directly, so it is the one serve-path module that depends on a concrete adapter. The coupling is expedient, not intended -- the agnostic handles that would let it dispatch through an adapter (a per-adapter router, and an ecosystem-neutral filter/projection) would let a second ecosystem reuse this orchestration unchanged.
Synopsis
- servePackument :: PackageName -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived
- headPackument :: PackageName -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived
- serveTarball :: PackageName -> Version -> Filename -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived
- headTarball :: PackageName -> Version -> Filename -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived
- servePublish :: PackageName -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived
The packument handler
servePackument :: PackageName -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived Source #
Serve a GET /{pkg} packument request end to end, over the request's
RequestCtx.
The mount's PackumentDeps and error renderer are read from the matched
MountBinding in context, not threaded as arguments. When the mount has no
packument-serve dependencies wired, the route is recognised but not served -- a
501 in the mount's surface -- rather than fabricating a result.
With dependencies wired: the edge token, if configured, is validated before any
upstream is touched. Then the private and public upstreams are fetched
concurrently -- the client's credential forwarded to the private origin, the public
origin anonymous -- each parse failure or unavailable upstream degrading to a missing
contribution rather than an error. Private versions are trusted as-is; public
versions are gated through the rules and the structural filter (the FilterPlan);
the surviving sets are merged (mergePackuments) and the MergePlan assembled
onto the raw upstream Values to build the served body,
which is then answered against the client's conditional request with our own ETag.
When nothing survives, the status follows the most recoverable cause via
packumentStatus. An origin whose self-reported packument name disagrees with the
route is validated out -- dropped as untrusted for this request and logged -- so a
single misreporting upstream never denies a package another upstream serves; when
that leaves no valid origin, the request is a 502 (a responding upstream
returned an invalid response), distinct from a genuine absence. Every refusal -- the
edge 401 and the no-survivors 403/503/502/500 -- is rendered through the
mount's MountRenderer.
headPackument :: PackageName -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived Source #
Serve a HEAD /{pkg} packument request: the identical pipeline and gating as
servePackument -- the same fetch, merge, filter, rule decision, and no-survivors
status -- answered with the identical status and headers as the GET (the would-be
merged body's Content-Length and the own ETag the conditional-request machinery
computes), but with the body suppressed (bodiless), as HTTP semantics require of a
HEAD reply.
A packument body is assembled locally (a metadata fetch plus the cross-upstream
merge), so -- unlike the tarball HEAD (headTarball) -- answering it pumps __no
artifact body__ and carries no egress-amplification risk: this is the HTTP-correctness
half of the explicit-HEAD handling, not the DoS lever the tarball path closes. The
merged body is still materialised, to size it and compute its ETag; only the bytes
are withheld from the reply.
The tarball handler
serveTarball :: PackageName -> Version -> Filename -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived Source #
Serve a GET /{pkg}/-/{file}.tgz artifact request end to end, over the
request's RequestCtx.
The mount's PackumentDeps and error renderer are read from the matched
MountBinding; an unwired mount is the recognised-but-unserved 501 stub (as for
servePackument). With dependencies wired and the edge token (if any) validated, the
two legs locate the tarball by the trust of their origin:
- the private leg is a conventional stable read: it fetches
{pdPrivateBaseUrl}/{pkg}/-/{file}by the requested filename (artifactRequestByFile), forwarding the client's credential and __without a private-packument fetch__; a2xxstreams the bytes through with bounded memory and answers the request, any other status (or a connection failure) is a clean miss that falls through. It applies no serve-time integrity floor -- the bytes are still verified client-side and by the mirror worker (see the module header → "Artifact path"); - on a private miss the public leg fetches that one version's metadata anonymously
and gates it against the rules; an admit honours the gated
dist.tarball, streaming the public bytes and enqueuing aMirrorJob(serve-then-enqueue, the enqueue best-effort and non-blocking), a reject renders the serve error model (403/503/500/404) through the mount's renderer.
The public-upstream fetch is always anonymous (the client credential is never sent to the
public upstream); the mirror job carries no credential. The serve path does not
verify dist.integrity (see the module header → "Artifact path").
headTarball :: PackageName -> Version -> Filename -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived Source #
Serve a HEAD /{pkg}/-/{file}.tgz artifact request end to end, over the
request's RequestCtx.
A HEAD must never run the full-GET streaming pump: a bodiless HEAD would
otherwise open the upstream artifact connection and pump a whole artifact body that
the reply then discards -- wasted upstream egress and a DoS-amplification lever (a
client forcing arbitrary full-artifact fetches with cheap HEADs). So this handler
gates the artifact through the identical pipeline as serveTarball -- the same
edge auth, host-allowlist, internal-range, and tarball-host policy, and the same
upstream-request construction -- but issues the upstream request as a HEAD and relays
its status and safe response headers (relayArtifact) with no body
(probeUpstreamWhen). On an admit no MirrorJob is enqueued: a
HEAD serves no bytes, so there is nothing to back-fill (mirroring stays demand-driven
on the GET path). A refusal renders the same serve error model with an empty body.
The first-party publish handler
servePublish :: PackageName -> Request -> (Response -> IO ResponseReceived) -> Handler ResponseReceived Source #