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. .NET Core IdentityServer4实战 第三章-使用EntityFramework Core进行持久化配置

    内容:本文带大家使用IdentityServer4进行使用使用EntityFramework Core进行配置和操作数据 作者:zara(张子浩) 欢迎分享,但需在文章鲜明处留下原文地址. 前两章内容 ...

  2. 一个Mini的ASP.NET Core框架的实现

    一.ASP.NET Core Mini 在2019年1月的微软技术(苏州)俱乐部成立大会上,蒋金楠老师(大内老A)分享了一个名为“ASP.NET Core框架揭秘”的课程,他用不到200行的代码实现了 ...

  3. Solr 17 - Solr的时间为什么比本地少8小时 (附修改方法)

    目录 1 为什么少8小时 2 如何查看Solr的时区 3 修改Solr的时区 3.1 Solr从数据库中同步数据的原理 3.2 为什么要修改时区 3.3 如何修改时区 1 为什么少8小时 (1) 原因 ...

  4. mvc、mvp和mvvm

    一.MVC 设计图: 可能由于MVP.MVVM的兴起,MVC在android中的应用变得越来越少了,但MVC是基础,理解好MVC才能更好的理解MVP,MVVM.因为后两种都是基于MVC发展而来的. 1 ...

  5. 一目了然呀的VS2017 Live Test

    刚刚试用了一下VS2017中的单元测试,发现,这一次,覆盖测试会自动标记出来.不用像以前一样要他细检查了.这次会自动帮你全部标记出来. 新建单元测试,使用MS的单元测试方案(VSTS使用的时候方便.) ...

  6. Jmeter + Grafana搭建实时监控可视化

    小贴士: 建议使用jmeter3.3+版本,在这个版本以后才有backend listenter 对接influxDB. Jmeter中backend listenter如图 ​ influxdbUr ...

  7. MongoDB 运维相关的命令

    1.在线释放内存 use admindb.runCommand({closeAllDatabases:1}) 注:3.2 版本 已经去掉了这个命令了 2.rs.status() 查询复制集状态 3.d ...

  8. 2018-2019-2 20164312 Exp1 PC平台逆向破解

    1.逆向及Bof基础实践说明 1.1 实践目标 实验对象:一个名为pwn1的linux可执行文件. 实验流程:main调用foo函数,foo函数会简单回显任何用户输入的字符串.该程序同时包含另一个代码 ...

  9. Use Wait & Notify to Implement Two Threads Run Alternatively

    public class ThreadCommunication { public static void main(String[] args) { Business business = new ...

  10. cglib根据数据动态生成对象

    最近有个任务:根据查询SQL直接导出报表 实现关键是,怎么根据sql查询的数据动态生成对象列表,想到Cglib动态代理实现 废话少说,上代码: 定义动态生成Java Bean类: import jav ...