Skip to content

about

REST framework框架本身在提供了异常处理,但是仅针对drf内部现有的接口开发相关的异常进行格式处理,比如限流、认证、权限之类的异常处理。

但是开发中我们还会使用到各种的数据或者进行各种网络请求,这些都有可能导致出现异常,这些异常在drf中是没有进行处理的,所以就会冒泡给django框架了,django框架会进行组织错误信息,作为html页面返回给客户端,也就是喜闻乐见的Django报错的大黄页了。

但对于前后端分离的项目中,通常都是json数据作为数据传输,你给个报错的大黄页,就不太合理了。因此为了避免出现这种情况,我们可以自定义一个属于自己的异常处理函数,对于drf无法处理的异常,我们自己编写异常处理的代码逻辑。

一些铺垫

isinstance

在restframework框架中,用到了isinstance来判断,一个对象所属于哪个类的实例化对象,这在继承中同样适用。

python
class Base:
    pass

class Foo(Base):
    pass

obj = Foo()
print(isinstance(obj, Foo))  # True
print(isinstance(obj, Base))  # True

关于视图

restframework框架的视图类都是APIView的子类,而APIView则是Django内置的View的子类。

python
class View:  # Django的视图类
    pass

class APIView(View):  # restframework框架的视图类
    pass

class UserViewSet(APIView):  # 我们自己写的视图类包括其它诸如ModelViewSet也继承的是APIView
    pass

关于返回response类

restframework框架中的Response类是Django的HttpResponseBase类的子类。

python
class HttpResponseBase:  # Django的返回对象的类的基类
    pass

class HttpResponse(HttpResponseBase):  # HttpResponseBase的子类
    pass

class JsonResponse(HttpResponse):  # HttpResponse的子类
    pass

class SimpleTemplateResponse(HttpResponse):  # HttpResponse的子类
    pass

class Response(SimpleTemplateResponse):  # 这个是restframework框架常用的Response类,它本质上也是HttpResponse的子类
    pass

关于异常类

restframework框架实现了自己的异常类APIException,其它细分的异常类如PermissionDenied、NotAuthenticated的异常类都是APIException的子类,而APIException异常类则是Python的Exception异常类的子类。

python
class Exception:  # Python 内置异常类
    pass

class APIException(Exception):  # restframework框架异常类的父类
    pass

class PermissionDenied(APIException):  # restframework框架的各个细分类都是APIException的子类
    pass

class NotAuthenticated(APIException):  # restframework框架的各个细分类都是APIException的子类
    pass

另外,在restframework框架中,框架本身实现了一些异常处理,那么对于框架之外的异常,比如需要根据业务需求返回不同的状态码、与第三方的连接出现了问题,比如数据库连接异常、短信接口异常,等等这些都不属于restframework框架本身的异常,当然restframework框架也没有实现相关的异常类。

所以我们在开发中要对各种异常进行统一处理,让返回结果具有一致性,遇到了问题也能通过错误状态码能及时的排查。

关于restframework框架内置的异常类

在restframework框架中,它内置了一些异常类,这些异常类在下面这个文件中可以找到:

python
from rest_framework import exceptions
异常类描述
APIException restframework框架中所有异常类的父类,而它又是Exception的子类
ParseError解析错误
AuthenticationFailed认证失败
NotAuthenticated尚未认证
PermissionDenied权限拒绝
NotFound404 未找到
MethodNotAllowed请求方式不支持
NotAcceptable要获取的数据格式不支持
Throttled超过限流次数
ValidationError校验失败

也就是说,很多的没有在上面列出来的异常,就需要我们在自定义异常中自己处理了。

关于assert断言

断言通常在测试中写测试用例用的比较多,比如写个测试用例对于一个接口进行测试,接口返回的状态码是200是我们的预期,那么在用例中就可以通过断言来做,如果指定接口返回的状态码是200,则说明用例测试通过,否则就是测试失败。

python
import pytest  # pip install pytest
import requests


def get():
    response = requests.get('http://httpbin.org/get')
    return response.status_code


def test_case01():
    code = get()
    assert (code, 200)


def test_case02():
    code = get()
    assert (code, 201)


if __name__ == '__main__':
    pytest.main(["-s", "test.py"])

当然了,来单独看看assert这个知识点:

python
import random


