Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Response headers

Every response — including a 404 — carries a fixed set of default headers. Some vary by request; none of this is configurable globally.

Always present

HeaderValue
access-control-allow-headers*
access-control-allow-methodsGET, POST, PUT, DELETE, OPTIONS
access-control-max-age86400
cache-controlno-store
x-content-type-optionsnosniff

Source: DEFAULT_RESPONSE_HEADERS in crates/apimock-server/src/constant.rs. A date header also appears on every response, but nothing in apimock-server sets it explicitly — it’s added by the underlying HTTP transport layer, not application code.

connection: keep-alive — HTTP/1.1 only

apimock-server sets connection: keep-alive on every response it builds, alongside the headers above — but unlike them, it isn’t always present on the wire. Connection is a hop-by-hop header defined for HTTP/1.1’s own connection-management model; HTTP/2 multiplexes many requests over one connection and has no equivalent concept, so RFC 9113 § 8.2.2 requires an intermediary to strip it, and hyper does so correctly before this project’s own DEFAULT_RESPONSE_HEADERS value ever reaches the wire. This is a transport-layer removal, not something apimock-server’s own code special-cases per protocol.

Verified against a running server, both protocols, same request:

$ curl -s -i --http1.1 http://127.0.0.1:3011/hello.json | grep -i connection
connection: keep-alive

$ curl -s -i -k --http2 https://127.0.0.1:3012/hello.json | grep -i connection
$ # (no output — the header is genuinely absent, not empty)

If you’re asserting on this header in a test against apimock, either force HTTP/1.1 or don’t assert on it at all when HTTP/2 is in play.

CORS — origin and credentials

access-control-allow-origin, vary, and (conditionally) access-control-allow-credentials depend on whether the request looks authenticated — defined as carrying a cookie or authorization header — and, if so, whether the request’s origin is allowed credentialed reflection (RFC 067):

Requestaccess-control-allow-originvaryaccess-control-allow-credentials
No cookie/authorization**(absent)
Credentialed, origin allowedThe request’s own origin value, reflected backOrigintrue
Credentialed, origin not allowed**(absent)

An origin is “allowed” if it’s http://localhost:* or http://127.0.0.1:* (implicitly, always — no configuration needed), or appears exactly in [service].cors_allow_credentials_origins (empty by default). An unlisted, non-loopback origin gets the same response as a request with no credentials at all — the response is still served, but without the headers a browser needs to expose it to a credentialed cross-origin read. See the threat model for why.

Source: default_response_headers / is_likely_authenticated_request / is_credentialed_reflection_allowed in crates/apimock-server/src/response_handler.rs.

OPTIONS requests

Handled before anything else in the request pipeline — before middleware, before rule matching, before parsing the body. Every OPTIONS request gets:

  • Status 204 No Content (not 200).
  • content-length: 0.
  • The full default header set above, including the CORS headers.

Source: handle_options in crates/apimock-server/src/server.rs. See Matching order and precedence for where this sits in the overall request flow.

Custom headers via respond.headers

respond.headers adds or overrides headers on a per-rule basis, uniformly across every respond shape — file_path (JSON, JSON5, CSV, binary, or plain text), text, json, and status-only, with or without a custom status code. An explicit content-type in respond.headers always overrides whatever content-type the response would otherwise derive: from a file’s extension, from text’s text/plain; charset=utf-8 default, or from json’s application/json default.

This section used to carry a per-shape table of exceptions — several respond shapes silently dropped custom headers entirely (RFC 045), and every shape that did honour a custom content-type still had it overwritten by the derived default immediately afterward (RFC 065). Both are now fixed by routing every response-building call site through one shared step (ResponseHandler::with_custom_headers, applied only after the body — and its derived content-type — is already set), so there’s no longer a shape-by-shape exception to list: if you set respond.headers, including content-type, it’s honoured, on every shape.