Skip to content

fix(serve-static): compare If-Modified-Since at whole-second precision#1394

Open
francisjohnjohnston-web wants to merge 1 commit into
h3js:mainfrom
francisjohnjohnston-web:fix/static-if-modified-since-subsecond
Open

fix(serve-static): compare If-Modified-Since at whole-second precision#1394
francisjohnjohnston-web wants to merge 1 commit into
h3js:mainfrom
francisjohnjohnston-web:fix/static-if-modified-since-subsecond

Conversation

@francisjohnjohnston-web

@francisjohnjohnston-web francisjohnjohnston-web commented May 24, 2026

Copy link
Copy Markdown

What

serveStatic now compares the If-Modified-Since request value against the file mtime truncated to whole-second precision, matching the granularity of the Last-Modified response header it emits.

Why

Last-Modified is serialized via Date.toUTCString(), which has whole-second granularity. The conditional-request check, however, compared the client's If-Modified-Since against the full-precision mtime. When a file's mtime carries sub-second milliseconds, a conditional request echoing back the server's own Last-Modified value never satisfied ifModifiedSince >= mtime, so the server returned a full 200 instead of 304 — silently defeating conditional-request caching for any asset whose mtime is not exactly on a second boundary (the common case on many filesystems).

Safety

Behaviour-preserving for whole-second mtimes. The only change is that sub-second-mtime assets now correctly return 304 on a matching conditional request. This mirrors the existing whole-second handling already used in handleCacheHeaders (cache.ts), so the two paths are now consistent.

Testing

Added "Handles cache (if-modified-since) for sub-second mtime" to test/static.test.ts (both web and node targets). It fails on the current code (expected 200 to deeply equal 304) and passes after the fix. Full test suite green (1154 passed / 14 skipped), typecheck clean, oxlint clean on changed files.

Impact

Restores correct 304 Not Modified responses for conditional requests on static assets with sub-second mtimes — fixing avoidable full-body re-transfers.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed timestamp precision handling in HTTP conditional requests to ensure proper cache validation. Timestamps are now normalized to second-level granularity, preventing mismatches when clients check if cached resources remain valid.

Review Change Stack

The `last-modified` header is emitted via `toUTCString()`, which truncates
to whole seconds (HTTP-date granularity). When `meta.mtime` carries sub-second
milliseconds, the `if-modified-since` comparison used the full-precision mtime,
so a client echoing back our own `last-modified` value never satisfied
`ifModifiedSince >= mtime` and received a full 200 response instead of 304 on
every conditional request. Truncate the mtime to whole seconds before
comparing, matching `handleCacheHeaders`.
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65f2bcda-224f-4f4b-b4fa-97403e3af7ba

📥 Commits

Reviewing files that changed from the base of the PR and between 84244b4 and 9932a77.

📒 Files selected for processing (2)
  • src/utils/static.ts
  • test/static.test.ts

📝 Walkthrough

Walkthrough

The serveStatic function now truncates mtime to whole-second precision by zeroing milliseconds before evaluating conditional GET requests and setting response headers, preventing client-side matching failures due to sub-second datetime mismatches. A test case validates the behavior when mtime contains milliseconds.

Changes

HTTP Date Precision Fix

Layer / File(s) Summary
HTTP date precision for conditional GET
src/utils/static.ts, test/static.test.ts
The serveStatic function truncates mtimeDate milliseconds to ensure if-modified-since comparison and last-modified emission use whole-second HTTP-date granularity; test validates that a 304 response is returned when the server-truncated last-modified matches the request header despite sub-second mtime values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • h3js/h3#1262: Normalizes mtime to whole-second precision in handleCacheHeaders to fix the same If-Modified-Since conditional GET mismatch with sub-second precision.

Suggested reviewers

  • pi0

Poem

🐰 A rabbit's watch ticks true and fast,
But HTTP clocks with seconds mast,
We trim the fractions, dust each millisecond away,
So browsers and servers agree on the day! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: fixing If-Modified-Since comparison to use whole-second precision in the serve-static functionality.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

1 participant