Skip to content

fix: support HTTP Range requests on virtual files (#9460)#9473

Merged
mscolnick merged 4 commits into
mainfrom
ms/fix/issue-9460
May 11, 2026
Merged

fix: support HTTP Range requests on virtual files (#9460)#9473
mscolnick merged 4 commits into
mainfrom
ms/fix/issue-9460

Conversation

@mscolnick
Copy link
Copy Markdown
Contributor

@mscolnick mscolnick commented May 7, 2026

Safari's <audio>/<video> elements require a 206 Partial Content response
to their initial range probe. Without it, mo.audio renders a disabled
player. Chrome and Firefox are more forgiving, so the bug was Safari-only.

Parse Range headers in the /@file/ endpoint and return 206 with
Content-Range/Content-Length, advertise Accept-Ranges: bytes on every
response, and 416 for invalid ranges. Full responses still omit
Content-Length to preserve the h11 fix from #8928. Adds an optional start
offset to the chunked virtual-file readers so partial reads don't
allocate the full buffer.

Closes #9460

Safari's <audio>/<video> elements require a 206 Partial Content response
to their initial range probe. Without it, mo.audio renders a disabled
player. Chrome and Firefox are more forgiving, so the bug was Safari-only.

Parse Range headers in the /@file/ endpoint and return 206 with
Content-Range/Content-Length, advertise Accept-Ranges: bytes on every
response, and 416 for invalid ranges. Full responses still omit
Content-Length to preserve the h11 fix from #8928. Adds an optional start
offset to the chunked virtual-file readers so partial reads don't
allocate the full buffer.
Copilot AI review requested due to automatic review settings May 7, 2026 18:45
@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment May 7, 2026 6:59pm

Request Review

Numpy → mo.audio routes through the virtual file endpoint, which is the
code path the Range support fix targets. Open in Safari to verify the
player is enabled.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Architecture diagram
sequenceDiagram
    participant SA as Safari Browser
    participant AS as Any Browser (Chrome/Firefox)
    participant FE as FastAPI Endpoint
    participant PR as _parse_range_header()
    participant VFC as read_virtual_file_chunked()
    participant VSM as VirtualFileStorageManager
    participant ST as Storage Backend

    Note over SA,ST: HTTP Range Request Flow for Virtual Files

    SA->>FE: GET /@file/{size}-{filename} (Range: bytes=0-99)
    FE->>FE: Parse filename_and_length
    FE->>FE: total_size = int(byte_length_str)
    FE->>FE: Guess mimetype from filename
    FE->>FE: Build headers with Accept-Ranges: bytes
    FE->>PR: _parse_range_header("bytes=0-99", total_size)
    alt Valid range
        PR-->>FE: (start=0, end=99)
        FE->>VFC: read_virtual_file_chunked(filename, 100, start=0)
        VFC->>VSM: read_chunked(filename, 100, start=0)
        VSM->>ST: read_chunked(filename, 100, 8192, start=0)
        ST-->>VSM: Iterator[bytes]
        VSM-->>VFC: Iterator[bytes]
        VFC-->>FE: Iterator[bytes]
        FE->>FE: Build Content-Range: bytes 0-99/{total_size}
        FE->>FE: Build Content-Length: 100
        FE-->>SA: 206 Partial Content + chunked bytes
    else Invalid/unsatisfiable range
        PR-->>FE: None
        FE-->>SA: 416 Range Not Satisfiable + Content-Range: bytes */{total_size}
    end

    AS->>FE: GET /@file/{size}-{filename} (no Range header)
    FE->>FE: Parse filename_and_length
    FE->>FE: total_size = int(byte_length_str)
    FE->>FE: Guess mimetype
    FE->>FE: Build headers with Accept-Ranges: bytes
    FE->>VFC: read_virtual_file_chunked(filename, total_size)
    VFC->>VSM: read_chunked(filename, total_size)
    VSM->>ST: read_chunked(filename, total_size, 8192)
    ST-->>VSM: Iterator[bytes]
    VSM-->>VFC: Iterator[bytes]
    VFC-->>FE: Iterator[bytes]
    Note over FE: Content-Length NOT set (chunked transfer encoding)
    FE-->>AS: 200 OK + Accept-Ranges: bytes

    alt Download request (?download=1)
        FE->>FE: Add Content-Disposition: attachment
    end

    SA->>FE: GET /@file/{size}-{filename} (Range: bytes=-50)
    FE->>PR: _parse_range_header("bytes=-50", total_size)
    PR->>PR: suffix=50, start=total_size-50, end=total_size-1
    PR-->>FE: (start, end)
    FE->>VFC: read_virtual_file_chunked(filename, 50, start=start)
    VFC->>VSM: read_chunked(filename, 50, start=start)
    VSM->>ST: read_chunked(filename, 50, 8192, start=start)
    ST-->>VSM: Iterator[bytes]
    VSM-->>VFC: Iterator[bytes]
    VFC-->>FE: Iterator[bytes]
    FE-->>SA: 206 Partial Content + last 50 bytes

    SA->>FE: GET /@file/{size}-{filename} (Range: bytes={size}-)
    FE->>PR: _parse_range_header(f"bytes={size}-", total_size)
    PR->>PR: start=size, end=total_size-1
    PR->>PR: start >= total_size
    PR-->>FE: None
    FE-->>SA: 416 Range Not Satisfiable + Content-Range: bytes */{total_size}
Loading

Tip: cubic could auto-approve low-risk PRs like this, if it thinks it's safe to merge. Learn more

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the @file virtual-file endpoint to support HTTP Range requests (returning 206 Partial Content) to improve Safari <audio>/<video> playback compatibility, and extends the virtual-file storage readers to support streaming from a byte offset to avoid unnecessary allocations.

Changes:

  • Add Range header parsing to GET /@file/{filename_and_length} and return 206 with Content-Range/Content-Length, 416 for unsatisfiable ranges, and Accept-Ranges: bytes on responses.
  • Extend virtual-file chunked readers (read_virtual_file_chunked and storage backends) with a start offset for partial reads.
  • Add endpoint-level tests covering bounded, open-ended, suffix, and out-of-range range requests.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

File Description
tests/_server/api/endpoints/test_assets.py Adds regression tests for Range requests against the virtual-file endpoint.
marimo/_server/api/endpoints/assets.py Implements Range handling for /@file/ responses and introduces a single-range parser.
marimo/_runtime/virtual_file/virtual_file.py Adds a start offset option to the chunked virtual-file reader wrapper.
marimo/_runtime/virtual_file/storage.py Adds start offset support to storage backends’ read_chunked() implementations.

Comment thread marimo/_server/api/endpoints/assets.py Outdated
Comment thread marimo/_server/api/endpoints/assets.py
Comment thread marimo/_runtime/virtual_file/storage.py
Comment thread marimo/_runtime/virtual_file/storage.py
Per RFC 9110 the range unit token is case-insensitive, so accept
'Bytes='/'BYTES=' too. Adds storage-level tests for the new ``start``
offset on the chunked readers.
@mscolnick mscolnick added the bug Something isn't working label May 7, 2026
Copy link
Copy Markdown
Member

@kirangadhave kirangadhave left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 tested in Safari

@mscolnick mscolnick merged commit 26ee423 into main May 11, 2026
46 checks passed
@mscolnick mscolnick deleted the ms/fix/issue-9460 branch May 11, 2026 19:02
@github-actions
Copy link
Copy Markdown

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.6-dev30

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mo.audio doesn't work in Safari

3 participants