Open-Source Wikis

/

Bun

/

Systems

/

HTTP stack

oven-sh/bun

HTTP stack

Bun ships its own HTTP/1.1, HTTP/2, HTTP/3, and WebSocket implementations rather than wrapping Node's http module. The client side lives in src/http/; the server side is concentrated in src/bun.js/api/server.zig. Both build on top of uSockets and BoringSSL.

Client side: src/http/

File Purpose
src/http/HTTPThread.zig The dedicated HTTP thread (~30 KB). Owns one uSockets loop, runs all outgoing requests, replies cross-thread to the main loop.
src/http/HTTPContext.zig Per-host SSL/TCP context (~38 KB). Wraps us_socket_context_t.
src/http/AsyncHTTP.zig One outgoing request (~17 KB). State machine: connect → send → recv → done.
src/http/InternalState.zig Per-request state shared with C-side parsers.
src/http/Headers.zig Request/Response headers. WHATWG-compatible.
src/http/Method.zig HTTP methods enum.
src/http/HeaderBuilder.zig, HeaderValueIterator.zig Header building/iteration.
src/http/Decompressor.zig gzip/deflate/brotli/zstd response bodies (uses vendored libdeflate, brotli, zstd).
src/http/Encoding.zig, FetchCacheMode.zig, FetchRedirect.zig, FetchRequestMode.zig fetch() enums.
src/http/H2Client.zig, H2FrameParser.zig, h2_client/ HTTP/2 client. Uses lshpack for HPACK.
src/http/H3Client.zig, h3_client/ HTTP/3 client. Uses lsquic (vendored) plus lsqpack.
src/http/ProxyTunnel.zig HTTP CONNECT proxy support.
src/http/SendFile.zig sendfile(2) integration.
src/http/MimeType.zig, mime_type_list.txt, mime_type_list_enum.zig MIME-type table (~256 KB enum).
src/http/URLPath.zig URL path normalisation.
src/http/websocket.zig, websocket_client.zig, websocket_http_client.zig, websocket_client/ WebSocket client (with permessage-deflate).

Server side

The HTTP server lives in src/bun.js/api/server.zig (~170 KB), the largest file in src/bun.js/api/. It implements Bun.serve({ ... }), including:

  • HTTP/1.1, HTTP/2 (with multiplexing), and unencrypted upgrade.
  • TLS termination via uSockets and BoringSSL.
  • WebSocket upgrade and pubsub broadcast.
  • Static file responses ({ static: { ... } }) — pre-computes the HTTP/2 frame for each entry at startup.
  • Sub-routing through declarative routes ({ routes: { "/api/:id": ... } }).
  • HTML imports — Bake's full-stack mode runs in this server too.

It is paired with C++ host classes in src/bun.js/bindings/ServerBunRequest.cpp, ServerBunResponse.cpp, and the route definitions in src/bun.js/api/server.classes.ts.

Outgoing fetch flow

sequenceDiagram
    participant JS as user code
    participant Fetch as fetch.zig
    participant HTTP as HTTPThread
    participant Sock as uSockets
    participant Server as remote

    JS->>Fetch: fetch(url, opts)
    Fetch->>HTTP: enqueue AsyncHTTP request (cross-thread)
    HTTP->>Sock: connect
    Sock-->>HTTP: writable
    HTTP->>Server: request line + headers + body
    Server-->>HTTP: response
    HTTP->>HTTP: parse via picohttpparser / nghttp2 logic
    HTTP-->>Fetch: post Task back to main loop
    Fetch-->>JS: Response object

The HTTP thread is shared by fetch(), bun install (registry calls), bunx, and bun upgrade. There's exactly one HTTPThread per process.

TLS

src/boringssl.zig is a thin wrapper that loads BoringSSL symbols and exposes the API surface uSockets and the HTTP client need. CA bundles ship with Bun (refreshed via .github/workflows/update-root-certs.yml). NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, and SSL_CERT_DIR are honoured.

HTTP/2 and HTTP/3

The HTTP/2 client uses lshpack for HPACK header compression. Frames are parsed in H2FrameParser.zig. The HTTP/3 client uses lsquic for QUIC, lsqpack for QPACK, and the QUIC TLS handshake. Both stacks are statically linked.

The server today serves HTTP/1.1 and HTTP/2 (TLS ALPN-negotiated). HTTP/3 server support is in flight as of 1.3.

WebSocket

The client (src/http/websocket_client.zig, ~75 KB) implements RFC 6455, permessage-deflate (websocket_client/), and a high-level promise-based API exposed as the standard WebSocket global. The server side handles the upgrade in server.zig and delegates frame handling to the same code.

Bun.serve's websocket: { ... } option exposes a pubsub interface with .subscribe()/.publish()/.unsubscribe() per connection, broadcasting via uSockets's underlying us_socket_context_t pubsub primitive.

DNS

src/dns.zig wraps c-ares (vendored at vendor/cares/). All DNS lookups in fetch() and the HTTP thread go through it; the runtime exposes the same as node:dns in src/bun.js/node/dns.zig. The default order is "verbatim" (no IPv4/IPv6 preference); see the BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS env var (default 30s).

Integration points

  • uSockets — All sockets, TLS contexts, timers go through packages/bun-usockets/.
  • BoringSSL — TLS, hashing, random.
  • Event loop — Outgoing requests post results back via ConcurrentTask. See Event loop.
  • ServerBun.serve lives in src/bun.js/api/server.zig. See Runtime.
  • Auto cert update.github/workflows/update-root-certs.yml refreshes the root CA bundle automatically.

Entry points for modification

  • To change fetch() behaviour, look at src/bun.js/webcore/fetch.zig first; protocol-level changes go in AsyncHTTP.zig.
  • To change WebSocket framing or pubsub, edit websocket_client.zig and the server hooks in server.zig.
  • To change the server's request lifecycle, edit src/bun.js/api/server.zig — most code is shaped by the RequestContext lifecycle there.
  • To change MIME table contents, regenerate mime_type_list_enum.zig from mime_type_list.txt.

Key source files

File Purpose
src/http/HTTPThread.zig The HTTP thread.
src/http/AsyncHTTP.zig One outgoing request.
src/bun.js/api/server.zig The HTTP server.
src/http/websocket_client.zig WebSocket client / shared frame logic.
src/boringssl.zig TLS bindings.
src/dns.zig c-ares wrapper.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

HTTP stack – Bun wiki | Factory