Opened 4 years ago

Closed 4 years ago

Last modified 3 months ago

#32023 closed New feature (wontfix)

HttpRequest.headers doesn't get updated when request.META is updated.

Reported by: lieryan Owned by: nobody
Component: HTTP handling Version: 3.1
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Currently, Django docs recommended using request.headers instead of META to access headers, however the behaviour of request.headers currently makes it hard to use correctly during tests.

Currently, the following code fails:

request.META["HTTP_USER_AGENT"] = "foobar"
assert request.headers["User-Agent"] == "foobar" # works
request.META["HTTP_USER_AGENT"] = "django"
assert request.headers["User-Agent"] == "django" # fails

This is because request.headers is a @cached_property that is initialised when request.headers is first accessed, so underlying changes to META don't get reflected to request.headers.

In regular Django request, this isn't that big of a deal, because arguable request.META should be immutable anyway, and you probably should be cursed if you modify request.META in production code.

However, this is rather annoying when writing tests, because often you want to reuse the same request object to pass to different methods, or you want to test the same method with slightly different header value. Due to this caching, you have the option of either recreating the request from scratch, which can be complicated if parts setting up META is spread between setUp() and the test method, or you'd have to delete the request.headers to force it to reinitialise, which is rather non obvious and error prone, since you might forget to delete it and cause some tests to pass/fail when they shouldn't.

Also, request.headers is read only, which means that you still have to use the WSGI environment names when setting headers in tests rather than the standard HTTP name.

I'd propose HttpHeaders should be reimplemented so that it isn't a real collection, but just an accessor for request.META, also that HttpHeaders should implement __getitem__() which should also update request.META.

Change History (3)

comment:1 by Mariusz Felisiak, 4 years ago

Resolution: wontfix
Status: newclosed
Summary: request.headers doesn't get updated when request.META is updatedHttpRequest.headers doesn't get updated when request.META is updated.

Thanks for this ticket, however IMO it's not a desired change. HttpRequest.headers are read-only and immutable by design (see comment), moreover META contains more than only HTTP headers so modifying META via headers would be misleading. If you need a low-level modifications in your tests I would recommend to access/modify META directly.

You can start a discussion on DevelopersMailingList if you don't agree.

comment:2 by Carlton Gibson, 4 years ago

I'd guess you'd be able to del request.headers to reset the property in your test.

in reply to:  2 comment:3 by Chris Wesseling, 3 months ago

Replying to Carlton Gibson:

I'd guess you'd be able to del request.headers to reset the property in your test.

Modification of META is documented behaviour:
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.get_host

Shouldn't request.META.__setitem__ and request.META.__delitem__ del request.headers to synchronize if the key starts with "HTTP_"? In the current code, if any middleware has peeked into request.headers, the data gets out of sync on mutation.

>>> from django.http import HttpRequest
>>> req = HttpRequest()
>>> req.META
{}
>>> req.headers
{}
>>> req.META["HTTP_X_FOO"] = 'asdf'
>>> req.headers
{}
>>> req.headers
{}
>>> req2 = HttpRequest()
>>> req2.META["HTTP_X_FOO"] = 'asdf'
>>> req2.META
{'HTTP_X_FOO': 'asdf'}
>>> req2.headers
{'X-Foo': 'asdf'}
>>> del req2.META["HTTP_X_FOO"]
>>> req2.headers
{'X-Foo': 'asdf'}
Last edited 3 months ago by Chris Wesseling (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top