Django-djangorestframework-異常模塊-源碼及自定義異常


異常模塊

為什么要自定義異常模塊

  1. 所有經過 drf APIView 視圖類產生的異常,都可以提供異常處理方案(沒有繼承 APIVIew 的視圖函數不會觸發)
  2. drf 默認提供了異常處理方案(rest_framework.views.exception_handler),但是處理范圍有限
  3. drf 提供的處理方案有兩種
    • 有對應處理,處理了返回異常信息
    • 沒有對應處理(處理范圍之外),返回 None,直接服務器拋異常給前台
  4. 自定義異常的目的就是解決 drf 沒有處理的異常,讓前台得到合理的異常信息返回,后台記錄異常具體的信息(方便事后排查)

如果程序報錯了,我們應該盡可能的隱藏后台的錯誤,返回給前台就是服務器錯誤(你返回給用戶用戶也看不懂呀,如果是黑客,那可能還會利用報錯襲擊服務器)

常見的幾種異常情況

  1. 像這種就比較可怕了,甚至連代碼文件位置都暴露了

  1. drf 異常處理模塊處理后的異常

  1. drf 異常處理模塊處理后的異常

  1. 異常信息經漢化后的報錯(django 配置了國際化后)

異常模塊源碼分析

視圖函數執行出現異常會自動觸發 handle_exception 函數

每個請求都會經歷這么一個過程,走到 dispatch 函數

E:/python3-6-4/Lib/site-packages/rest_framework/views.py 源碼

	# ...
	def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)  # 三大認證

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)  # 上面 try 代碼體內代碼出現異常會自動觸發這個函數 <---------

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

handle_exception 源碼

    def handle_exception(self, exc):
        """
        Handle any exception that occurs, by returning an appropriate response,
        or re-raising the error.
        """
        if isinstance(exc, (exceptions.NotAuthenticated,
                            exceptions.AuthenticationFailed)):
            # WWW-Authenticate header for 401 responses, else coerce to 403
            auth_header = self.get_authenticate_header(self.request)

            if auth_header:
                exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN

        exception_handler = self.get_exception_handler()  # 獲取處理異常的句柄(方法) <---------

        context = self.get_exception_handler_context()
        # 異常處理的結果
        # 自定義異常就是提供 exception_handler 異常處理函數,處理的目的就是讓 response 一定有值
        response = exception_handler(exc, context)

        if response is None:
            self.raise_uncaught_exception(exc)  # 亂七八糟的異常就是這里拋出來的

        response.exception = True
        return response

如何獲取異常類?

get_exception_handler_context 源碼,異常處理類是從配置中拿來的

    def get_exception_handler(self):
        """
        Returns the exception handler that this view uses.
        """
        return self.settings.EXCEPTION_HANDLER

    # API policy implementation methods

E:/python3-6-4/Lib/site-packages/rest_framework/settings.py

    # Exception handling
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',

獲取到異常類如何處理?

返回 None 就會觸發 handle_exception 源碼中的報錯

E:/python3-6-4/Lib/site-packages/rest_framework/views.py drf 自帶的異常處理類

def exception_handler(exc, context):
    """
    Returns the response that should be used for any given exception.

    By default we handle the REST framework `APIException`, and also
    Django's built-in `Http404` and `PermissionDenied` exceptions.

    Any unhandled exceptions may return `None`, which will cause a 500 error
    to be raised.
    """
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()

    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        if isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            data = {'detail': exc.detail}

        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)

    return None  # 其他的異常 drf 未處理,返回 None,讓其報錯(最上面的那種報錯)

自定義 drf 異常處理

自定義異常處理模塊就是提供 exception_handler 異常處理函數,處理的目的就是讓 response 一定有值

顯而易見,我們只需要自定義一個異常處理方法,先調用系統自帶的那個異常處理函數,然后把 drf 自帶那個異常函數沒有處理的情況處理了就好了(處理后返回一個 Response 對象即可,一定要有返回值,否則沒多大意義)

歩鄹

  1. 先將異常處理交給 rest_framework.views 的 exception_handler 去處理
  2. 判斷處理的結果(返回值)response,有值代表 drf 已經處理了,None 需要自己處理
    • 可以根據 exc 的類型再細化處理 if isinstance(exc, '哪個異常'): # 再怎么處理

api/exception.py

記得自己把報錯信息記到日志里面去

from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.views import Response
from rest_framework import status


def exception_handler(exc, context):
    # drf的exception_handler做基礎處理
    response = drf_exception_handler(exc, context)
    # 為空,說明 drf 中沒有對應的處理,咱們自定義二次處理
    if response is None:
        # print(exc)
        # # Book matching query does not exist
        
        # print(context)
        # # {'view': <api.views.Book object at 0x000001FED29DD860>}, 'args': (), 'kwargs': {'pk': '4'}, 'request': <rest_framework.request.Request object at 0x000001FED2CD9EF0>
        
        # 這里后期應該寫成系統日志才對(這只是演示的偽代碼)
        print('%s - %s - %s' % (context['view'], context['request'].method, exc))
        # <api.views.Book object at 0x000002505A2A9A90> - GET - Book matching query does not exits.
        return Response({
            'detail': '服務器錯誤'
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
    return response

配置上,讓其生效

dg_proj/settings.py

# 1.確保已注冊 drf
INSTALLED_APPS = [
	# ...
    'api.apps.ApiConfig',

    'rest_framework',  # 注冊 drf
]

# 2.在 restframework 的配置中配置該自定義異常模塊
REST_FRAMEWORK = {
    # ...

    'EXCEPTION_HANDLER': 'api.exception.exception_handler',  # 全局配置異常模塊
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM