Skip to content

HTTP interface

The contract for iiiris's HTTP surface: the routes it serves, the middleware every request passes through, the status codes it returns, and the caching/CORS/conditional-GET semantics on every response. This is the transport-layer contract — distinct from the IIIF parameter grammar (iiif-compliance.md), the decode pipeline (image-decode.md), and the auth service shapes (auth.md).

What it does

A stdlib net/http.ServeMux routes requests through a fixed middleware chain to version-specific IIIF handlers (plus health, readiness, and admin endpoints). The IIIF handlers reconstruct the request path and delegate grammar parsing to iiif/v{2,3}.ParsePath, then run the redirect → auth → cache → render flow. Every IIIF response is emitted through http.ServeContent so conditional GETs, ranges, and ETags work uniformly.

Surface

Routes

Route Handler
GET /{$} Landing page (HTML).
GET /health Liveness — always 200 while the process is up.
GET /ready Readiness — runs every source's Healthchecker in parallel; 200 if all pass, 503 if any fails.
GET /iiif/3/{rest...} · GET /iiif/2/{rest...} Catch-all image + info.json requests; the handler splits {rest} into identifier + IIIF tail and calls the v3/v2 parser.
/admin/ Operator UI (HTTP Basic). Returns 404 when admin credentials are unset (the subsystem is off, not merely locked).
/iiif/auth/... The IIIF Auth 2.0/1.0 services — mounted only when auth profiles are configured (auth.md).

Middleware chain (outer → inner)

collapseSlashes → request counter + per-route stats → request log → optional per-IP concurrency cap → panic recoverer → handler.

  • collapseSlashes folds runs of / in the path before routing, so //iiif///3/... routes identically.
  • Per-route stats feed the /admin/ dashboard (count + latency percentiles per classified route).
  • Per-IP cap is wired only when image.max_concurrent_per_ip > 0; it sits inside count+log (so a rejection is counted and logged) and outside the recoverer.
  • Recoverer turns a handler panic into a 500 (logged), never a dropped connection.

Contracts

Status codes

Condition Status
Path-syntax or parameter-value rejection (bad region/size/rotation/quality, region origin outside source, upscale without ^) 400
image.max_pixel_area (source) or max_output_area (output) guard exceeded 400
Auth denied (hard deny) 401, or as supplied by the Authorizer
Identifier not found in the source 404
Per-IP concurrency cap exceeded 429
Decode / encode / source-I/O failure 500 (also a recovered panic)
A source healthcheck fails (on /ready) 503
Hook.Redirect returns a URL, or an auth Decision.Redirect 303
If-None-Match / If-Modified-Since matches the served entry 304

Status mapping is single-sourced in the handlers; the parameter-level detail behind a 400 is in iiif-compliance.md.

Response semantics (every /iiif/... response)

  • Cache + conditional GET. Each response carries Cache-Control and an ETag, and is written via http.ServeContent, so If-None-Match / If-Modified-Since yield 304.
  • CORS. Access-Control-Allow-Origin: * on every IIIF response.
  • Content negotiation. info.json honours Accept: application/ld+json (vs application/json), with the matching JSON-LD Content-Type.
  • Redirect precedence. Hook.Redirect is consulted at the top of the image and info handlers — before auth and parameter parsing — so a non-empty URL fires a 303 immediately (content relocation), distinct from an auth-conditional 303.

Test coverage

In internal/server/ (integration tests against an httptest server):

  • collapse_slashes_test.go — slash folding before routing.
  • content_neg_test.goinfo.json Accept negotiation + JSON-LD type.
  • cache_integration_test.goETag / conditional-GET 304.
  • iplimit_test.go — the per-IP cap returns 429.
  • hook_redirect_test.goHook.Redirect fires a 303 before parsing/auth.
  • stats_test.go — per-route classification for the dashboard.
  • server_integration_test.go — end-to-end routing + status mapping.

Out of scope

  • IIIF parameter grammar + supported features — region/size/rotation/ quality/format coverage and the info.json field set live in iiif-compliance.md; the grammar itself is in iiif/v{2,3}.ParsePath.
  • The decode/render pipelineimage-decode.md.
  • Auth service request/response shapesauth.md.
  • Admin UI internals (pages, SSE, config editor) — admin.md.