Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bun always sets Content-Length to 0 for HEAD response #15355

Closed
kensnyder opened this issue Nov 22, 2024 · 5 comments · Fixed by #16099
Closed

Bun always sets Content-Length to 0 for HEAD response #15355

kensnyder opened this issue Nov 22, 2024 · 5 comments · Fixed by #16099
Assignees
Labels
bug Something isn't working bun:serve Bun.serve and HTTP server confirmed bug We can reproduce this issue

Comments

@kensnyder
Copy link

What version of Bun is running?

1.1.36

What platform is your computer?

Darwin 24.1.0 arm64 arm

What steps can reproduce the bug?

Consider the following code:

const server = Bun.serve({
  fetch() {
    return new Response(null, {
      status: 200,
      headers: {
        'Content-Type': 'image/jpeg',
        'Content-Length': '1000',
      },
    });
  },
});

const resp = await fetch(server.url, { method: 'HEAD' });

// should be 1000, but is actually 0
const contentLength = resp.headers.get('Content-Length');
console.log(`Content-length=${contentLength}`);

server.stop(true);

What is the expected behavior?

To conform to HTTP Server specs, I'd like to be able to respond to HEAD requests with a Content-Length of the size of the actual resource.

What do you see instead?

Bun appears to change the Content-Length to 0 before responding.

Additional information

Also reported in Discord on May 15, 2024 by fry69: https://discord.com/channels/876711213126520882/1240212448552816710/1240212448552816710

@kensnyder kensnyder added bug Something isn't working needs triage labels Nov 22, 2024
@cirospaciari cirospaciari self-assigned this Nov 22, 2024
@RiskyMH RiskyMH added bun:serve Bun.serve and HTTP server confirmed bug We can reproduce this issue labels Nov 24, 2024
@Kapsonfire-DE
Copy link
Contributor

Kapsonfire-DE commented Nov 27, 2024

@cirospaciari I'd like to work on this, since I also did the OPTIONS thing.
The same part of code is responsible here. But HEAD is different, since RFC says it must not send a body, but headers should be same as if its a GET Request... so i think its reasonable to make an exception in Code for HEAD Request, not changing headers, but ommiting the body.

If you want me working on this, just set me as assignee

@kensnyder
Copy link
Author

kensnyder commented Dec 3, 2024

Funny story... just today I ran into a new place this bug is affecting me. Let me explain and share my workaround code.

When Cloudflare Stream downloads a video you ask it to transcode, it appears recently started performing a HEAD request on the video URL before requesting the whole video. My videos are being served by Bun, so the HEAD responses will always have Content-Length of 0. When Cloudflare Stream sees the 0, gives the following error, and fails to transcode the video:

{
  messages: [
    {
      code: 10010,
      message: "Performed a HTTP HEAD request, but HEAD request did not include a content-length header. Please make sure your origin returns content length headers."
    }
  ]
}

I took 3 steps to work around this bug:

  1. Updated my Bun code to also return an X-Content-Length header with the correct content length.
  2. Created a Cloudflare Worker (code below) that intercepts requests and overwrites the incorrect content length header with the correct one.
  3. Deployed the Worker under Settings > Domains & Routes > Add Route.
export default {
  async fetch(request, env, ctx) {
    // Fetch the response from my server
    const response = await fetch(request);
    
    // Bun bug: Bun always sets Content-Length to 0 for HEAD responses
    // see https://github.com/oven-sh/bun/issues/15355
    if (
      request.method === 'HEAD' && 
      response.headers.get('content-length') === '0' && 
      response.headers.has('x-content-length')
    ) {
      // Clone the response to allow modifying its headers
      const clone = new Response(response.body, response);

      // Overwrite the Content-length header
      clone.headers.set('Content-Length', response.headers.get('x-content-length'));

      return clone;
    }
    else {
      // Return unmodified response
      return response;
    }
  },
};

This is a decent workaround if you use Cloudflare in front of your Bun server. Still, I'd love to see this bug fixed! 😄

@heimskr
Copy link
Member

heimskr commented Dec 3, 2024

Seems we're doing this intentionally. Not sure why. I'll try replacing the entire if statement with the body of the if-true block.

@kensnyder
Copy link
Author

Seems we're doing this intentionally...

Looking at line 3944, I think the correct behavior for HEAD is: If content-length isn't already set, then do go ahead and set it to '0'. Developers often use a content-length of 0 for dynamic resources requested with HEAD and all responses should have a content-length header.

IIRC, the technically correct behavior is to always return a correct content-length for HEAD. In many situations, HTTP clients don't really care about content-length for HEAD. But some file downloaders will check the content-type and content-length before downloading a file.

@kensnyder
Copy link
Author

For reference, this bug was fixed in Bun v1.1.43, as mentioned in the release notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working bun:serve Bun.serve and HTTP server confirmed bug We can reproduce this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants