Every static file is served with Last-Modified set to SystemTime::now() rather than the file's real modification time. The header therefore changes on every request and carries no cache-validation information. There is also no ETag or Cache-Control, and the local-filesystem and database-filesystem "modified since" comparisons disagree (> vs >=), so conditional-request behavior differs by backend.
Reproduction (in-memory SQLite, no other setup)
mkdir /tmp/sp && cd /tmp/sp
printf 'hello static world\n' > asset.txt
touch -t 202001010000 asset.txt # file mtime is 2020-01-01
SQLPAGE_DATABASE_URL='sqlite::memory:' sqlpage &
curl -sI http://localhost:8080/asset.txt | grep -i last-modified
sleep 1
curl -sI http://localhost:8080/asset.txt | grep -i last-modified
Observed (SQLPage v0.44.1), despite the file mtime being 2020-01-01:
last-modified: Fri, 12 Jun 2026 12:02:22 GMT
last-modified: Fri, 12 Jun 2026 12:02:23 GMT
The value is "now" and advances by one second between the two requests. A client doing If-Modified-Since revalidation can never get a meaningful answer, and caches cannot key on it.
Root cause
Last-Modified is built from SystemTime::now() when serving a file: src/webserver/http.rs#L469.
The comparison contract also differs between backends: the local filesystem uses strict > (src/filesystem.rs#L254) while the database filesystem query uses >= (src/filesystem.rs#L303), so a file modified in the same second as the last check is treated differently depending on where it is stored.
Suggested fix
Set Last-Modified from the actual source mtime (the value is already read for cache validation in modified_since), or omit the header when the mtime is unknown. Pick one comparison contract (> or >=) and use it for both the local and DB filesystems. Optionally add ETag / Cache-Control for static assets while here.
Risk and mitigation
Low. Correct Last-Modified strictly improves caching and revalidation. The mtime is available for both local files (fs::metadata) and DB-backed files (the last_modified column on sqlpage_files), so no information is missing.
Split out from #1249 (item 3).
Every static file is served with
Last-Modifiedset toSystemTime::now()rather than the file's real modification time. The header therefore changes on every request and carries no cache-validation information. There is also noETagorCache-Control, and the local-filesystem and database-filesystem "modified since" comparisons disagree (>vs>=), so conditional-request behavior differs by backend.Reproduction (in-memory SQLite, no other setup)
Observed (SQLPage v0.44.1), despite the file mtime being 2020-01-01:
The value is "now" and advances by one second between the two requests. A client doing
If-Modified-Sincerevalidation can never get a meaningful answer, and caches cannot key on it.Root cause
Last-Modifiedis built fromSystemTime::now()when serving a file: src/webserver/http.rs#L469.The comparison contract also differs between backends: the local filesystem uses strict
>(src/filesystem.rs#L254) while the database filesystem query uses>=(src/filesystem.rs#L303), so a file modified in the same second as the last check is treated differently depending on where it is stored.Suggested fix
Set
Last-Modifiedfrom the actual source mtime (the value is already read for cache validation inmodified_since), or omit the header when the mtime is unknown. Pick one comparison contract (>or>=) and use it for both the local and DB filesystems. Optionally addETag/Cache-Controlfor static assets while here.Risk and mitigation
Low. Correct
Last-Modifiedstrictly improves caching and revalidation. The mtime is available for both local files (fs::metadata) and DB-backed files (thelast_modifiedcolumn onsqlpage_files), so no information is missing.Split out from #1249 (item 3).