def always_true():
    return random.choice([True, False]) == True


# 如果这个函数返回了True,测试就会通过,通过的话就什么也不做
# 否则就抛出异常AssertionError: This should be True
# 你可以尝试多次运行这个测试,看看是否会偶尔通过
# 关于assert的具体用法
#   第一个参数是一个布尔值,如果为 False 就会抛出异常
#   第二个参数是具体的异常信息,可以任意指定
assert always_true(), 'This should be True'

源码梳理

drf在处理一个请求时,都会走APIView视图类的dispatch方法。这个方法中处理包括请求的封装,认证、权限、限流、版本,也包括对于异常的处理。

python
"""
观察APIView中的dispatch做了什么
"""
# 位置
# from rest_frameworkviews import APIView

class APIView(View):
    def dispatch(self, request, *args, **kwargs):
        # 封装request对象
        request = self.initialize_request(request, *args, **kwargs)
		# 异常处理从下面的try开始
        try:
            # 在initial中处理版本、认证、权限、限流
            self.initial(request, *args, **kwargs)

            # 根据请求方式反射让我们自己的写的视图类的对应的方法执行
            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
            response = handler(request, *args, **kwargs)

        except Exception as exc:
            # 核心代码:对于请求异常的,都走这个分支,对异常进行处理
            response = self.handle_exception(exc)
		# 核心代码:最终走finalize_response方法对于响应结果进行统一化处理,然后返回给客户端
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
python
"""
从APIView中的dispatch中,对于异常的处理,走了self.handle_exception方法,我们观察这个方法做了什么
class APIView(View):
    def dispatch(self, request, *args, **kwargs):
        try:
        	pass
        except Exception as exc:
            # 对于请求异常的,都走这个分支,对异常进行处理
            response = self.handle_exception(exc)

handle_exception方法本质上就做了:
1. 对于认证相关的异常进行响应头和状态码的处理
2. 调用具体的异常处理函数对错误信息进行处理,最终生成响应对象response
3. 对于restframework框架处理不了的异常,直接交给Django处理
"""
# 位置
# from rest_frameworkviews import APIView
class APIView(View):
    def handle_exception(self, exc):
        # 对于认证相关的异常进行响应头和状态码的处理
        if isinstance(exc, (exceptions.NotAuthenticated,
                            exceptions.AuthenticationFailed)):
            # WWW-Authenticate header for 401 responses, else coerce to 403
            # 设置401或者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
		# 核心代码:调用并执行处理异常,生成Response的函数
        exception_handler = self.get_exception_handler()  # 找到那个函数
        context = self.get_exception_handler_context()  # 封装字典,包含后续异常处理中可能需要的,比如request对象,各种参数
        response = exception_handler(exc, context)  # 调用那个函数,拿到最终封装好的response
		
        # 如果exception_handler函数的返回值是None,表示异常类型不是restframework框架能处理的了的异常,那就表示这个异常
        # 需要交给Django来处理了,就走下面的if语句了
        if response is None:  
            # Django处理异常直接就报错返回大黄页了,这个方法执行到此结束,就走不到下面那个return response了
            self.raise_uncaught_exception(exc)
        
        # 如果能走到这里,一定拿到了exception_handler函数封装后并返回response对象
        # 这里再给它添加个exception等于True的属性,然后返回,表示异常处理restframework处理完了
        # 将最后封装好的response对象给客户端返回
        response.exception = True
        return response
python
"""
从APIView中的handle_exception方法中,执行了self.get_exception_handler()方法,我们看下这个方法内部实现
class APIView(View):
    def handle_exception(self, exc):
		# 核心代码:调用并执行处理异常,生成Response的函数
        exception_handler = self.get_exception_handler()  # 找到那个函数
"""
# 位置
# from rest_frameworkviews import APIView
class APIView(View):
    def get_exception_handler(self):
        # 这里就把默认的异常处理函数进行返回,未来我们自己定义异常处理函数时,也就是覆盖掉它这里拿到的异常处理函数即可
        return self.settings.EXCEPTION_HANDLER
python
"""
从APIView中的get_exception_handler方法拿到的具体函数就来自于restframework框架对于异常处理的默认的配置项
"""
# 位置
from rest_framework.settings import DEFAULTS # DEFAULTS是个字典,它内部存储的是restframework框架内置的一些默认配置

