-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Update existing vary headers in response instead of overwriting them. #5047
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
Conversation
936de4f
to
6a15f3e
Compare
I'd be happy to read a bit about why the change is required. |
I'm +0 on this, since I see where DRF is completely wiping out any existing
It will always be returned with just Assuming that's the case, that has the potential to cause issues with downstream proxies who rely on the |
Ah yes I blew straight past the context there didn't I 😀 Because the vary header is used by caches to decide when not to cache things, removing parts of the header can result in dangerous behaviour where responses get cached that the view defined shouldn't be cached. Here's a (contrived) example: @api_view(['GET'])
@auth_classes([SuperSecretHTTPAuth])
@allow_caching(vary='Authorization') # super important, vary on authorization to make sure users can only get their own data from the cache!
def secret_user_details(request):
user = user_from_auth_header(request.META['HTTP_AUTHORIZATION'])
serializer = SecretUserData(instance=user)
return Response(serializer.data) The example is very silly, but we have this issue in a more complicated version of that, where we determine permissions from a JWT and need to cache responses, but need to make sure the cached responses are tied to a user. With the current logic, when the response gets to DRF's Patching the vary header instead of replacing it is also the common pattern in other bits of django, for example in the csrf middleware: # Content varies with the CSRF cookie, so set the Vary header.
patch_vary_headers(response, ('Cookie',)) |
current status: http://www.commitstrip.com/en/2016/01/26/genius-or-stupid/ @tomchristie what's the rational behind the vary header being wiped ? |
Possibly a simpler alternative would be to only set headers in the view that haven't already been set on the response. That'd be a simple and consistent approach that I think would cover what we'd need. Instead use:
That'd work fine for your use case @kirberich, right? You'd just want to make sure to included 'Accept' yourself. Thoughts? |
Historical only. Out of our defaults "Accept" and "Vary", would either of them be set already on a |
@tomchristie that fix would solve our particular use case, but it might introduce weird bugs for people, because I think it can break content negotiation. Here's another very contrived example to explain my thinking:
I'm not sure about the It's annoying that django doesn't have a simple |
I don't have a problem with the user being responsible for ensuring that they set the Vary header correctly if they do set it, though perhaps I'm misreading here? "with the vary header having been overwritten as Vary: Authorization" Do you mean because user code explicitly included |
I completely agree that the user should be responsible for setting the headers correctly, i.e. not overwriting existing ones, but I don't think the user can be responsible for also including headers that third-party code might add, as it requires knowledge of what goes on inside DRF and ties those behaviours together: @api_view(['GET'])
def test_view(request):
response = Response(some_serializer.data)
# make sure we vary this response per user, be careful not to wipe out existing headers!
patch_vary_headers(response, ['Authorization'])
# Return response with patched headers
return response With a view like that, one of the following would happen (assuming multiple renderer classes are active)
If adding the from rest_framework.settings import api_settings
@api_view(['GET'])
def test_view(request):
response = Response(some_serializer.data)
# make sure we vary this response per user, be careful not to wipe out existing headers!
vary_headers = ['Authorization']
# DRF adds a vary: accept header if multiple renderer classes are used, make sure we include this header here.
if len(api_settings.DEFAULT_RENDERER_CLASSES) > 1:
vary_headers.append('Accept')
patch_vary_headers(response, vary_headers)
# Return response with patched headers
return response I hope this makes sense! I'm also happy to do a quick hangout or something at some point to talk through this properly 🙂 |
rest_framework/views.py
Outdated
# Add new vary headers to existing ones instead of overwriting. | ||
new_vary_headers = cc_delim_re.split(self.headers.pop('Vary')) | ||
patch_vary_headers(response, new_vary_headers) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'm convinced.
Let's tweak the code block slightly tho. The pop
is hidden away inside the rest of the code so it's not totally clear at first why it doesn't jsut later get overridden.
How about...
vary = self.headers.pop('Vary', None)
if vary is not None:
...
Thanks for taking the time to walk through things, @kirberich. |
Previously, any existing vary headers would simply be wiped out by DRF. Using patch_vary_headers assures that existing headers remain.
6a15f3e
to
9ebd5a2
Compare
@tomchristie no worries, thank you for the quick responses! I updated the code, let me know if that looks better 🙂 - I'm a bit unsure about the comment, I think it needs one, but I'm not sure if it's being clear enough. |
Yeah works for me. |
Thank you! ✨ |
Description
I've changed the behaviour of finalize_response to update vary headers in the response - previously, any existing vary headers would be wiped out going through DRF. Using patch_vary_headers assures that existing headers remain.
I ran the tests but am getting a bunch of (hopefully unrelated?) problems, so I'm passing the problem on to travis.
I'll happily add a test for this as well, some guidance on where it might best go would be good though :)