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

Fix header casing compatibility with Rails 7 #790

Merged
merged 1 commit into from
Sep 5, 2023

Conversation

skipkayhil
Copy link
Member

@skipkayhil skipkayhil commented Jul 29, 2023

Ref rails/rails#48874
Fixes rails/rails#47096 for Rails < 7.1

To support Rack 3, response headers in Sprockets::Server were all downcased. However, this has led to issues with Rack 2 applications (ex. Rails 7) since they still expect mixed case (ex. Content-Type) headers.

To ensure compatibility with both Rack 2 and Rack 3 applications, this commit makes the casing of the headers conditional on the Rack version. Rack itself provides constants to do this easily for most of the headers used (Content-Type, Content-Length, Cache-Control, and ETag) and the rest are added as constants under Rack::Server.

As an alternative to this, the responses could instead be wrapped using Rack::Headers (and Rack::Utils::HeaderHash in Rack 2), but making the header casing conditional seems better to me because it is relatively easier to implement and there will be less churn if/when Rack 2 support is eventually removed.

@skipkayhil
Copy link
Member Author

Tests should be fixed by #791

@rafaelfranca
Copy link
Member

Wait, what?

Doesn't rack >= 2.2.4 don't care about the casing of the headers? #758 (comment)

@skipkayhil
Copy link
Member Author

skipkayhil commented Aug 2, 2023

Doesn't rack >= 2.2.4 don't care about the casing of the headers?

For any library that aims to support Rack 2 and Rack 3, the casing of headers matters unless those libraries return Rack::Utils::HeaderHash/Rack::Headers in all responses and all middleware that read/write headers use Rack::Utils::HeadersHash/Rack::Headers as well.

So Rails returns HeadersHash because it uses ActionDispatch::Response, but these responses are plain hashes. This means a Rack 2 middleware immediately above Sprockets::Server that tries to read Content-Type will not work correctly since these responses are hashes that only contain content-type.


So as an alternative to this PR, we could apply Headers to these responses, something like this:

def bad_request_response(env)
  if head_request?(env)
    [ 400, Headers[{ "content-type" => "text/plain", Rack::CONTENT_LENGTH => "0" }], [] ]
  else
    [ 400, Headers[{ "content-type" => "text/plain", Rack::CONTENT_LENGTH => "11" }], [ "Bad Request" ] ]
  end
end

where Headers looks like: https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/http/response.rb#L37-L45


I wrote some thoughts on using Headers vs conditional casing here: rails/rails#48874 (comment) and rails/rails#48874 (comment)

Comment on lines +15 to +17
X_CASCADE = "X-Cascade"
VARY = "Vary"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
X_CASCADE = "X-Cascade"
VARY = "Vary"
X_CASCADE = "X-Cascade" # :nodoc:
VARY = "Vary" # :nodoc:

I don't want to maintain constants for strings just because Rack 3 is incompatible with Rack 2

To support Rack 3, response headers in `Sprockets::Server` were all
downcased. However, this has led to issues with Rack 2 applications (ex.
Rails 7) since they still expect mixed case (ex. `Content-Type`)
headers.

To ensure compatibility with both Rack 2 and Rack 3 applications, this
commit makes the casing of the headers conditional on the Rack version.
Rack itself provides constants to do this easily for most of the headers
used (`Content-Type`, `Content-Length`, `Cache-Control`, and `ETag`) and
the rest are added as constants under `Rack::Server`.

As an alternative to this, the responses could instead be wrapped using
`Rack::Headers` (and `Rack::Utils::HeaderHash` in Rack 2), but making
the header casing conditional seems better to me because it is
relatively easier to implement and there will be less churn if/when Rack
2 support is eventually removed.
@rafaelfranca rafaelfranca merged commit 42f7d5e into rails:main Sep 5, 2023
@skipkayhil skipkayhil deleted the hm-fix-rails-7-compat branch September 5, 2023 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lowercase headers returned from Sprockets 4.2.0 not caught within Actionpack router
2 participants