DEFAULTS = {
    ...省略...
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    ...省略...
}
python
"""
我们来看看这个默认的异常函数都做了什么吧
本质上来说,默认的异常函数就是对特殊的认证和权限做一些额外的设置、然后判断异常类型是否是restframework框架能处理的了得异常,能处理的了,则封装一下相应的错误信息,如果处理,直接返回None,也就是我不处理了
而我们将来要做的就是重写这个异常函数,将restframework框架能处理的和不能处理的异常,都在这里进行处理好
"""
# 位置
from rest_framework.views import exception_handler

def exception_handler(exc, context):
    """
    exc: 异常实例对象,发生异常时实例化出来的
    context: 字典,异常发生时python解释器收集的执行上下文信息。所谓的执行上下文就是python解释器
             执行代码时保存在内存中的变量、函数、类、对象、模块等一系列的信息组成的环境信息
    """
    if isinstance(exc, Http404):  # 当数据没有拿到时的异常
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):  # 权限异常
        exc = exceptions.PermissionDenied()

        
    # 因为restframework中所有的异常都继承自APIException,所以走这个
    # 分支表示异常都是restframework框架内置异常类能处理的了的异常
    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
            
		# exc.detail就是我们异常类传的字符串类型的值,比如ValidationError("手机号格式不正确!")
        # 当然也有可能是这么传的ValidationError([xxx, xxx])或者 ValidationError({"k": "v"})
        # 下面的判断就是根据传的值不同,做不同的封装,客户端看到的也就不一样了
        if isinstance(exc.detail, (list, dict)): # 如果是列表或者字典,用户看到的就是你传的列表或者字典
            data = exc.detail
        else:  # 如果异常类传的是字符串,就封装成字典,例如: {"detail": "手机号格式不正确!"}
            data = {'detail': exc.detail}
            
		# 执行一些事务的回滚操作
        set_rollback()
        
        # 最终生成一个response对象再返回给客户端
        return Response(data, status=exc.status_code, headers=headers)
    # 对于restframework框架处理不了的异常,比如int("abc")这种报错,直接返回None,表示我不处理了,就会冒泡交给Django处理了
    # 当然了,Django处理就会来个大黄页....
    return None
python
"""
这里得到APIView中dispatch方法执行下面代码拿到的response,进行最后的封装处理,才返回给客户端
class APIView(View):
    def dispatch(self, request, *args, **kwargs):
        try:
			# 核心代码:请求正常,拿到的response
            response = handler(request, *args, **kwargs)

        except Exception as exc:
            # 核心代码:对于请求异常的,都走这个分支,对异常进行处理
            response = self.handle_exception(exc)
		# 核心代码:最终走finalize_response方法对于响应结果进行统一化处理,然后返回给客户端
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
        
        
                
整体来说,这个finalize_response方法主要作用就是:
1. 通过断言检查相应对象是否是HttpResponseBase子类实例化的对象
2. 如果response对象是Response的实例化对象的话,就去根据请求对象request,去判断该如何渲染响应结果
   例如,如果是浏览器访问的接口,restframework框架就会返回一个调试页面,把相呼应结果嵌套到调试页面中
   如果是其他类型的请求,比如是postman请求的,就直接返回json数据了
3. 对响应头做一些处理
当代码走完了了finalize_response,那么对于响应对象response的处理就算是完事了,可以返回给客户端了
"""
# 位置
# from rest_frameworkviews import APIView
class APIView(View):
    def finalize_response(self, request, response, *args, **kwargs):
        
        # 这个assert语法就为了判断拿到的response是不是HttpResponseBase子类实例化的对象,是就什么也不错
        # 如果不是,则说明response是个野生的response对象,人家不认,就直接报错了
        assert isinstance(response, HttpResponseBase), (
            'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
            'to be returned from the view, but received a `%s`'
            % type(response)
        )
		# 如果这个response是Response的实例化对象,就添加一些额外的信息
        if isinstance(response, Response):
            # 去找下request对象中有没有accepted_renderer,也就是决定如何渲染响应结果
            if not getattr(request, 'accepted_renderer', None):
                neg = self.perform_content_negotiation(request, force=True)
                request.accepted_renderer, request.accepted_media_type = neg
			# restframework框架内置rest风格调试页面,下面这个response.accepted_renderer就能知道本次请求的客户端是什么
            #比如浏览器发送的请求,相应内容就会呈现在页面中
            # 	response.accepted_renderer: <rest_framework.renderers.BrowsableAPIRenderer object at 0x00000247CF7B30D0>
            # 	response.accepted_media_type: text/html
            # 	response.renderer_context: {'view': <apps.drf.views.exc.ExceptionTestView object at 0x00000247CF7B0F90>, 'args': (), 'kwargs': {}, 'request': <rest_framework.request.Request: GET '/drf/exc/'>}
            # 如果是其他客户端比如postman发送的请求,则会直接返回json类型的响应数据
            #   response.accepted_renderer: <rest_framework.renderers.JSONRenderer object at 0x00000247CF670D90>
            #   response.accepted_media_type: application/json
            #   response.renderer_context: {'view': <apps.drf.views.exc.ExceptionTestView object at 0x00000247CF7BB410>, 'args': (), 'kwargs': {}, 'request': <rest_framework.request.Request: GET '/drf/exc/'>}
            response.accepted_renderer = request.accepted_renderer
            response.accepted_media_type = request.accepted_media_type
            response.renderer_context = self.get_renderer_context()

        # 下面这几行就是处理响应头
        vary_headers = self.headers.pop('Vary', None)
        if vary_headers is not None:
            patch_vary_headers(response, cc_delim_re.split(vary_headers))
        for key, value in self.headers.items():
            response[key] = value
            
		# 最终将response返回给客户端
        return response

自定义异常函数

通过上面的源码梳理,我们知道,restframework框架对于不能处理的异常是直接不管的,交给了Django来做,而我们就需要去重写这个异常函数,去完善异常处理逻辑,把项目中所有的异常都能进行统一处理才是一个比较合理的状态。

那么怎么来做呢?需要两步。

1. 自定义异常处理函数

我在项目的根目录下创建了一个utils目录,在这个目录内创建一个exc_exceptions.py,当然你可以创建在别的目录和器其他的文件名称,这都无所谓的。

重要的是异常函数你要根据具体的项目需求进行设计:

python
from django.core.exceptions import PermissionDenied
from django.http.response import Http404
from rest_framework import status
from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.views import set_rollback

# 把常见的异常都在字典中进行映射,每个异常都有对应的错误码和错误信息,将来遇到新的异常,也都可以添加到这个字典中
# 至于异常对应的字典中,想要给客户端返回什么信息,都可以很自由根据需求来添加即可
errors_dict = {
    "ValueError": {"error_code": 2001, "error_xxx": "ooo"},
    "ZeroDivisionError": {"error_code": 2002},
    "Http404": {"error_code": 1001},
    "PermissionDenied":  {"error_code": 1002},
    "AuthenticationFailed":  {"error_code": 1003},
    "MethodNotAllowed":  {"error_code": 1004},
}

def my_exception_handler(exc, context):
    """
    自定义异常处理函数
    :param exc: 异常类型对象
    :param context: 抛出异常的上下文,是个字典,异常发生时python解释器收集的执行上下文信息。
        所谓的执行上下文就是python解释器在执行代码时保存在内存中的
        变量、函数、类、对象、模块等一系列的信息组成的环境信息。
    :return: 封装好的Response响应对象
    """
    # 定义异常返回的响应data,关于异常的所有要返回的信息,都往这个data字典中添加就行了
    data = {}
    # 定义一个响应头
    headers = {}
    # 拿到具体异常类的类名,方便去全局异常字典中提取对应的异常错误码和错误信息
    exc_class_name = str(exc.__class__.__name__)

    # 这几个if判断是restframework框架自己对几个特殊的异常进行的特殊处理
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()

    # 如果这个是restframework框架定义的异常,做的进一步封装
    if isinstance(exc, exceptions.APIException):
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait
        # 这里把restframework框架定义的异常的错误信息,添加到data字典中
        data['error_detail'] = exc.detail
        set_rollback()  # 事务的回滚就由restframework框架来处理,我们不动

    # 对于restframework框架处理不了的异常,我们都在这里处理
    # 从字典中找到我们对应的异常类型,如果存在,返回对应的错误信息
    if errors_dict.get(exc_class_name):  # 去字典中看下程序报错的异常,我们有没有提前收录到字典中
        #  把字典中收录的异常初始定义的错误码和错误信息,添加到data字典中
        data.update(errors_dict.get(exc_class_name, {}))
    else:  # 对于我们没有在字典中收录的异常,都走这里统一处理就完了
        # code是-1表示这个异常我们没有提前收录到字典中,错误码都统一为-1,你可以根据需求指定其他的错误码
        data['error_code'] = -1

    # 添加异常报错内容
    data['error_detail'] = str(exc)
    # 添加异常报错位置,想要再添加其它信息,你可以print一下context,看下哪些对你有用
    data['error_context'] = str(context['view'])
    # 添加下异常类型
    data['error_type'] = exc_class_name

    # 最终将异常对象封装好了,返回给调用者,也就是源码的handle_exception方法
    return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR, headers=headers)
