fix: rewrite static asset middleware and filesystem provider - #29
Merged
Conversation
Address the static asset middleware issues reported against main@c545db2: - Match the mount as a path prefix on a segment boundary and never slice the URL before the mount relationship is validated, so unrelated or shorter routes (e.g. GET /api, GET /a) fall through instead of dying on an out-of-range slice that aborted the process. - Resolve asset ids under a virtual root so ".." segments are normalized away and can never escape the configured root. - Probe index files with correct path joining, so the mount root and directories serve their index.html (a trailing "/" no longer 404s). - get_meta now performs real filesystem checks and reports actual size/mtime/type metadata instead of returning Some for every candidate; asset ids that are directories are skipped so probing continues. - Built-in MIME table: responses now carry a real Content-Type, plus Content-Length and a weak ETag (If-None-Match yields 304). - Native backend uses moonbitlang/async/fs for all file I/O instead of blocking synchronous calls inside async request handlers; the JS backend keeps moonbitlang/x/fs (no async fs exists for JS yet) and documents the blocking behavior and missing metadata. - Distinguish not-found from other I/O failures: ENOENT/ENOTDIR map to 404 (or fallthrough), anything else surfaces as 500. - Preserve fallthrough: providers can opt in, letting later routes and other methods through; the "/" example uses it to keep its routes. - ServeStaticProvider::get_meta/get_contents are now async; the provider gains optional fallthrough/index_names configuration. Adds black-box tests driving dispatch_http end to end: mounted paths, unrelated and short routes, directory indexes, MIME types, HEAD, conditional requests, fallthrough, and root confinement. Verified live against a native server with the issue's original reproduction table. Co-Authored-By: Claude <noreply@anthropic.com>
oboard
force-pushed
the
worktree-fix-static-assets-issue
branch
from
August 21, 2026 09:55
3c55b4f to
392d78f
Compare
Replace the hand-rolled MIME table in the static file provider with the oboard/mimetype@0.2.0 database. mime_type_of keeps its signature and still appends charset=utf-8 for text/* types, so served Content-Type values are unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the static asset middleware issues reported against
main@c545db2: unrelated routes dying on an out-of-range slice (PanicErrorkilling the process), directory indexes never served, missingContent-Type, a filesystem provider that returnedSomemetadata for every candidate without checking the filesystem, synchronousmoonbitlang/x/fsI/O inside async request handlers, and all I/O errors collapsing to 404.Changes
Middleware (
static.mbt)/assetsmatches/assetsand/assets/..., not/assetsx), and the URL is only sliced after that relationship is validated — shorter, unrelated URLs fall through to the router untouched instead of panicking...segments can never escape the configured root (the provider only ever sees confined, absolute virtual ids)./sub+index.html→/sub/index.html) and no longer mutates the provider's index-name array.Content-Lengthis emitted for empty files too.Provider contract (
ServeStaticProvider)get_meta/get_contentsare nowasync; the contract documents thatget_metamust returnNoneonly for absent/unservable candidates and raise real I/O failures (surfaced as 500, not a misleading 404).Filesystem provider (
static_file)get_metaperforms real filesystem checks and reports actual size/mtime/type metadata; directories are skipped so index probing continues.mime_type_of) — responses carry a realContent-Type, plusContent-Lengthand a weakETag(If-None-Match→ 304).moonbitlang/async/fs(new internalnativefshelper package) instead of blocking the event loop. The JS backend keepsmoonbitlang/x/fs— no async fs exists for JS yet — with the blocking behavior and missing size/mtime metadata documented.fallthrough/index_namesconfiguration;examples/static_assets(mounted at/) opts intofallthrough=trueso its explicit routes keep working.Tests
static_wbtest.mbt: middleware-level tests with an in-memory provider — mounted paths, unrelated/short routes, segment boundary, indexes, MIME, HEAD, ETag/304, fallthrough, root confinement.static_file/static_file_blackbox_test.mbt(+ native-onlystatic_file_metadata_test.mbt): black-box tests over a real fixture tree throughdispatch_http— the issue's full reproduction table.Verification
moon test --target native: 75/75 pass;moon test --target js: 80/80 pass;moon checkclean on both targets.GET /assetsindex fixtureGET /assets/index fixtureGET /assets/app.txttext/plain,Content-Length,ETagGET /apiapi okGET /aGET /assets/../secret.txtPOST /assets/app.txtAllow: GET, HEADIf-None-Match🤖 Generated with Claude Code