Skip to content

Honor Accept header q-values during renderer selection#9988

Open
zainnadeem786 wants to merge 2 commits into
encode:mainfrom
zainnadeem786:fix/accept-header-q-values
Open

Honor Accept header q-values during renderer selection#9988
zainnadeem786 wants to merge 2 commits into
encode:mainfrom
zainnadeem786:fix/accept-header-q-values

Conversation

@zainnadeem786

Copy link
Copy Markdown
Contributor

Summary

This PR adds support for HTTP Accept header quality (q) values during renderer selection in DefaultContentNegotiation.

Issue

DRF previously selected renderers using media type specificity and renderer order, but did not take q values into account.

This meant media ranges explicitly marked as unacceptable with q=0 could still be selected.

For example:

Accept: application/json;q=0, text/html;q=1

could still select application/json.

Similarly:

Accept: text/html;q=0, */*;q=1

could still select text/html through renderer ordering or wildcard matching.

Changes

This PR:

  • Treats media ranges with q=0 as unacceptable.
  • Prefers higher nonzero q values before applying existing specificity ordering.
  • Preserves existing specificity ordering as the tie-breaker.
  • Preserves renderer order as the final tie-breaker.
  • Keeps behavior unchanged for Accept headers without q values.
  • Handles invalid or out-of-range q values safely by falling back to the default quality.
  • Updates the content negotiation documentation.
  • Adds focused regression tests.

Compatibility

This is a targeted behavior change for requests that include q values in the Accept header.

Existing behavior for Accept headers without q values is preserved.

Tests

Focused test run:

.venv\Scripts\python.exe -m pytest tests/test_negotiation.py -q -p no:cacheprovider

Result:

17 passed

Related discussion: #9986

Comment on lines -36 to -39
!!! 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @browniebroke

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=0 as 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @browniebroke , that's a fair concern. I took another look at Django's implementation.

The relevant code is here:

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?

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.

2 participants