oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
https://bun.sh
Other
71.7k stars 2.54k forks source link

Bun.file with Response() #11048

Open fry69 opened 1 month ago

fry69 commented 1 month ago

What version of Bun is running?

1.1.8+89d25807f

What platform is your computer?

Darwin 23.4.0 arm64 arm (same behavior with same Bun version on a Linux Ubuntu 20.22 virtual server x86_64)

What steps can reproduce the bug?

There seems to be a bug in Bun.file when using in a Response() from Bun.serve(). I detected this problem as I tried to served a cached rss.xml file, my feed reader reliably refused to accept this file, even though everything else seemed fine, even the online RSS validators did liked my feed endpoint. I tracked this problem down to a suspicious Content-Disposition header, that was only present for this feed endpoint, all other cached files did not show this additional header.

[Update: Further testing show that the Content-Disposition header do show up in other endpoints too, but only the RSS reader seems to have a problem with this (or with other side effects I have not yet understood/tracked down).]

relevant (working) code snippet (see repo for full code):

          if (gzipFilePath.endsWith("rss.xml.gz")) {
            return new Response(fs.readFileSync(gzipFilePath), {
              headers: {
                "Content-Encoding": "gzip",
                "Content-Type": "application/rss+xml",
              },
            });

When I use Bun.file() instead of fs.readFileSync() I see the problems described above, below are some curl traces with additional information.

Note the content-disposition header:

* Connected to localhost (::1) port 3000
> GET /rss HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.4.0
> Accept: */*
> Accept-Encoding: deflate, gzip
> 
< HTTP/1.1 200 OK
< Content-Encoding: gzip
< Content-Type: application/rss+xml
< content-disposition: filename="rss.xml.gz"
< content-length: 3159
< 
{ [3159 bytes data]
100  3159  100  3159    0     0   139k      0 --:--:-- --:--:-- --:--:--  140k
* Connection #0 to host localhost left intact

Sometimes I see spurious excess warnings and closing connections:

* Connected to localhost (::1) port 3000
> GET /rss HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.4.0
> Accept: */*
> Accept-Encoding: deflate, gzip
> 
< HTTP/1.1 200 OK
< Content-Encoding: gzip
< Content-Type: application/rss+xml
< content-disposition: filename="rss.xml.gz"
< content-length: 3159
< 
{ [3161 bytes data]
* Excess found in a read: excess = 2, size = 3159, maxdownload = 3159, bytecount = 0
100  3159  100  3159    0     0  1074k      0 --:--:-- --:--:-- --:--:-- 1542k
* Closing connection

Generating directly works (wihout using Bun.file to serve a cached gzipped file):

* Connected to localhost (::1) port 3000
> GET /rss HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.4.0
> Accept: */*
> Accept-Encoding: deflate, gzip
> 
< HTTP/1.1 200 OK
< Content-Type: application/rss+xml
< Date: Mon, 13 May 2024 17:55:08 GMT
< Content-Length: 41825
< 
{ [41825 bytes data]
100 41825  100 41825    0     0  2392k      0 --:--:-- --:--:-- --:--:-- 2402k
* Connection #0 to host localhost left intact

Using fs.readFileSync instead of Bun.file works flawlessly serving the cached gzipped files:

* Connected to localhost (::1) port 3000
> GET /rss HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.4.0
> Accept: */*
> Accept-Encoding: deflate, gzip
> 
< HTTP/1.1 200 OK
< Content-Encoding: gzip
< Content-Type: application/rss+xml
< Date: Mon, 13 May 2024 18:02:43 GMT
< Content-Length: 3157
< 
{ [3157 bytes data]
100  3157  100  3157    0     0   153k      0 --:--:-- --:--:-- --:--:--  154k
* Connection #0 to host localhost left intact

What is the expected behavior?

Bun.file serving the file without problems like fs.readFileSync

What do you see instead?

See above.

Additional information

No response

fry69 commented 1 month ago

I rewrote my code to using async, but the described problem with using bare Bun.file() in a Response() persists. Using await Bun.file().arrayBuffer() works fine though, at the small cost that I have to get Content-Type now manually with Bun.file().type.