Djangoリダイレクトの究極のガイド
Django frameworkを使用してPython Webアプリケーションを構築する場合、ある時点でユーザーをあるURLから別のURLにリダイレクトする必要があります。
このガイドでは、HTTPリダイレクトとDjangoでそれらを処理する方法について知る必要があるすべてを学びます。 このチュートリアルの最後では、次のことを行います。
-
ユーザーをあるURLから別のURLにリダイレクトできる
-
一時的なリダイレクトと永続的なリダイレクトの違いを知る
-
リダイレクトを操作するときによくある落とし穴を避ける
このチュートリアルでは、https://docs.djangoproject.com/en/2.1/topics/http/views/[views]やhttps://docs.djangoprojectなどのDjangoアプリケーションの基本的な構成要素に精通していることを前提としています。 .com/en/2.1/topics/http/urls/[URLパターン]。
Djangoリダイレクト:非常に簡単な例
Djangoでは、ビューから + HttpResponseRedirect +`または `+ HttpResponsePermanentRedirect +`のインスタンスを返すことにより、ユーザーを別のURLにリダイレクトします。 これを行う最も簡単な方法は、モジュール `+ django.shortcuts +からhttps://docs.djangoproject.com/en/2.1/topics/http/shortcuts/#redirect [
+ redirect()+ ]関数を使用することです
。 例を示しましょう。
# views.py
from django.shortcuts import redirect
def redirect_view(request):
response = redirect('/redirect-success/')
return response
ビューにURLを指定して `+ redirect()`を呼び出すだけです。 これは、 ` HttpResponseRedirect +`クラスを返し、それからビューから戻ります。
リダイレクトを返すビューは、他のビューと同様に、 `+ urls.py +`に追加する必要があります。
# urls.py
from django.urls import path
from .views import redirect_view
urlpatterns = [
path('/redirect/', redirect_view)
# ... more URL patterns here
]
これがDjangoプロジェクトのメインの `+ urls.py `であると仮定すると、URL `/redirect/`は `/redirect-success/+`にリダイレクトされます。
URLのハードコーディングを回避するには、ビュー、URLパターン、またはモデルの名前を指定して `+ redirect()`を呼び出し、リダイレクトURLのハードコーディングを回避できます。 キーワード引数 ` permanent = True +`を渡すことにより、永続的なリダイレクトを作成することもできます。
この記事はここで終わるかもしれませんが、「Djangoリダイレクトの究極のガイド」と呼ぶことはできません。すぐに `+ redirect()`関数を詳しく見て、HTTPステータスコードとさまざまな ` HttpRedirectResponse +`クラスの詳細についても詳しく説明しますが、一歩戻って、基本的な質問。
リダイレクトする理由
そもそもなぜユーザーを別のURLにリダイレクトしたいのか疑問に思うかもしれません。 リダイレクトの意味を理解するには、フレームワークがデフォルトで提供する機能にDjango自体がどのようにリダイレクトを組み込むかを見てください。
-
ログインしていない場合、Django管理者など、認証が必要なURLを要求すると、Djangoはログインページにリダイレクトします。
-
ログインに成功すると、Djangoは最初にリクエストしたURLにリダイレクトします。
-
Django adminを使用してパスワードを変更すると、変更が成功したことを示すページにリダイレクトされます。
-
Django adminでオブジェクトを作成すると、Djangoはオブジェクトリストにリダイレクトします。
リダイレクトなしの代替実装はどのようになりますか? ユーザーがログインしてページを表示する必要がある場合は、「ここをクリックしてログインします」などのページを表示するだけで済みます。これは機能しますが、ユーザーにとっては不便です。
それ以外の場合、リダイレクトは単に便利な問題ではありません。 リダイレクトは、Webアプリケーションを介してユーザーを導くための不可欠な手段です。 オブジェクトの作成や削除など、副作用を伴う何らかの操作を実行した後、別のURLにリダイレクトして、誤って2回操作を実行しないようにすることをお勧めします。
このリダイレクトの使用例の1つにフォーム処理があります。この場合、ユーザーはフォームを正常に送信した後、別のURLにリダイレクトされます。 以下は、通常フォームをどのように処理するかを示すコードサンプルです。
1 from django import forms
2 from django.http import HttpResponseRedirect
3 from django.shortcuts import redirect, render
4
5 def send_message(name, message):
6 # Code for actually sending the message goes here
7
8 class ContactForm(forms.Form):
9 name = forms.CharField()
10 message = forms.CharField(widget=forms.Textarea)
11
12 def contact_view(request):
13 # The request method 'POST' indicates
14 # that the form was submitted
15 if request.method == 'POST': # 1
16 # Create a form instance with the submitted data
17 form = ContactForm(request.POST) # 2
18 # Validate the form
19 if form.is_valid(): # 3
20 # If the form is valid, perform some kind of
21 # operation, for example sending a message
22 send_message(
23 form.cleaned_data['name'],
24 form.cleaned_data['message']
25 )
26 # After the operation was successful,
27 # redirect to some other page
28 return redirect('/success/') # 4
29 else: # 5
30 # Create an empty form instance
31 form = ContactForm()
32
33 return render(request, 'contact_form.html', {'form': form})
このビューの目的は、ユーザーがメッセージを送信できる連絡先フォームを表示および処理することです。 手順を追って説明しましょう。
-
最初に、ビューは要求メソッドを調べます。 ユーザーがこのビューに接続されたURLにアクセスすると、ブラウザーは `+ GET +`リクエストを実行します。
-
ビューが `+ POST `リクエストで呼び出される場合、 ` POST `データは ` ContactForm +`オブジェクトのインスタンス化に使用されます。
-
フォームが有効な場合、フォームデータは `+ send_message()+`に渡されます。 この関数はこのコンテキストには関係ないため、ここでは示しません。
-
メッセージを送信した後、ビューはURL「/success/」へのリダイレクトを返します。 これは私たちが興味を持っているステップです。 簡単にするために、ここではURLがハードコーディングされています。 後でそれを回避する方法については後で説明します。
-
ビューが
+ GET +`リクエスト(正確には、 `+ POST +`リクエストではないあらゆる種類のリクエスト)を受信すると、 `+ ContactForm +`のインスタンスを作成し、 `+ django.shortcuts.renderを使用します()+ `で
+ contact_form.html + `テンプレートをレンダリングします。
ユーザーがリロードをヒットした場合、 /success/
URLのみがリロードされます。 リダイレクトがなければ、ページをリロードするとフォームが再送信され、別のメッセージが送信されます。
舞台裏:HTTPリダイレクトの仕組み
リダイレクトが理にかなっている理由はわかりましたが、どのように機能しますか? WebブラウザーのアドレスバーにURLを入力するとどうなるかを簡単に説明しましょう。
HTTPのクイックプライマー
パス「/hello/」を処理する「Hello World」ビューを持つDjangoアプリケーションを作成したと仮定します。 Django開発サーバーでアプリケーションを実行しているため、完全なURLは `+ http://127.0.0.1:8000/hello/+`です。
ブラウザにそのURLを入力すると、サーバーのIPアドレス「127.0.0.1」でポート「8000」に接続し、パス「/hello/」に対してHTTP「+ GET +」リクエストを送信します`。 サーバーはHTTP応答で応答します。
HTTPはテキストベースであるため、クライアントとサーバーの間を行き来するのは比較的簡単です。 コマンドラインツールhttps://curl.haxx.se/docs/manpage.html [+ curl +
]をオプション `+-include +`とともに使用して、ヘッダーを含む完全なHTTPレスポンスを確認できます。このような:
$ curl --include http://127.0.0.1:8000/hello/
HTTP/1.1 200 OK
Date: Sun, 01 Jul 2018 20:32:55 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 11
Hello World
ご覧のとおり、HTTP応答は、ステータスコードとステータスメッセージを含むステータス行で始まります。 ステータス行の後には、任意の数のHTTPヘッダーが続きます。 空の行は、ヘッダーの終わりと応答本文の始まりを示し、サーバーが送信する実際のデータが含まれます。
HTTPリダイレクトのステータスコード
リダイレクト応答はどのように見えますか? パス `/redirect/`が前述の `+ redirect_view()`によって処理されると仮定します。 ` curl `で ` http://127.0.0.1:8000/redirect/+`にアクセスすると、コンソールは次のようになります。
$ curl --include http://127.0.0.1:8000/redirect/
HTTP/1.1 302 Found
Date: Sun, 01 Jul 2018 20:35:34 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Content-Type: text/html; charset=utf-8
Location:/redirect-success/
X-Frame-Options: SAMEORIGIN
Content-Length: 0
2つの応答は似ているように見えますが、いくつかの重要な違いがあります。 リダイレクト:
-
異なるステータスコードを返します(
+ 302 +`と `+ 200 +
) -
相対URLを持つ `+ Location +`ヘッダーが含まれます
-
リダイレクト応答の本文が空であるため、空行で終了します
主な差別化要因はステータスコードです。 HTTP標準の仕様には、次のことが記載されています。
_ 302(Found)ステータスコードは、ターゲットリソースが一時的に別のURIの下にあることを示します。 リダイレクションは時々変更されるかもしれないので、クライアントは将来のリクエストに有効なリクエストURIを使い続けるべきです。 サーバーは、異なるURIのURI参照を含む応答にLocationヘッダーフィールドを生成する必要があります。 ユーザーエージェントは、自動リダイレクトにロケーションフィールド値を使用してもよい[MAY]。 (https://tools.ietf.org/html/rfc7231#section-6.4 [ソース]) _
つまり、サーバーが「302」のステータスコードを送信するたびに、クライアントに「ちょっと、今、探しているものはこの別の場所で見つかる」と言います。
仕様のキーフレーズは、「自動リダイレクトにLocationフィールドの値を使用できます」です。つまり、クライアントに別のURLを強制的にロードさせることはできません。 クライアントは、ユーザーの確認を待つか、URLをまったくロードしないかを選択できます。
リダイレクトは、 `+ 3xx `ステータスコードと ` Location `ヘッダーを持つ単なるHTTPレスポンスであることがわかりました。 ここで重要なことは、HTTPリダイレクトは古いHTTP応答と似ていますが、空の本文、3xxステータスコード、および ` Location +`ヘッダーがあることです。
それでおしまい。 これを一時的にDjangoに結び付けますが、最初に、その「+ 3xx +」ステータスコード範囲内の2種類のリダイレクトを見て、Web開発に関してそれらが重要な理由を見てみましょう。
一時的vs. 永続的なリダイレクト
HTTP標準では、すべてが+ 3xx の範囲にあるいくつかのリダイレクトステータスコードを指定しています。 最も一般的な2つのステータスコードは、「 301 Permanent Redirect +」と「+302 Found +」です。
ステータスコード「+302 Found +」は一時的なリダイレクトを示します。 一時的なリダイレクトでは、「現時点では、探しているものはこの別のアドレスで見つけることができます。」 「店舗は現在改装工事のため閉鎖されています。 角を曲がったところにある他の店に行ってください。」これは一時的なものであるため、次に買い物をするときに元の住所を確認します。
注意: HTTP 1.0では、ステータスコード302のメッセージは `+ Temporary Redirect `でした。 メッセージはHTTP 1.1で ` Found +`に変更されました。
名前が示すように、永続的なリダイレクトは永続的であると想定されています。 永続的なリダイレクトはブラウザに次のように伝えます。「探しているのはこのアドレスではなくなりました。 現在はこの新しい住所にあり、以前の住所に戻ることはありません。」
永続的なリダイレクトは、「移動しました。 新しい店がすぐ近くにあります。」この変更は永続的なものであるため、次回店舗に行きたいときには、新しい住所に直行します。
*注:*永続的なリダイレクトは、意図しない結果をもたらす可能性があります。 永続的なリダイレクトを使用する前にこのガイドを終了するか、「永続的なリダイレクトは永続的です」セクションに直接ジャンプしてください。
ブラウザは、リダイレクトを処理するときも同様に動作します。URLが永続的なリダイレクト応答を返すと、この応答はキャッシュされます。 ブラウザが次に古いURLに遭遇すると、リダイレクトを記憶し、新しいアドレスを直接要求します。
リダイレクトをキャッシュすると、不要なリクエストが保存され、ユーザーエクスペリエンスが向上し、高速になります。
さらに、一時リダイレクトと永続リダイレクトの区別は、検索エンジン最適化に関連しています。
Djangoのリダイレクト
リダイレクトは、 `+ 3xx `ステータスコードと ` Location +`ヘッダーを持つ単なるHTTPレスポンスであることがわかりました。
通常の `+ HttpResponse +`オブジェクトからこのような応答を自分で作成できます。
def hand_crafted_redirect_view(request):
response = HttpResponse(status=302)
response['Location'] = '/redirect/success/'
return response
このソリューションは技術的には正しいものですが、かなりの入力が必要です。
`+ HTTPResponseRedirect +`クラス
`+ HttpResponse `のサブクラスであるクラス ` HttpResponseRedirect +`を使用すると、入力の手間を省くことができます。 リダイレクトするURLを最初の引数としてクラスをインスタンス化するだけで、クラスは正しいステータスとLocationヘッダーを設定します:
def redirect_view(request):
return HttpResponseRedirect('/redirect/success/')
Pythonシェルで `+ HttpResponseRedirect +`クラスを再生して、何が得られているかを確認できます。
>>>
>>> from django.http import HttpResponseRedirect
>>> redirect = HttpResponseRedirect('/redirect/success/')
>>> redirect.status_code
302
>>> redirect['Location']
'/redirect/success/'
永続的なリダイレクト用のクラスもあり、これは適切に「+ HttpResponsePermanentRedirect 」という名前が付けられています。 ` HttpResponseRedirect `と同じように機能しますが、唯一の違いは、ステータスコードが `+301(Moved Permanently)`であることです。
*注意:*上記の例では、リダイレクトURLはハードコードされています。 URLをハードコーディングすることは悪い習慣です。URLが変更された場合、すべてのコードを検索して、出現箇所を変更する必要があります。 それを修正しましょう!
https://docs.djangoproject.com/en/2.1/ref/urlresolvers/#reverse [+ django.urls.reverse()+
]を使用してURLを作成できますが、より便利な方法があります次のセクションで説明します。
`+ redirect()+`関数
生活を楽にするために、Djangoは、はじめに紹介した汎用性の高いショートカット機能を提供しています:https://docs.djangoproject.com/en/2.1/topics/http/shortcuts/#redirect[`django.shortcuts。 redirect() `]。
この関数を呼び出すには:
-
https://docs.djangoproject.com/en/2.1/ref/models/instances/#get-absolute-url [
+ get_absolute_url()+
]メソッドを使用したモデルインスタンス、またはその他のオブジェクト -
URLまたはビュー名と位置引数および/またはキーワード引数
-
URL
引数をURLに変換し、 `+ HTTPResponseRedirect `を返すために適切な手順を実行します。 ` permanent = True `を渡すと、 ` HttpResponsePermanentRedirect +`のインスタンスが返され、永続的なリダイレクトが行われます。
さまざまなユースケースを説明する3つの例を次に示します。
-
モデルを渡す:
from django.shortcuts import redirect
def model_redirect_view(request):
product = Product.objects.filter(featured=True).first()
return redirect(product)
+ + redirect()+`は `+ product.get_absolute_url()+`を呼び出し、その結果をリダイレクトターゲットとして使用します。 指定されたクラス(この場合は `+ Product +
)に `+ get_absolute_url()`メソッドがない場合、これは ` TypeError +`で失敗します。
. URL名と引数を渡す:
from django.shortcuts import redirect
def fixed_featured_product_view(request):
...
product_id = settings.FEATURED_PRODUCT_ID
return redirect('product_detail', product_id=product_id)
+ `+ redirect()+`は、指定された引数を使用してURLを反転しようとします。 この例では、URLパターンに次のようなパターンが含まれていることを前提としています。
path('/product/<product_id>/', 'product_detail_view', name='product_detail')
-
URLを渡す:
from django.shortcuts import redirect
def featured_product_view(request):
return redirect('/products/42/')
+ `+ redirect()`は、 `/`または `。+`を含む文字列をURLとして扱い、リダイレクトターゲットとして使用します。
`+ RedirectView +`クラスベースビュー
リダイレクトを返すだけのビューがある場合は、クラスベースのビューhttps://docs.djangoproject.com/en/2.1/ref/class-based-views/base/#redirectview[`+ django.views.generic.base.RedirectView + `]。
さまざまな属性を使用して、ニーズに合わせて `+ RedirectView +`を調整できます。
クラスに `+ .url +`属性がある場合、リダイレクトURLとして使用されます。 文字列フォーマットのプレースホルダーは、URLの名前付き引数に置き換えられます。
# urls.py
from django.urls import path
from .views import SearchRedirectView
urlpatterns = [
path('/search/<term>/', SearchRedirectView.as_view())
]
# views.py
from django.views.generic.base import RedirectView
class SearchRedirectView(RedirectView):
url = 'https://google.com/?q=%(term)s'
URLパターンは引数 `+ term `を定義します。これはリダイレクトURLを構築するために ` SearchRedirectView `で使用されます。 アプリケーションのパス `/search/kittens/`は、 ` https://google.com/?q = kittens +`にリダイレクトします。
+ urldirect`属性を上書きするために
+ RedirectView + をサブクラス化する代わりに、
+ urlpatterns + でキーワード引数
+ url + を
+ as_view()+ `に渡すこともできます。
#urls.py
from django.views.generic.base import RedirectView
urlpatterns = [
path('/search/<term>/',
RedirectView.as_view(url='https://google.com/?q=%(term)s')),
]
また、 `+ get_redirect_url()+`を上書きして、完全にカスタムの動作を取得することもできます。
from random import choice
from django.views.generic.base import RedirectView
class RandomAnimalView(RedirectView):
animal_urls = ['/dog/', '/cat/', '/parrot/']
is_permanent = True
def get_redirect_url(*args, **kwargs):
return choice(self.animal_urls)
このクラスベースのビューは、 `+ .animal_urls +`からランダムに選択されたURLにリダイレクトします。
`+ django.views.generic.base.RedirectView +`には、カスタマイズのためのフックがいくつか用意されています。 これが完全なリストです:
-
+ .url +
この属性が設定されている場合、リダイレクト先のURLを含む文字列である必要があります。 `%(name)s +`のような文字列フォーマットのプレースホルダーが含まれている場合、ビューに渡されるキーワード引数を使用して展開されます。 -
+ .pattern_name +
+この属性が設定されている場合、リダイレクト先のURLパターンの名前にする必要があります。 ビューに渡される位置引数およびキーワード引数は、URLパターンを逆にするために使用されます。 -
+ .permanent +
この属性が ` True `の場合、ビューは永続的なリダイレクトを返します。 デフォルトは ` False +`です。 -
+ .query_string +
この属性が ` True `の場合、ビューは指定されたクエリ文字列をリダイレクトURLに追加します。 デフォルトの ` False +`の場合、クエリ文字列は破棄されます。 -
+ get_redirect_url(* args、** kwargs)+
このメソッドは、リダイレクトURLの構築を担当します。 このメソッドが「 None 」を返す場合、ビューは「+410 Gone +」ステータスを返します。 +デフォルトの実装は最初に ` .url `をチェックします。 ビューに渡された名前付きURLパラメーターを使用して名前付き書式指定子を展開し、「。url 」を「古いスタイル」のhttps://realpython.com/python-string-formatting/[format string]として処理します。 + ` .url `が設定されていない場合、 ` .pattern_name +`が設定されているかどうかをチェックします。 存在する場合は、それを使用して、受け取った位置引数およびキーワード引数でURLを反転します。 +このメソッドを上書きすることにより、任意の方法でその動作を変更できます。 URLを含む文字列を返すことを確認してください。
*注意:*クラスベースのビューは強力な概念ですが、頭を包むのが少し難しい場合があります。 コードの流れをたどるのが比較的簡単な通常の関数ベースのビューとは異なり、クラスベースのビューは、ミックスインと基本クラスの複雑な階層で構成されています。
クラスベースのビュークラスを理解するための優れたツールは、Webサイトhttp://ccbv.co.uk/[Classy Class-Based Views]です。
この単純な関数ベースのビューを使用して、上記の例の `+ RandomAnimalView +`の機能を実装できます。
from random import choice
from django.shortcuts import redirect
def random_animal_view(request):
animal_urls = ['/dog/', '/cat/', '/parrot/']
return redirect(choice(animal_urls))
ご覧のように、クラスベースのアプローチは、隠れた複雑さを追加する一方で、明らかな利点を提供しません。 それは疑問を提起します:いつ `+ RedirectView +`を使うべきですか?
`+ urls.py `にリダイレクトを直接追加したい場合、 ` RedirectView `を使用するのが理にかなっています。 しかし、 ` get_redirect_url +`を上書きしていることに気付いた場合、関数ベースのビューは理解しやすく、将来の機能強化のためにより柔軟になるかもしれません。
高度な使用法
おそらく `+ django.shortcuts.redirect()+`を使用したいことがわかったら、別のURLへのリダイレクトは非常に簡単です。 しかし、それほど明白ではない高度なユースケースがいくつかあります。
リダイレクトでパラメーターを渡す
場合によっては、リダイレクト先のビューにいくつかのパラメーターを渡したいことがあります。 最適なオプションは、リダイレクトURLのクエリ文字列でデータを渡すことです。つまり、次のようなURLにリダイレクトします。
http://example.com/redirect-path/?parameter=value
`+ some_view()`から ` product_view()`にリダイレクトしたいが、オプションのパラメーター ` category +`を渡すと仮定します。
from django.urls import reverse
from urllib.parse import urlencode
def some_view(request):
...
base_url = reverse('product_view') # 1/products/
query_string = urlencode({'category': category.id}) # 2 category=42
url = '{}?{}'.format(base_url, query_string) # 3/products/?category=42
return redirect(url) # 4
def product_view(request):
category_id = request.GET.get('category') # 5
# Do something with category_id
この例のコードは非常に高密度なので、手順を追って説明しましょう。
-
最初に、 `+ django.urls.reverse()`を使用して、 ` product_view()+`へのURLマッピングを取得します。
-
次に、クエリ文字列を作成する必要があります。 それが疑問符の後の部分です。 特殊文字を適切にエンコードするため、 `+ urllib.urlparse.urlencode()+`を使用することをお勧めします。
-
ここで、 `+ base_url `と ` query_string +`を疑問符で結合する必要があります。 そのためにフォーマット文字列は問題なく機能します。
-
最後に、 `+ url `を ` django.shortcuts.redirect()+`またはリダイレクト応答クラスに渡します。
-
リダイレクト先の `+ product_view()`では、パラメーターは ` request.GET `辞書で利用できます。 パラメーターが欠落している可能性があるため、 ` requests.GET ['category'] `ではなく、 ` requests.GET.get( 'category')`を使用する必要があります。 前者はパラメータが存在しない場合は ` None +`を返し、後者は例外を発生させます。
*注意:*クエリ文字列から読み取るデータは必ず検証してください。 リダイレクトURLを作成したため、このデータは管理下にあるように思われるかもしれません。
実際には、リダイレクトはユーザーによって操作される可能性があり、他のユーザー入力のように信頼されてはなりません。 適切な検証を行わないと、https://www.owasp.org/index.php/Top_10-2017_A5-Broken_Access_Control [攻撃者が不正アクセスを取得できる可能性があります]。
特別なリダイレクトコード
Djangoは、ステータスコード `+ 301 `および ` 302 `のHTTP応答クラスを提供します。 これらはほとんどのユースケースをカバーするはずですが、ステータスコード「+303 +」、「 307+」、または「308」を返す必要がある場合、独自のレスポンスクラスを簡単に作成できます。 `+ HttpResponseRedirectBase `をサブクラス化し、 ` status_code +`属性を上書きするだけです:
class HttpResponseTemporaryRedirect(HttpResponseRedirectBase):
status_code = 307
または、 `+ django.shortcuts.redirect()+`メソッドを使用して、応答オブジェクトを作成し、戻り値を変更できます。 この方法は、ビューまたはURLの名前、またはリダイレクト先のモデルがある場合に意味があります。
def temporary_redirect_view(request):
response = redirect('success_view')
response.status_code = 307
return response
*注意:*ステータスコードが「+ 3xx 」の範囲にある3番目のクラスがあります。ステータスコードが「+304」の「+ HttpResponseNotModified +」です。 コンテンツURLが変更されておらず、クライアントがキャッシュバージョンを使用できることを示しています。
「+304 Not Modified +」レスポンスはキャッシュされたバージョンのURLにリダイレクトされると主張することもできますが、それは少しばかりです。 その結果、HTTP標準のhttps://tools.ietf.org/html/rfc7231#section-6.4[「Redirection3xx」セクション]にはリストされなくなりました。
落とし穴
リダイレクトしないだけのリダイレクト
`+ django.shortcuts.redirect()+`の単純さは欺くことができます。 関数自体はリダイレクトを実行しません。リダイレクト応答オブジェクトを返すだけです。 ビューから(またはミドルウェアで)この応答オブジェクトを返す必要があります。 それ以外の場合、リダイレクトは行われません。
ただし、 `+ redirect()+`を呼び出すだけでは不十分であることがわかっていても、単純なリファクタリングを行うことで、このバグを動作中のアプリケーションに簡単に導入できます。 以下に例を示します。
ショップを建設していて、製品を表示するビューを持っていると仮定しましょう。 製品が存在しない場合、ホームページにリダイレクトします:
def product_view(request, product_id):
try:
product = Product.objects.get(pk=product_id)
except Product.DoesNotExist:
return redirect('/')
return render(request, 'product_detail.html', {'product': product})
次に、製品の顧客レビューを表示する2番目のビューを追加します。 また、存在しない製品のホームページにリダイレクトする必要があるため、最初のステップとして、この機能を `+ product_view()`からヘルパー関数 ` get_product_or_redirect()+`に抽出します。
def get_product_or_redirect(product_id):
try:
return Product.objects.get(pk=product_id)
except Product.DoesNotExist:
return redirect('/')
def product_view(request, product_id):
product = get_product_or_redirect(product_id)
return render(request, 'product_detail.html', {'product': product})
残念ながら、リファクタリング後、リダイレクトは機能しなくなりました。
リダイレクトを停止しないリダイレクト
リダイレクトを処理する場合、URL AがURL Aへのリダイレクトを返すURL Bを指すリダイレクトをURL Aに返すなどして、誤ってリダイレクトループを作成する可能性があります。 ほとんどのHTTPクライアントは、この種類のリダイレクトループを検出し、多数のリクエストの後にエラーメッセージを表示します。
残念ながら、この種のバグは、サーバー側ではすべてが正常に見えるため、見つけるのが難しい場合があります。 ユーザーが問題について苦情を言わない限り、何かが間違っている可能性があることを示す唯一の兆候は、1つのクライアントから多数のリクエストがあり、その結果すべてが迅速に連続してリダイレクトレスポンスになりますが、「+ 200 OK +」のレスポンスがないことです状態。
リダイレクトループの簡単な例を次に示します。
def a_view(request):
return redirect('another_view')
def another_view(request):
return redirect('a_view')
この例は原理を示していますが、非常に単純です。 現実の世界で遭遇するリダイレクトループは、おそらく見つけにくいでしょう。 より複雑な例を見てみましょう:
def featured_products_view(request):
featured_products = Product.objects.filter(featured=True)
if len(featured_products == 1):
return redirect('product_view', kwargs={'product_id': featured_products[0].id})
return render(request, 'featured_products.html', {'product': featured_products})
def product_view(request, product_id):
try:
product = Product.objects.get(pk=product_id, in_stock=True)
except Product.DoesNotExist:
return redirect('featured_products_view')
return render(request, 'product_detail.html', {'product': product})
`+ featured_products_view()`はすべての注目製品、つまり ` .featured `が ` True `に設定された ` Product `インスタンスを取得します。 特色のある製品が1つだけ存在する場合、 ` product_view()`に直接リダイレクトされます。 それ以外の場合は、 ` featured_products +`クエリセットを使用してテンプレートをレンダリングします。
`+ product_view +`は前のセクションからおなじみに見えますが、2つの小さな違いがあります。
-
ビューは、「。in_stock +」を「 True 」に設定することで示される、在庫の「 Product +」を取得しようとします。
-
商品が在庫にない場合、ビューは `+ featured_products_view()+`にリダイレクトします。
このロジックは、ショップが成功の犠牲者になり、現在お持ちの機能製品が在庫切れになるまで正常に機能します。 `+ .in_stock `を ` False `に設定したが、 ` .featured `を ` False `に設定することを忘れると、 ` feature_product_view()+`への訪問者はリダイレクトループでスタックします。 。
この種のバグを防ぐ防弾の方法はありませんが、適切な出発点は、リダイレクト先のビューがリダイレクト自体を使用しているかどうかを確認することです。
永続的なリダイレクトは永続的です
パーマネントリダイレクトは悪いタトゥーのようなものです。それらは当時は良いアイデアのように思えるかもしれませんが、一度間違いだと気づいたら、それらを取り除くのは非常に困難です。
ブラウザがURLの永続的なリダイレクト応答を受信すると、この応答を無期限にキャッシュします。 将来、古いURLをリクエストするたびに、ブラウザはそのURLをロードせずに、新しいURLを直接ロードします。
ブラウザーに、永続的なリダイレクトを返したURLをロードするように説得するのは非常に難しい場合があります。 Google Chromeは、リダイレクトのキャッシュに関して特に積極的です。
なぜこれが問題になるのでしょうか?
Djangoを使用してWebアプリケーションを構築するとします。 ドメインを「+ myawesomedjangowebapp.com 」で登録します。 最初のステップとして、ブログアプリを ` https://myawesomedjangowebapp.com/blog/+`にインストールして、起動メーリングリストを作成します。
サイトの「+ https://myawesomedjangowebapp.com/」のホームページはまだ作成中なので、「 https://myawesomedjangowebapp.com/blog/+」にリダイレクトします。 パーマネントリダイレクトはキャッシュされると聞いたため、パーマネントリダイレクトを使用することにしました。キャッシュはGoogleの検索結果のランキングの要因であるため、キャッシングはより高速になります。
結局のところ、あなたは優れた開発者であるだけでなく、才能のある作家でもあります。 ブログが人気になり、ローンチメーリングリストが拡大します。 数か月後、アプリの準備が整います。 現在、光沢のあるホームページがあり、最終的にリダイレクトを削除します。
特別な割引コードを記載したアナウンスメールを、かなり大きなローンチメーリングリストに送信します。 身を乗り出し、サインアップ通知が届くのを待ちます。
恐ろしいことに、メールボックスには、アプリにアクセスしたいが、常にブログにリダイレクトされている混乱した訪問者からのメッセージがいっぱいになります。
何が起きたの? あなたのブログの読者は、 `+ https://myawesomedjangowebapp.com/blog/`へのリダイレクトがまだアクティブなときに ` https://myawesomedjangowebapp.com/+`にアクセスしていました。 これは永続的なリダイレクトであるため、ブラウザにキャッシュされていました。
ローンチアナウンスメールのリンクをクリックしても、ブラウザーは新しいホームページを確認することをせず、ブログに直接アクセスしました。 成功したローンチを祝うのではなく、ユーザーに `+ chrome://net-internals +`をいじってブラウザのキャッシュをリセットする方法をユーザーに指示するのに忙しくしています。
永続的なリダイレクトの永続的な性質は、ローカルマシンでの開発中に噛みつくこともあります。 myawesomedjangowebapp.comにその運命的なパーマネントリダイレクトを実装した瞬間に巻き戻しましょう。
開発サーバーを起動して、「+ http://127.0.0.1:8000/」を開きます。 意図したとおり、アプリはブラウザを ` http://127.0.0.1:8000/blog/+`にリダイレクトします。 作業に満足したら、開発サーバーを停止して昼食に行きます。
あなたは完全な腹を持って戻り、クライアントの仕事に取り組む準備ができています。 クライアントはホームページの簡単な変更を望んでいるので、クライアントのプロジェクトをロードして開発サーバーを起動します。
しかし、ここで何が起こっているのでしょうか? ホームページは壊れており、404を返します! 午後の不調により、クライアントのプロジェクトには存在しない `+ http://127.0.0.1:8000/blog/+`にリダイレクトされていることに気付くまでに時間がかかります。
ブラウザにとって、URL「+ http://127.0.0.1:8000/」が完全に異なるアプリケーションに対応するようになったことは問題ではありません。 ブラウザにとって重要なのは、過去に一度このURLが ` http://127.0.0.1:8000/blog/+`への永続的なリダイレクトを返したことだけです。
この話から得られることは、今後二度と使用するつもりのないURLに対してのみ永続的なリダイレクトを使用することです。 恒久的なリダイレクトの場所がありますが、その結果に注意する必要があります。
本当に恒久的なリダイレクトが必要であると確信している場合でも、最初に一時的なリダイレクトを実装し、すべてが意図したとおりに機能することを100%確信できたら、恒久的な従兄弟に切り替えることをお勧めします。
未検証のリダイレクトはセキュリティを侵害する可能性があります
セキュリティの観点から、リダイレクトは比較的安全な手法です。 攻撃者はリダイレクトを使用してWebサイトをハッキングすることはできません。 結局のところ、リダイレクトは、攻撃者がブラウザのアドレスバーに入力するだけのURLにリダイレクトするだけです。
ただし、リダイレクトURLとして適切に検証せずに、URLパラメーターなどの何らかの種類のユーザー入力を使用すると、フィッシング攻撃のために攻撃者に悪用される可能性があります。 この種のリダイレクトは、https://cwe.mitre.org/data/definitions/601.html [オープンまたは未検証のリダイレクト]と呼ばれます。
ユーザー入力から読み取られたURLにリダイレクトするための正当なユースケースがあります。 代表的な例は、Djangoのログインビューです。 ログイン後にユーザーがリダイレクトされるページのURLを含むURLパラメーター `+ next +`を受け入れます。 ログイン後にユーザーをプロファイルにリダイレクトするには、URLは次のようになります。
https://myawesomedjangowebapp.com/login/?next=/profile/
Djangoは `+ next +`パラメータを検証しますが、しばらくは検証しないと仮定します。
検証がなければ、攻撃者はURLを作成して、ユーザーを自分の管理下にあるWebサイトにリダイレクトする可能性があります。例
https://myawesomedjangowebapp.com/login/?next=https://myawesomedjangowebapp.co/profile/
Webサイト `+ myawesomedjangowebapp.co +`はエラーメッセージを表示し、ユーザーに資格情報の再入力を促します。
オープンリダイレクトを回避する最善の方法は、リダイレクトURLを作成するときにユーザー入力を使用しないことです。
URLがリダイレクトに対して安全であることを確認できない場合は、関数 `+ django.utils.http.is_safe_url()+`を使用して検証できます。 docstringはその使用法を非常によく説明しています:
_ _
+ is_safe_url(url、host = None、allowed_hosts = None、require_https = False)+
URLが安全なリダイレクトである場合(つまり、 別のホストを指すものではなく、安全なスキームを使用しています)。 空のURLでは常に `+ False `を返します。 「 require_https 」が「 True 」の場合、デフォルトの「 False +」の「http」および「https」とは対照的に、「https」のみが有効なスキームと見なされます。 (https://github.com/django/django/blob/53a3d2b2454ff9a612a376f58bb7c61733f82d12/django/utils/http.py#L280 [ソース]) _ _
いくつかの例を見てみましょう。
相対URLは安全と見なされます。
>>>
>>> # Import the function first.
>>> from django.utils.http import is_safe_url
>>> is_safe_url('/profile/')
True
別のホストを指すURLは一般に安全とは見なされません。
>>>
>>> is_safe_url('https://myawesomedjangowebapp.com/profile/')
False
ホストが `+ allowed_hosts +`で提供されている場合、別のホストを指すURLは安全と見なされます。
>>>
>>> is_safe_url('https://myawesomedjangowebapp.com/profile/',
... allowed_hosts={'myawesomedjangowebapp.com'})
True
引数 `+ require_https `が ` True `の場合、 ` http +`スキームを使用したURLは安全とは見なされません。
>>>
>>> is_safe_url('http://myawesomedjangowebapp.com/profile/',
... allowed_hosts={'myawesomedjangowebapp.com'},
... require_https=True)
False
概要
これは、DjangoでのHTTPリダイレクトに関するこのガイドのまとめです。 おめでとうございます:これで、リダイレクトのあらゆる側面について、HTTPプロトコルの低レベルの詳細からDjangoでの高レベルの方法に至るまで触れました。
HTTPリダイレクトが内部でどのように見えるか、さまざまなステータスコードが何であるか、永続リダイレクトと一時リダイレクトがどのように異なるかを学びました。 この知識はDjangoに固有のものではなく、あらゆる言語でのWeb開発に役立ちます。
リダイレクト応答クラス `+ HttpResponseRedirect `と ` HttpResponsePermanentRedirect `を使用するか、便利な関数 ` django.shortcuts.redirect()+`を使用して、Djangoでリダイレクトを実行できるようになりました。 いくつかの高度なユースケースのソリューションを見て、一般的な落とし穴を避ける方法を知っています。
HTTPリダイレクトについてさらに質問がある場合は、下にコメントを残してください。