首页 > 其他分享 >DRF之全局异常处理、接口文档书写

DRF之全局异常处理、接口文档书写

时间:2024-04-24 20:00:14浏览次数:21  
标签:exception exc self 接口 handler 文档 异常 response DRF

一、全局异常处理

1、drf默认异常处理源码分析

在DRF中,继承APIView后,它的执行流程是首先去除了所有请求的csrf认证,然后把视图类的request对象变成了新的request对象,新的reqeust对象是DRF的,但是以前Django的request对象用起来是一样的,同时把新的reqeust对象放到了视图类的对象中,然后在执行视图类的方法之前,又执行了三大认证。最后就是在执行三大认证或视图类方法的过程中只要报错了,就会被全局异常捕获处理。而在这里我们要说的就是这个DRF执行流程中最后一个环节,全局异常处理。

  • 只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler
  • 查看dispatch源码:如下
    def dispatch(self, request, *args, **kwargs):

        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)

            # 视图类的方法开始执行
            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)
			'''
        	这里self是视图类的对象,现在在它的父类APIView里面,所以我们从APIView中找找有没有
        	这个handle_exception方法,另外这个括号里面的exc就是异常对象
        	这个handle_exception方法也是我们主要分析的方法
        	'''
    	self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
  • 执行handle_exception 方法时就会执行方法中exception_handler = self.get_exception_handler() 这一行代码,但
  • 是这一行代码就是前面 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler’中的rest_framework.views.exception_handle函数
   # 在APIView里面找到了handle_exception方法
   def handle_exception(self, exc):
	  # 如果没有认证通过,http响应状态码变成403,并且往响应头中放入了数据
       if isinstance(exc, (exceptions.NotAuthenticated,
                           exceptions.AuthenticationFailed)):
           # WWW-Authenticate header for 401 responses, else coerce to 403
           # 如果异常是 NotAuthenticated 或 AuthenticationFailed,处理授权相关的逻辑
           auth_header = self.get_authenticate_header(self.request)

			# 如果存在认证头,将其添加到异常中
           if auth_header:
               exc.auth_header = auth_header
           else:
           	   # 如果不存在认证头,将状态码更改为 403 FORBIDDEN
               exc.status_code = status.HTTP_403_FORBIDDEN

	 # 这里的exception_handler就是我们自定义异常处理重写的函数
	 '''
	self是视图类对象,已知视图类对象中没有get_excpetion_handler这个方法,然后去父类APIView中查找到了
	def get_exception_handler(self):
       return self.settings.EXCEPTION_HANDLER
    可以看到,它是去配置文件中拿这个EXCEPTION_HANDLER了,这也是为什么我们自定义的异常处理需要配置,如果在配置文件中没有拿到,就会去DRF的配置文件中拿到它
        'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
	'''
       exception_handler = self.get_exception_handler()
	  # 获取异常处理上下文
       context = self.get_exception_handler_context()

	  '''
	 这里的exception_handler就是我们自定义异常处理重写的函数,这也就是为什么我们需要传两个参数的原因	
	  exc就是错误对象,context就是上下文,它是一个对象,并且里面存着当次请求的request和视图类
	  这个response就是最后返回的异常结果,这就是为什么我们重写后最后需要返回一个响应对象
	  '''
       response = exception_handler(exc, context)
	  '''
	  如果响应对象是None就是抛出异常,就不会捕获,直接返回给前端,所以这个全局异常只能捕获DRF的异常
	  这也是为什么我们需要重写,就是为了返回全局异常无法捕获的异常
	  '''
       if response is None:
           self.raise_uncaught_exception(exc)
           '''
			也可以看一下这个方法,还是一样self是视图类对象,然后在父类APIView中找到了这个方法
			def raise_uncaught_exception(self, exc):
		        if settings.DEBUG: 在调试模式下处理未捕获的异常
		            request = self.request  获取当次请求
		            请求接受的渲染格式
		            renderer_format = getattr(request.accepted_renderer, 'format')
		            然后根据渲染格式渲染到页面
		            use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin')
		            request.force_plaintext_errors(use_plaintext_traceback)
		        最后将未捕获的异常直接渲染给页面
		        raise exc
			'''
	  # 这里将响应对象的exception属性设置为True,表示异常已被处理
       response.exception = True
       # 最后返回处理后的响应对象
       return response
  • 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'

  • 接下来我们单独去读读DRF默认配置的异常处理

