Honor Accept header q-values during renderer selection#9988
Honor Accept header q-values during renderer selection#9988zainnadeem786 wants to merge 2 commits into
Conversation
| !!! note | ||
| "q" values are not taken into account by REST framework when determining preference. The use of "q" values negatively impacts caching, and in the author's opinion they are an unnecessary and overcomplicated approach to content negotiation. | ||
|
|
||
| This is a valid approach as the HTTP spec deliberately underspecifies how a server should weight server-based preferences against client-based preferences. |
There was a problem hiding this comment.
This will need to be checked. What does "negatively impacts caching" mean? We'll need to understand the trade-off we're making here. It might not be worth it after all...
There was a problem hiding this comment.
Thanks for raising this. I spent some time looking into the history and the relevant HTTP specifications to better understand the trade-off.
From what I could find, this note was introduced in commit efabd2b (2012), but I couldn't find any accompanying discussion or rationale beyond the inline documentation itself.
Looking at RFC 9110/9111, my understanding is that the caching concern is primarily about server-driven content negotiation and Vary: Accept, rather than q-values themselves. Supporting q-values can increase Accept header variation and therefore cache-key cardinality, which may reduce shared-cache hit rates, but it does not inherently make caching incorrect.
RFC 9111 even references q-values as a ranking mechanism when selecting among stored representations, and frameworks such as Django and Werkzeug already take quality values into account (with their own policy choices around wildcards and server preference).
My goal with this PR was to keep the change as small and targeted as possible:
- preserve existing behaviour when q-values are absent,
- treat
q=0as unacceptable, - prefer higher non-zero q-values,
- preserve the existing specificity ordering and renderer order as tie-breakers.
That said, if the cache-key/cardinality trade-off is considered too significant for the default negotiator, I'm happy to discuss alternative approaches or narrow the scope further.
There was a problem hiding this comment.
Supporting q-values can increase Accept header variation and therefore cache-key cardinality, which may reduce shared-cache hit rates
Yes, exactly. I think that's the main thing we need to consider here: if we introduce support for q-values, folks using will see their cache usage suddenly increase. Given how much this was requested (never before you did), I'm so far in the camp that it's not worth the risk changing this.
frameworks such as Django and Werkzeug already take quality values into account
Can you link to where this is happening in Django? That would be helpful to learn more...
There was a problem hiding this comment.
Thanks @browniebroke , that's a fair concern. I took another look at Django's implementation.
The relevant code is here:
-
HttpRequest.accepted_types,accepted_types_by_precedence,accepted_type(),get_preferred_type(), andaccepts():
https://github.com/django/django/blob/main/django/http/request.py#L93-L151 -
MediaType.quality, which parses theqparameter:
https://github.com/django/django/blob/main/django/http/request.py#L726-L793
I should slightly qualify my earlier statement. Django does take q values into account in its request helper APIs it parses the Accept header, filters out media ranges with q=0, and sorts accepted media types by quality and specificity. However, Django is not performing DRF-style renderer selection, so it isn't a direct one-to-one comparison.
I also agree with your cache-cardinality concern. My understanding is that q values themselves don't make caching incorrect (that is handled through Vary: Accept), but supporting more client preference variations can reduce cache reuse and change renderer selection for existing clients that already send q values.
Given that compatibility concern, I'd be happy to narrow the scope of this PR if you think that would be a better direction. One option would be to only honor q=0, ensuring DRF never selects a renderer that the client has explicitly marked as unacceptable, while preserving the current renderer-order behaviour for non-zero q values.
Would a narrower q=0-only approach be something you'd be more comfortable considering?
Summary
This PR adds support for HTTP
Acceptheader quality (q) values during renderer selection inDefaultContentNegotiation.Issue
DRF previously selected renderers using media type specificity and renderer order, but did not take
qvalues into account.This meant media ranges explicitly marked as unacceptable with
q=0could still be selected.For example:
Accept: application/json;q=0, text/html;q=1could still select
application/json.Similarly:
Accept: text/html;q=0, */*;q=1could still select
text/htmlthrough renderer ordering or wildcard matching.Changes
This PR:
q=0as unacceptable.qvalues before applying existing specificity ordering.Acceptheaders withoutqvalues.qvalues safely by falling back to the default quality.Compatibility
This is a targeted behavior change for requests that include
qvalues in theAcceptheader.Existing behavior for
Acceptheaders withoutqvalues is preserved.Tests
Focused test run:
Result:
Related discussion: #9986