I've heard that the Accept headers sent by browsers used to be bad, but I
didn't know just how bad they were until recently.
Naturally, this was prompted by a pull request to Rails.
Rails has used a workaround since 2010 that assumes any
Acceptheader containing*/*is from a browser and defaults to HTML
Oh, that's kinda odd. Why does it do this?
Accepting Our Mistakes
Well, as I mentioned before, browsers' Accept headers used to be "bad". What
exactly is "bad"?
application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
This was WebKit's Accept header back in 2010, meaning it was used by both
Safari and Chrome1.
Notice the order of the content types: first application/xml, then
application/xhtml+xml, and only then text/html. RFC 9110 (which
defines HTTP Semantics, including the Accept header) dictates that earlier
types in the list should be preferred over later ones. Therefore, if a web
server were to follow the spec, it would respond to requests from Safari and
Chrome with XML instead of HTML!
We Don't Have to Accept This
While it can be used for other things, Rails is primarily a web application framework that serves HTML webpages. So to provide a better "out of the box" experience for developers, Rails should do something to ensure that Safari and Chrome aren't accidentally given XML pages when they most likely expect HTML.
The first approach taken by Rails was to just ignore the Accept
header completely. Initially, this was going to be the default
behavior, but it was changed a few weeks later to be opt-in.
A year later (as part of the Merb-ge?), the logic was tweaked to be
"smarter". Why should non-browser clients have their Accept header ignored
just because browsers misbehave? The new version would respect a request's
Accept header if
- the request is
xhr?2 - or the
Acceptheader only contains one value
This was a big improvement because ignoring the Accept header was no longer an
absolute. However, a bug report was opened because Accept headers with
multiple values were now ignored unconditionally. In response, the logic was
refined to only ignore the header if it contains the wildcard value (*/*).
And that logic has (mostly3) persisted until today!
Change is Hard to Accept
Well, now its 2026, and all of the major browsers send much more reasonable
Accept headers. Chrome and Safari were both fixed by the same commit to
Webkit in 2011 which changed the default Accept header to match Firefox's,
which was much more reasonable:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
This commit landed in Chrome 12 and Safari 5.1, which were released in June and July of 2011, a full 15 years ago!
It's time for Rails to go back to the spec, and that's exactly what the pull request I mentioned at the beginning of this post does: it adds a new configuration so that applications can opt-in to exact RFC 9110 behavior (and newly created applications are automatically opted-in).
-
If you're interested in seeing an even worse
Acceptheader, read this blog post which also looks at Internet Explorer. ↩ -
If the
x-requested-withheader containsXMLHttpRequest, you can see the code here ↩ -
For completion's sake: initially the
*/*only caused theAcceptheader to be ignored if it was at the end, and*/*in other positions was only added later, and then improved. ↩