def exception_handler(exc, context):
   		# 看到DRF默认配置的全局异常处理,就可以知道为什么我们要那么配置自定义的了,其实就是照着这个写的
   		# 这里就是自己的异常
	    if isinstance(exc, Http404):
	        exc = exceptions.NotFound()
	    elif isinstance(exc, PermissionDenied):
	        exc = exceptions.PermissionDenied()
		
		# 这里是DRF的所有异常,只要是DRF的异常都会走这里,因为DRF的异常都继承了APIException
	    if isinstance(exc, exceptions.APIException):
	        # 如果异常的详细信息是列表或字典,直接使用该信息
	        if isinstance(exc.detail, (list, dict)):
	            data = exc.detail
	        else:
	           # 否则,将详细信息包装在 detail 键下
	            data = {'detail': exc.detail}
			# 如果想要验证drf出现异常是否会执行上面exception_handler函数的话,可以在这打印一句话
            # 然后再视图类中抛出raise APIException('我出错误了')

            # 返回处理后的响应
	        '''
	        所以这里有两个情况
	        (1) Response(data:{'detail': exc.detail})
	        (2) Response(data:错误信息(字典或列表))
	        所以这种情况,这也是为什么我们自定义的时候,取detail时,怕取不到设置了自定义的一个异常信息或者在or一个data这样会		  更好一些,例如:
	        detail = res.data.get('detail') or res.data or "drf异常,请联系系统管理员"
        	return Response({'code':666,'message':detail})
	        '''
	        return Response(data, status=exc.status_code, headers=headers)

		# 对于其他类型的异常,返回 None,让 DRF 使用默认的异常处理机制,就是不处理直接返回给前端页面
	    return None

2、自定义异常处理

针对上面的源码,我们也可以自定义异常处理,DRF默认的异常处理,只处理APIException及其子类的异常,处理不了的会返回None,我们可以判断异常是否属于APIException及其子类的异常,如果是则返回错误信息,如果不是,那么我们则返回服务器错误。

  • 自定义异常处理
from rest_framework.views import exception_handler
from rest_framework.response import Response
'''
需求:
0 执行原来的exception_handler
    判断返回值是否为 None:
        如果是None,说明是非drf异常,自己包装一个Response
        如果不是None,说明是drf异常,包装一个Response
1 错误状态码可能会有很多类型: 通过exc 错误对象,判断具体是哪个类的对象(是什么错误)--》更细粒度区分不同的错误
    991
    992
    993
    996
    998
    999
2 只要程序走到common_exception_handler ,就说明有异常,通常做法
    -返回给前端固定错误描述: 服务器异常,请稍后再试
    -使用日志,记录错误:越详细越好
        -时间,请求方式,请求的地址,客户端ip,用户id。。。。
'''

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}, 错误的视图类是{view.__class__.__name__}')

    # 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
  • 视图类
from rest_framework.views import APIView
from utils.exceptions import PasswordException
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError, AuthenticationFailed, APIException


class UserView(APIView):
    def get(self, request, *args, **kwargs):
        '''
        这个接口用来查询xx
        '''
        # 1 drf异常,主动抛
        # raise ValidationError('小伙子,出错了')
        # raise AuthenticationFailed('小伙子,出错了,没认证通过吖')
        raise APIException('错误了!!!')

        # 2 django的异常或python的异常
        # res = 9 / 0
        # raise PasswordException('ccccc')

        # l=[2,3,4]
        # print(l[9])

        # raise Exception('错误')

        # return Response('正常返回xx')
  • 配置文件
# 自定义异常配置文件中要加入
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
}
  • 总结使用步骤
#1  定义一个函数
def common_exception_handler(exc, context):
    # 加入日志的逻辑,错误中记录日志---> 越详细越好
    ...日志逻辑...
    response = exception_handler(exc, context)
    if response:
        return Response(data={'code': 9998, 'msg': response.data})
    else:
        return Response(data={'code': 9999, 'msg': '服务器异常,请联系系统管理员'})
    
 # 2 在配置文件中配置
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler', 
}

3、总结

  • 对于前端来讲,无论后端什么情况,前端收到的都是统一的格式

    • 正常响应:

    • 不正常响应:

    • 出了错:{code:999,msg:错误信息}

  • 只要三大认证,视图类的方法出了异常,都会执行一个函数:rest_framework.views import exception_handler

  • 注意:exception_handler

    • 如果异常对象是drf的APIException对象,就会返回Response
    • exception_handler只处理了drf的异常,其它的异常需要我们自己处理
    • 如果异常对象不是drf的APIException对象,就会返回None
  • 补充:

    • isinstance() 判断一个对象是不是某个类的对象 isinstance(对象,类)
    • ssubclass() 判断一个类,是不是另一个类的子类

