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