中间件

2019年11月12日 阅读数:52
这篇文章主要向大家介绍中间件,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。
复制代码
1、介绍
官方的说法:中间件是一个用来处理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',
]



2、中间件的使用
1、 做用
    全局改变Django的请求和响应

二、 Django中自定义中间件
    1.新建一个python page包,包内新建py文件
      在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

    2.五个方法(执行的时间点、执行的顺序、参数、返回值)
   1. process_request(self,request)             *****
        0. 执行的时间点:
            请求进来以后
        1. 执行顺序:
            按照中间件的注册顺序执行
        2. 参数
            当前请求对象(从哪一个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. 响应对象: 直接返回


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')
View Code


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  # 必须返回一个对象
View Code


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方法')
View Code


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
View Code


中间件
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方法')
View Code


5.process_exception(self, request, exception)
views.py
def test(request):
    print(request, id(request))
    print('-------------in test--------------------------------------')
    raise ValueError('英雄联盟')
    return HttpResponse('我是test视图函数')
View Code
 
  


中间件
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')
View Code
 
  


3、中间件的流程图
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作最后的响应处理。




五、拓展
注册中间件的时候,咱们写入的是字符串,那么django是如何找到我定义的具体的中间件函数呢?
实际上,使用的是importlib模块和反射
例如:我注册是中间件是 'mymiddleware.my_midware.CheckLogin'html

 
  

import importlib
# importlib相似于反射,importlib能够一直反射到模块(py文件)
# 反射只能单一反射某个模块内的变量
# 所以importlib和反射配合使用能够达到把字符串反射成咱们须要的变量python

 
  

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)django





4、示例
一、中间件版登陆验证
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/')
View Code
 
  

 

2.settings.py配置白名单浏览器

# 白名单可写在中间件那里,也能够写在settings那里,为了方便后续的修改,通常写在settingsession

# 白名单:不须要登陆便可访问的URLapp

WHITE_URLS = ['/login/']框架

 

3.中间件ide

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
View Code

 

 

二、访问频率限制函数

1.settings.py设置访问频率限制post

ACCESS_LIMIT = 10

 

2.中间件

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect, render, HttpResponse
from django.conf import settings
from myapp.models import *
import time


# 存放每一个ip访问页面的大字典
ACCESS_RECORD = {}
# 自定义访问频率限制
class Throttle(MiddlewareMixin):

    def process_request(self, request):
        access_limit = settings.ACCESS_LIMIT if hasattr(settings, 'ACCESS_LIMIT') else 60
        # 拿到当前请求的ip地址
        ip = request.META.get('REMOVE_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('')
View Code

 

 

 
复制代码