我们从浏览器发出一个请求 Request,得到一个响应后的内容 HttpResponse ,这个请求传递到 Django的过程如下:
也就是说,每一个请求都是首先通过中间件中的process_request
函数,最后返回自中间件的process_response
函数。
process_request
函数返回 None 或者 HttpResponse 对象,如果返回前者,继续处理其它中间件,如果返回一个 HttpResponse,就处理本中间件及之前中间件的process_request
,返回到网页上。
中间件不用继承自任何类(可以继承 object ),下面一个中间件大概的样子:
class CommonMiddleware(object):
def process_request(self, request):
return None
def process_response(self, request, response):
return response
还有 process_view, process_exception 和 process_template_response 函数。
版本变化
变化一:settings配置
Django 1.9 和以前的版本:
MIDDLEWARE_CLASSES = (
'zqxt.middleware.BlockedIpMiddleware',
...其它的中间件
)
Django 1.10 版本 更名为 MIDDLEWARE(单复同形),写法也有变化
如果用 Django 1.10版本开发,部署时用 Django 1.9版本或更低版本,要特别小心此处。
MIDDLEWARE = (
'zqxt.middleware.BlockedIpMiddleware',
...其它的中间件
)
Django 会从 MIDDLEWARE_CLASSES 或 MIDDLEWARE 中按照从上到下的顺序一个个执行中间件中的 process_request 函数,而其中 process_response 函数则是最前面的最后执行。
变化二:接口
Django 1.9 和以前的版本:
class SimpleMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
# 调用 view 之前的代码
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
# 调用 view 之后的代码
return response
Django 1.10 接口发生变化,变得更加简洁
class MiddlewareMixin(object):
"""
__call__ 方法会先调用 self.process_request(request),
接着执行self.get_response(request) ,
然后调用 self.process_response(request, response)
"""
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
class RbacMiddleware(MiddlewareMixin):
"""
自定义的中间件必须继承自MiddlewareMixin,代码如上
"""
def process_request(self, request):
return None
def process_response(self, request, response):
return response
让 你写的中间件 兼容 Django新版本和旧版本
try:
from django.utils.deprecation import MiddlewareMixin # Django 1.10.x
except ImportError:
MiddlewareMixin = object # Django 1.4.x - Django 1.9.x
class SimpleMiddleware(MiddlewareMixin):
def process_request(self, request):
pass
def process_response(request, response):
pass
常见应用:
一、拦截器
比如我们要做一个拦截器 ,发现有恶意访问网站的人,就拦截他。
假如我们通过一种技术,比如统计一分钟访问页面数,太多就把他的 IP 加入到黑名单 BLOCKED_IPS(这部分没有提供代码,主要讲中间件部分)
class BlockedIpMiddleware(object):
def process_request(self, request):
if request.META['REMOTE_ADDR'] in getattr(settings, "BLOCKED_IPS", []):
return http.HttpResponseForbidden('Forbidden
')
这里的代码的功能就是 获取当前访问者的 IP (request.META['REMOTE_ADDR']),如果这个 IP 在黑名单中就拦截,如果不在就返回 None (函数中没有返回值其实就是默认为 None),把这个中间件的 Python 路径写到settings.py中。
二、DEBUG模式
再比如,我们在网站放到服务器上正式运行后,DEBUG改为了 False,这样更安全,但是有时候发生错误我们不能看到错误详情,调试不方便,有没有办法处理好这两个事情呢?
- 普通访问者看到的是友好的报错信息
- 管理员看到的是错误详情,以便于修复 BUG
当然可以有,利用中间件就可以做到!代码如下:
import sys
from django.views.debug import technical_500_response
from django.conf import settings
class UserBasedExceptionMiddleware(object):
def process_exception(self, request, exception):
if request.user.is_superuser or request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
return technical_500_response(request, *sys.exc_info())
把这个中间件像上面一样,加到你的 settings.py 中的 MIDDLEWARE_CLASSES 中,可以放到最后,这样可以看到其它中间件的 process_request的错误。
这样,当访问者为管理员时,就给出错误详情,比如访问本站的不存在的页面:
普通人看到的是普通的 404。
三、分享一个简单的识别手机的中间件
MOBILE_USERAGENTS = ("2.0 MMP","240x320","400X240","AvantGo","BlackBerry",
"Blazer","Cellphone","Danger","DoCoMo","Elaine/3.0","EudoraWeb",
"Googlebot-Mobile","hiptop","IEMobile","KYOCERA/WX310K","LG/U990",
"MIDP-2.","MMEF20","MOT-V","NetFront","Newt","Nintendo Wii","Nitro",
"Nokia","Opera Mini","Palm","PlayStation Portable","portalmmm","Proxinet",
"ProxiNet","SHARP-TQ-GX10","SHG-i900","Small","SonyEricsson","Symbian OS",
"SymbianOS","TS21i-10","UP.Browser","UP.Link","webOS","Windows CE",
"WinWAP","YahooSeeker/M1A1-R2D2","iPhone","iPod","Android",
"BlackBerry9530","LG-TU915 Obigo","LGE VX","webOS","Nokia5800")
class MobileTemplate(object):
"""
If a mobile user agent is detected, inspect the default args for the view
func, and if a template name is found assume it is the template arg and
attempt to load a mobile template based on the original template name.
"""
def process_view(self, request, view_func, view_args, view_kwargs):
if any(ua for ua in MOBILE_USERAGENTS if ua in
request.META["HTTP_USER_AGENT"]):
template = view_kwargs.get("template")
if template is None:
for default in view_func.func_defaults:
if str(default).endswith(".html"):
template = default
if template is not None:
template = template.rsplit(".html", 1)[0] + ".mobile.html"
try:
get_template(template)
except TemplateDoesNotExist:
pass
else:
view_kwargs["template"] = template
return view_func(request, *view_args, **view_kwargs)
return None
四、登录验证
class LoginMiddleware(MiddlewareMixin):
'''
登录验证
访问所有请求都验证是否已登录,否则返回登录页面
'''
def process_request(self,requset):
l = ['/login/','/register/','/test/'] #白名单
if requset.path in l:
return None
else:
if requset.session.get(settings.USER_INFO):
return
else:
return redirect('/login/')