diff options
| author | Malcolm Tredinnick <malcolm.tredinnick@gmail.com> | 2009-03-22 07:58:29 +0000 |
|---|---|---|
| committer | Malcolm Tredinnick <malcolm.tredinnick@gmail.com> | 2009-03-22 07:58:29 +0000 |
| commit | b203db6ec850fee9ad8f2e2c8873be986325572b (patch) | |
| tree | 349736a64d97e83f5817d79e7e12ad84973cdc0a /docs/topics/conditional-view-processing.txt | |
| parent | 5ac154e06568b9815e85b32f144ab4ee10190a61 (diff) | |
Fixed #5791 -- Added early-bailout support for views (ETags and Last-modified).
This provides support for views that can have their ETag and/or Last-modified
values computed much more quickly than the view itself. Supports all HTTP
verbs (not just GET).
Documentation and tests need a little more fleshing out (I'm not happy with the
documentation at the moment, since it's a bit backwards), but the functionality
is correct.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@10114 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'docs/topics/conditional-view-processing.txt')
| -rw-r--r-- | docs/topics/conditional-view-processing.txt | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt new file mode 100644 index 0000000000..95ad52878b --- /dev/null +++ b/docs/topics/conditional-view-processing.txt @@ -0,0 +1,134 @@ +.. _topics-conditional-processing: + +=========================== +Conditional View Processing +=========================== + +.. versionadded:: 1.1 + +HTTP clients can send a number of headers to tell the server about copies of a +resource that they have already seen. This is commonly used when retrieving a +web page (using an HTTP ``GET`` request) to avoid sending all the data for +something the client has already retrieved. However, the same headers can be +used for all HTTP methods (``POST``, ``PUT``, ``DELETE``, etc). + +For each page (response) that Django sends back from a view, it might provide +two HTTP headers: the ``ETag`` header and the ``Last-Modified`` header. These +headers are optional on HTTP responses. They can be set by your view function, +or you can rely on the :class:`~django.middleware.common.CommonMiddleware` +middleware to set the ``ETag`` header. + +When the client next requests the same resource, it might send along a header +such as `If-modified-since`_, containing the date of the last modification +time it was sent, or `If-none-match`_, containing the ``ETag`` it was sent. +If there is no match with the ETag, or if the resource has not been modified, +a 304 status code can be sent back, instead of a full response, telling the +client that nothing has changed. + +.. _If-none-match: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 +.. _If-modified-since: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 + +Django allows simple usage of this feature with +:class:`django.middleware.http.ConditionalGetMiddleware` and +:class:`~django.middleware.common.CommonMiddleware`. However, whilst being +easy to use and suitable for many situations, they both have limitations for +advanced usage: + + * They are applied globally to all views in your project + * They don't save you from generating the response itself, which may be + expensive + * They are only appropriate for HTTP ``GET`` requests. + +.. conditional-decorators: + +Decorators +========== + +When you need more fine-grained control you may use per-view conditional +processing functions. + +The decorators ``django.views.decorators.http.etag`` and +``django.views.decorators.http.last_modified`` each accept a user-defined +function that takes the same parameters as the view itself. The function +passed ``last_modified`` should return a standard datetime value specifying +the last time the resource was modified, or ``None`` if the resource doesn't +exist. The function passed to the ``etag`` decorator should return a string +representing the `Etag`_ for the resource, or ``None`` if it doesn't exist. + +.. _ETag: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11 + +For example:: + + # Compute the last-modified time from when the object was last saved. + @last_modified(lambda r, obj_id: MyObject.objects.get(pk=obj_id).update_time) + def my_object_view(request, obj_id): + # Expensive generation of response with MyObject instance + ... + +Of course, you can always use the non-decorator form if you're using Python +2.3 or don't like the decorator syntax:: + + def my_object_view(request, obj_id): + ... + my_object_view = last_modified(my_func)(my_object_view) + +Using the ``etag`` decorator is similar. + +In practice, though, you won't know if the client is going to send the +``Last-modified`` or the ``If-none-match`` header. If you can quickly compute +both values and want to short-circuit as often as possible, you'll need to use +the ``conditional`` decorator described below. + +HTTP allows to use both "ETag" and "Last-Modified" headers in your response. +Then a response is considered not modified only if the client sends both +headers back and they're both equal to the response headers. This means that +you can't just chain decorators on your view:: + + # Bad code. Don't do this! + @etag(etag_func) + @last_modified(last_modified_func) + def my_view(request): + # ... + + # End of bad code. + +The first decorator doesn't know anything about the second and might +answer that the response is not modified even if the second decorators would +determine otherwise. In this case you should use a more general decorator - +``django.views.decorator.http.condition`` that accepts two functions at once:: + + # The correct way to implement the above example + @condition(etag_func, last_modified_func) + def my_view(request): + # ... + +Using the decorators with other HTTP methods +============================================ + +The ``conditional`` decorator is useful for more than only ``GET`` and +``HEAD`` requests (``HEAD`` requests are the same as ``GET`` in this +situation). It can be used also to be used to provide checking for ``POST``, +``PUT`` and ``DELETE`` requests. In these situations, the idea isn't to return +a "not modified" response, but to tell the client that the resource they are +trying to change has been altered in the meantime. + +For example, consider the following exchange between the client and server: + + 1. Client requests ``/foo/``. + 2. Server responds with some content with an ETag of ``"abcd1234"``. + 3. Client sends and HTTP ``PUT`` request to ``/foo/`` to update the + resource. It sends an ``If-Match: "abcd1234"`` header to specify the + version it is trying to update. + 4. Server checks to see if the resource has changed, by computing the ETag + the same way it does for a ``GET`` request (using the same function). + If the resource *has* changed, it will return a 412 status code code, + meaning "precondition failed". + 5. Client sends a ``GET`` request to ``/foo/``, after receiving a 412 + response, to retrieve an updated version of the content before updating + it. + +The important thing this example shows is that the same functions can be used +to compute the ETag and last modification values in all situations. In fact, +you *should* use the same functions, so that the same values are returned +every time. + |
