Django学习-中间件
Django的中间件在用户请求传递到URLconf之前执行,并在视图返回之后再执行,可以认为是request和response处理的钩子。它是一个轻量级底层插件,可以设置全局更改Django业务的输入与输出。感觉更像是对用户访问的一些预处理及后处理操作。
中间件一般包含5个方法:
process_request() # 请求处理
process_view() # 视图处理
process_exception() # 异常处理
process_template_response() # 模版处理
process_response() # 响应处理
中间件的定义在settings.py的MIDDLEWARE属性中:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
注意:中间件的加载顺序时有要求的,在request进入时,按照从上到下的顺序依次调用中间件,在response返回的时候,按照从下到上的顺序依次调用。
中间件的具体执行顺序:
注:黄线表示正常执行顺序,橘红线表示当函数发生异常或有返回值时,则直接跳转至异常处理。
中间件的初始化源码在/site-packages/django/core/handlers/base.py中。
def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE (or the deprecated
MIDDLEWARE_CLASSES).
Must be called after the environment is fixed (see __call__ in subclasses).
"""
self._request_middleware = []
self._view_middleware = []
self._template_response_middleware = []
self._response_middleware = []
self._exception_middleware = []
if settings.MIDDLEWARE is None:
warnings.warn(
"Old-style middleware using settings.MIDDLEWARE_CLASSES is "
"deprecated. Update your middleware and use settings.MIDDLEWARE "
"instead.", RemovedInDjango20Warning
)
handler = convert_exception_to_response(self._legacy_get_response)
for middleware_path in settings.MIDDLEWARE_CLASSES:
mw_class = import_string(middleware_path)
try:
mw_instance = mw_class()
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if six.text_type(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue
if hasattr(mw_instance, 'process_request'):
self._request_middleware.append(mw_instance.process_request)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.append(mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.insert(0, mw_instance.process_template_response)
if hasattr(mw_instance, 'process_response'):
self._response_middleware.insert(0, mw_instance.process_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.insert(0, mw_instance.process_exception)
else:
handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
try:
mw_instance = middleware(handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if six.text_type(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
自定义中间件时:
因为在中间件具体执行时,按照上述五个方法顺序执行,所以只需要实现至少一个上述五个函数就可以完成,当未找到中间件对应方法时,会跳过该中间件的处理。
def process_request(request):
参数申明:
第一个参数为传入的HttpRequest对象。
方法的返回值:
它的返回值只能是None或者一个HttpResponse对象。如果它返回了None,则Django会按照正常流程操作request,执行其他中间件。如果返回的是一个HttpResponse对象,Django会停止后续的操作,跳过剩下的中间件处理以及view视图操作,将该HttpResponse作为中间件的process_exception()输入,过程见上面的流程图。
方法调用时间:
该方法的执行在Django决定使用哪个视图来响应它,即在URLconf之前
def process_view(request, view_func, view_args, views_kwargs):
参数申明:
第一个参数为传入的HttpRequest对象。
第二个参数为一个关于python的函数本体,实际上是一个函数对象,而不是函数名,是Django对应于该请求即将执行的视图函数对象
第三个参数为一个传递给该视图函数的参数列表,但不包括request对象
第四个参数为一个传递给该视图函数的关键字参数字典。同样不包括request参数。
方法调用时间:
该方法的执行仅在Django执行具体视图函数之前,即它已经确定了使用了哪个视图函数来响应该请求。
方法的返回值:
同样该方法应当仅能返回None或者HttpResponse对象。如果返回None,则继续按规定执行,如果返回HttpResponse对象,跳过剩下的中间件处理以及view视图操作,将该HttpResponse作为中间件的process_exception()输入,过程见上面的流程图
注:对于request中POST的数据访问,应该放在中间件的process_view及process_request中,这样可以避免request在视图中进行服务器状态更改操作。但是CsrfViewMiddleware中间件类是一个例外,这个类提供改了csrf_exempt()以及csrf_protect()装饰器允许视图能够显示的控制在什么地方可以进行CSRF验证。
def process_template_response(request, response):
参数申明:
第一个参数为请求的HttpRequest对象,当然是经过其他前面工序的,可能和原始request不同,被更改了。
第二个参数是TemplateResponse对象,或者通过Django视图函数或者通过中间件返回的HttpResponse对象。如果第二个参数response对象有render()渲染方法,表名这是一个TemplateResponse或等价对象。
方法调用时间:
该方法的执行时间在视图完成它的流程后被调用。
方法用法:
该方法可以通过更改响应对象内部的模版名称template_name属性或者上下文管理数据context_data来更改响应,或者你也可以创建一个全新的TemplateResponse对象来返回。在该方法中你不需要显示的渲染模版,因为一旦所有中间件的process_template_response被调用完成,会自动渲染模版。
方法的返回值:
该方法的返回必须为实现了render()方法的响应对象。
def process_response(request, response):
参数申明:
第一个参数为HttpRequest对象
第二个参数为Django视图或中间件输入处理返回的HttpResponse对象或StreamingHttpResponse。
方法调用时间:
这个方法将在所有响应返回给浏览器的之前一步执行。
方法返回值:
必须返回一个HttpResponse或者StreamingHttpResponse对象。
方法用法:
该方法可以更改第二个参数response来修改响应,或者创建一个全新的HttpResponse或StreamingHttpResponse对象返回。不同于process_request及process_views可能被跳过,这个方法总是会被调用,即使这个方法所在的中间件被跳过了(如前一个中间件的process_request直接返回了HttpResponse),事实上,这个意味着你的process_response方法不能依赖于setup来完成配置。
附:响应式流处理StreamingHttpResponse
不同于HttpResponse,响应式流处理没有上下文管理器content属性,结果就是,经过中间件处理的响应并不都包含content,如果终端使用过程中需要访问这个属性,则必须手动验证不是流处理响应,并根据返回结果来更改表现。如果是流处理响应且需要访问content属性,可以设置response的streaming_content属性,但是请注意,把streaming_conteno包装成生成器,避免占用内存太大而无法放入内存中。
if response.streaming:
response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
response.content = alter_content(response.content)
常见的把streaming_content封装成生成器的方式:
def wrap_streaming_content(content):
for chunk in content:
yield alter_content(chunk)
def process_exception(request, exception):
方法参数:
第一个参数为传入的request
第二个参数为一个视图函数未捕获的Exception对象。
方法调用时间:
当一个视图引发一个未捕获的异常时,该方法被调用
方法返回值:
应该返回None或者一个HttpResponse对象,如果它返回了一个HttpResponse对象,则会执行中间件的process_template_response以及process_response将会被调用,然后将最终的响应返回给浏览器。否则,将执行默认的异常处理。另外,如果有一个异常中间件响应了引发的异常并返回了一个response,则接下去的中间件的process_exception将都不会再被调用。
def __init__(self):
绝大部分中间件都不需要初始化函数,因为中间件本质上就是执行process_*的函数方法,但是如果你想要一些全局参数或状态数据,可以使用__init__初始化函数,然而又以下几个注意事项:
1.Django在初始化中间件时,不会传入任何参数,所以不要在定义初始化函数时要求输入参数。
2.不同于process_*一类的方法,在每次接到请求的时候都会执行,初始化函数只会再web服务响应第一个请求的时候执行一次。
中间件过期方式:
有时候,可能希望再运行时将某些中间件过期掉,即不发生作用,则可以通过在__init__初始化方法中引发一个django.core.exceptions.MiddlewareNotUsed异常,Django会捕获这个异常并从中间件处理队列中移除相应的中间件。如果设置了DEBUG属性为True,则会将debug信息日志保存至django所设置的request日志中。
中间件的启用:
1.在settiongs.py配置文件的MIDDLEWARE_CLASSES属性中添加中间件的python导入路径(至中间件类名)
MIDDLEWARE属性也可以。
MIDDLEWARE_CLASSES = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
]
注意:中间件的排列顺序很重要,执行过程上面已经讲过,process_request => process_view =>URL/VIEWS =》process_exception => process_template_response => process_response。部分中间件的某些执行可能依赖于其他中间件的预处理,所以中间件的排列规则会有一定讲究。
附加说明:
1.中间件不需要对任何类进行子类化
2.你可以把中间件放在任何python可以导入的地方,唯一要注意的是在MIDDLEWARE_CLASSES属性设置中要设置正确的路径
3.在编辑自定义中间件时,可以查看django默认支持的中间件作为参考
4.建议贡献自己的中间件给Django,如果真的对大多数人有用,Django团队会考虑将它作为默认支持的中间件。
上一篇: 设计模式:行为模式 - 状态模式
下一篇: Fiddler查看接口响应时间