| revision-up-to: | 17812 (1.4) |
|---|
HTTP クライアントは様々なヘッダを送信することで、すでに閲覧されたリソースについ
てサーバに伝えられます。 Web ページが (HTTP の GET リクエストを使って) 取得
されるとき、クライアントが既に取得しているデータを送信しないために使われます。
同じハンドラはすべての HTTP メソッド (POST 、 PUT 、 DELETE など)
に使われます。
Django がビューから返す、各ページ (レスポンス) は HTTP ヘッダを 2 つ提供します。
ETag ヘッダと Last-Modified です。これらのヘッダは HTTP レスポンスの
オプションです。ビュー関数によって設定するか、 ETag ヘッダを設定する
CommonMiddleware ミドルウェアを使ってでき
ます。
クライアントが同じリソースを次にリクエストしたとき送られるヘッダは例えば、
If-modified-since や、 If-none-match などになるでしょう。
If-modified-since はリソースが送られたときの最終変更日時を保持し、
If-none-match はリソースが送られたときの ETag を保持しています。ページの
現在のバージョンがクライアントに送信された ETag に一致するか、リソースが変更
されていない場合は、 304 ステータスコードが返されます。クライアントにリソースの
変更が無いことを伝えるため、フルレスポンスの代わりに返されます。
より細かいコントロールが必要なら、ビューごとに条件付き処理関数を使ってください。
condition デコレータ¶リソースへの ETag の値、最終変更日時を素早く算出する関数を、ビューをすべて コーディングする必要 なしに 作れます。ときおり (実際には頻繁に) 役立ちます。 Django ではそれらの関数を使用することで、ビューの処理のための “早期救済 (early bailout)” オプションを提供できます。クライアントに、コンテンツの内容が 最後のリクエスト以来修正されていないと伝えます。
これら 2 つの関数は、パラメータとして django.views.decorators.http.condition
デコレータに渡されます。HTTP リクエストのヘッダがコンテンツ上のものとマッチする
とき、このデコレータは 2 つの関数を使って解決します。 (両者を素早く、容易に
計算できない場合、どちらか 1 つを提供する必要があります) 。マッチしない場合は、
リソースの新しいコピーができ、通常のビューが呼ばれます。
condition デコレータは以下のようになります:
condition(etag_func=None, last_modified_func=None)
ETag と最終変更日時を計算する 2 つの関数は、 request オブジェクトとその他の
同じ引数を、同じ順序で、ラップされるビュー関数と同様に受け取ります。
last_modified_func に渡される関数が返すべき値は、リソースが変更された時間を
特定する標準の datetime 値です。リソースが存在しないなら None です。
etag デコレータに渡される関数が返すべき値は、リソースへの Etag を表す
文字列です。リソースが存在しないなら None です。
例で、この機能の有用な使い方を説明します。以下のような 2 つのモデルがあると仮定 します。これは単純なブログシステムを表現しています:
import datetime
from django.db import models
class Blog(models.Model):
...
class Entry(models.Model):
blog = models.ForeignKey(Blog)
published = models.DateTimeField(default=datetime.datetime.now)
...
最新のブログ記事を表示するフロントページが、新しいブログ記事を追加したときのみ
変更されるとすると、このときの最終変更日時は非常に素早く計算できます。 その
ブログに関連付けられた全エントリのうち、最新の published 日時が必要です。
以下のようになります:
def latest_entry(request, blog_id):
return Entry.objects.filter(blog=blog_id).latest("published").published
この関数を使って、フロントページビューが変更されていないことを知らせられます:
from django.views.decorators.http import condition
@condition(last_modified_func=latest_entry)
def front_page(request, blog_id):
...
一般的なルールとして、 ETag と最終変更日時の 両方を 計算する関数を提供できる なら、そうするべきです。任意の HTTP クライアントがどのヘッダを送るかは分からない ので、両方を扱えるよう用意すべきです。しかし、 1 つの値だけを計算するのは簡単 なので、 Django は ETag か最終変更日時の 1 つだけを計算するデコレータを提供して います。
django.views.decorators.http.etag と
django.views.decorators.http.last_modified デコレータには同じ種類の関数を
渡します。 condition デコレータと同じです。こんな感じです:
etag(etag_func)
last_modified(last_modified_func)
両デコレータのうちの、 last-modified 関数を使用して前述の例を書けます:
@last_modified(latest_entry)
def front_page(request, blog_id):
...
...あるいは:
def front_page(request, blog_id):
...
front_page = last_modified(latest_entry)(front_page)
condition を使う¶両方の前提条件を検査するとき、 etag 、 last_modified デコレータを
繋いで使うば良いと思えるかもしれません。しかしこれは不正な動作を起こします。
# ダメコード。マネしないでね!
@etag(etag_func)
@last_modified(last_modified_func)
def my_view(request):
# ...
# ダメコード終了
1 つめのデコレータは 2 つめについては何も知りません。 2 つめのデコレータが違う
方法で答えを出したとしても、 1 つめのデコレータはレスポンスが修正されないと
答えます。 condition デコレータは正しい動作をさせるために同時に両コール
バック関数を使います。
condition デコレータは GET 、 HEAD リクエスト以外にも使えます (この
場合 HEAD リクエストは GET と同じです) 。 POST 、 PUT 、
DELETE リクエストのチェックを提供するために使えます。この場合、
“not modified” レスポンスは返されません。変更しようとしたリソースが、その間に
すでに変わっていたことをクライアントに伝えます。
例えば、次のクライアントとサーバのやりとりを考えてみましょう:
/foo/ をリクエストします。"abcd1234" を返します。/foo/ の更新に HTTP PUT リクエストを送信します。更新
しようとしているバージョンを特定する If-Match: "abcd1234" ヘッダも送信
します。GET リクエストのとき
と同じ方法 (同じ関数) で ETag を計算します。リソースが変更 されていた
ときは、”前提条件失敗 (precondition failed)” を意味する 412 ステータスコード
が返されます。/foo/ に GET リクエスト
を送信します。コンテンツの変更前に、すでに変更されていたバージョンを取得
します。この例が示す重要なことは、すべての状況において ETag および最終変更日時を計算する ために、同じ関数を使用できるということです。実際には同じ関数を 使うべき です。 そうすることで、いつでも同じ値が返されます。
Django は、簡単で分かりやすい条件付き GET 処理を
django.middleware.http.ConditionalGetMiddleware と
CommonMiddleware によって提供しています。
確かに使いやすく、多くの状況に適していますが、それらミドルウェア機能性の一部には
高度な使用法に制限があります:
GET リクエストにのみ使えます。問題にあわせて適したものを選ぶべきです。 ETag と変更日時を素早く計算でき、ある
ビューがコンテンツの生成に時間がかかる場合、先述した condition デコレータの
使用を考慮すべきです。すべてがすでに高速化されているなら、ミドルウェアの使用が
相応しいでしょう。ビューが変更されていなければクライアントに送信されるネット
ワークのトラフィック量は、さらに小さくなります。
Oct 26, 2017