この記事は「Django Advent Calendar 2016 - Qiita」の13日目の記事です。
Djangoにはミドルウェアというフレームワークがあるため、リクエスト/レスポンス処理をフックして処理を追加できます。
Middleware | Django documentation | Django
また、DjangoはWSGI規格に沿っていることから、WSGIミドルウェアでも処理を追加できます。
第3回 WSGIミドルウェアの作成:WSGIとPythonでスマートなWebアプリケーション開発を|gihyo.jp … 技術評論社
そこで今回は、
をそれぞれ作成し、Djangoに組み込んでみます。
目次
環境
http://localhost:8000/myapp/
にアクセスすると「Hello world」を表示するDjangoアプリを用意します。
myproject/urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^myapp/', include('myapp.urls', 'my')),
]
myapp/urls.py
urlpatterns = [
url(r'^$',
MyAppView.as_view(),
name='index',
),
]
myapp/views.py
from django.views import View
from django.http import HttpResponse
class MyAppView(View):
def get(self, request, *args, **kwargs):
print('called: MyAppView')
return HttpResponse('Hello world')
Django1.10より、Djangoミドルウェアの書き方が変更されました。そこで今回は1.10以降のDjangoミドルウェアの作法に従って書いてみます。
今回は各タイミングでprint()するミドルウェアを作成しました。
myproject/django_middleware_in_project.py
class DjangoMiddlewareInProject(object):
def __init__(self, get_response):
self.get_response = get_response
print('proj: one-time cofiguration')
def __call__(self, request):
print('proj: before request')
response = self.get_response(request)
print('proj: after request')
return response
MIDDLEWARE
に追加します。
myproject/settings.py
MIDDLEWARE = [
...
'myproject.django_middleware_in_project.DjangoMiddlewareInProject',
]
runserverし、http://localhost:8000/myapp/
にアクセス後のログを見ます。
# 起動直後
(env) >python manage.py runserver
...
Django version 1.10.4, using settings 'myproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
proj: one-time cofiguration
proj: one-time cofiguration
# http://localhost:8000/myapp/にアクセス後
proj: before request
called: MyAppView
proj: after request
各タイミングでDjangoミドルウェアが動作していることが分かりました*1。
複数Djangoミドルウェアを組み込んだ場合の動作順が気になったため、試してみます。
先ほど作成したDjangoMiddlewareInProjectに加え、それと同じように動作するミドルウェアを
- myapp/django_middleware_in_app1.py
- myapp/django_middleware_in_app2.py
として用意します。
また、MIDDLEWARE
の設定を以下とします。
MIDDLEWARE = [
...
'myapp.django_middleware_in_app1.DjangoMiddlewareInApp1',
'myproject.django_middleware_in_project.DjangoMiddlewareInProject',
'myapp.django_middleware_in_app2.DjangoMiddlewareInApp2',
]
runserverし、http://localhost:8000/myapp/
にアクセス後のログを見ます。
(env) >python manage.py runserver
...
app2: one-time cofiguration
proj: one-time cofiguration
app1: one-time cofiguration
app2: one-time cofiguration
proj: one-time cofiguration
app1: one-time cofiguration
app1: before request
proj: before request
app2: before request
called: MyAppView
app2: after request
proj: after request
app1: after request
配置場所(myproject/
or myapp/
)に関係なく、組み込み順(app1 > project > app2)とは逆順(app2 > project > app1)で動作するようです。
wsgi_lineprofの組み込み
以下を参考に、WSGIミドルウェアのwsgi_lineprof
を組み込みます。
How to deploy with WSGI | Django documentation | Django
念のため、wsgi_lineprofのソースコードを確認すると、def __call__(self, env, start_response):
がありました(このあたり)。
そのため、WSGIミドルウェアとしてDjangoに組み込めそうです。
まずはpipでインストールします。Windowsでも問題なくインストールできました。
(env) >pip install wsgi_lineprof
...
Successfully installed wsgi-lineprof-0.2.0
(env) >pip list
Django (1.10.4)
pip (7.1.2)
setuptools (18.2)
six (1.10.0)
wheel (0.24.0)
wsgi-lineprof (0.2.0)
続いて、WSGIミドルウェアを組み込みます。
myproject/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
application = get_wsgi_application()
from wsgi_lineprof.middleware import LineProfilerMiddleware
application = LineProfilerMiddleware(application)
runserverしてhttp://localhost:8000/myapp/
にアクセス後のログを見ます。
...
File: path\to\django_project_dir\env\lib\site-packages\django\core\handlers\wsgi.py
Name: __init__
Total time: 6.1594e-06 [sec]
Line Hits Time Code
===================================
32 def __init__(self, stream, limit, buf_size=64 * 1024 * 1024):
33 1 4 self.stream = stream
34 1 2 self.remaining = limit
35 1 2 self.buffer = b''
36 1 4 self.buf_size = buf_size
[**/Dec/2016 **:**:**] "GET /myapp/ HTTP/1.1" 200 11
wsgi_lineprofが動作しています。
ミドルウェアでの例外ハンドリングについて
Djangoアプリで送出した例外のハンドリングについて
以前、WSGIアプリの例外をハンドリングするWSGIミドルウェアを作りました。
WSGIアプリの例外をハンドリングするWSGIミドルウェア | Pythonで、WSGIミドルウェアを作ってみた - メモ的な思考的な
そこで、今回も同様のWSGIミドルウェアを組み込んでみます。
まずは例外を出すViewを作成します。
myapp/views.py
class MyExceptionView(View):
def get(self, request, *args, **kwargs):
print('called: MyExceptionView')
raise AssertionError(request)
return HttpResponse('raised exception')
myapp/urls.py
urlpatterns = [
...
url(r'^exception$',
MyExceptionView.as_view(),
name='exception',
)
]
続いて、例外をハンドリングするWSGIミドルウェアを作成します。
myporject/wsgi_middleware_exception_handling.py
class WSGIMiddlewareExceptionHandling(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
print("called: WSGIMiddlewareExceptionHandling")
try:
return self.app(environ, start_response)
except:
print('handled exception by WSGIMiddlewareExceptionHandling')
start_response('500 Internal Server Error', [('Content-Type', 'text/plain')])
return [b"raised exception."]
最後にDjangoに組み込みます。
myproject/wsgi.py
application = get_wsgi_application()
from .wsgi_middleware_exception_handling import WSGIMiddlewareExceptionHandling
application = WSGIMiddlewareExceptionHandling(application)
runserverし、http://localhost:8000/myapp/exception
にブラウザでアクセスすると、
AssertionError at /myapp/exception
<WSGIRequest: GET '/myapp/exception'>
とDjangoのエラーページが表示されました。
ログを見ます。
(env) D:\Sandbox\Django_WSGI_middleware_sample>python manage.py runserver
...
app2: one-time cofiguration
proj: one-time cofiguration
app1: one-time cofiguration
app2: one-time cofiguration
proj: one-time cofiguration
app1: one-time cofiguration
called: WSGIMiddlewareErrorHandling
app1: before request
proj: before request
app2: before request
called: MyExceptionView
Internal Server Error: /myapp/exception
Traceback (most recent call last):
...
AssertionError: <WSGIRequest: GET '/myapp/exception'>
app2: after request
proj: after request
app1: after request
[**/Dec/2016 **:**:**] "GET /myapp/exception HTTP/1.1" 500 75449
「WSGIMiddlewareErrorHandling」は起動しているようです。
一方、「handled exception by WSGIMiddlewareErrorHandling」が表示されていないため、Djangoアプリが送出した例外をWSGIミドルウェアではハンドリングできていないようです。
Djangoのドキュメントを読むと、以下に例外ハンドリングに関する記載がありました。
意訳すると、
とようです。
そこで、その挙動を確認するDjangoミドルウェアを作成してみます。
myproject/django_middleware_exception_handling.py
class DjangoMiddlewareExceptionHandling(object):
def __init__(self, get_response):
self.get_response = get_response
print('django_middleware: one-time cofiguration')
def __call__(self, request):
print('django_middleware: before request')
try:
response = self.get_response(request)
print('Django response data:{}'.format(response))
except:
print('handled exception by DjangoMiddlewareExceptionHandling')
print('django_middleware: after request')
return response
def process_exception(self, request, exception):
print('handled exception by process_exception in DjangoMiddlewareExceptionHandling')
Djangoミドルウェアとして登録します。
myproject/settings.py
MIDDLEWARE = [
...
'myproject.django_middleware_exception_handling.DjangoMiddlewareExceptionHandling',
]
再度http://localhost:8000/myapp/exception
にアクセスし、ログを見ます。
(env) >python manage.py runserver
...
django_middleware: one-time cofiguration
django_middleware: one-time cofiguration
called: WSGIMiddlewareExceptionHandling
django_middleware: before request
called: MyExceptionView
# process_exception()の動作ログ
handled exception by process_exception in DjangoMiddlewareExceptionHandling
Internal Server Error: /myapp/exception
Traceback (most recent call last):
...
AssertionError: <WSGIRequest: GET '/myapp/exception'>
# get_response()コールバックの戻り値
Django response data:<HttpResponse status_code=500, "text/html">
django_middleware: after request
[**/Dec/2016 **:**:**] "GET /myapp/exception HTTP/1.1" 500 75197
これらより、
- get_response()コールバックの戻り値に、ステータスコードなどがある
- 例外をexceptした時のログなし
- process_exception()が動作したときのログあり
ということが分かりました。
Djangoアプリの例外はDjangoミドルウェアでハンドリングできそうです。
DjangoミドルウェアとWSGIミドルウェアの挙動をみてみます。
まずは例外を送出するWSGIミドルウェアを作成します。
wsgi_middleware_raise_exception.py
class WSGIMiddlewareRaiseException(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
print('called: WSGIMiddlewareRaiseException')
raise AssertionError
self.app(environ, start_response)
Djangoに組み込みます。
myproject/wsgi.py
from .wsgi_middleware_raise_exception import WSGIMiddlewareRaiseException
application = WSGIMiddlewareRaiseException(application)
from .wsgi_middleware_exception_handling import WSGIMiddlewareExceptionHandling
application = WSGIMiddlewareExceptionHandling(application)
runserverし、http://localhost:8000/myapp/exception
にブラウザでアクセスすると、
raised exception.
と表示されました。
ログを見ます。
(env) D:\Sandbox\Django_WSGI_middleware_sample>python manage.py runserver
...
django_middleware: one-time cofiguration
django_middleware: one-time cofiguration
called: WSGIMiddlewareExceptionHandling
called: WSGIMiddlewareRaiseException
handled exception by WSGIMiddlewareExceptionHandling
[**/Dec/2016 **:**:**] "GET /myapp/exception HTTP/1.1" 500 17
これらより、
ということが分かりました。
GitHubに上げました。
thinkAmi-sandbox/Django_WSGI_middleware-sample