ビューの条件付き処理

revision-up-to:17812 (1.4)

HTTP クライアントは様々なヘッダを送信することで、すでに閲覧されたリソースについ てサーバに伝えられます。 Web ページが (HTTP の GET リクエストを使って) 取得 されるとき、クライアントが既に取得しているデータを送信しないために使われます。 同じハンドラはすべての HTTP メソッド (POSTPUTDELETE など) に使われます。

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):
    ...

1 つの値を計算するだけのショートカット

一般的なルールとして、 ETag と最終変更日時の 両方を 計算する関数を提供できる なら、そうするべきです。任意の HTTP クライアントがどのヘッダを送るかは分からない ので、両方を扱えるよう用意すべきです。しかし、 1 つの値だけを計算するのは簡単 なので、 Django は ETag か最終変更日時の 1 つだけを計算するデコレータを提供して います。

django.views.decorators.http.etagdjango.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 を使う

両方の前提条件を検査するとき、 etaglast_modified デコレータを 繋いで使うば良いと思えるかもしれません。しかしこれは不正な動作を起こします。

# ダメコード。マネしないでね!
@etag(etag_func)
@last_modified(last_modified_func)
def my_view(request):
    # ...

# ダメコード終了

1 つめのデコレータは 2 つめについては何も知りません。 2 つめのデコレータが違う 方法で答えを出したとしても、 1 つめのデコレータはレスポンスが修正されないと 答えます。 condition デコレータは正しい動作をさせるために同時に両コール バック関数を使います。

このデコレータを他の HTTP メソッドに使う

condition デコレータは GETHEAD リクエスト以外にも使えます (この 場合 HEAD リクエストは GET と同じです) 。 POSTPUTDELETE リクエストのチェックを提供するために使えます。この場合、 “not modified” レスポンスは返されません。変更しようとしたリソースが、その間に すでに変わっていたことをクライアントに伝えます。

例えば、次のクライアントとサーバのやりとりを考えてみましょう:

  1. クライアントが /foo/ をリクエストします。
  2. サーバがコンテンツと ETag "abcd1234" を返します。
  3. クライアントが /foo/ の更新に HTTP PUT リクエストを送信します。更新 しようとしているバージョンを特定する If-Match: "abcd1234" ヘッダも送信 します。
  4. サーバはリソースが変更されたかどうかチェックします。 GET リクエストのとき と同じ方法 (同じ関数) で ETag を計算します。リソースが変更 されていた ときは、”前提条件失敗 (precondition failed)” を意味する 412 ステータスコード が返されます。
  5. 412 レスポンスを受信した後に、クライアントは /foo/GET リクエスト を送信します。コンテンツの変更前に、すでに変更されていたバージョンを取得 します。

この例が示す重要なことは、すべての状況において ETag および最終変更日時を計算する ために、同じ関数を使用できるということです。実際には同じ関数を 使うべき です。 そうすることで、いつでも同じ値が返されます。

ミドルウェアによる条件処理との比較

Django は、簡単で分かりやすい条件付き GET 処理を django.middleware.http.ConditionalGetMiddlewareCommonMiddleware によって提供しています。 確かに使いやすく、多くの状況に適していますが、それらミドルウェア機能性の一部には 高度な使用法に制限があります:

  • プロジェクトの全てのビューに適応されます。
  • レスポンスそのものの生成の助けになりません。重い処理になることがあります。
  • HTTP GET リクエストにのみ使えます。

問題にあわせて適したものを選ぶべきです。 ETag と変更日時を素早く計算でき、ある ビューがコンテンツの生成に時間がかかる場合、先述した condition デコレータの 使用を考慮すべきです。すべてがすでに高速化されているなら、ミドルウェアの使用が 相応しいでしょう。ビューが変更されていなければクライアントに送信されるネット ワークのトラフィック量は、さらに小さくなります。