Repository navigation
feat(server): stream fs.read responses with HTTP Range support - #53088
Merged
Merged
Conversation
kitlangton
force-pushed
the
fs-read-range
branch
from
October 7, 2026 00:22
3913203 to
788fccf
Compare
kitlangton
marked this pull request as ready for review
October 7, 2026 00:39
Build the response from the size, mtime, and stream of the single stat FileSystem.read already performs instead of re-statting through HttpPlatform. Full reads are bounded to the stat size, conditional and If-Range checks compare headers directly, and Cache-Control: no-transform replaces the content-encoding workaround for keeping compression away from Content-Length. This drops the HttpPlatform wiring in routes.
Without Cache-Control, Last-Modified lets browsers and Electron cache file reads heuristically, so the app could show a stale file for minutes after an agent edit. no-cache keeps the validators but forces a conditional request each time.
2 of 6 tasks
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.
Why
GET /api/fs/read/*previously buffered the entire file into memory viafs.readFileand returned a plain200 OKbyte array withoutAccept-Ranges,Content-Range,ETag, orLast-Modifiedheaders. Native media clients such as iOSAVPlayercould not stream or seek video and had to download whole files upfront, while lexical or symlink path escapes surfaced as unhandled500defects instead of404responses.What Changes
FileSystem.readnow resolves and confines the target path, stats the file once, and returns its size, mtime, MIME type, and a lazy disk stream without buffering. Thefs.readhandler builds the response from that one stat: it streams exactlyContent-Lengthbytes (a file that grows mid-read is not over-sent), computes a weakETagandLast-Modifiedfrom the same size and mtime, and answers single-range and conditional requests by comparing headers.Every
200,206, and304carriesCache-Control: no-cache, no-transform.no-cachekeeps browsers and Electron from heuristically caching onLast-Modified, so the app revalidates and sees agent edits immediately;no-transformkeeps the compression middleware from re-encoding the body, soContent-Lengthand byte ranges always describe the file on disk.GET /api/fs/read/<path>200 OK(streamed)Accept-Ranges: bytes,Content-Length,Content-Type, weakETag,Last-Modified,Cache-ControlHEAD /api/fs/read/<path>200 OK(no body, file not opened)GETRange: bytes=start-end/bytes=start-/bytes=-suffix206 Partial Content(streamed slice)Content-Range: bytes start-end/size,Content-Length(end - start + 1), plus the200headersRange(start >= size,start > end,bytes=-0, or empty file)416 Range Not SatisfiableContent-Range: bytes */size,Accept-Ranges: bytesbytes=0-1, 3-4) or malformedRange200 OK(full stream)200 OKheadersIf-None-MatchorIf-Modified-Since304 Not ModifiedETag,Last-Modified,Cache-ControlRangewith matchingIf-Range(ETagorLast-Modified)206 Partial ContentRangewith mismatchedIf-Range200 OK(full stream)200 OKheaders.., symlink outside location) or non-file404 Not FoundFileNotFoundErrorflowchart TD Req["GET / HEAD /api/fs/read/*"] --> Resolve["FileSystem.read: confine path + one stat"] Resolve -- "missing / escape / non-file" --> E404["404 FileNotFoundError"] Resolve -- "regular file (size, mtime, stream)" --> Cond{"If-None-Match / If-Modified-Since match?"} Cond -- "yes" --> E304["304 Not Modified"] Cond -- "no" --> IsGetRange{"GET with Range & matching If-Range?"} IsGetRange -- "no (or HEAD / If-Range mismatch)" --> S200["200 OK: stream(offset 0, size bytes)"] IsGetRange -- "yes" --> Parse{"parseRange(range, size)"} Parse -- "malformed / multi-range" --> S200 Parse -- "unsatisfiable" --> E416["416 Range Not Satisfiable (bytes */size)"] Parse -- "{ start, end }" --> S206["206 Partial Content: stream(start, end - start + 1 bytes)"]Scope
This PR owns streaming and single-range HTTP
Range/ conditional request support for confined location-relativeGET /api/fs/read/*(and router-dispatchedHEAD) requests, plus returning404 FileNotFoundErrorwhen a path escapes the location or targets a non-file. Absolute-path reads, multipart byte ranges, and signed URLs remain out of scope.Verification
packages/core/test/location-filesystem.test.ts: 10 pass, 0 fail.packages/serverfull suite: 70 pass, 5 skip, 0 fail.test/fs.test.tscovers everyRangeform,416, multi-range and malformed fallback,304,If-Range,HEAD,Cache-Control, path confinement, a gzip-accepting request for a compressible text file that keeps its exactContent-Length, 4 MiB byte-for-byte full and partial reads, and thatHEADleaves no open file handle while an aborted download closes its handle (checked through/proc/self/fdon Linux andlsofon macOS).probe:workerd, both generated-output checks,bun run typecheck(36/36), andbun run lintpass.curloutput against a 1,000-bytesample.mp4: