Skip to content

feat(fetch): streaming / non-blocking HTTP fetch API#36

Merged
niklabh merged 1 commit into
mainfrom
feat/streaming-fetch
Apr 17, 2026
Merged

feat(fetch): streaming / non-blocking HTTP fetch API#36
niklabh merged 1 commit into
mainfrom
feat/streaming-fetch

Conversation

@niklabh
Copy link
Copy Markdown
Owner

@niklabh niklabh commented Apr 17, 2026

The existing api_fetch blocks the guest until the whole response body is downloaded, which freezes the frame loop and makes LLM token streams, chunked feeds, and progressive downloads impossible. This adds a second, handle-based fetch API that returns immediately and streams body chunks into the guest as they arrive.

Host (oxide-browser/src/fetch.rs — new module)

  • FetchState: lazily-initialised tokio Runtime + HashMap<u32, Arc>.
  • Each api_fetch_begin spawns an async task that drives reqwest's Client::send() and then bytes_stream(), pushing chunks into a Mutex<VecDeque<Vec>>.
  • AtomicU32 ready-state and HTTP status; AtomicBool abort flag checked between chunks so cancellation is prompt.

New host functions (all in the "oxide" import module):

  • api_fetch_begin(method, url, content_type, body) -> u32 handle
  • api_fetch_state(id) -> u32 0 PENDING / 1 STREAMING / 2 DONE / 3 ERROR / 4 ABORTED
  • api_fetch_status(id) -> u32 (HTTP status, 0 until headers arrive)
  • api_fetch_recv(id, ptr, cap) -> i64

    =0 bytes written, -1 pending, -2 EOF, -3 error, -4 unknown handle.
    Chunks larger than cap are split and the remainder is re-queued at
    the front so no bytes are dropped.

  • api_fetch_error(id, ptr, cap) -> i32
  • api_fetch_abort(id), api_fetch_remove(id)

The existing synchronous api_fetch is untouched for back-compat.

HostState (oxide-browser/src/capabilities.rs)

  • New fetch: Arc<Mutex<Optioncrate::fetch::FetchState>> field, initialised to None and registered via crate::fetch::register_fetch_functions at the bottom of register_host_functions.

SDK (oxide-sdk/src/lib.rs)

  • extern "C" imports for all seven api_fetch_* functions.
  • Public wrappers: fetch_begin, fetch_begin_get, fetch_state, fetch_status, fetch_recv (returns FetchChunk::{Data, Pending, End, Error}), fetch_recv_into (zero-alloc), fetch_error, fetch_abort, fetch_remove.
  • FETCH_* ready-state constants and a new "HTTP (streaming)" row in the module-level API table.

Example

  • examples/stream-fetch-demo: progressive UI with state badge, HTTP status, byte / chunk counters, live body preview, and Abort / Restart buttons. Defaults to httpbin.org/drip so the streaming behaviour is observable by default. Added to the workspace members list.

Roadmap

  • Tick "Non-blocking fetch with callback/promise-style API" and "Streaming response bodies (chunked transfer)" in Phase 4 Async I/O.

Verification

  • cargo fmt --all
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace
  • cargo build -p oxide-browser
  • cargo build --target wasm32-unknown-unknown --release -p stream-fetch-demo

Made-with: Cursor

Summary by CodeRabbit

  • New Features

    • Streaming HTTP fetch API enabling non-blocking, chunked response handling with support for aborting and monitoring request progress in real-time.
    • Added interactive demo showcasing streaming fetch functionality with live UI updates.
  • Documentation

    • Updated roadmap marking streaming responses and non-blocking fetch as completed.

The existing api_fetch blocks the guest until the whole response body is
downloaded, which freezes the frame loop and makes LLM token streams,
chunked feeds, and progressive downloads impossible. This adds a second,
handle-based fetch API that returns immediately and streams body chunks
into the guest as they arrive.

Host (oxide-browser/src/fetch.rs — new module)

* FetchState: lazily-initialised tokio Runtime + HashMap<u32, Arc<FetchInner>>.
* Each api_fetch_begin spawns an async task that drives reqwest's
  Client::send() and then bytes_stream(), pushing chunks into a
  Mutex<VecDeque<Vec<u8>>>.
* AtomicU32 ready-state and HTTP status; AtomicBool abort flag checked
  between chunks so cancellation is prompt.

New host functions (all in the "oxide" import module):

* api_fetch_begin(method, url, content_type, body) -> u32 handle
* api_fetch_state(id) -> u32
    0 PENDING / 1 STREAMING / 2 DONE / 3 ERROR / 4 ABORTED
* api_fetch_status(id) -> u32 (HTTP status, 0 until headers arrive)
* api_fetch_recv(id, ptr, cap) -> i64
    >=0 bytes written, -1 pending, -2 EOF, -3 error, -4 unknown handle.
    Chunks larger than cap are split and the remainder is re-queued at
    the front so no bytes are dropped.
