ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Registry.Npm.Request

Description

Request shaping and URL building for the npm data plane.

Three details of the wire protocol are load-bearing and handled here:

  • Content negotiation. Metadata comes in two forms selected by Accept: the abbreviated install view (application/vnd.npm.install-v1+json), which the proxy treats as primary, and the full packument (application/json), needed when a rule reasons over publish age (the abbreviated form drops the time map). MetadataForm selects between them; both request Accept-Encoding: gzip, since popular packuments are megabytes.
  • Scoped-name path encoding. A scoped name @scope/name is encoded on the wire as @scope%2Fname: the scope separator is percent-encoded but the leading @ is not. metadataRequest builds this from an already-parsed PackageName, never from raw client path segments.
  • Streaming and buffering. artifactRequest marks its request non-decompressing (decompress returns False): a tarball is opaque binary that must reach the client byte-for-byte, so the .tgz is never gunzipped in flight (and its dist.integrity stays valid).
Synopsis

Content negotiation

data MetadataForm Source #

Which of npm's two metadata documents to request, selected by the Accept header (see metadataAccept).

Constructors

Abbreviated

The install-optimised abbreviated packument (application/vnd.npm.install-v1+json). Smaller and the proxy's primary view, but it drops the time map.

Full

The full packument (application/json). Larger, but the only form carrying the time map a publish-age rule needs.

metadataAccept :: MetadataForm -> ByteString Source #

The Accept header value selecting a MetadataForm.

>>> metadataAccept Abbreviated
"application/vnd.npm.install-v1+json"
>>> metadataAccept Full
"application/json"

Conditional-GET validators

data Validators Source #

The conditional-GET validators to relay on a metadata fetch. Replaying an upstream's ETag as If-None-Match (or its Last-Modified as If-Modified-Since) lets the upstream answer 304 Not Modified with no body: the cheap freshness check the proxy uses on a cache revalidation. Both are forwarded only when present.

Constructors

Validators 

Fields

Instances

Instances details
Show Validators Source # 
Instance details

Defined in Ecluse.Core.Registry.Npm.Request

Eq Validators Source # 
Instance details

Defined in Ecluse.Core.Registry.Npm.Request

noValidators :: Validators Source #

No conditional-GET validators: an unconditional fetch.

Request building

metadataRequest :: Text -> Maybe Secret -> MetadataForm -> Validators -> PackageName -> Either UrlFormationError Request Source #

Build the metadata GET request for a package: the URL is {baseUrl}/{encoded-name} with the Accept header for the chosen MetadataForm, Accept-Encoding: gzip, an optional bearer token, and any relayed conditional-GET Validators.

The package path is derived from an already-parsed PackageName, then the scope separator is percent-encoded (@scope/name -> @scope%2Fname). Fails with a UrlFormationError only when the URL cannot be formed (an empty base URL).

artifactRequest :: Text -> Maybe Secret -> PackageName -> Version -> Either UrlFormationError Request Source #

Build the artifact GET request for one version's tarball.

The request is marked non-decompressing (decompress returns False) so the .tgz bytes are streamed through verbatim: a tarball is opaque binary and must reach the client byte-for-byte for its dist.integrity to verify. The artifact URL is the registry-served tarball location, derived like metadataRequest but addressing the version's artifact path. Exposed so the web layer can bracket it for bounded-memory streaming (see the module header).

Fails with a UrlFormationError only when the URL cannot be formed.

artifactRequestByFile :: Text -> Maybe Secret -> PackageName -> Text -> Either UrlFormationError Request Source #

Build the artifact GET request addressing a tarball by its __preserved on-the-wire filename__, at {baseUrl}{encoded-pkg}-/{filename}.

The serve path fetches an artifact by the exact filename the client requested: the authoritative name for the bytes: rather than reconstructing it from (package, version) as artifactRequest does, so a registry whose tarball naming differs from the proxy's own convention still resolves. The filename is taken verbatim (the classifier has already passed it through the component-safety gate), and the package segment is the same scope-percent-encoded path artifactRequest uses. The request is marked non-decompressing for the same reason: a .tgz is opaque binary streamed byte-for-byte so its dist.integrity verifies. Exposed so the web layer can bracket it for bounded-memory streaming.

Fails with a UrlFormationError only when the URL cannot be formed.

artifactRequestByUrl :: Text -> Maybe Secret -> Text -> Either UrlFormationError Request Source #

Build the artifact GET request addressing a tarball at its __authoritative upstream location__: the absolute url the projection preserved from the upstream's dist.tarball: rather than reconstructing it from (base, package, file).

The artifact location is server-chosen data, not a derivable fact: a registry may serve a version's tarball from a different host or a path the npm - convention cannot rebuild. Honouring the preserved location is what lets Écluse front those registries; the URL it fetches is the same one the served packument's dist.integrity is paired with, so the bytes still verify.

The request is marked non-decompressing for the same reason as artifactRequest: a .tgz is opaque binary streamed byte-for-byte. Fails with a UrlFormationError only when the url cannot be parsed into a request.

artifactFileUrl :: Text -> PackageName -> Text -> Either UrlFormationError Text Source #

The artifact (tarball) URL addressing a preserved filename: {baseUrl}{encoded-name}-/{encoded-filename}. The filename is the exact on-the-wire name (not {base}-{version}.tgz rebuilt from the coordinate), so the bytes are fetched by the name the client requested; it is percent-encoded as a single component (encodeComponent) so a once-decoded escape in it cannot reach the upstream raw. Exposed so the serve path can record the public artifact location on a mirror job (the same URL its public fetch targets).

Fails with a UrlFormationError only when the URL cannot be formed.

Shared internals