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

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