Django与DRF结合的全局异常处理方案详解

 更新时间:2022年02月23日 10:22:08   作者:Cao Lilu  
这篇文章主要为大家详细介绍了Django与DRF结合的全局异常处理方案,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

前言

Django 和 DRF(django rest framawork) 的结合在 python 后台中经常出现的组合。对于异常的全局处理,我们系统能有一个统一的解决方案,在开发环境能看到比较全的异常堆栈,而在生产环境能更好的给用户一个友好的提示,本文旨在提出一个统一个全局异常处理方案,仅供参考使用。

实现的目标

如果没有 DRF,我们只需要在 Django 中加一个中间件就可以解决全局异常的处理问题,但是 DRF 会帮我们处理一些异常并自动返回到客户端,因此我们要协调两者的异常处理策略。

同时我们希望能使用 Django 的 admin 进行一些后台的数据查看和修改,因此最好要保留 admin 的内部异常处理行为。

本文的目标如下:

  • 保留 Django 自带的 admin 的异常处理行为
  • 拦截 DRF 的异常并进行全局异常行为处理
  • 拦截除 DRF 的异常之外的其他 Django 异常并进行全局异常行为处理

DRF 全局异常拦截的解决思路

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

我们进一步查看这个函数的源码:

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 会处理所有继承自 APIException 的异常类,并且还会额外的处理 Django 内置的 Http404 和 PermissionDenied 异常,并将这些异常的处理结果返回到前台。

如果不再这些处理范围之内,函数会返回 None,这时候会给 Django 抛出一个 500 的服务器错误异常。

DRF 支持单独配置异常处理函数,因此第一步现在 setting 中指定自定义的异常处理函数的位置:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'server.exception.exception_globe.globe_exception_handler'
}

然后我们定义自己的异常处理程序:

第一步,调用 DRF 自己的异常处理函数第二步,对 DRF 拦截的异常进行处理第三步,将其他异常抛给 Django 处理

def globe_exception_handler(exc, context):
    """
        Below is the global exception handler of drf
        Http404 / PermissionDenied / APIException
    """
    # Call REST framework's default exception handler
    response = exception_handler(exc, context)
    request = context['request']
    # Exceptions form DRF and Django built-in `Http404` and `PermissionDenied`
    if response is not None:
        if isinstance(response.data, list):
            msg = '; '.join(response.data)
        elif isinstance(response.data, str):
            msg = response.data
        else:
            msg = 'Sorry, we make a mistake (* ̄︶ ̄)!'
        ex_data = {
            "msg": msg,
            "error_code": 1000,
            "request": request.path
        }
        return JsonResponse(data=ex_data, status=response.status_code)
    # Exceptions from others
    # 如果 response 为 None 则直接触发上面的 ExceptionGlobeMiddleware
    return response

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

总结

通过上述的配置,我们可以对完成 Django 结合 DRF 的全局异常处理,并且保留了 Django 自带 admin 的异常处理策略。

在实际的环境中,我们可以通过环境变量进行有选择的日志打印和记录。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!  

相关文章

  • python统计cpu利用率的方法

    python统计cpu利用率的方法

    这篇文章主要介绍了python统计cpu利用率的方法,涉及Python中win32pdh模块的使用技巧,需要的朋友可以参考下
    2015-06-06
  • 利用Python绘制多种风玫瑰图

    利用Python绘制多种风玫瑰图

    这篇文章主要介绍了利用Python绘制多种风玫瑰图,风玫瑰是由气象学家用于给出如何风速和风向在特定位置通常分布的简明视图的图形工具,下文绘制实现详情,需要的小伙伴可以参考一下
    2022-05-05
  • Python高级架构模式知识点总结

    Python高级架构模式知识点总结

    在本篇文章里小编给大家整理了一篇关于Python高级架构模式知识点总结内容,有兴趣的朋友们可以学习参考下。
    2021-08-08
  • python使用requests.post方法传递form-data类型的Excel数据的示例代码

    python使用requests.post方法传递form-data类型的Excel数据的示例代码

    这篇文章介绍了python使用requests.post方法传递form-data类型的Excel数据的示例代码,某些post接口,需要发送multipart/form-data类型的数据,如何使用python requests来模拟这种类型的请求发送呢?补充讲解了python使用requests post请求发送form-data类型数据,一起看看吧
    2024-01-01
  • Python中os模块功能与用法详解

    Python中os模块功能与用法详解

    这篇文章主要介绍了Python中os模块功能与用法,总结分析了Python os模块基本功能、内置函数、使用方法及相关操作注意事项,需要的朋友可以参考下
    2020-02-02
  • python多线程抓取天涯帖子内容示例

    python多线程抓取天涯帖子内容示例

    这篇文章主要介绍了python多线程抓取天涯帖子内容示例,需要的朋友可以参考下
    2014-04-04
  • Python生成器的使用方法和示例代码

    Python生成器的使用方法和示例代码

    今天小编就为大家分享一篇关于Python生成器的使用方法和示例代码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • Python中re模块常用方法总结分析

    Python中re模块常用方法总结分析

    这篇文章主要为大家介绍了Python中re模块常用方法,并对这些常用方法进行总结分析,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-09-09
  • Python 统计列表中重复元素的个数并返回其索引值的实现方法

    Python 统计列表中重复元素的个数并返回其索引值的实现方法

    这篇文章主要介绍了Python 统计列表中重复元素的个数并返回其索引值,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • scrapy爬虫:scrapy.FormRequest中formdata参数详解

    scrapy爬虫:scrapy.FormRequest中formdata参数详解

    这篇文章主要介绍了scrapy爬虫:scrapy.FormRequest中formdata参数详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-04-04

最新评论