Skip to content

Comments

Use consistent casing for headers#379

Merged
tgxworld merged 1 commit intodiscourse:mainfrom
moberegger:moberegger/consistent-header-casing
Feb 11, 2026
Merged

Use consistent casing for headers#379
tgxworld merged 1 commit intodiscourse:mainfrom
moberegger:moberegger/consistent-header-casing

Conversation

@moberegger
Copy link
Contributor

@moberegger moberegger commented Feb 10, 2026

Since the new 4.5 release, I've noticed the same header being written to the rack.hijack buffer multiple times. For example

HTTP/1.1 200 OK
cache-control: must-revalidate, private, max-age=0
content-type: application/json; charset=utf-8
pragma: no-cache
expires: 0
x-frame-options: DENY
x-xss-protection: 1; mode=block
x-download-options: noopen
x-permitted-cross-domain-policies: none
referrer-policy: origin-when-cross-origin
content-security-policy: default-src 'none'; base-uri 'none'; script-src 'none'; upgrade-insecure-requests
Connection: close
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff

(Note: I trimmed out some headers for brevity)

content-type is there twice; once with application/json; charset=utf-8, once with text/plain; charset=utf-8. Notice that the casing is different. Root cause appears to be changes in #371 that switched casing in some places, but not others. For example MessageBus::Client would check for Content-Type but MessageBus::Rack::Middleware set content-type. Easier to explain in my code annotations below.

Simplest fix is to just use lowercase headers everywhere. I considered using Rack constants instead, but figured this was easier. content-type is the only header that I noticed this behaviour with, but I felt it was best to be consistent with the casing across the board to help prevent/catch future regressions.

@io.write(HTTP_11)
@headers.each do |k, v|
next if k == "Content-Type"
next if k == "content-type"
Copy link
Contributor Author

@moberegger moberegger Feb 10, 2026

Choose a reason for hiding this comment

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

This was the root cause for the behaviour described in the description.

The middleware sets content-type to application/json; charset=utf-8 in the default headers: https://github.com/discourse/message_bus/blob/main/lib/message_bus/rack/middleware.rb#L84-L88

The intent if this line appears to be to "skip" writing the content-type to the string buffer because the client will do so later. But since the casings didn't match, the default content-type ended up in the buffer. Then later...


TYPE_TEXT = "Content-Type: text/plain; charset=utf-8\r\n".freeze
TYPE_JSON = "Content-Type: application/json; charset=utf-8\r\n".freeze
TYPE_TEXT = "content-type: text/plain; charset=utf-8\r\n".freeze
Copy link
Contributor Author

@moberegger moberegger Feb 10, 2026

Choose a reason for hiding this comment

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

The client added the Content-Type: text/plain; charset=utf-8 here (or at least does for chunked encoding). This is why content-type was in the http response twice.

@moberegger moberegger marked this pull request as ready for review February 10, 2026 19:29
@tgxworld
Copy link
Contributor

@moberegger Thank you for this! We started encountering issues when deploying the new gem as well.

@tgxworld tgxworld merged commit 2d00142 into discourse:main Feb 11, 2026
11 checks passed
@moberegger
Copy link
Contributor Author

@tgxworld Oops! I just realized I forgot to add an entry to the change log. Do you mind taking care of that for me when you gear up the next release?

@tgxworld
Copy link
Contributor

@moberegger No worries. I'll add it when I cut the new release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants