【python之DRF学习】drf全局异常

title:  【python之DRF学习】drf全局异常
date:  2024-04-18 15:10:30 星期四
updated: 2024-04-18 15:10:33 星期四
description: 
Cover: https://www.jb51.net/article/238484.htm

全局异常

一、前言:

当系统报错时,希望系统进行统一报告,比如常见的:系统繁忙,请稍后再试、服务异常,请稍后再试这种报错。
另外,drf不能处理非drf的异常,比如 list = [1, 2, 3] print(l[5]),这种时候会报错,不会抛异常,有时候我们不希望程序报错,也需要使用全局异常处理。


而且这些异常对于前端来说,最好后端即使报错也统一格式返回到前端,以便处理

二、目标

如果没有 DRF,我们只需要在 Django 中加一个中间件就可以解决全局异常的处理问题,但是 DRF 会帮我们处理一些异常并自动返回到客户端,因此我们要协调两者的异常处理策略。
同时我们希望能使用 Django 的 admin 进行一些后台的数据查看和修改,因此最好要保留 admin 的内部异常处理行为。
具体目标:
保留 Django 自带的 admin 的异常处理行为
拦截 DRF 的异常并进行全局异常行为处理
拦截除 DRF 的异常之外的其他 Django 异常并进行全局异常行为处理

三、drf全局异常拦截的解决思路

首先 DRF 的异常都是继承自 APIException 这个类的,并且 DRF 跑出的异常会被 exception_handler 这个异常处理函数拦截(这个函数的位置在 /python3.10/site-packages/rest_framework/views.py中)。

四、源码:

def exception_handler(exc, context):
    if isinstance(exc, Http404):
        exc = exceptions.NotFound(*(exc.args))
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied(*(exc.args))
    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 会处理所有继承自 APIException 的异常类,并且还会额外的处理 Django 内置的 Http404 和 PermissionDenied 异常,并将这些异常的处理结果返回到前台。
如果不再这些处理范围之内,函数会返回 None,这时候会给 Django 抛出一个 500 的服务器错误异常。
DRF 支持单独配置异常处理函数,因此第一步现在 setting 中指定自定义的异常处理函数的位置:
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler'
}

五、自定义异常处理程序

第一步,调用 DRF 自己的异常处理函数

第二步,对 DRF 拦截的异常进行处理

第三步,将其他异常抛给 Django 处理

from rest_framework.views import exception_handler
from rest_framework.response import Response

class PasswordException(Exception):
    def __init__(self,msg):
        self.msg=msg


def common_exception_handler(exc, context):
    # 日志记录放这里即可:请求方式,请求的地址,客户端ip,用户id
    request=context.get('request')
    view=context.get('view') # 哪个视图类出的错
    user_id=request.user.pk or '匿名用户'
    print(f'请求方式是:{request.method},请求地址是:{request.get_full_path()},客户端ip:{request.META.get("REMOTE_ADDR")},用户id:{user_id}')
    print(type(exc))
    # 1 返回 Response:说明是drf的异常      2 返回None:说明不是drf的异常
    response = exception_handler(exc, context)
    if response:
        if isinstance(response.data,dict):
            err=response.data.get('detail','系统错误,请联系系统管理员')
        elif isinstance(response.data,list):
            err = response.data[0]
        else:
            err ='系统错误,请联系系统管理员'
        response = Response({'code': 998, 'msg': f'【drf的异常】:{err}'})
        pass
    else:
        if isinstance(exc,ZeroDivisionError):
            response = Response({'code': 991, 'msg': '不能除以0'})
        elif isinstance(exc,Exception):
            response = Response({'code': 992, 'msg': 'Exception错误'})
        else:
            # 表明是 非drf的异常
            err=str(exc)
            response = Response({'code': 999, 'msg': f'【django的异常】:{err}'})
    return response


# 无论后端什么情况,前端收到的都是统一的格式
'''
正常响应:{code:100,msg:成功}
不正常响应:{code:101,msg:登录失败}
出了错:{code:999,msg:错误信息}   {code:998,msg:错误信息}

六、django异常处理方案

从上一步的结果我们知道,DRF 处理不了的异常我们抛给了 Django,而 Django 支持通过定义中间件进行全局异常处理,因此接下来我们只需要定一个 Django 全局异常处理的中间件,并将中间件配置到 setting 文件中的 MIDDLEWARE 数组即可。

try:
    from django.utils.deprecation import MiddlewareMixin  # Django 1.10.x
except ImportError:
    MiddlewareMixin = object  # Django 1.4.x - Django 1.9.x

class ExceptionGlobeMiddleware(MiddlewareMixin):
    """
        Below is the global exception handler of django
    """
    def process_exception(self, request, exception):
        # 直接抛出 django admin 的异常
        if str(request.path).startswith('/admin/'):
            return None
        # 捕获其他异常,直接返回 500
        ex_data = {
            "msg": "Sorry, we make a mistake (* ̄︶ ̄)!",
            "error_code": 1000,
            "request": request.path
        }
        return JsonResponse(data=ex_data, status=500)

值得注意的是,我们可以在中间件的处理函数中拿到 request 对象,因此,我们可以通过这个对象拿到用户请求的 url,这样,我们通过判断 url 就可以得到那些请求是来自 Django 自带的 admin 的。

参考代码

# 直接抛出 django admin 的异常
if str(request.path).startswith('/admin/'):
    return None

七、drf定义的异常部分异常

APIException 所有异常的父类
ParseError 解析错误
AuthenticationFailed 认证失败
NotAuthenticated 尚未认证
PermissionDenied 权限决绝
NotFound 未找到
MethodNotAllowed 请求方式不支持
NotAcceptable 要获取的数据格式不支持
Throttled 超过限流次数
ValidationError 校验失败
通过上述的配置,我们可以对完成 Django 结合 DRF 的全局异常处理,并且保留了 Django 自带 admin 的异常处理策略。
posted @   Unfool  阅读(45)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示