Skip to content

Add ETag support and fix conditional request handling in ServeDir#691

Open
jlizen wants to merge 1 commit into
tower-rs:mainfrom
jlizen:feat/etag
Open

Add ETag support and fix conditional request handling in ServeDir#691
jlizen wants to merge 1 commit into
tower-rs:mainfrom
jlizen:feat/etag

Conversation

@jlizen
Copy link
Copy Markdown
Member

@jlizen jlizen commented May 23, 2026

Closes #324, closes #411

Motivation

ServeDir only supports time-based conditional requests today. This causes real problems: if you roll back a deploy, files get an older mtime and clients keep getting 304s for stale content. HTTP dates also have 1-second granularity, so rapid redeploys within the same second are invisible to If-Modified-Since.

Solution

Generate a strong ETag from file metadata (mtime with nanosecond precision + file size) and implement the full RFC 9110 §13.2.2 precondition evaluation order:

  1. If-Match → 412 (enables safe range request resumption)
  2. If-Unmodified-Since (when If-Match absent) → 412
  3. If-None-Match → 304
  4. If-Modified-Since (when If-None-Match absent) → 304

The ETag format is "<hex_secs>.<hex_nanos>-<hex_size>". Similar to what nginx does (<mtime>-<size>) but with nanosecond precision. No content hashing, no extra I/O; we already call metadata() on every request.

304 responses now include ETag and Last-Modified headers per RFC 9110 §15.4.5 (previously they were bare, which was a spec violation).

No new public API surface. All new types are pub(super). The ETag is always-on, matching the existing Last-Modified behavior.

Testing

Covers: ETag presence on 200, If-None-Match producing 304 (exact match, wildcard, weak prefix), If-Match producing 412 (non-matching, weak prefix rejected by strong comparison), RFC precedence (ETag conditions override time-based when both present), and validator headers on 304 responses.

Comment thread tower-http/src/services/fs/serve_dir/headers.rs
Comment thread tower-http/src/services/fs/serve_dir/open_file.rs
@jlizen jlizen force-pushed the feat/etag branch 5 times, most recently from 08240fb to 333503f Compare May 28, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ServeDir lacks the Last-Modified response header Hash-based etag layer

2 participants