Serving — design
Class:
design· Function:serve(usesregistryfor dispatch,storagefor extract; gated byauthon writes). The whole-system view isdesign.md.
Two front doors, one backend
handle_http2_request(suburl, body)(server/lib/src/exposed/http.rs) — the native-protocol door. A stockcargo/pip/npm/docker/helmclient hits the HTTP gateway (port 8081); the backend parses the sub-URL into a protocol verb and returns(status, headers, bytes). This is the door the Nexus/Artifactory benchmark drives — package delivery is always measured over the real protocol.- gRPC (
server/lib/src/grpc.rs, port 50051) — the holger-native door, three services inproto/holger.proto(packageholger.v1).
Both bottom out in backend.fetch(id) → ZnippyReader::extract_file(path): random-access, blake3-verified, no full-archive decompress.
The gRPC contract (RepositoryService)
The identity threading every RPC is ArtifactId { namespace, name, version }.
| RPC | request → response | notes |
FetchArtifact |
{repository, id} → {data, content_type, size_bytes} |
whole blob in one message; NOT_FOUND if absent |
ListArtifacts |
{repository, name_filter, limit, page_token} → {artifacts[], next_page_token} |
name_filter optional substring; token pagination |
PutArtifact |
{repository, id, data} → {success, message} |
PERMISSION_DENIED if !is_writable() (and RBAC gate) |
StreamArtifact |
{repository, id} → stream ArtifactChunk { data } |
same request as FetchArtifact; 64 KB chunks for a progress bar |
ArtifactEntry { id, size_bytes, content_type, published_unix_ms?, sha256? } is one ListArtifacts row — the shape a tree/list view consumes.
The last two are proto3 optional (explicit field presence), and the word is load-bearing. Absent means the serving backend does not know, which is a different thing from 0 and from "" — a zero timestamp sorts as 1970 and an empty digest compares equal to another empty digest, so a proto3 scalar default would be a fabricated answer from a backend that has none. Presence makes "unknown" a state rather than a value, which is what lets an old peer and a new peer talk in either direction without either of them inventing one. (Same reasoning as StartMigrationRequest.skip_digest_verification, which is inverted on the wire so the proto3 zero is the safe one; here the safe state says its own name.)
Backends differ in what they can answer without reading artifact bytes, and that is a cost, not a preference — a listing that reads and hashes the store is a worse defect than a missing column. holger_traits::ListingProvenance, derived from ArtifactFormat::home() so no backend can be left out of the answer, records which is which:
| backend | time | digest | why |
every znippy-* |
– | – | ZnippyReader is list_files/file_size/contains/extract_file: no mtime, no per-entry digest |
raw |
yes | – | mtime is free from the walk's own stat; a digest would mean reading every blob |
file-rust |
yes | when memoized | the sparse-index path caches (len, mtime) → sha256; the listing peeks and never hashes |
file-oci |
yes | yes | the listed artifact IS the manifest, OCI is content-addressed, and the read is capped |
Two further services complete the proto: ArchiveService (seal / verify / manifest — see the promotion reasoning) and AdminService. FetchArtifact / PutArtifact / StreamArtifact are wired end-to-end through backend.fetch/backend.put; the reference tonic client is RemoteHolger (server/lib/src/object.rs).
Proto codegen — no protoc, no shell-out
The proto Rust is generated by pure-Rust protox via cargo xtask gen-proto (xtask/src/gen_proto.rs): protox::compile → FileDescriptorSet → tonic_build … .skip_protoc_run().compile_fds(fds) → committed server/lib/src/generated/holger.v1.rs, which src/grpc.rs include!s. build.rs is only a rerun-if-changed hint — a normal cargo build runs no subprocess and needs no protoc. The public module path stays holger_proto::*. Guarded by server/lib/tests/unit_tests.rs::generated_proto_types_roundtrip.
Endpoints & health
ExposedEndpoint (RON) names a listen surface and carries ron_max_body_bytes (the shared body/ingest cap, default 1 GiB). The deployment exposes /healthz (liveness) and /readyz (readiness). One exposed endpoint fronts many repos via its FastRoutes table; a repo is reachable at both doors simultaneously.