一、中间件介绍 我们在最开始介绍 django 生命周期的时候提及到过,浏览器发出的请求并不是直接到达路由层,视图层处理完的结果也不是直接返回浏览器的,都要经过中间件的处理。中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能。
对于中间件的配置可以在项目中的 settings.py 中修改,django 为我们默认提供了七个中间件,每一个中间件都有自己特殊的功能。中间件的执行顺序是按照他们在配置文件中的先后顺序来的。
1 2 3 4 5 6 7 8 9 10 11 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' , ]
我们可以简单选择两个中间件观察一下他们的源码
1 from django.middleware.security import SecurityMiddleware
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import refrom django.conf import settingsfrom django.http import HttpResponsePermanentRedirectfrom django.utils.deprecation import MiddlewareMixinclass SecurityMiddleware (MiddlewareMixin ): def __init__ (self, get_response=None ): self .sts_seconds = settings.SECURE_HSTS_SECONDS self .sts_include_subdomains = settings.SECURE_HSTS_INCLUDE_SUBDOMAINS self .sts_preload = settings.SECURE_HSTS_PRELOAD self .content_type_nosniff = settings.SECURE_CONTENT_TYPE_NOSNIFF self .xss_filter = settings.SECURE_BROWSER_XSS_FILTER self .redirect = settings.SECURE_SSL_REDIRECT self .redirect_host = settings.SECURE_SSL_HOST self .redirect_exempt = [re.compile (r) for r in settings.SECURE_REDIRECT_EXEMPT] self .get_response = get_response def process_request (self, request ): path = request.path.lstrip("/" ) if (self .redirect and not request.is_secure() and not any (pattern.search(path) for pattern in self .redirect_exempt)): host = self .redirect_host or request.get_host() return HttpResponsePermanentRedirect( "https://%s%s" % (host, request.get_full_path()) ) def process_response (self, request, response ): if (self .sts_seconds and request.is_secure() and 'strict-transport-security' not in response): sts_header = "max-age=%s" % self .sts_seconds if self .sts_include_subdomains: sts_header = sts_header + "; includeSubDomains" if self .sts_preload: sts_header = sts_header + "; preload" response["strict-transport-security" ] = sts_header if self .content_type_nosniff and 'x-content-type-options' not in response: response["x-content-type-options" ] = "nosniff" if self .xss_filter and 'x-xss-protection' not in response: response["x-xss-protection" ] = "1; mode=block" return response
1 from django.contrib.sessions.middleware import SessionMiddleware
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import timefrom importlib import import_modulefrom django.conf import settingsfrom django.contrib.sessions.backends.base import UpdateErrorfrom django.core.exceptions import SuspiciousOperationfrom django.utils.cache import patch_vary_headersfrom django.utils.deprecation import MiddlewareMixinfrom django.utils.http import cookie_dateclass SessionMiddleware (MiddlewareMixin ): def __init__ (self, get_response=None ): self .get_response = get_response engine = import_module(settings.SESSION_ENGINE) self .SessionStore = engine.SessionStore def process_request (self, request ): session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) request.session = self .SessionStore(session_key) def process_response (self, request, response ): """ If request.session was modified, or if the configuration is to save the session every time, save the changes and set a session cookie or delete the session cookie if the session has been emptied. """ try : accessed = request.session.accessed modified = request.session.modified empty = request.session.is_empty() except AttributeError: pass else : if settings.SESSION_COOKIE_NAME in request.COOKIES and empty: response.delete_cookie( settings.SESSION_COOKIE_NAME, path=settings.SESSION_COOKIE_PATH, domain=settings.SESSION_COOKIE_DOMAIN, ) else : if accessed: patch_vary_headers(response, ('Cookie' ,)) if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty: if request.session.get_expire_at_browser_close(): max_age = None expires = None else : max_age = request.session.get_expiry_age() expires_time = time.time() + max_age expires = cookie_date(expires_time) if response.status_code != 500 : try : request.session.save() except UpdateError: raise SuspiciousOperation( "The request's session was deleted before the " "request completed. The user may have logged " "out in a concurrent request, for example." ) response.set_cookie( settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None , httponly=settings.SESSION_COOKIE_HTTPONLY or None , ) return response
我们会发现,实际上中间件就是一个类,并且类中要包含一些固定的方法。将类再添加到配置文件的 MIDDLEWARE 列表中,该类就会变为一个中间件。中间件有以下四种方法,下面会一一介绍:
1 2 3 4 5 6 7 process_request process_view process_exception process_response
二、自定义中间件 2.1 process_request 请求会先依次触发中间件中的 process_request 方法,再去执行对应的视图方法。我们通过一个简单的例子来验证一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from django.utils.deprecation import MiddlewareMixinfrom django.shortcuts import HttpResponseclass MyMiddleware_1 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_1 request" )class MyMiddleware_2 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_2 request" )
1 2 3 4 5 6 7 8 9 10 11 12 13 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' , 'app01.MyMiddleware.MyMiddleware_1' , 'app01.MyMiddleware.MyMiddleware_2' , ]
1 2 3 4 5 6 7 8 from django.conf.urls import urlfrom app01 import views urlpatterns = [ url(r'^index/' , views.index), ]
1 2 3 4 5 6 7 8 from django.shortcuts import render, HttpResponsedef index (request ): print ("index......." ) return HttpResponse("INDEX" )
启动浏览器访问 http://127.0.0.1:8000/index/,观察后端打印结果
1 2 3 MyMiddleware_1 request MyMiddleware_2 requestindex .......
可见,请求是先按照中间件的顺序,从上而下 执行了所有中间件中的 process_request ,最后才执行视图中的 index。需要注意的是,不能在 process_request 设置 return 值,对于 process_request 方法来说,执行流程是这样的:首先后端接收到请求,接着所有中间件中的 process_request 会对请求进行进一步处理,最后将处理好的结果再传给视图。如果 process_request 中出现了response 相关的 return,后端就会直接将 process_request 中的返回值返回给浏览器,不会继续执行后面的中间件和视图部分 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from django.utils.deprecation import MiddlewareMixinfrom django.shortcuts import HttpResponseclass MyMiddleware_1 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_1 request" ) return HttpResponse("middleware1" )class MyMiddleware_2 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_2 request" )
修改中间件后,重新访问 http://127.0.0.1:8000/index/,此时页面显示的是 “middleware1”,后台中打印的结果为 “MyMiddleware_1 request”,后面的中间件 middleware_2 和 视图 index 都没有执行(如果在settings.py中把 middleware_1 和middleware_2的顺序换一下,middleware_2 是可以执行的 ),即
2.2 process_response 视图函数再处理完对应的业务逻辑后,会返回一个 HttpResponse 对象,该对象并不是直接返回给浏览器的,而是先自下而上 传给每一个中间件中的 process_response 方法,进一步处理后再返回给浏览器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from django.utils.deprecation import MiddlewareMixinfrom django.shortcuts import HttpResponseclass MyMiddleware_1 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_1 request" ) def process_response (self, request, response ): print ("MyMiddleware_1 response" ) return response class MyMiddleware_2 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_2 request" ) def process_response (self, request, response ): print ("MyMiddleware_2 response" ) return response
1 2 3 4 5 6 7 8 9 # 后台打印结果如下: MyMiddleware_1 request MyMiddleware_2 request index....... MyMiddleware_2 response MyMiddleware_1 response # 可见对于 process_request,执行顺序是按照配置文件中中间件从上到下的顺序;对于 process_response 则是按照从下往上的顺序。
需要注意的是,对于 process_response 方法,必须要有一个返回值,且返回值一般是自己接收到的那个 response 对象;如果 process_response 返回一个自定义的 HttpResponse 对象也是可以的,这样的话浏览器渲染的就是返回的自定义的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from django.utils.deprecation import MiddlewareMixinfrom django.shortcuts import HttpResponseclass MyMiddleware_1 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_1 request" ) def process_response (self, request, response ): print ("MyMiddleware_1 response" ) return HttpResponse("OK" ) class MyMiddleware_2 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_2 request" ) def process_response (self, request, response ): print ("MyMiddleware_2 response" ) return response
1 2 此时访问 http:// 127.0 .0.1 :8000 /index/ ,页面显示的内容是 "OK"
当 process_request 中设有返回值时,process_response 返回的对象将是 process_request 中设置的返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from django.utils.deprecation import MiddlewareMixinfrom django.shortcuts import HttpResponseclass MyMiddleware_1 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_1 request" ) return HttpResponse("middleware1" ) def process_response (self, request, response ): print ("MyMiddleware_1 response" ) return response class MyMiddleware_2 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_2 request" ) def process_response (self, request, response ): print ("MyMiddleware_2 response" ) return response
1 2 此时访问 http:// 127.0 .0.1 :8000 /index/ ,页面显示的内容是 "middleware1"
总结:
只要 process_response 中有自定义的返回值时,最终返回给浏览器的都是该值
当 process_response 中没有自定义的返回值,但是 process_request 中有设置的返回值时,最终返回给浏览器的是 process_request 中的返回值
当 process_response 中没有自定义的返回值, process_request 也没有设置的返回值时,最终返回给浏览器的是视图的结果
2.3 process_view 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from django.utils.deprecation import MiddlewareMixinfrom django.shortcuts import HttpResponseclass MyMiddleware_1 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_1 request" ) def process_response (self, request, response ): print ("MyMiddleware_1 response" ) return response def process_view (self, request, callback, callback_args, callback_kwargs ): print ("MyMiddleware_1 view" )class MyMiddleware_2 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_2 request" ) def process_response (self, request, response ): print ("MyMiddleware_2 response" ) return response def process_view (self, request, callback, callback_args, callback_kwargs ): print ("MyMiddleware_2 view" )
相关参数介绍:
callback 通过路由层映射找到的要执行的视图函数
callback_args/callback_kwargs 视图函数的参数
1 2 3 4 5 6 7 8 9 # 测试时后端打印结果 MyMiddleware_1 request MyMiddleware_2 request MyMiddleware_1 view MyMiddleware_2 view index....... MyMiddleware_2 response MyMiddleware_1 response
可见 process_view 是在 process_request 之后且在视图函数之前执行的,即
当最后一个中间的 process_request 到达路由关系映射之后,返回到中间件1的 process_view,然后依次往下,到达 views 函数,最后通过 process_response 依次返回到达浏览器。
由于参数 callback 实际上就是对应的视图函数的名称空间,我们也可以在 process_view 中直接提前执行视图函数
1 2 3 4 5 6 def process_view (self, request, callback, callback_args, callback_kwargs ): print (">>>>>>>>>>>>" , callback) print ("MyMiddleware_2 view" ) response=callback(request,*callback_args,**callback_kwargs) return response
同样的,由流程图可以看出,process_view 如果有返回值,会越过其他的 process_view 以及视图函数,但是所有的 process_response 都还会执行。
1 2 3 def process_view (self, request, callback, callback_args, callback_kwargs ): print ("MyMiddleware_2 view" ) return HttpResponse("ok" )
2.4 process_exception 只有视图中出现错误时,才会触发该方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from django.utils.deprecation import MiddlewareMixinfrom django.shortcuts import HttpResponseclass MyMiddleware_1 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_1 request" ) def process_response (self, request, response ): print ("MyMiddleware_1 response" ) return response def process_view (self, request, callback, callback_args, callback_kwargs ): print ("MyMiddleware_1 view" ) def process_exception (self, request, exception ): print ("CustomerMiddleware1 process_exception" )class MyMiddleware_2 (MiddlewareMixin ): def process_request (self, request ): print ("MyMiddleware_2 request" ) def process_response (self, request, response ): print ("MyMiddleware_2 response" ) return response def process_view (self, request, callback, callback_args, callback_kwargs ): print ("MyMiddleware_2 view" ) def process_exception (self, request, exception ): print ("CustomerMiddleware2 process_exception" )
1 2 3 4 5 6 7 8 from django.shortcuts import render, HttpResponsedef index (request ): print ("index......." ) aaaa return HttpResponse("INDEX" )
1 2 3 4 5 6 7 8 9 10 11 # 后台打印结果 MyMiddleware_1 request MyMiddleware_2 request MyMiddleware_1 view MyMiddleware_2 view index....... CustomerMiddleware2 process_exception CustomerMiddleware1 process_exception MyMiddleware_2 response MyMiddleware_1 response
视图函数执行到 aaaa 时会报错,此时不再继续执行后面的代码,而是去执行中间件中的 process_exception 方法,执行顺序是按照中间件从下往上的顺序,如果当前中间件中的 process_exception 没有对异常进行处理,机会继续向上走,直到某一个中间件对异常进行了处理,那么该中间件上面的中间件的 process_exception 就不会执行了,次数会回过头去执行最后一个中间件中的 process_response方法;如果一直没有中间件对异常进行处理,就会返回默认的错响应。
1 2 3 4 5 6 def process_exception (self, request, exception ): print ("CustomerMiddleware2 process_exception" ) return HttpResponse(exception)