python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import PermissionDenied, ParseError, MethodNotAllowed

class ExceptionTestAuth(BaseAuthentication):
    def authenticate(self, request):
        int('adad')
        # 1 / 0
        # raise AuthenticationFailed("认证失败")
        # return None

class ExceptionTestView(APIView):
    authentication_classes = [ExceptionTestAuth]

    def get(self, request):
        return Response("OK")
bash
{
    "error_code": 2001,
    "error_xxx": "ooo",
    "error_detail": "invalid literal for int() with base 10: 'adad'",
    "error_context": "<apps.drf.views.exc_view.ExceptionTestView object at 0x000001DAF231B0D0>",
    "error_type": "ValueError"
}

2. 在settings中的restframework框架配置中进行应用

python
# drf相关配置以后编写在下面的字典中
REST_FRAMEWORK = {
    "EXCEPTION_HANDLER": "utils.exc_exceptions.my_exception_handler",
}

其它的思路

除了上面的实现思路之外,自定义异常函数你也可以这样处理:

python
from django.http import Http404
from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import Throttled
from rest_framework.exceptions import PermissionDenied
from rest_framework.exceptions import NotAuthenticated
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.views import set_rollback
from rest_framework.exceptions import APIException


class ExtraException(APIException):
    pass


def exception_handler(exc, context):
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
        exc.ret_code = 2001
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()
        exc.ret_code = 2002
    elif isinstance(exc, (AuthenticationFailed, NotAuthenticated)):
        exc.ret_code = 2003
    elif isinstance(exc, Throttled):
        exc.ret_code = 2004
    elif isinstance(exc, ValidationError):
        exc.ret_code = 2005
    # ...

    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:
        #     exc_code = getattr(exc, 'ret_code', None) or -1
        #     data = {'code': exc_code, 'detail': exc.detail}

        exc_code = getattr(exc, 'ret_code', None) or -1
        data = {'code': exc_code, 'detail': exc.detail}

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

    # return None
    data = {'code': -1, 'detail': str(exc)}
    return Response(data, status=500)

也可以这样:

