Django中间件(中间件版登陆验证、访问频率限制)
一、介绍 官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。 但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。 说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。 我们一直都在使用中间件,只是没有注意到而已,打开Django项目的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', ] 二、中间件的使用 1、 作用 全局改变Django的请求和响应 2、 Django中自定义中间件 1.在项目下新建一个python page包(例如:mymiddleware),包内新建py文件(例如:my_middleware) 在py文件内定义中间件 示例: 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 然后去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', 'mymiddleware.my_midware.MD1', ]
2.五个方法(执行的时间点、执行的顺序、参数、返回值) 1. process_request(self,request) ***** 0. 执行的时间点: 请求进来之后,执行view视图函数前 1. 执行顺序: 按照中间件的注册顺序执行 2. 参数 request:当前请求对象(从哪个url进来的,request在中间件和views函数中都是那个url的request) 3. 返回值 1. 返回None继续执行后续的 2. 返回响应对象,不继续执行后续的流程,直接返回响应 2. process_response(self, request, response) ***** 0. 执行的时间点: 返回响应之后(即经过了views.py里面的函数之后) 1. 执行顺序: 按照中间件注册的倒序执行 2. 参数 1. request:当前的请求对象 2. response: 从views.py对应的函数中传递过来的响应对象 3. 返回值: 1. 必须返回一个响应对象 3. process_view(self, request, view_func, view_args, view_kwargs) 0. 执行的时间点: process_request之后,视图函数之前执行 1. 执行顺序 按照注册的顺序执行 2. 参数 1. request:请求对象 2. view_func: 将要执行的视图函数对象 3. view_args/view_kwargs: 将要执行的函数的参数 3. 返回值 1. None:继续往后执行 2. 响应对象: 直接返回了,不会执行后续的视图函数 4. process_template_response(self,request,response) 0. 执行的时间点 当视图函数中返回带render方法的响应对象,这个方法才会执行 1. 执行顺序: 注册的倒序 2. 参数: 1. request:请求对象 2. response:响应对象 3. 返回值 1. 必须返回响应对象 5. process_exception(self, request, exception) 0. 执行的时间点 当视图函数中抛出异常的时候才会执行这个方法 1. 执行顺序: 注册的倒序 2. 参数: 1. request:请求对象 2. exception: 视图函数抛出的异常 3. 返回值: 1. None:继续执行后续 2. 响应对象: 直接返回
6.小结
1.中间件的执行顺序:
只要还没到视图函数,中间件的执行顺序就是 注册的顺序
只要到了视图函数,或者已经执行过了视图函数,中间件的执行顺序就是 注册的倒序 3、例子 1.process_request(self, request)
class M1(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M1 process_request') # return HttpResponse('ok') class M2(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M2 process_request')
2.process_response(self, request, response)
class M1(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M1 process_request') # return HttpResponse('ok') def process_response(self, request, response): print('in M1 process_response') return response # 必须返回一个对象 class M2(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M2 process_request') def process_response(self, request, response): print('in M2 process_response') return response # 必须返回一个对象
3.process_view(self, request, view_func, view_args, view_kwargs)
class M1(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M1 process_request') # return HttpResponse('ok') def process_response(self, request, response): print('in M1 process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print(view_func) # view_func(request) # 在中间件中手动调用将要执行的视图函数 print('这是M1里面的process_view方法') # return HttpResponse('OJ8K') class M2(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M2 process_request') def process_response(self, request, response): print('in M2 process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print(view_func) print('这是M2里面的process_view方法')
4.process_template_response(self,request,response)
views.py
def test(request): print(request, id(request)) print('-------------in test--------------------------------------') rep = HttpResponse('我是原本的响应') def render(): return HttpResponse('我有render方法啦,记住是方法!') rep.render = render # 给响应对象设置一个属性render,给这个属性添加render方法 return rep
中间件
class M1(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M1 process_request') # return HttpResponse('ok') def process_response(self, request, response): print('in M1 process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print(view_func) print('这是M1里面的process_view方法') # return HttpResponse('OJ8K') def process_template_response(self, request, response): print('这是M1中process_template_response方法') return(response) # 必须要有响应对象 class M2(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M2 process_request') def process_response(self, request, response): print('in M2 process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print(view_func) print('这是M2里面的process_view方法')
5.process_exception(self, request, exception)
views.py
def test(request): print(request, id(request)) print('-------------in test--------------------------------------') raise ValueError('英雄联盟') return HttpResponse('我是test视图函数')
中间件
class M1(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M1 process_request') # return HttpResponse('ok') def process_response(self, request, response): print('in M1 process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print(view_func) print('这是M1里面的process_view方法') # return HttpResponse('OJ8K') def process_template_response(self, request, response): print('这是M1中process_template_response方法') return(response) def process_exception(self, request, exception): print('这是M1中的process_exception') print(exception) return HttpResponse('视图函数报错啦!') class M2(MiddlewareMixin): def process_request(self, request): print(request, id(request)) print('in M2 process_request') def process_response(self, request, response): print('in M2 process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print(view_func) print('这是M2里面的process_view方法') def process_exception(self, request, exception): print('这是M2中的process_exception')
三、中间件的流程图 1、django框架完整流程图
2、process_request和process_response 当process_request没有返回响应对象的时候,会继续执行后面的操作,顺利执行完后,会执行视图函数,最后执行process_response, 当process_request返回了响应对象,那么它会立刻执行自己的process_response方法, 此时process_response中的response参数接收的是process_request返回的响应对象 3、process_request和process_response和process_view 当process_request没有返回响应对象的时候,会继续执行后面的process_request操作, 当process_request返回了响应对象,那么它会立刻执行自己的process_response方法, 此时process_response中的response参数接收的是process_request返回的响应对象, 当process_request没有返回响应对象,顺利执行完后,将会执行process_view, 当process_view没有返回响应对象的时候,会继续执行后面的process_view操作,顺利执行完后,会执行视图函数,最后执行process_response, 当process_view返回了一个响应对象的时候,后续操作(除了process_response)将不会执行, 而是把响应对象返回给最后一个process_response方法。 4、总结 请求进来的时候,按顺序执行process_request,然后执行process_view,执行完后,再执行views.py里面的视图函数,最后按倒叙执行process_response, process_response无论如何都是会执行的,它是做响应处理,然后把响应返回给浏览器的函数, 当process_request返回了一个响应对象的时候,后续操作(除了process_response)将不会执行,而是直接把响应对象返回给自己的process_response方法, 当process_view返回了一个响应对象的时候,后续操作(除了process_response)将不会执行,而是把响应对象返回给最后一个process_response方法。
注意: 一般情况下是process_response做响应处理。 当视图函数中返回带render方法的响应对象时,执行process_template_response做响应处理,然后process_template_response会把响应再返回给process_response做最后的响应处理。 当视图函数中抛出异常的时候,执行process_exception做响应处理,然后process_exception会把响应再返回给process_response做最后的响应处理。
5、拓展
注册中间件的时候,我们写入的是字符串,那么django是如何找到我定义的具体的中间件函数呢?
实际上,使用的是importlib模块和反射
例如:我注册是中间件是 'mymiddleware.my_midware.CheckLogin'
import importlib
# importlib类似于反射,importlib可以一直反射到模块(py文件)
# 反射只能单一反射某个模块内的变量
# 因此importlib和反射配合使用可以达到把字符串反射成我们需要的变量
s = 'mymiddleware.my_midware.CheckLogin' # 具体到模块的变量,importlib不能反射,需要分割一下
s1 = s.rsplit('.', 1) # ['mymiddleware.my_midware', 'CheckLogin']
module_obj = importlib.import_module(s1[0]) # 把字符串'mymiddleware.my_midware'反射出来
if hasattr(module_obj, s1[1]):
class_obj = getattr(module_obj, s1[1]) # 把'CheckLogin'反射出来
print(class_obj)
四、示例
1、中间件版登录验证
1.views.py
def login(request): if request.method == 'POST': username = request.POST.get('username') pwd = request.POST.get('password') is_rem = request.POST.get('remember', None) # 不推荐使用get,因为get取不到值会报错 user_obj = UserInfo.objects.filter(name=username, password=pwd).first() if user_obj: return_url = request.GET.get('returnUrl', '/index/') # 设置session的键值对 request.session['user'] = user_obj.name # 判断是否记住密码 if is_rem: # 是就保存七天 request.session.set_expiry(60 * 60 * 24 * 7) else: # 不是就不保存 request.session.set_expiry(0) return redirect(return_url) else: return render(request, 'login.html', {'error_msg': '用户名或者密码错误'}) return render(request, 'login.html') def index(request): return render(request, 'index.html') def home(request): return render(request, 'home.html') def logout(request): request.session.flush() return redirect('/login/')
2.settings.py配置白名单
# 白名单可写在中间件那里,也可以写在settings那里,为了方便后续的修改,一般写在setting
# 白名单:不需要登录即可访问的URL
WHITE_URLS = ['/login/']
3.中间件
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect, render, HttpResponse from django.conf import settings from myapp.models import * # 自定义登录验证的中间件 class CheckLogin(MiddlewareMixin): def process_request(self, request): # 判断请求的url是否在白名单 while_urls = settings.WHITE_URLS if hasattr(settings, 'WHITE_URLS') else [] if request.path_info in while_urls: return None # 查看请求的数据中是否携带我设置的登录状态 is_login = request.session.get('user', None) if not is_login: # 没有登录,跳转到登录界面 # 获取当前访问的url next_url = request.path_info return redirect('/login/?returnUrl={}'.format(next_url)) else: # 已经登录了,获取登录的对象 user_obj = UserInfo.objects.get(name=is_login) # 把登录对象赋值给request的use属性(自定义属性) request.user = user_obj
2、访问频率限制
1.settings.py设置访问频率限制
ACCESS_LIMIT = 10
2.中间件
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect, render, HttpResponse from django.conf import settings import time import redis # 存放每个ip访问页面的大字典 ACCESS_RECORD = {} # 自定义访问频率限制 class Throttle(MiddlewareMixin): def process_request(self, request): access_limit = settings.ACCESS_LIMIT if hasattr(settings, 'ACCESS_LIMIT') else 10 # 拿到当前请求的ip地址 ip = request.META.get('REMOTE_ADDR') # 初始化字典,把每个新进来的请求存到ACCESS_RECORD大字典里面 if ip not in ACCESS_RECORD: ACCESS_RECORD[ip] = [] # 从字典中拿到当前访问的ip的列表[列表只存距离现在10秒内的访问时间点] ip_access_list = ACCESS_RECORD[ip] # 拿到当前时间 now = time.time() # 把距离当前时间大于10秒的访问时间点从列表中删除 while ip_access_list and now - ip_access_list[-1] > access_limit: ip_access_list.pop() # 距离当前时间小于10秒的访问时间追加进访问列表里面 ip_access_list.insert(0, now) # 判断最近10秒中之内访问次数是否大于3 if len(ip_access_list) > 3: return HttpResponse('滚')
3.redis版本
# -*- coding: utf-8 -*- from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect, render, HttpResponse from django.conf import settings import time import redis conn = redis.Redis(host='localhost', port=6379, decode_responses=True) # 自定义访问频率限制 class RedisThrottle(MiddlewareMixin): def process_request(self, request): # 频率 access_limit = settings.ACCESS_LIMIT if hasattr(settings, 'ACCESS_LIMIT') else 10 # 拿到当前请求的ip地址 ip = request.META.get('REMOTE_ADDR') # 把访问的ip存到redis: ip: 访问次数 if conn.exists(ip): conn.incr(ip) else: conn.set(ip, 1) conn.expire(ip, access_limit) # 如果访问次数超过访问频率, 10秒内只能访问3次 if int(conn.get(ip)) > 3: return HttpResponse('滚')