Django - 中间件
一. 什么是中间件
官方: 中间件是一个用来处理Django的请求和相应的框架级别的钩子, 他是一个轻量, 低级别的插件系统, 用于在全局范围内改变Django的输入和输出, 每个中间件都负责做一些特定的功能.
大白话: 中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作, 本质就是一个自定类, 类中定义了几个方法,
Django框架会处理请求的特定的时间去执行这些方法. 影响的是全局, 谨慎使用.
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', ]
二. 定义中间件
中间件定义可以定义五个方法, (主要的是 process_request和 process_response)
process_request(self.rerquest)
process_view(self, request, view_func, view_args, view_kwargs)
process_template_response(self, request, response)
process_exception(self, request, exception)
process_response(self, request, respoonse)
以上方法的返回值可以是None或HttpResponse对象, 如果是None, 则继续按照django定义的规则向后继续进行, 如果是 HttpResponse对象, 则直接将该对象返回给用户.
1. process_request(self.rerquest)
执行时间: 在视图函数执行之前
参数: rsquest --> 跟视图函数中的是同一个
执行顺序: 按照注册顺序, 顺序执行
返回值: None --> 正常路程
HttpResponse对象 --> 不执行后面中间中的procrss_request方法, 不执行视图函数, 直接执行当前中间件中的process_response 方法, 后面正常走.
from django.utils.deprecation import MiddlewareMixin
class MD1(MiddlewareMixin): def process_request(self, request): print("MD1里面的 process_request")
class MD2(MiddlewareMixin): def process_request(self, request): print("MD2里面的 process_request")
在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', 'middlewares.MD1', # 自定义中间件MD1 'middlewares.MD2' # 自定义中间件MD2 ]
总结:
- 中间件的process_request方法是在执行视图函数之前执行的。
- 当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
- 不同中间件之间传递的request都是同一个对象
2. process_response(self, request, respoonse)
执行时间: 在视图函数执行之后, process_request之后, 路由匹配之后
参数: request --> 跟函数中的是同一个
response --> 视图函数中传递的相应对象
执行顺序: 按照注册顺序, 倒叙执行
返回值: None -->正常流程
HttpResponse对象: 必须是相应对象
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request): print("MD1里面的 process_request") def process_response(self, request, response): print("MD1里面的 process_response") return response class MD2(MiddlewareMixin): def process_request(self, request): print("MD2里面的 process_request") def process_response(self, request, response): print("MD2里面的 process_response") return response
总结:
process_response方法是在视图函数之后执行的,并且顺序是MD1比MD2先执行。(此时settings.py中 MD2比MD1先注册)
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。
3.process_view(self, request, view_func, view_args, view_kwargs)
执行时间: 在process_request方法之后, 在视图函数执行之前
参数: request --> 跟视图函数的是同一个
view_func --> 视图函数
view_args --> 视图函数的位置函数
view_kwargs --> 视图函数的关键字参数
执行顺序: 按照注册顺序 顺序执行
返回值: None --> 正常执行
HttpResponse对象 -->
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request): print("MD1里面的 process_request") def process_response(self, request, response): print("MD1里面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD1 中的process_view") print(view_func, view_func.__name__) class MD2(MiddlewareMixin): def process_request(self, request): print("MD2里面的 process_request") def process_response(self, request, response): print("MD2里面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD2 中的process_view") print(view_func, view_func.__name__)
4. process_exception(self, request, exception)
参数:request ——》 跟视图函数中的是同一个
exception——》 异常对象
执行顺序:按照注册顺序 倒叙执行
返回值:None: 正常流程
HttpResponse对象:不执行后面中间中的process_exception方法,直接执行最后一个中间件中的process_response方法,后面正常走
from django.utils.deprecation import MiddlewareMixin class MD1(MiddlewareMixin): def process_request(self, request): print("MD1里面的 process_request") def process_response(self, request, response): print("MD1里面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD1 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") class MD2(MiddlewareMixin): def process_request(self, request): print("MD2里面的 process_request") def process_response(self, request, response): print("MD2里面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD2 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD2 中的process_exception")
5. process_template_response(self, request, response)
参数:request ——》 跟视图函数中的是同一个
response ——》 视图函数中传递的响应对象
执行顺序:按照注册顺序 倒叙执行
返回值:必须返回response对象
class MD1(MiddlewareMixin): def process_request(self, request): print("MD1里面的 process_request") def process_response(self, request, response): print("MD1里面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD1 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD1 中的process_exception") return HttpResponse(str(exception)) def process_template_response(self, request, response): print("MD1 中的process_template_response") return response class MD2(MiddlewareMixin): def process_request(self, request): print("MD2里面的 process_request") def process_response(self, request, response): print("MD2里面的 process_response") return response def process_view(self, request, view_func, view_args, view_kwargs): print("-" * 80) print("MD2 中的process_view") print(view_func, view_func.__name__) def process_exception(self, request, exception): print(exception) print("MD2 中的process_exception") def process_template_response(self, request, response): print("MD2 中的process_template_response") return response
三. 中间件的执行流程
四. 中间件版登录注册
注意: 要把session 同步到数据库中
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/$', views.login, name='login'), url(r'^index/$', views.index, name='index'), url(r'^home/$', views.home, name='home'), ]
views.py
from django.shortcuts import render, HttpResponse, redirect def index(request): return HttpResponse('this is index') def home(request): return HttpResponse('this is home') def login(request): if request.method == "POST": user = request.POST.get("user") pwd = request.POST.get("pwd") if user == "alex" and pwd == "alex3714": # 设置session request.session["user"] = user # 获取跳到登陆页面之前的URL next_url = request.GET.get("next") # 如果有,就跳转回登陆之前的URL if next_url: return redirect(next_url) # 否则默认跳转到index页面 else: return redirect("/index/") return render(request, "login.html")
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>登录页面</title> </head> <body> <form action="{% url 'login' %}" method="post"> {% csrf_token %} <p> <label for="user">用户名:</label> <input type="text" name="user" id="user"> </p> <p> <label for="pwd">密 码:</label> <input type="text" name="pwd" id="pwd"> </p> <input type="submit" value="登录"> </form> </body> </html>
middlewares.py
rom django.utils.deprecation import MiddlewareMixin class AuthMD(MiddlewareMixin): white_list = ['/login/', ] # 白名单 black_list = ['/black/', ] # 黑名单 def process_request(self, request): from django.shortcuts import redirect, HttpResponse next_url = request.path_info print(request.path_info, request.get_full_path()) # 黑名单的网址限制访问 if next_url in self.black_list: return HttpResponse('This is an illegal URL') # 白名单的网址或者登陆用户不做限制 elif next_url in self.white_list or request.session.get("user"): return else: return redirect("/login/?next={}".format(next_url))
settings.py
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', 'middlewares.AuthMD' ]
AuthMD中间件注册后,所有的请求都要走AuthMD的process_request方法。
如果URL在黑名单中,则返回This is an illegal URL的字符串;
访问的URL在白名单内或者session中有user用户名,则不做阻拦走正常流程;
正常的URL但是需要登录后访问,让浏览器跳转到登录页面。
注:AuthMD中间件中需要session,所以AuthMD注册的位置要在session中间的下方。
# visit_dict = {} # class Throttle(MiddlewareMixin): # # def process_request(self, request): # # 获取到ip # ip = request.META.get('REMOTE_ADDR') # # 获取到访问记录 # history = visit_dict.get(ip, []) # if not history: # visit_dict[ip] = history # # now = time.time() # new = [] # for i in history: # if now - i > 5: # new.append(i) # for i in new: # history.remove(i) # # while history and now - history[-1] > 5: # history.pop() # # if len(history) >= 3: # return HttpResponse('访问频率太快了') # # history.insert(0, now)
class Throttle(MiddlewareMixin): def process_request(self, request): history = request.session.get('history', []) now = time.time() while history and now - history[-1] > 10: history.pop() if len(history) >= 3: return HttpResponse('你的访问频率太快') history.insert(0, now) request.session['history'] = history
五. 补充
1. 跨站请求伪造, csrf中间件
两个装饰器
from django.views.decorator.csrf import csrf_exempt, csrf_protect
@carf_exempt --> 给视图加上装饰器后, 当前的视图不需要CSRF校验
@csrf_protect --> 给视图加上装饰器后, 当前的视图需要CSRF校验
2. process_request
从cookie中获取csrftoken的值 —— 》 request.META['CSRF_COOKIE']
3. process_view
1. 视图函数加上csrf_exempt装饰器,不进行CSRF校验 2. 请求方式 是'GET', 'HEAD', 'OPTIONS', 'TRACE' 也不进行校验 3. csrf_token = request.META.get('CSRF_COOKIE') # cookie中获取csrftoken的值 # 获取提交的csrfmiddlewaretoken的值 request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') 如果或许不到csrfmiddlewaretoken的值 再尝试从请求头中获取X_CSRFTOKEN的值 —— 》request_csrf_token 4. request_csrf_token 和 csrf_token 进行比较 1. 能比较成功 通过校验 2. 不能比较成功 拒绝