* api_fetch_error(id, ptr, cap) -> i32
* api_fetch_abort(id), api_fetch_remove(id)

The existing synchronous api_fetch is untouched for back-compat.

HostState (oxide-browser/src/capabilities.rs)

* New fetch: Arc<Mutex<Option<crate::fetch::FetchState>>> field,
  initialised to None and registered via
  crate::fetch::register_fetch_functions at the bottom of
  register_host_functions.

SDK (oxide-sdk/src/lib.rs)

* extern "C" imports for all seven api_fetch_* functions.
* Public wrappers: fetch_begin, fetch_begin_get, fetch_state,
  fetch_status, fetch_recv (returns FetchChunk::{Data, Pending, End,
  Error}), fetch_recv_into (zero-alloc), fetch_error, fetch_abort,
  fetch_remove.
* FETCH_* ready-state constants and a new "HTTP (streaming)" row in the
  module-level API table.

Example

* examples/stream-fetch-demo: progressive UI with state badge, HTTP
  status, byte / chunk counters, live body preview, and Abort / Restart
  buttons. Defaults to httpbin.org/drip so the streaming behaviour is
  observable by default. Added to the workspace members list.

Roadmap

* Tick "Non-blocking fetch with callback/promise-style API" and
  "Streaming response bodies (chunked transfer)" in Phase 4 Async I/O.

Verification

* cargo fmt --all
* cargo clippy --workspace --all-targets -- -D warnings
* cargo test --workspace
* cargo build -p oxide-browser
* cargo build --target wasm32-unknown-unknown --release -p stream-fetch-demo

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR introduces a new streaming, non-blocking HTTP fetch API across oxide-browser and oxide-sdk layers, enabling guest WebAssembly modules to perform asynchronous HTTP requests with chunked response handling. A new demo application is included to showcase the functionality, along with workspace and roadmap updates.

Changes

Cohort / File(s) Summary
Workspace & Roadmap
Cargo.toml, ROADMAP.md
Registered new example workspace member and marked streaming fetch features as completed in Phase 4 roadmap.
Demo Application
examples/stream-fetch-demo/Cargo.toml, examples/stream-fetch-demo/src/lib.rs
New WebAssembly demo implementing a streaming HTTP fetch UI with live fetch state tracking, URL input, and response body preview rendering.
Host Fetch Implementation
oxide-browser/src/fetch.rs, oxide-browser/src/capabilities.rs, oxide-browser/src/lib.rs
Added async streaming fetch module with Tokio-backed HTTP requests, per-request state lifecycle management, queued chunk buffering, and host function registration. Integrated fetch state into HostState and exposed module in crate interface.
SDK Wrapper Layer
oxide-sdk/src/lib.rs
Exposed streaming fetch API to guest modules via handle-based polling functions (fetch_begin, fetch_state, fetch_recv) with chunked body retrieval (FetchChunk enum) and lifecycle constants (FETCH_PENDING, FETCH_STREAMING, FETCH_DONE, FETCH_ERROR, FETCH_ABORTED).

Sequence Diagram(s)

sequenceDiagram
    participant Guest as Guest Module<br/>(Demo App)
    participant Host as Host (oxide-browser)
    participant Queue as Chunk Queue
    participant Network as HTTP Network
    
    Guest->>Host: fetch_begin_get(url)
    activate Host
    Host->>Host: Allocate request ID<br/>Create FetchInner
    Host->>Network: Spawn async Tokio task
    deactivate Host
    activate Network
    Host-->>Guest: Return handle
    
    Guest->>Host: fetch_state(handle)
    Host-->>Guest: FETCH_STREAMING
    
    Network->>Queue: Stream response chunks
    
    loop Poll for chunks
        Guest->>Host: fetch_recv(handle, capacity)
        activate Host
        Host->>Queue: Drain up to capacity
        Host-->>Guest: Data chunk or sentinel
        deactivate Host
    end
    
    Network->>Host: Response complete
    Network->>Host: Update state → FETCH_DONE
    deactivate Network
    
    Guest->>Host: fetch_recv(handle, capacity)
    activate Host
    Host-->>Guest: End sentinel
    deactivate Host
    
    Guest->>Host: fetch_remove(handle)
    Host->>Host: Free request resources
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A streaming fetch so swift and spry,
Chunks arrive without a dry goodbye—
No blocking waits in WASM's way,
Just polls and queues throughout the day!
Async hops through Tokio's domain,
Where network requests flow like rain! 🌊

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(fetch): streaming / non-blocking HTTP fetch API' directly and accurately summarizes the main change: adding a new streaming/non-blocking HTTP fetch API to the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/streaming-fetch

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.

@niklabh niklabh merged commit c677adb into main Apr 17, 2026
4 of 5 checks passed
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