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