#16035: GZipMiddleware doesn't change an ETag
-------------------------------+------------------------------------
     Reporter:  anonymous      |                    Owner:  ext
         Type:  Bug            |                   Status:  reopened
    Component:  HTTP handling  |                  Version:  1.4
     Severity:  Normal         |               Resolution:
     Keywords:                 |             Triage Stage:  Accepted
    Has patch:  1              |      Needs documentation:  0
  Needs tests:  0              |  Patch needs improvement:  0
Easy pickings:  1              |                    UI/UX:  0
-------------------------------+------------------------------------

Comment (by mrmachine):

 This is definitely a bug, and it depends on the position of
 `GZipMiddleware` relative to `CommonMiddleware` (and probably other
 middleware).

 `CommonMiddleware` generates an etag based on `response.content`, and
 returns an `HttpResponseNotModified` when the `If-None-Match` header is
 the same as the generated etag.

 `GZipMiddleware` alters an existing etag (adding ";gzip"), but it never
 checks `If-None-Match` and never returns an `HttpResponseNotModified`.

 If `GZipMiddleware` comes before `CommonMiddleware` in the
 `MIDDLEWARE_CLASSES` setting (`GZipMiddleware.process_response` runs after
 `CommonMiddleware.process_response`), the browser will supply a ";gzip"
 etag for the `If-None-Match` header in subsequent requests, that
 `ContentMiddleware` will see as different to the etag that it generates,
 and will never return an `HttpResponseNotModified`.

 If `GZipMiddleware` comes after `CommonMiddleware`, and the user hasn't
 explicitly set an etag on their response (which is usually the case --
 people expect `CommonMiddleware` to do this for them), then things get
 crazy. You will likely get a different etag on every request, even though
 the content is the same.

 The reason is because `GZipMiddleware` will not an etag when there is
 none. `CommonMiddleware` will then try to generate an etag for the
 compressed content. But all gzip streams must include a timestamp, and
 `GzipFile` will use the current timestamp if none is supplied. So the etag
 that `CommonMiddleware` generates will be different for the compressed
 version of the same content, if the requests are a few seconds apart.

 We can resolve this side-issue by adding an arbitrary timestamp to
 `GzipFile`. The `GzipFile` docs say that the module ignores the timestamp
 when decompressing, but some programs such as `gunzip` make use of it. I'm
 not sure if this is the right thing to do here.

 Back to the main issue. `GZipMiddleware`, and any other middleware that
 alters the etag header, should also generate an etag when there is none.
 And `GZipMiddleware` needs to do it *before* it compresses the content
 (unless we fudge the timestamp).

 And generally speaking, if we're supposed to generate a different etag if
 content OR headers change, then shouldn't we be generating etags based on
 `response.serialize()`, not `response.content`?

 But I'm not convinced that `GZipMiddleware` (or any middleware) should
 alter existing etags at all. If we allow any middleware to alter an etag
 (or encourage by example), that middleware must also set etags when there
 are none and compare the new or updated etag against the `If-None-Match`
 header and return `HttpResponseNotModified` when they match (including
 moving cookies from the old response to the `HttpResponseNotModified`).
 This is repetitive code (basically half of
 `CommonMiddleware.process_response`) that shouldn't need to be repeated.

 Perhaps the creation of etags should be moved out of middleware and be
 taken care of in the `response.content` setter, and have it take into
 account the full HTTP message including headers? We would need to make
 sure that compresesd content is deterministic in this case (by fudging the
 timestamp), but middleware would no longer need to worry about altering or
 setting etags. If middleware (or view code) assigns to `response.content`,
 the etag would be updated (if etags are enabled).

 This would leave the issue of when to return an `HttpResponseNotModified`.
 I would argue that this should happen after all middleware is applied, and
 should not happen inside any middleware classes. This way, the etag of the
 final response (after having passed through all response middleware) is
 the one that is compared to the `If-None-Match` header, and there is no
 need for repetitive code in middleware to make this comparison and return
 `HttpResponseNotModified`.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/16035#comment:15>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to