Skip to content

Rack 3.1 is missing content-length on 0 byte responses #2218

@zarqman

Description

@zarqman

In a Rails 7.1 app, empty responses are now missing the content-length header when used with Rack 3.1. On Rack 3.0, the header is present with the expected value of 0.

I'm not positive, but this might be a byproduct of #2149 since that deferred setting content-length. If there's no body to iterate, perhaps content-length gets missed?

I don't have a thorough grasp on how Rails and Rack interact in the case of an empty response, so it's possible this issue belongs to Rails instead of Rack. However, I'm starting here since changing the Rack version alone is enough to trigger the behavior difference.

Why this matters

Some HTTP servers revert to chunked encoding when the payload size is unknown. This is inefficient.

Additionally, some HTTP caches use content-length to decide what is and isn't cacheable and its absence can cause the entire request to become uncacheable, causing excess load on app servers.

Reproduction example
# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  gem "rails"
  # gem "rack", "~> 3.0.0"
  gem "rack", "~> 3.1.0"
end

require "action_controller/railtie"

class TestApp < Rails::Application
  config.root = __dir__
  config.hosts << "www.example.com"
  config.secret_key_base = "secret_key_base"
  config.action_dispatch.show_exceptions = :rescuable
  config.logger = Logger.new($stdout)
  Rails.logger  = config.logger

  routes.draw do
    get "/head" => "test#one"
    get "/empty" => "test#two"
    get "/body" => "test#three"
  end
end

class TestController < ActionController::Base
  def one
    head 200
  end
  def two
    render plain: ''
  end
  def three
    render plain: 'a', layout: false
  end
end

require "minitest/autorun"

class TestControllerTest < ActionDispatch::IntegrationTest

  # works on rack 3.0
  # content-length key is missing on rack 3.1
  def test_head
    get "/head"
    assert_response 200
    assert_equal '0', headers['content-length']
    # assert headers.key?('content-length')
  end

  # works on rack 3.0
  # content-length key is missing on rack 3.1
  def test_empty_body
    get "/empty"
    assert_response 200
    assert_equal '0', headers['content-length']
    # assert headers.key?('content-length')
  end

  # works on rack 3.0 and 3.1
  def test_with_body
    get "/body"
    assert_response 200
    assert_equal '1', headers['content-length']
  end

  private
    def app
      Rails.application
    end
end

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

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions