Django 中间件

Tips:

更新日志: 2019.01.31 更新django中间件加载源码阅读

博文图片挂了临时解决办法

Django中间件可看作是包裹在django处理机制的外层,Httprequest和Httpresponse都要经中间件处理,从而起到全局钩子的作用,可以达到一些目的:如过滤请求,预处理请求,响应修改等。

我理解,很多基于会话的应用系统,都可以设计中间件环节。如数据库系统。中间件可以起到全局钩子的作用。django的中间件的设计就是一种递归顺序调用,利用httprequest作为递归调用的参数,httpresponse作为递归调用的return返回。

Django提供了内置的一些中间件。思考:request对象中的user属性中的用户对象是怎么来的,就是会话中间件和认证中间件处理session_id,获取用户对象,从而将用户对象放入request对象中,再交与view中进行处理。request对象在中间件开发中起到了载体的作用,非常重要。

中间件框架,设计上就是嵌套调用,初始化一个中间件函数或者中间件对象;中间件函数的初始化通过一个外层的中间件工厂函数;中间件对象的初始化当然是中间件类的_init_()函数喽。这两种的初始化,工厂函数和类_init_()都需要传入一个get_response函数,这个get_response的传入是django引擎会带入的,一般是初始化后的中间件列表的下一个中间件函数,或者对象,相当于就是嵌套调用递归下去,当最后的view函数处理完后再一层一层返回response,再进行response返回的中间件过程处理。原理设计就是嵌套递归返回模型(我自己起的名字,知道想表达的意思就好):

Tips: 如果图片不好理解,可以查看下面对中间件源码的阅读

自定义中间件 - - - 大体两种方式

中间件工厂函数方式

重点

def simple_middleware(get_response):
# One-time configuration and initialization. def middleware(request):
# Code to be executed for each request before
# the view (and later middleware) are called.
# 注意这里的代码是view之前还是之后,两种情景的分隔就是下面这个函数调用,这是基于递归调用设计的关键之处。 response = get_response(request) # 下面的就是view处理过后,可以处理httpresponse对象。
# Code to be executed for each request/response after
# the view is called. return response return middleware # 这是django初始化时调用中间件工厂函数,返回中间件函数。

基于中间件类方式

要点

  • 导入父类from django.utils.deprecation import MiddlewareMixin
  • 自定义自己的中间件类继承上条导入的父类
  • 定义要中间件处理的django生命周期的环节处理函数:
    • process_request(self,request)
    • process_view(self, request, callback_view, callback_args, callback_kwargss)
    • process_template_response(request, response)
    • process_response(self, request, response)
    • process_exception(self, request, response, except)
  • 激活中间件,在settings中的MIDDLEWARE列表中放置中间件类或者中间件工厂函数。注意放置在列表中的位置,这个很重要,因为中间可能存在依赖关系(request和response就像就中间件之间传递个的信息的载体;如auth中间件就要放在session中间件后面)。列表中就中间件的full python path(python全路径)字符串.
  • 至于中间件程序模块文件,可以放在python path的任何地方,建议和组件相关放一起。
# 基于类的方式一
from django.utils.deprecation import MiddlewareMixin class MyMiddle01(MiddlewareMixin):
# 1. 定义中间件功能,具体处理整个django请求和响应的生命周期的哪一环节。
# 2. 主要有request请求到达环节;路由Urlconf后View处理前的view预处理环节;View处理过程中抛出异常的对该异常响应的处理环节(异常情况);正常情况下view返回的response环节;还有一个模版环节(不常用);
def process_request(self, request):
pass def process_view(self, request, callback_view, callback_args, callback_kwargs):
callback_view(request, *callback_args, *callback_kwargs)
pass def process_template_response(request, response):
pass def process_response(self, request, response):
return response def process_exception(request, except):
pass # 基于类的方式二 class SimpleMiddleware:
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. response = self.get_response(request) # Code to be executed for each request/response after
# the view is called. return response def process_view():
pass def process_exception():
pass def process_template_response():
pass

将中间件移除

  1. 方式一:将中间件从配置中移除。
  2. 方式二:中间件初始化是抛出MiddlewareNotUsed异常即可,在初始化中间件函数或者对象时。

实例

权限鉴别中间件

from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse
import re class RbacPermissionMiddleware(MiddlewareMixin):
"""
请求访问权限中间件
""" def process_request(self, request):
"""
权限check
:param request:
:return:
"""
req_path = request.path_info
print('用户请求uri', req_path)
# 定义白名单url
white_list = ['/login/', '/register/']
for url in white_list:
url = '^%s$' % url
if re.match(url, req_path):
return None # 如果请求权限是白名单中,那么中间件返回一个None,就会进行下一个中间件处理流。 # 从session中获取权限url列表
p_list = request.session.get('permissions')
if not p_list:
return HttpResponse('未获取用户权限信息,请登录!') p_flag = False
for p_url in p_list:
p_url = '^%s$' % p_url
if re.match(p_url, req_path):
p_flag = True
break
if not p_flag:
return HttpResponse('没有访问权限!')

中间件加载源码阅读

前面也提到了,中间件实际就是一个callable的对象或者函数。中间件的初始化时通过工厂函数或者工厂类产生的。django的多个中间件通过有序的责任链模式设计,通过在settings中配置中间件工厂类的列表顺序。中间件加载或者说实例化时,需要有下一个调用的作为callback参数出入实例化中间件。对于下一个就可以从列表的获取到下一个。而最终就是具体的view视图处理了。而且再实例化每一个中间件的过程中,还从中间件中获取到对视图异常和视图处理前及渲染后的处理三个子流程。这些子流程的执行放在一个叫做BaseHandler._get_response(request)函数中,这个函数是对最终视图view执行的代理者,所以中间件只需要关注_get_response这个代理者,这个代理者再处理进入对应的上个子流程。下面开始一步一步分析源码:从wsgi.py开始

  1. wsgi.py--->wsgi.application--->get_wsgi_application()--->WSGIHandler()
  2. 从WSGIHandler() 开始看源码,这个其实就是wsgi协议的入口函数实例了,WSGIHandler.call(self, environ, start_response)
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware() # 在初始化对象时,就会加载我们的中间件,也就是通过中间件工厂函数或者中间件类实例化出我们的中间件函数,并放于内存栈中,并将第一个中间件存与self.__middleware_chain,作为中间件的起点接口。 下面查看load_middleware()的源码 def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request) # 执行BaseHandler.get_response(),这里算是 response._handler_class = self.__class__ status = '%d %s' % (response.status_code, response.reason_phrase)
response_headers = list(response.items())
for c in response.cookies.values():
response_headers.append(('Set-Cookie', c.output(header='')))
start_response(status, response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response
  1. BaseHanler.load_middleware
    def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE. Must be called after the environment is fixed (see __call__ in subclasses).
"""
self._view_middleware = [] # 存放每个中间件视图前处理单元
self._template_response_middleware = [] # 存放每个中间件视图模板渲染后处理单元
self._exception_middleware = [] # 存放每个中间件视图发生异常后处理单元 handler = convert_exception_to_response(self._get_response) # 包裹视图代理函数_get_response,将视图处理异常进行捕获返回适当的响应。
for middleware_path in reversed(settings.MIDDLEWARE): # 反向便利注册的中间件,从最后一个开始实例,因为只有视图处理函数是可以通过代理_get_response呈现出来的。因为每一个中间都依赖下一个中间作为callback参数才能实例化。
middleware = import_string(middleware_path)
try:
mw_instance = middleware(handler) # 第一次循环,handler就是_get_response(),这里通过middleware工厂函数得到了mw_instance 中间件实例。
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if str(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue if mw_instance is None:
raise ImproperlyConfigured(
'Middleware factory %s returned None.' % middleware_path
) if hasattr(mw_instance, 'process_view'): # 注册中间件的三类子过程。
self._view_middleware.insert(0, mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.append(mw_instance.process_template_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.append(mw_instance.process_exception) handler = convert_exception_to_response(mw_instance) 将中间件作为下轮循环的handler。 # We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._middleware_chain = handler 循环外层后,最后实例化的中间件赋值给与了_middleware_chian
  1. WSGIHandler初始化构造完成
  2. 进入__call__() django request 生命周期, 主要关注__call__中调用的时get_response(),查看get_response()调用的是_middleware_chain
 def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
set_urlconf(settings.ROOT_URLCONF) response = self._middleware_chain(request) # 这里就是到了我们的中间件列表的第一个中间件处理函数了。接下的就是各个中间里面的处理流程,我们看一个中间件,其他都一样了。
  1. 在django中,中间件都继承自from django.utils.deprecation import MiddlewareMixin
class MiddlewareMixin:
def __init__(self, get_response=None):
self.get_response = get_response
super().__init__() def __call__(self, request):
response = None
if hasattr(self, 'process_request'): 执行我们定义个process_request处理流程,如果在这里响应了,那么就不会走后续的中间件处理。如果中间件处理没有出问题,那么process_request什么也别处理。
response = self.process_request(request)
response = response or self.get_response(request) 这里就会执行我们的get_response调用下一个中间件了,这里开始就是递归调用,直到拿到响应。
if hasattr(self, 'process_response'):
response = self.process_response(request, response) 开始处理响应流程
return response

可以看出中间件处理流程就是这样。我们还忽略了最后一点就是最后一个中间件到视图调用的流程。

开始我也说过有一个叫做_get_response的代理视图处理的代理人。我们看下这个代理人的代码:

    def _get_response(self, request):
"""
Resolve and call the view, then apply view, exception, and
template_response middleware. This method is everything that happens
inside the request/response middleware.
"""
response = None if hasattr(request, 'urlconf'): # 获取URLResolver对象,这是一个根路由对象,如果想知道django URL路由原理,可以查看我的另一篇博客https://www.cnblogs.com/ZJiQi/p/10339006.html
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver() resolver_match = resolver.resolve(request.path_info) # 通过主路由对象路由到resolvermatch对象
callback, callback_args, callback_kwargs = resolver_match # 这里的callback就是url路由对应的视图函数,callback_args 和 call_back_kwargs 就是视图参数。
request.resolver_match = resolver_match # Apply view middleware 这里就是我说的开始 视图前的处理子过程
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break if response is None:
wrapped_callback = self.make_view_atomic(callback) #这里将根据配置判定是否将视图函数中所有的数据库操作作为一个原子操作,
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs) # 最终执行视图逻辑
except Exception as e:
response = self.process_exception_by_middleware(e, request) # Complain if the view returned None (a common error).
if response is None:
if isinstance(callback, types.FunctionType): # FBV
view_name = callback.__name__
else: # CBV
view_name = callback.__class__.__name__ + '.__call__' raise ValueError(
"The view %s.%s didn't return an HttpResponse object. It "
"returned None instead." % (callback.__module__, view_name)
) # If the response supports deferred rendering, apply template
# response middleware and then render the response
elif hasattr(response, 'render') and callable(response.render): # 如果响应是一个通过render 模板的响应。那么就开始了模板渲染后的子流程。这个过程提供了中间件方式修改响应内容。
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
# Complain if the template response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_template_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__)
) try:
response = response.render()
except Exception as e:
response = self.process_exception_by_middleware(e, request) # 发生异常都会进入中间件异常处理子流程。 return response
  1. 到这里也就大致阅读完了中间件处理的源码,其他细节遇到时可看。

总结

  1. 中间件函数就类似一个view函数,参数获取request,return返回response。只不过需要递归调用下一个中间件函数。(下一个中间件函数,是中间件工厂函数通过闭包传递给中间件函数的)

  2. 自定义中间件有多种形式:工厂函数,中间件类。

  3. 中间件注册到django项目时,有顺序性。

  4. 中间件会影响全局性能,毕竟所有请求都会进出都需要通过中间件。

  5. 额外的request_process()等都是只能创建在基于类的中间件,因为这些都是通过类的反射方式调用的。 或者异常触发,或者urlconf触发。。。

  6. 中间件函数还提供内部额外的改变请求和响应的路由路径。如:

    视图函数正常执行时:

视图函数抛出异常时:

Django学习之七:Django 中间件的更多相关文章

  1. Django学习之django自带的contentType表 GenericRelation GenericForeignKey

    Django学习之django自带的contentType表   通过django的contentType表来搞定一个表里面有多个外键的简单处理: 摘自:https://blog.csdn.net/a ...

  2. day 94 Django学习之django自带的contentType表

    Django学习之django自带的contentType表   通过django的contentType表来搞定一个表里面有多个外键的简单处理: 摘自:https://blog.csdn.net/a ...

  3. day 93 Django学习之django自带的contentType表

    Django学习之django自带的contentType表   通过django的contentType表来搞定一个表里面有多个外键的简单处理: 摘自:https://blog.csdn.net/a ...

  4. Django学习之django自带的contentType表

    Django学习之django自带的contentType表 通过django的contentType表来搞定一个表里面有多个外键的简单处理: 摘自:https://blog.csdn.net/aar ...

  5. day 91 Django学习之django自带的contentType表

      Django学习之django自带的contentType表   通过django的contentType表来搞定一个表里面有多个外键的简单处理: 摘自:https://blog.csdn.net ...

  6. Django学习笔记 Django的工程目录

    mysite├── manage.py 管理项目:包括数据库建立.服务器运行.测试……└── mysite    ├── __init__.py     ├── settings.py 配置文件:应用 ...

  7. django学习-10.django连接mysql数据库和创建数据表

    1.django模型 Django对各种数据库提供了很好的支持,包括:PostgreSQL.MySQL.SQLite.Oracle. Django为这些数据库提供了统一的调用API. 我们可以根据自己 ...

  8. Django学习day3——Django的简单使用

    开始一个项目 切换到django的虚拟环境中 执行: django-admin startproject mysite 创建第一个django项目mysite django生成的目录如下: E:. └ ...

  9. Django学习系列之中间件

    中间件的定义 中间件是一个.一个的管道,如果相对任何所有的通过Django的请求进行管理都需要自定义中间件 中间件可以对进来的请求和出去的请求进行控制 中间件是一类 django请求生命周期 自定义中 ...

随机推荐

  1. Spark学习之编程进阶总结(一)

    一.简介 这次介绍前面没有提及的 Spark 编程的各种进阶特性,会介绍两种类型的共享变量:累加器(accumulator)与广播变量(broadcast variable).累加器用来对信息进行聚合 ...

  2. 同源策略 & 高效调试CORS实现

    # 目录 为什么有同源策略? 需要解决的问题 CORS跨域请求方案 preflight withCredentials 附:高效.优雅地调试CORS实现 ----------------------- ...

  3. 对Tomcat 8.0进行JVM层面的优化(基于Oracle JDK 8)

    目录 1 Tomcat的内存调优 1.1 Tomcat的内存占用 1.2 内存配置相关参数 1.3 内存调优实践 1.4 验证配置效果 2 GC策略调优实践 Tomcat容器是运行在JVM上的, 其默 ...

  4. 【Python3爬虫】常见反爬虫措施及解决办法(二)

    这一篇博客,还是接着说那些常见的反爬虫措施以及我们的解决办法.同样的,如果对你有帮助的话,麻烦点一下推荐啦. 一.防盗链 这次我遇到的防盗链,除了前面说的Referer防盗链,还有Cookie防盗链和 ...

  5. C#-Xamarin的Android项目开发(三)——发布、部署、打包

    前言 部署,通常的情况下,它其实也是项目开发的一个难点. 为什么这么说呢?因为,它不是代码开发,所以很多开发者本能的拒绝学习它. 并且一个项目配置好一次以后,部署的步骤和部署的人通常很固定,所以大部分 ...

  6. docker常规操作——启动、停止、重启容器实例

    一.启动一个已经停止的容器实例 docker start 容器ID或容器名,建议使用容器ID,容器ID支持模糊查询而容器名称不支持1. 先查看已经暂停的容器实例信息 2. 通过docker start ...

  7. ajax调用WebAPI添加数据

    //获取账号名 var Name = document.getElementById("Text1").value;//获取密码 var Pass = document.getEl ...

  8. VS2015编译GEOS的debug和release版本

    目前GEOS最新的3.7.1版本支持camke进行编译.经过尝试发现通过cmake生成的工程在vs2015下面编译的时候还是存在问题,而且在中文网上也没找到解决方案. 所以还是采用了nmake进行编译 ...

  9. 【原】无脑操作:Gitblit服务器搭建及IDEA整合Git使用

    背景:虽然有GitHub.GitLab这样强大的Git仓库,但是涉及私有Git库要收费,所以自己动手搭建免费的用用 环境:windows 7 旗舰版.JDK 1.8.IDEA 2017 ------- ...

  10. 多线程总结之旅(1):线程VS进程

    一.进程:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,也就是应用程序的执行实例,进程是系统进行资源分配和调度的一个独立单位.每个进程是由私有的虚拟地址空间.代码.数据和其它各种系统资 ...