Skip to content

Range requests broken for new Response(Bun.file()) on Bun — regression since 1.4.23 #1868

@alexkahndev

Description

@alexkahndev

What version of Elysia is running?

1.4.28 (regression introduced in 1.4.23)

What platform is your computer?

Linux x86_64 (also reproducible on macOS)

What environment are you using

bun 1.3.13

Are you using dynamic mode?

No

What steps can reproduce the bug?

import { Elysia } from 'elysia'

await Bun.write('/tmp/sample.bin', 'abcdefghij') // 10 bytes

new Elysia()
  .get('/file', () => new Response(Bun.file('/tmp/sample.bin')))
  .listen(3000)
curl -sI -H 'Range: bytes=2-5' http://localhost:3000/file

What is the expected behavior?

HTTP/1.1 206 Partial Content
content-range: bytes 2-5/10
content-length: 4
accept-ranges: bytes

This is what Bun.serve returns for a bare new Response(Bun.file()) — Bun's HTTP layer auto-handles HTTP byte-range requests when the response body is a Bun.file. It is also what Elysia 1.4.22 returned.

What do you see instead?

HTTP/1.1 200 OK
content-length: 10

Full file, no range support, no Accept-Ranges. Bun's auto-Range never fires.

Real-world impact

Mobile Safari refuses to play <video> elements when the server doesn't honor byte-range requests (Apple requires this). Every static asset served by @elysiajs/static is new Response(Bun.file()) under the hood, so the regression silently broke video playback on iOS for every Elysia app on Bun using the static plugin. Desktop browsers tolerate it — they just download the whole file — which is why this went unnoticed.

Downstream issues: elysiajs/elysia-static#19 (open since 2024), elysiajs/elysia-static#79 (open draft).

Root cause

Commit 0d9b0401 ("fix: can't modify immutable headers", in 1.4.23) changed createResponseHandler in src/adapter/utils.ts to always rewrap:

const newResponse = new Response(response.body, {
    headers: mergeHeaders(response.headers, set.headers),
    status: mergeStatus(response.status, set.status)
})

The original motivation was correct — on Node and Cloudflare Workers, response.headers is immutable, so mutating it in place threw. But the fix applies on Bun too, and reading response.body as a ReadableStream severs the Bun.file association that Bun.serve needs to provide auto-Range. Result: a regression that affects every Bun user but not Node/CF users.

Related

#1768 (open) reports the same root cause — createResponseHandler always rewrapping — for a different downstream symptom (ReadableStream cancel callback never invoked). The reporter quotes the same line in createResponseHandler. The same structural fix would resolve both.

Suggested fix

Take the mutate-in-place path only when (a) we're on Bun and (b) we don't actually need to rewrap (status unchanged, no streaming required). Otherwise fall through to the existing rewrap path so Node/CF still works. PR opening shortly.

Have you try removing the node_modules and bun.lockb and try again yet?

Yes — reproducible on a fresh install of elysia@1.4.28.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions