Django自定义中间件

友情提醒:本文最后更新于 2036 天前,文中所描述的信息可能已发生改变,请谨慎使用。

一. 什么是中间件

中间件是一个钩子框架,它们可以介入Django 的请求和响应处理过程。 它是一个轻量级、底层的插件系统,用于在全局修改Django 的输入或输出

我们从浏览器发出一个请求 Request,得到一个响应后的内容 HttpResponse ,这个请求传递到 Django的过程如下:

也就是说,每一个请求都是先通过中间件中的 process_request 函数,这个函数返回 None 或者 HttpResponse 对象,如果返回前者,继续处理其它中间件,如果返回一个 HttpResponse,就处理中止,返回到网页上。

我们先来看一下Django基础中间件的源码:

django.utils.deprecation.py
class MiddlewareMixin(object):
    def __init__(self, get_response=None):
        self.get_response = get_response
        super(MiddlewareMixin, self).__init__()
    def __call__(self, request):
        response = None
        if hasattr(self, 'process_request'):
            response = self.process_request(request)
        if not response:
            response = self.get_response(request)
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)
        return response

要习惯于看源码,上面的这段代码并不复杂,下面我们来一一解释。

def __init__(self, get_response=None):
    self.get_response = get_response
    super(MiddlewareMixin, self).__init__()

熟悉python类的都不陌生__init__方法, 这里主要是一次性配置和初始化

def __call__(self, request):
    response = None
    if hasattr(self, 'process_request'):
        response = self.process_request(request)
    if not response:
        response = self.get_response(request)
    if hasattr(self, 'process_response'):
        response = self.process_response(request, response)
    return response

__call__ 为每个请求/响应执行的代码

self.process_request(request) 为每个请求到调用视图之前的操作,通常可以在这里做一些用户请求频率的控制。

self.get_response(request) 为调用视图

self.process_response(request, response) 为调用视图完成后的操作。

1、中间件共分为

process_request(self,request)
process_view(self, request, callback, callback_args, callback_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)

2、使用方法

process_request(self, request)  # 该方法在请求到来的时候调用。 
process_view(self ,request, fnc , arg ,kwarg)  # 在本次将要执行的View函数被调用前调用本函数。 
process_response(self,request,response)  # 在执行完View函数准备将响应发到客户端前被执行。 
**process_exception(self,request, exception)**  # View函数在抛出异常时该函数被调用,得到的exception参数是实际上抛出的异常实例。通过此方法可以进行很好的错误控制,提供友好的用户界面。

二. 自定义中间件

刚才了解了基础中间件,现在就开始编写我们自己的中间件。通常我们回去继承基础中间件来实现自己的功能

if django.VERSION[:3] >= (1, 10, 0):
    # 新版本中,middle需要继承这个类
    from django.utils.deprecation import MiddlewareMixin
else:
    MiddlewareMixin = object


class PermissionMiddlewareMixin(MiddlewareMixin):
    """
    django 中间件
    """
    def process_request(self,request):
        print '--------------request'

    def process_view(self,request, view_func, *view_args, **view_kwargs):
        print '--------------view'

    def process_response(self,request, response):  # 该方法必须返回 response
        print '--------------response'
        return response

三. 注册中间件

setting.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',
    'appname.middleware.middleware_name',   # 在这里添加中间件的路径,就是注册中间件
]

四. 举个栗子?

1、将GET、POST参数整合到parameters

# -*- coding: utf-8  -*-
'''
请求初始化预处理:
    1. GET、POST参数整合到parameters
'''
import django
from django.http import HttpResponseBadRequest, QueryDict

if django.VERSION[:3] >= (1, 10, 0):
    # 新版本中,middle需要继承这个类
    from django.utils.deprecation import MiddlewareMixin
    BaseMiddleCls = MiddlewareMixin
else:
    BaseMiddleCls = object


class RequestInitMiddleware(BaseMiddleCls):
    '''请求初始化预处理'''
    def process_request(self, request):
        try:
            # 1. GET参数整合到parameters
            request.parameters = request.GET.copy()
            if request.method == "POST":
                # 2. 处理request.body内的query_string更新到request.parameters
                # multipart/form-data 时不处理表单里的参数,因为没有经过nginx的参数签名校验,无法保证参数合法性
                if request.META['CONTENT_TYPE'].startswith('multipart/form-data'):
                    return None
                # application/x-www-form-urlencoded 时不处理request.body
                elif request.META['CONTENT_TYPE'] == 'application/x-www-form-urlencoded':
                    pass
                # xml or json 时不处理request.body
                elif request.body.startswith("<") or request.body.startswith("{") or request.body.startswith("["):
                    pass
                # 其他情况更新request.body内的query_string到request.parameters
                elif '=' in request.body:
                    request.parameters.update(QueryDict(request.body, encoding='utf-8'))

                # 3. 表单参数更新到request.parameters
                for k in request.POST:
                    # 使用setlist以支持类似复选控件一个name多个value的情况
                    request.parameters.setlist(k, request.POST.getlist(k))
            return None
        except:
            response = HttpResponseBadRequest()
            return response

2、客户端请求频率限制

# coding:utf-8

from django.utils.deprecation import MiddlewareMixin
from django.http.response import HttpResponse
from django_redis import get_redis_connection
from hashlib import md5


class RequestBlockMiddlewareMixin(MiddlewareMixin):
    """
    django中间件客户端请求频率限制
    """

    limit = 4  # 单位时间内允许请求次数
    expire = 1  # 限制时间
    cache = "default"  # 获取django cache

    def process_request(self, request):
        num = self.set_key(request)
        if num > self.limit:
            return HttpResponse("请求频率过快,请稍后重试", status=503)

    @staticmethod
    def get_ident(request):
        """
        Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
        if present and number of proxies is > 0. If not use all of
        HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
        """
        NUM_PROXIES = 1
        xff = request.META.get('HTTP_X_FORWARDED_FOR')
        remote_addr = request.META.get('REMOTE_ADDR')
        num_proxies = NUM_PROXIES

        if num_proxies is not None:
            if num_proxies == 0 or xff is None:
                return remote_addr
            addrs = xff.split(',')
            client_addr = addrs[-min(num_proxies, len(addrs))]
            return client_addr.strip()

        return ''.join(xff.split()) if xff else remote_addr

    def get_md5(self, request):
        """
        获取IP md5值
        :param request:
        :return:
        """
        ip_str = self.get_ident(request)
        ip_md5 = md5()
        ip_md5.update(ip_str.encode("utf-8"))
        return ip_md5.hexdigest()

    def set_key(self, request):
        """
        通过redis lua脚本设置请求请求次数和限制时间
        :param request:
        :return: 限制时间内请求次数
        """
        lua = """
            local current
            current = redis.call("incr",KEYS[1])
            if tonumber(current) == 1 then
                redis.call("expire",KEYS[1],ARGV[1])
            end
            return tonumber(redis.call("get", KEYS[1]))
            """
        key = self.get_md5(request)
        redis_cli = get_redis_connection(self.cache)
        data = redis_cli.eval(lua, 1, key, self.expire, self.limit)
        return data

相关链接:
http://lxpgj.com/article/60
https://my.oschina.net/esdn/blog/825194
https://code.ziqiangxuetang.com/django/django-middleware.html
https://carey.akhack.com/2018/11/20/django%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%A4%84%E7%90%86/
https://gitee.com/careyjike_173/codes/0v4fwk8ytdm62iue3aj9h28

上一篇:Python操作Redis

下一篇:Django URL重定向的3种方法