[Answered ]-Django conditional view processing decorator adds stale Etag

2👍

I agree that there’s a bug here, though I think it’s a bit different from what you describe.

Conditional Requests are defined in RFC 7232, but unfortunately that document is not very explicit about exactly when the conditional headers should be used in a response. It does say:

2.4. When to Use Entity-Tags and Last-Modified Dates

In 200 (OK) responses to GET or HEAD, an origin server…

That might lead one to assume that use of the headers is not defined in other responses.

However, RFC 7231 explicitly allows for the use of ETags in the response to a PUT, matching the new representation (as was your intuition). However, note this caveat:

An origin server MUST NOT send a validator header field (Section 7.2), such as an ETag or Last-Modified field, in a successful response to PUT unless the request’s representation data was saved without any transformation applied to the body…

That is, the client will use the presence or absence of the ETag to determine whether or not its representation (that it just sent as the body to PUT) was the one actually stored. (See this question for more detail on this point.)

However, Django’s conditional request API does not allow for making this distinction. Specifically, there’s no way for the user to indicate whether or not a view saved the representation without "transformation applied to the body". So there’s no way for the condition() decorator to know whether or not adding an ETag is warranted.

So the only thing to do is to be conservative and not return conditional headers at all in this case. Feel free to create a ticket (or else I can do it).

0👍

Create a custom middleware to handle etag in the GET/HEAD request.
The following code(Django 1.10) shows how to create and process etag using middleware.

Note: Don’t enable USE_ETAGS in the settings file

from django.utils.cache import get_conditional_response, set_response_etag
from django.utils.http import unquote_etag


class ETag(object):
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # before view

        response = self.get_response(request)

        # after view
        try:
            if request.method in ('GET', 'HEAD'):
                if not response.has_header('ETag'):
                    set_response_etag(response)
                etag = response.get('ETag')
                return get_conditional_response(
                    request,
                    etag=unquote_etag(etag),
                    last_modified=None,
                    response=response,
                )
        except Exception, e:
            pass

        return response

i am using Django 1.10. If you are using lower versions then override process_response(self, request, response) method with the logic implemented inside the __call__ method. And don’t forget to add this to the MIDDLEWARE/MIDDLEWARE_CLASSES in the settings file

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',


    # myapp contains middleware.py file and 
    # ETag class is implemented inside the middleware.py file 
    'myapp.middleware.Etag',
]

Leave a comment