python
from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status
from django.db import DatabaseError
# 针对mysql、mongoDB、redis、第三方数据接口
def custom_exception_handler(exc, context):
    """
    自定义异常函数
    exc: 异常实例对象,发生异常时实例化出来的
    context: 字典,异常发生时python解释器收集的执行上下文信息。
             所谓的执行上下文就是python解释器在执行代码时保存在内存中的变量、函数、类、对象、模块等一系列的信息组成的环境信息。
    """
    response = exception_handler(exc, context)
    print(f"context={context}")
    if response is None:
        """当前异常,drf无法处理"""
        view = context["view"] # 获取异常发生时的视图类
        request = context["request"] # 获取异常发生时的客户端请求对象
        if isinstance(exc, ZeroDivisionError):
            response = Response({"detail":"数学老师还有30秒达到战场,0不能作为除数!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        if isinstance(exc, TypeError):
            print('[%s]: %s' % (view, exc))
            response = Response({'detail': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    return response

自定义异常类

有的场景中,我们可以通过自定义异常类,来简化业务代码,比如支付相关,可以根据业务需求来主动触发异常。

python
"""
可以将自定义的异常类在这个文件中实现
"""
from django.core.exceptions import PermissionDenied
from django.http.response import Http404
from rest_framework import status
from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.views import set_rollback
from rest_framework.exceptions import APIException

# 把常见的异常都在字典中进行映射,每个异常都有对应的错误码和错误信息,将来遇到新的异常,也都可以添加到这个字典中
# 至于异常对应的字典中,想要给客户端返回什么信息,都可以很自由根据需求来添加即可
errors_dict = {
    "ValueError": {"error_code": 2001, "error_xxx": "ooo"},
    "ZeroDivisionError": {"error_code": 2002},
    "Http404": {"error_code": 1001},
    "PermissionDenied":  {"error_code": 1002},
    "AuthenticationFailed":  {"error_code": 1003},
    "MethodNotAllowed":  {"error_code": 1004},
    "PayException":  {"error_code": 3001},   # 为所有支付相关的异常指定错误码

}

class PayException(APIException):  # 继承APIException即可
    """ 自定义支付相关的异常类 """
    pass

class XxxException(APIException):  # 想为某个业务整个异常类,都直接定义即可,非常方便
    """ 自定xxx相关的异常类 """
    pass


def my_exception_handler(exc, context):
    """
    自定义异常处理
    :param exc: 异常类型
    :param context: 抛出异常的上下文,字典,异常发生时python解释器收集的执行上下文信息。
        所谓的执行上下文就是python解释器在执行代码时保存在内存中的变量、函数、类、对象、模块等一系列的信息组成的环境信息。
    :return: Response响应对象
    """
    # 定义异常返回的响应data,关于异常的所有要返回的信息,都往这个data字典中添加就行了
    data = {}
    # 定义一个响应头
    headers = {}
    # 拿到具体异常类的类名,方便去全局异常字典中提取对应的异常错误码和错误信息
    exc_class_name = str(exc.__class__.__name__)

    # 这几个if判断是restframework框架自己对几个特殊的异常进行的特殊处理
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()

    # 如果这个是restframework框架定义的异常,做的进一步封装
    if isinstance(exc, exceptions.APIException):
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait
        # 这里把restframework框架定义的异常的错误信息,添加到data字典中
        data['error_detail'] = exc.detail
        set_rollback()  # 事务的回滚就由restframework框架来处理,我们不动

    # 对于restframework框架处理不了的异常,我们都在这里处理
    # 从字典中找到我们对应的异常类型,如果存在,返回对应的错误信息
    if errors_dict.get(exc_class_name):  # 去字典中看下程序报错的异常,我们有没有提前收录到字典中
        #  把字典中收录的异常初始定义的错误码和错误信息,添加到data字典中
        data.update(errors_dict.get(exc_class_name, {}))
    else:  # 对于我们没有在字典中收录的异常,都走这里统一处理就完了
        # code是-1表示这个异常我们没有提前收录到字典中,需要我们统一处理
        data['error_code'] = -1

    # 添加异常报错内容
    data['error_detail'] = str(exc)
    # 添加异常报错位置,想要再添加其它信息,你可以print一下context,看下哪些对你有用
    data['error_context'] = str(context['view'])
    # 添加下异常类型
    data['error_type'] = exc_class_name

    # 最终将异常对象封装好了,返回给调用者,也就是源码的handle_exception方法
    return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR, headers=headers)
python
"""
在视图类中,可以根据业务需要,主动触发自定义的异常类,传递具体的错误信息
"""
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import PermissionDenied, ParseError, MethodNotAllowed
from utils.exc_exceptions import PayException

class ExceptionTestAuth(BaseAuthentication):
    def authenticate(self, request):
        # int('adad')
        # 1 / 0
        # raise AuthenticationFailed("认证失败")
        return None

class ExceptionTestView(APIView):
    authentication_classes = [ExceptionTestAuth]

    def get(self, request):
        # 原来如果用户支付时余额不足,我们可以直接return response对象
        # return Response({"code": 3001, "msg": "余额不足"})
        
        # 如果用自定义异常类的话,可以根据具体的业务需求来主动触发异常
        # if xxx:
        #     raise PayException('余额不足')
        # if xxx:
        #     raise PayException("查询的订单不存在")
        # if ooo:
        #     raise PayException("订单状态不正确")
        return Response("OK")
bash
{
    "error_detail": "余额不足",
    "error_code": 3001,
    "error_context": "<apps.drf.views.exc_view.ExceptionTestView object at 0x0000023086E02A90>",
    "error_type": "PayException"
}