Django中间件Middleware

django中间件

一、中间件详细分析

1.django配置文件settings.py

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
         ...  ... 
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
     # 把自己定义的middlerware.py中间件添加进来
    'pay_gateway.process_handle_middleware.ProcessHandleMiddleware'
]

2.自定义中间件middleware.py

# middleware.py
from django.utils.deprecation import MiddlewareMixin

class ProcessHandleMiddleware(MiddlewareMixin):
    """
    以下几个函数的顺序,刚好也是中间件的执行顺序,
    按需在适当的位置做操作
    """
    def __init__(self, *args, **kwargs):
        """
        如果需要额外初始化数据则定义
        """
        super(ProcessHandleMiddleware, self).__init__(*args, **kwargs)
        self.start_time = time.time()

    def process_request(self, request):
        """request请求一进来的预处理函数。
            - 在Django接收到request之后,未解析URL到对应视图函数之前。Django向它传入相应的Request对象,以便在方法中修改。
            - 若返回None,Django将继续处理这个request,执行后续的中间件, 然后调用相应的 view。
            - 若返回HttpResponse对象,Django将不再执行任何除了process_response(返回浏览器的最后一层干预)以外其它的中间件
              以及相应的view,Django将立即返回该HttpResponse
            - 这里如果报错,后边所有的中间件都就不执行了,直接返回(由内部对error处理,报500)
        """
        print("request arrive ...")

    def process_view(self, request, callback, callback_args, callback_kwargs):
        """进view前的预处理函数
            - 在Django执行完request预处理,路由解析完毕,确定待执行的view函数(即callback函数)之后,但在view实际执行之前
        :param request: HttpRequest 对象
        :param callback: Django将调用的处理request的python函数. 这是实际的函数对象本身, 而不是字符串表述的函数名
        :param callback_args: 将传入view的位置参数列表,但不包括request参数(它通常是传入view的第一个参数)
        :param callback_kwargs: 将传入view的关键字参数字典
        :return:
            - 如果返回None, Django将继续处理这个request ,执行后续的中间件, 然后调用相应的view
            - 如果返回HttpResponse对象,Django将不再执行任何其它的中间件(不论种类)以及相应的view,Django会立即返回
            - 这里如果报错,后边所有的中间件都就不执行了,直接返回(由内部对error处理,报500)
        """
        print("before view")

    def process_template_response(self):
        """template模板渲染函数
            - 默认不执行,只有在view函数返回的结果对象中有render方法才会执行
            - 若返回的话,会把对象内执行render方法后的返回值返回给用户(不返回view返回的对象,而是其对象内render方法的返回值)
        """
        print("template response ")

    def process_exception(self, request, exception):
        """只有view函数处理抛出的错误才能接到,其他的错误,这里并不能捕获
            - 可做异常通知,错误日志搜集,或尝试从错误中自动恢复
            - 若返回None,Django将用框架内置的异常处理机制继续处理相应request
            - 若返回HttpResponse对象,Django将使用该response对象,而短路框架内置的异常处理机制,直接返回(其他异常处理便不再执行)
        """
        print("process exception")

    def process_response(self, request, response):
        """在view函数返回response对象之后,马上要返回给用户之前,对其进行二次处理
            - 如:用gzip压缩返回数据
            - 这里必须要返回HttpResponse对象!!!   (可以是view返回的,也可是全新的,也可是处理过的,但必须是HttpResponse对象)
        """
        print("process response ... ... ")
        return response

3.注意事项:

  • process_viewsettings.py中的MIDDLEWARE中,后放的先执行,其他过程是先放的先执行。

  • process_response一定要有reurn否则会报错,自定义的中间件response方法没有return,会交给下一个中间件,导致http请求中断了。

  • process_view(self, request, callback,callback_args, callback_kwargs)方法介绍:

    • (1)执行完所有中间件的request方法
    • (2)url匹配成功
    • (3)拿到视图函数的名称、参数(注意不执行),再执行process_view()方法(传递view函数以及相关参数,但是还是不执行)
    • (4)最后去执行视图函数

4.看图分析

二、简化版,以及参数分析

class LoggerMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        print("start request".center(50, "*"))
        print(request)   # <WSGIRequest: GET '/apis/author/?limit=1'>
        print(type(request))   # <class 'django.core.handlers.wsgi.WSGIRequest'>
        print(dir(request))
        # req_method = request.method
        # if req_method == 'GET':
        #     req_data = request.

        print(request.method)  # yes
        print(request.content_type)
        print(request.META)   # meta数据
        print(request.META['REMOTE_ADDR'])   # 127.0.0.1
        print(type(request.META['REMOTE_ADDR']))   # <class 'str'>
        # print(request.META['HTTP_X_REAL_IP'])  # 这个可能有,可能没有。要和REMOTE_ADDR组合起来使用
        print(request.content_params)  # {}
        print(request.encoding)
        print(request.body)   # 二进制 b'{\n  "birthday": "2019-07-04",\n  "telephone": "string",\n  "addr": "\xe5\x95\x8a\xe5\x95\x8a\xe5\x95\x8a"\n}'
        print(str(request.body, encoding="utf-8"))  # {"birthday": "2019-07-05", "telephone": "string", "addr": "string"}
        print(request.get_full_path())   # /apis/author/?limit=1   请求从根路径开始的所有路径
        print(request.get_host())      #  127.0.0.1:8000    服务器主机
        print(request.scheme)   # http
        print(request.GET)   # <QueryDict: {'limit': ['1']}>
        print(type(request.GET))   # <class 'django.http.request.QueryDict'>
        print(dir(request.GET))
        print(request.GET.dict())   # {'limit': '1'}  get方法时用这个参数接查询字符串
        print(request.POST)  # <QueryDict: {}>
        print(type(request.POST))  # <class 'django.http.request.QueryDict'>
        print(dir(request.POST))
        print(request.POST.dict())
        print(request.POST.items())
        print(request.POST.lists())
        # print({k:v for k, v in request.POST.items()})
        for k, v in request.POST.items():
            print("%s=%s"%(k,v))
        print(request.POST.keys())  # dict_keys([])

        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = self.get_response(request)

        print("get response data".center(50, ">"))
        print(type(response))   # <class 'rest_framework.response.Response'> 这个用的是rest_framework的Response进行的数据返回
        print(dir(response))
        print(response)   # <Response status_code=200, "application/json">
        print(response.status_code)  # 200
        # print(response.status_text)  # OK  JsonResponse没有这个属性
        # print(response.data)     # resp  data 就记录这个就行, JsonResponse没有这个属性
        print(response.content)   # 二进制的resp返回数据
        # print(response.context_data)  # None,  JsonResponse没有这个属性
        print(response.items())  # JsonResponse有,   dict_values([('Content-Type', 'application/json'), ('Allow', 'GET, POST, PUT, DELETE, HEAD, OPTIONS')])
        print(response.readable())  # JsonResponse有,   False
        print(response.serialize())  # b'Content-Type: application/json\r\nAllow: GET, POST, PUT, DELETE, HEAD, OPTIONS\r\n\r\n{"name": "lili"}'

        # Code to be executed for each request/response after
        # the view is called.

        return response

三、应用实例

# 添加了每次请求的详细信息的日志记录
# 添加了返回值的二次处理封装成统一的格式
# 统一处理了自定义错误的处理,以及返回格式的统一

from django.http.response import JsonResponse

class ServerException(Exception):
    def __init__(self, reason, *args):
        self.reason = reason
        self.code = error_dict[self.reason]['code']
        self.data = None
        self.msg = error_dict[self.reason]["msg"] % args

    def to_response(self):
        return JsonResponse({
            "code": self.code,
            "reason": self.reason,
            "data": self.data,
            "msg": self.msg
        })


class ProcessHandleMiddleware(MiddlewareMixin):

    def __init__(self, *args, **kwargs):
        super(ProcessHandleMiddleware, self).__init__(*args, **kwargs)
        self.start_time = time.time()

    def process_request(self, request):
        """
        - 这里不能调用Response进行数据返回!
        - Response或SimpleTemplateResponse等实际返回的是Response().render(),
          而如果想进行渲染,那么在实例化时需要指定各种渲染类。
          但Response中是没有渲染类的默认值,而在View中会取默认值给Response,
          因而经过view的可以返回,自己在此位置直接调用并返回是不可以。
        - HTTPResponse和JSONResponse是直接拼凑二进制数据的响应,不存在渲染操作,所以可以直接返回
        """

    def process_exception(self, request, exception):
        capture_exception()
        if isinstance(exception, ServerException):
            return exception.to_response()

    def process_response(self, request, response):
        """
        1.如果想实现参数封装,这里只能使用rest_framework.response.Response !!!
        2.只针对接口函数的返回值做封装+日志记录
        """
        if str(request.path).startswith("/open-api"):
            # 参数封装
            if hasattr(response, 'data') and isinstance(response, Response):
                data = response.data
                response.data = {
                    'code': 0,
                    'data': data,
                    'reason': "SUCCESS",
                    'msg': '成功'
                }
                # 这里因为已经是实例了,渲染类相关的都已经存在于对象中了,所以这里可以这么操作
                # 因返回时已经render过response,要想让这里的修改有效,需要手动在render一次
                response._is_rendered = False
                response.render()

            # 记录日志
            req_data = ""
            if request.method == 'GET':
                req_data = request.GET.dict()
            elif request.method == 'POST':
                req_data = str(request.body, encoding='utf-8')

            resp = ""
            if hasattr(response, "content"):
                resp = response.content.decode("utf-8")
            end_time = time.time()
            log_info = "<[REQUEST MESSAGE]>: [req_path]: %s, [mehtod]: %s, [cost_time]: %.2f s, [status]: %s, [req_data]: %s, [resp_data]: %s" % (
                request.path,
                request.method,
                end_time - self.start_time,
                response.status_code,
                req_data,
                resp
            )
            logging.info(log_info)
        return response

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章