Skip to content

perf: Node-side fetch bridge bypassing undici Request/Response churn #89

Closed

Description

@devin-ai-integration

Problem

bench/RESULTS-v2.md (post-#82): chttp-fetch-node runs at 53–73% of chttp-node. The gap is not dispatch — it is per-request undici new Request(...) + new Headers + new Response construction plus resp.text() re-buffering in the Node→fetch bridge (bench/apps/chttp-fetch.ts shows the pattern: IncomingMessage → undici Request → app.fetch → undici Response → ServerResponse).

Direction

WebReqView/ResView are already structural interfaces — the runtime never nominally requires undici objects. c.req.raw on the fetch arm returns the WebReqView, not a real Request. So a Node bridge can:

  1. Build a lightweight LiteRequest straight over IncomingMessage implementing the WebReqView members (method, url, headers-shaped map, buffered text()/arrayBuffer(), lazy body stream) — skipping undici's WHATWG URL/Headers machinery.
  2. Serialize ResView/FetchResponseSink output straight to ServerResponse — skipping new Response + text().
  3. Lazily materialize a real undici Request only when a consumer calls fetch(c.req.raw) or otherwise needs the host object (edge case — document).

Ship as an opt-in Node entry (e.g. app.serveFetch(port) or a documented bridge helper) — app.fetch keeps returning a real Response for WinterCG hosts.

Steps

  • node --cpu-prof the current chttp-fetch-node cell first to confirm the undici ctor share before writing the lite path.
  • Expected recovery: most of the 27–47% gap; ELF path (fetchShim primitives) already pays none of this.
  • Bench chttp-fetch-node re-run is the acceptance gate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions