conditional-view-processing.txt 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. ===========================
  2. Conditional View Processing
  3. ===========================
  4. HTTP clients can send a number of headers to tell the server about copies of a
  5. resource that they have already seen. This is commonly used when retrieving a
  6. Web page (using an HTTP ``GET`` request) to avoid sending all the data for
  7. something the client has already retrieved. However, the same headers can be
  8. used for all HTTP methods (``POST``, ``PUT``, ``DELETE``, etc.).
  9. For each page (response) that Django sends back from a view, it might provide
  10. two HTTP headers: the ``ETag`` header and the ``Last-Modified`` header. These
  11. headers are optional on HTTP responses. They can be set by your view function,
  12. or you can rely on the :class:`~django.middleware.http.ConditionalGetMiddleware`
  13. middleware to set the ``ETag`` header.
  14. When the client next requests the same resource, it might send along a header
  15. such as either `If-modified-since`_ or `If-unmodified-since`_, containing the
  16. date of the last modification time it was sent, or either `If-match`_ or
  17. `If-none-match`_, containing the last ``ETag`` it was sent.
  18. If the current version of the page matches the ``ETag`` sent by the client, or
  19. if the resource has not been modified, a 304 status code can be sent back,
  20. instead of a full response, telling the client that nothing has changed.
  21. Depending on the header, if the page has been modified or does not match the
  22. ``ETag`` sent by the client, a 412 status code (Precondition Failed) may be
  23. returned.
  24. .. _If-match: https://tools.ietf.org/html/rfc7232#section-3.1
  25. .. _If-none-match: https://tools.ietf.org/html/rfc7232#section-3.2
  26. .. _If-modified-since: https://tools.ietf.org/html/rfc7232#section-3.3
  27. .. _If-unmodified-since: https://tools.ietf.org/html/rfc7232#section-3.4
  28. When you need more fine-grained control you may use per-view conditional
  29. processing functions.
  30. .. _conditional-decorators:
  31. The ``condition`` decorator
  32. ===========================
  33. Sometimes (in fact, quite often) you can create functions to rapidly compute the ETag_
  34. value or the last-modified time for a resource, **without** needing to do all
  35. the computations needed to construct the full view. Django can then use these
  36. functions to provide an "early bailout" option for the view processing.
  37. Telling the client that the content has not been modified since the last
  38. request, perhaps.
  39. .. _ETag: https://tools.ietf.org/html/rfc7232#section-2.3
  40. These two functions are passed as parameters to the
  41. ``django.views.decorators.http.condition`` decorator. This decorator uses
  42. the two functions (you only need to supply one, if you can't compute both
  43. quantities easily and quickly) to work out if the headers in the HTTP request
  44. match those on the resource. If they don't match, a new copy of the resource
  45. must be computed and your normal view is called.
  46. The ``condition`` decorator's signature looks like this::
  47. condition(etag_func=None, last_modified_func=None)
  48. The two functions, to compute the ETag and the last modified time, will be
  49. passed the incoming ``request`` object and the same parameters, in the same
  50. order, as the view function they are helping to wrap. The function passed
  51. ``last_modified_func`` should return a standard datetime value specifying the
  52. last time the resource was modified, or ``None`` if the resource doesn't
  53. exist. The function passed to the ``etag`` decorator should return a string
  54. representing the `ETag`_ for the resource, or ``None`` if it doesn't exist.
  55. .. versionchanged:: 1.11
  56. In older versions, the return value from ``etag_func()`` was interpreted as
  57. the unquoted part of the ETag. That prevented the use of weak ETags, which
  58. have the format ``W/"<string>"``. The return value is now expected to be
  59. an ETag as defined by the specification (including the quotes), although
  60. the unquoted format is also accepted for backwards compatibility.
  61. Using this feature usefully is probably best explained with an example.
  62. Suppose you have this pair of models, representing a simple blog system::
  63. import datetime
  64. from django.db import models
  65. class Blog(models.Model):
  66. ...
  67. class Entry(models.Model):
  68. blog = models.ForeignKey(Blog)
  69. published = models.DateTimeField(default=datetime.datetime.now)
  70. ...
  71. If the front page, displaying the latest blog entries, only changes when you
  72. add a new blog entry, you can compute the last modified time very quickly. You
  73. need the latest ``published`` date for every entry associated with that blog.
  74. One way to do this would be::
  75. def latest_entry(request, blog_id):
  76. return Entry.objects.filter(blog=blog_id).latest("published").published
  77. You can then use this function to provide early detection of an unchanged page
  78. for your front page view::
  79. from django.views.decorators.http import condition
  80. @condition(last_modified_func=latest_entry)
  81. def front_page(request, blog_id):
  82. ...
  83. Shortcuts for only computing one value
  84. ======================================
  85. As a general rule, if you can provide functions to compute *both* the ETag and
  86. the last modified time, you should do so. You don't know which headers any
  87. given HTTP client will send you, so be prepared to handle both. However,
  88. sometimes only one value is easy to compute and Django provides decorators
  89. that handle only ETag or only last-modified computations.
  90. The ``django.views.decorators.http.etag`` and
  91. ``django.views.decorators.http.last_modified`` decorators are passed the same
  92. type of functions as the ``condition`` decorator. Their signatures are::
  93. etag(etag_func)
  94. last_modified(last_modified_func)
  95. We could write the earlier example, which only uses a last-modified function,
  96. using one of these decorators::
  97. @last_modified(latest_entry)
  98. def front_page(request, blog_id):
  99. ...
  100. ...or::
  101. def front_page(request, blog_id):
  102. ...
  103. front_page = last_modified(latest_entry)(front_page)
  104. Use ``condition`` when testing both conditions
  105. ------------------------------------------------
  106. It might look nicer to some people to try and chain the ``etag`` and
  107. ``last_modified`` decorators if you want to test both preconditions. However,
  108. this would lead to incorrect behavior.
  109. ::
  110. # Bad code. Don't do this!
  111. @etag(etag_func)
  112. @last_modified(last_modified_func)
  113. def my_view(request):
  114. # ...
  115. # End of bad code.
  116. The first decorator doesn't know anything about the second and might
  117. answer that the response is not modified even if the second decorators would
  118. determine otherwise. The ``condition`` decorator uses both callback functions
  119. simultaneously to work out the right action to take.
  120. Using the decorators with other HTTP methods
  121. ============================================
  122. The ``condition`` decorator is useful for more than only ``GET`` and
  123. ``HEAD`` requests (``HEAD`` requests are the same as ``GET`` in this
  124. situation). It can also be used to provide checking for ``POST``,
  125. ``PUT`` and ``DELETE`` requests. In these situations, the idea isn't to return
  126. a "not modified" response, but to tell the client that the resource they are
  127. trying to change has been altered in the meantime.
  128. For example, consider the following exchange between the client and server:
  129. 1. Client requests ``/foo/``.
  130. 2. Server responds with some content with an ETag of ``"abcd1234"``.
  131. 3. Client sends an HTTP ``PUT`` request to ``/foo/`` to update the
  132. resource. It also sends an ``If-Match: "abcd1234"`` header to specify
  133. the version it is trying to update.
  134. 4. Server checks to see if the resource has changed, by computing the ETag
  135. the same way it does for a ``GET`` request (using the same function).
  136. If the resource *has* changed, it will return a 412 status code,
  137. meaning "precondition failed".
  138. 5. Client sends a ``GET`` request to ``/foo/``, after receiving a 412
  139. response, to retrieve an updated version of the content before updating
  140. it.
  141. The important thing this example shows is that the same functions can be used
  142. to compute the ETag and last modification values in all situations. In fact,
  143. you **should** use the same functions, so that the same values are returned
  144. every time.
  145. Comparison with middleware conditional processing
  146. =================================================
  147. Django provides simple and straightforward conditional ``GET`` handling via
  148. :class:`django.middleware.http.ConditionalGetMiddleware`. While being easy to
  149. use and suitable for many situations, the middleware has limitations for
  150. advanced usage:
  151. * It's applied globally to all views in your project.
  152. * It doesn't save you from generating the response, which may be expensive.
  153. * It's only appropriate for HTTP ``GET`` requests.
  154. You should choose the most appropriate tool for your particular problem here.
  155. If you have a way to compute ETags and modification times quickly and if some
  156. view takes a while to generate the content, you should consider using the
  157. ``condition`` decorator described in this document. If everything already runs
  158. fairly quickly, stick to using the middleware and the amount of network
  159. traffic sent back to the clients will still be reduced if the view hasn't
  160. changed.