二、接口文档

1、为什么要写接口文档

在我们后端写完接口之后,应该清楚

  • 地址
  • 携带参数
  • 请求方式
  • 编码格式
  • 返回数据格式
  • ...

并将这些内容必须写成接口文档,方便前端参照,而前端的话在拿到接口文档,应该参考接口文档进行前端的相关工作开发。

2、接口文档展示形式

3、接口文档规范

这要取决你所在的公司有着什么样的规范,毕竟是自己公司内部员工看的,所以需要统一

4、coreapi使用

(1)安装

pip3 install coreapi

(2)路由中配置

from rest_framework.documentation import include_docs_urls
urlpatterns = [
    path('docs/', include_docs_urls(title='coreapi自动生成接口文档'))
]

(3)本地配置文件中配置

REST_FRAMEWORK = {
    	'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
	}

(4)正常写视图类

  • 注意:在方法中加的注释,会在接口文档中体现。在类里面但是在方法外的注释是不会体现的

  • 在序列化类中写required,help_text等参数也会在接口文档中体现

标签:exception,exc,self,接口,handler,文档,异常,response,DRF
From: https://www.cnblogs.com/xiao01/p/18156204

相关文章

  • 接口脚本编写
     如果返回值是一个列表,而你需要从中根据判断条件获取对应的值 //假设响应体中的数据是一个列表,如下://[//{"id":1,"name":"Alice"},//{"id":2,"name":"Bob"},//{"id":3,"name":"Charlie"}......
  • jmeter有很多个接口需要用到token,怎么简单操作?
     一、实现方法添加HTTP请求默认值:在你的测试计划中,添加一个HTTP请求默认值配置元件(HTTPRequestDefaults),用于设置所有HTTP请求的公共属性,包括服务器地址、端口号等。你可以在这里设置token,以便在所有请求中都能使用它。获取token:在测试计划中,添加一个HTTP请求,......
  • 双向链表接口设计
    双向链表接口设计/***@filename:双向链表接口设计(非循环接口)*@brief*@[email protected]*@date2024/04/23*@version1.0:*@property:*@note*CopyRight(c)[email protected]*/构造双向循环链表结构体//指......
  • JS之调用高德地图接口进行打卡
    调用高德地图接口进行打卡1.安装依赖"@amap/amap-jsapi-loader":"^1.0.1"2.增加代码如下:orientation.jsimportAMapLoaderfrom'@amap/amap-jsapi-loader';import{gcj02towgs84}from'./coordTransform.js';exportfunctiongetOrientati......
  • PostScript 是一种页面描述语言,最初由 Adobe 公司开发。它被设计用于描述页面的外观和
    PostScript的起源可以追溯到1982年,当时由Adobe公司的创始人之一约翰·沃诺克(JohnWarnock)和查尔斯·格什克(CharlesGeschke)共同开发。沃诺克和格什克当时都是在施乐帕克研究中心工作,他们在那里开始了对一种新的页面描述语言的研究和开发。当时的打印技术面临着一些挑战,......
  • 双向链表的接口的接口程序
    双向链表的接口的接口程序/********************************************************************* filename: 双向链表的接口的接口程序* author :[email protected]* date :2024-4-23* function:* note :None** CopyRight(c)20241764757......
  • Google Play App Store API 采集谷歌安卓应用商城app的数据接口 - 2024最新
    iDataRiver平台https://www.idatariver.com/zh-cn/提供开箱即用的谷歌安卓应用商城googleplayappstore数据采集API,供用户按需调用。接口使用详情请参考GooglePlayAppStore接口文档接口列表1.获取指定app的基础信息参数类型是否必填默认值示例值描述apik......
  • 单向循环链表的接口程序
    自定义单向循环链表的增删改查接口函数/********************************************************************文件名称: 01单向循环链表的接口程序文件作者:[email protected]创建日期:2024/04/23文件功能:对单向循环链表的增删改查功能的定义注意事项:......
  • 设计单向循环链表的接口
    /***********************************************************************************filename:003_单向循环链表.cauthor:[email protected]:2024/04/23function:设计单向循环链表的接口note:......
  • 性能测试——压测工具jmeter接口测试
    柠檬班jmeter教程参考:https://www.bilibili.com/video/BV1st411Y7QW/?spm_id_from=333.337.search-card.all.click&vd_source=79bbd5b76bfd74c2ef1501653cee29d6 黑马jmeter教程参考:https://www.bilibili.com/video/BV1ty4y1q72g/?spm_id_from=333.337.search-card.all.click&v......