文章目录

  • 1.异常处理流程
    • 1.异常注册
    • 2.异常触发
    • 3.异常处理
  • 2.flask-jwt集成,认证相关异常处理
  • 参考博客

要做一个python web系统,做简单的信息管理和案例展示,还要考虑后续功能的扩展。就想着自己搭建一个python web框架。本来想着像.net、java一样,有现成的脚手架项目,拿来改吧改吧就用了,结果发现Python web这一块还真是奇葩:

  1. 很多python web应用或脚手架的维护3~4年前就已经停止维护了
  2. 只有python web的基础框架,像flask、Django,倒是维护的挺活跃。但是flask太轻,需要自己集成;Diango太重,想要的不想要的都给你集成在一起了。

本篇总结之-Web异常处理

刚开始使用时flask异常模块时,业务处理中直接触发HTTPException异常,导致出现了莫名其妙的问题,异常无法正常捕获,最终复盘发现了原因。总结一下异常注册、异常触发、异常处理的流程,对比查看一下flask-jwt集成flask的异常实现。

1.异常处理流程

1.异常注册

  1. 工厂模式注册
def create_app(config_name="default"):app = Flask(__name__)app.config.from_object(config_by_name[config_name])register_extensions(app)# Register error handlerapp.register_error_handler(Exception, handle_error)def handle_error(e):"""Handle errors, formatting them as JSON if requested"""message = str(e)trace = Nonedescription = Nonestatus_code = 500if isinstance(e, werkzeug.exceptions.HTTPException):status_code = e.codedescription = e.descriptionif current_app.debug:trace = traceback.format_exc()details = {'message': message,'type': error_type,}if description is not None:details['description'] = descriptionif trace is not None:details['trace'] = trace.split('\n')return fail_api(message, status_code, details)
  1. 装饰器模式注册
@app.errorhandler(Exception)
def handle_error(e):"""Handle errors, formatting them as JSON if requested"""......
  1. 异常注册类型

一般Web应用异常分类HttpException异常(客户端异常)和Exception异常(服务端异常),注册的时候可以指定具体处理的异常类型,当然也可以统一处理。

  1. 基于蓝图注册异常

根据应用需要,也可以根据blueprint进行异常注册处理。既可以把异常注册到具体的blueprint上,也可以把异常注册到全局的flask app上。flask在寻找异常处理程序时,优先查找注册在blueprint上的处理器。当一个蓝图在处理抛出异常的请求时,在蓝图中注册的出错处理器优先于在应用中全局注册的出错处理器。但是,蓝图无法处理404路由错误,因为404发生的路由级别还不能检测到蓝图。

def _find_error_handler(self, e: Exception) -> t.Optional[ft.ErrorHandlerCallable]:"""Return a registered error handler for an exception in this order:blueprint handler for a specific code, app handler for a specific code,blueprint handler for an exception class, app handler for an exceptionclass, or ``None`` if a suitable handler is not found."""exc_class, code = self._get_exc_class_and_code(type(e))names = (*request.blueprints, None)for c in (code, None) if code is not None else (None,):for name in names:handler_map = self.error_handler_spec[name][c]if not handler_map:continuefor cls in exc_class.__mro__:handler = handler_map.get(cls)if handler is not None:return handlerreturn None

2.异常触发

  1. assert触发异常

assert语句又称作断言,指的是程序期望满足指定的条件。定义的约束条件不满足的时候,它会触发AssertionError异常,所以assert语句可以当作条件式的raise语句。

assert 逻辑表达式,data      # data是可选的,通常是一个字符串,当表达式的结果为False时,作为异常类型的描述信息使用。

等同于

if not 逻辑表达式:raise AssertionError(data)
  1. raise触发异常
raise [exceptionName [(reason)]]
# 其中,用 [] 括起来的为可选参数,其作用是指定抛出的异常名称,以及异常信息的相关描述。如果可选参数全部省略,则 raise 会把当前错误原样抛出;如果之前没有触发异常,触发RuntimeError;如果仅省略 (reason),则在抛出异常时,将不附带任何的异常描述信息。
  • raise
  • raise exceptionName
  • raise exceptionName(reason)
  1. abort触发异常

在视图函数执行过程中,我们可以使用abort函数立即终止视图函数的执行。通过abort函数,可以返回一个app.aborter中存在的错误状态码,表示出现的错误信息。类似于python中raise。

abort(404)
  1. 不要直接触发HTTPException异常

不要直接触发HttpException异常,这样会导致错误不能被正常捕获处理。
首先flask会单独判断一下HTTPException异常,如果是的话转入handle_http_exeption处理器进行处理。然后handle_http_exeption处理逻辑中,会默认访问code属性。但是我们直接触发HTTPException时是无法设置code属性的。因此这时会引发新的异常,导致错误处理不会走我们注册的处理程序,造成莫名其妙的错误。我们要么触发类似NotFound这些基于HTTPException扩展的异常,要么可以基于HTTPException继承自定义的异常。

def handle_user_exception(self, e: Exception
) -> t.Union[HTTPException, ft.ResponseReturnValue]:"""This method is called whenever an exception occurs thatshould be handled. A special case is :class:`~werkzeug.exceptions.HTTPException` which is forwarded to the:meth:`handle_http_exception` method. This function will eitherreturn a response value or reraise the exception with the sametraceback... versionchanged:: 1.0Key errors raised from request data like ``form`` show thebad key in debug mode rather than a generic bad requestmessage... versionadded:: 0.7"""if isinstance(e, BadRequestKeyError) and (self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]):e.show_exception = Trueif isinstance(e, HTTPException) and not self.trap_http_exception(e):return self.handle_http_exception(e)handler = self._find_error_handler(e)if handler is None:raisereturn self.ensure_sync(handler)(e)
def handle_http_exception(self, e: HTTPException
) -> t.Union[HTTPException, ft.ResponseReturnValue]:"""Handles an HTTP exception.  By default this will invoke theregistered error handlers and fall back to returning theexception as response... versionchanged:: 1.0.3``RoutingException``, used internally for actions such asslash redirects during routing, is not passed to errorhandlers... versionchanged:: 1.0Exceptions are looked up by code *and* by MRO, so``HTTPException`` subclasses can be handled with a catch-allhandler for the base ``HTTPException``... versionadded:: 0.3"""# Proxy exceptions don't have error codes.  We want to always return# those unchanged as errorsif e.code is None:return e# RoutingExceptions are used internally to trigger routing# actions, such as slash redirects raising RequestRedirect. They# are not raised or handled in user code.if isinstance(e, RoutingException):return ehandler = self._find_error_handler(e)if handler is None:return ereturn self.ensure_sync(handler)(e)

3.异常处理

  1. 异常处理一般包括日志记录、堆栈记录方便错误排查。然后进行错误信息统一格式化,返回前端,方便前端操作人员理解系统故障。
def handle_error(e):"""Handle errors, formatting them as JSON if requested"""# log stack infoformat_exec_info = traceback.format_exc()if format_exec_info:current_app.logger.error(format_exec_info)error_type = type(e).__name__message = str(e)trace = Nonedescription = Nonestatus_code = 500if isinstance(e, werkzeug.exceptions.HTTPException):status_code = e.codedescription = e.descriptionif current_app.debug:trace = traceback.format_exc()details = {'message': message,'type': error_type,}if description is not None:details['description'] = descriptionif trace is not None:details['trace'] = trace.split('\n')return fail_api(message, status_code, details)

2.flask-jwt集成,认证相关异常处理

我们看看flask-jwt是怎么进行异常处理的

  1. 定义了一些自定义异常:CSRFError、DecodeError、FreshTokenRequired、MissingRequiredClaimError等。
class JWTExtendedException(Exception):"""Base except which all flask_jwt_extended errors extend"""passclass JWTDecodeError(JWTExtendedException):"""An error decoding a JWT"""pass
  1. 定义每个异常的默认处理方法

这样的话就是flask-jwt框架可以有一个默认的异常处理行为,在我们没有注册自己的异常处理方法时,由框架定义的默认处理方法进行处理。

self._decode_key_callback = default_decode_key_callback
self._encode_key_callback = default_encode_key_callback
self._expired_token_callback = default_expired_token_callback
self._invalid_token_callback = default_invalid_token_callback
self._jwt_additional_header_callback = default_jwt_headers_callback
self._needs_fresh_token_callback = default_needs_fresh_token_callback
self._revoked_token_callback = default_revoked_token_callback
self._token_in_blocklist_callback = default_blocklist_callback
self._token_verification_callback = default_token_verification_callback
self._unauthorized_callback = default_unauthorized_callback
self._user_claims_callback = default_additional_claims_callback
self._user_identity_callback = default_user_identity_callback
self._user_lookup_callback: Optional[Callable] = None
self._user_lookup_error_callback = default_user_lookup_error_callback
self._token_verification_failed_callback = (default_token_verification_failed_callback
)def default_invalid_token_callback(error_string: str) -> ResponseReturnValue:"""By default, if an invalid token attempts to access a protected endpoint, wereturn the error string for why it is not valid with a 422 status code:param error_string: String indicating why the token is invalid"""return (jsonify({config.error_msg_key: error_string}),HTTPStatus.UNPROCESSABLE_ENTITY,)
  1. 异常注册

这里就回到flask的异常注册了。这里把flask-jwt自定义的异常行为和自定义的异常回调注册到flask异常处理模块。由于flask在查找异常处理器时,优先子类异常,因此这些自定义的异常会优先查找处理器,也就把这些flask-jwt自定义异常的处理权限交给了框架处理。

def _set_error_handler_callbacks(self, app: Flask) -> None:@app.errorhandler(CSRFError)def handle_csrf_error(e):return self._unauthorized_callback(str(e))@app.errorhandler(DecodeError)def handle_decode_error(e):return self._invalid_token_callback(str(e))@app.errorhandler(ExpiredSignatureError)def handle_expired_error(e):return self._expired_token_callback(e.jwt_header, e.jwt_data)...
  1. 自定义异常处理

当然flask-jwt也提供了我们处理这些自定义异常的入口,这样我们可以自定义如何处理这些flask-jwt定义的异常。如果我们需要接管flask-jwt这些自定义异常的处理,就要按照flask-jwt定义的装饰器,逐个注册处理器,用来覆盖默认的处理器。我这里注册了未授权处理器、无效token处理器,这个可以根据自己的项目需要。

@jwt.unauthorized_loader
def unauthorized_callback(callback):return permission_error(callback)@jwt.invalid_token_loader
def invalid_token_callback(callback):return permission_error(callback)@jwt.token_verification_failed_loader
def token_verification_failed(callback):return permission_error(callback)

总的来说,python框架真的是短小精悍,却面面俱到,用来学习各种web应用的实现机制真的是很容易。

参考博客

flask 全局异常处理
Flask开发技巧之异常处理
应用错误处理

Python Web异常处理相关推荐

  1. 我的第一个python web开发框架(40)——后台日志与异常处理

    我的第一个python web开发框架(40)--后台日志与异常处理 参考文章: (1)我的第一个python web开发框架(40)--后台日志与异常处理 (2)https://www.cnblog ...

  2. python web 框架例子_最快的 Python Web 框架入门

    原标题:最快的 Python Web 框架入门 来源:Python开发 ID:PythonPush 速度比较 框架 实现基础 每秒请求数 平均时间 Sanic Python 3.5 + uvloop ...

  3. “脚踢各大Python Web框架”,Sanic真有这能耐么?

    在Github上,Sanic第一句介绍语就是: "Sanic is a Flask-like Python 3.5+ web server that's written to go fast ...

  4. python web微信应用(一) 微信协议分析

    文章目录 前言 一.__get_uuid,获取 uuid 二.__gen_qrcode,生成二维码 三.__login,手机扫码登录 四.__get_params,获取登录参数信息 五.__initi ...

  5. python web开发框架flask_Python Web 开发框架,Flask 与 Django那个更好

    本文把 Flask 和 Django 做一个比对,因为我对这两个 Python Web 框架都有实际的开发经验.希望我可以帮助您选择学习哪个框架,因为学习一个框架可能会非常耗时 -- 当然也很有趣! ...

  6. Python Web实战:Python+Django+MySQL实现基于Web版的增删改查

    本文使用Python Web框架Django连接和操作MySQL数据库学生信息管理系统(SMS),主要包含对学生信息增删改查功能. 1.创建项目(sms) 创建Django项目 django-admi ...

  7. 程序员新手第一个python web开发框架

    接下来正式进入网站的功能开发.要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX. 在进行接口开发之前, ...

  8. python django web典型模块开发实战下载_Django实战 Python Web典型模块与项目开发

    本书结合样例,介绍 Django 的基础知识.主要模块的开发以及权限管理等高级内容,并且通过图书管理系统.博客系统.车费管理系统 3 个项目的开发实战,使读者既能掌握 Django 的重要开发技术,又 ...

  9. 简单介绍Python中异常处理用法

    这篇文章主要给大家分享的是 Python中异常处理用法,为了保证程序的健壮性与容错性,即在遇到错误时候程序不会崩溃,我们需要对异常进行处理,下面来看看文章对此的用法,需要的朋友可以参考一下 为了保证程 ...

最新文章

  1. 阿里新生的面试经,与老人分享的职业进阶攻略及规划
  2. 非线性方程组求解Matlab实现 (多元牛顿方法、Broyden方法、Broyden方法2)
  3. redis 集群目标、集群查看、配置方法及过程、哨兵配置启动
  4. NOIP信息奥赛--1995“同创杯”初中复赛题题解(二)
  5. 中文电子病例命名实体识别项目
  6. 设置PL/SQL工具SQL窗口的字体大小及颜色
  7. eclipse+java+selenium+testNG搭建自动化测试框架
  8. Mac 苹果OS X小技巧:如何更改文件的默认打开方式
  9. 伯克利cs61b总结贴
  10. Python中hashlib.sha1()和hashlib.MD5()哈希算法的区别
  11. Excel中万能的查询函数——VLOOKUP(使用方法+实操)
  12. 大一计算机专业学期计划范文,大一新学期学习计划范文(通用5篇)
  13. 如何计算802.11 PHY Data Rate (11ac/11ax)
  14. mysql a foreign key constraint fails_外键记录有存在,插入数据却报错a foreign key constraint fails...
  15. QoS服务质量二令牌桶算法及QoS业务分类
  16. 零售-商品/门店管理系统 | 进销存系统
  17. cots 常见问题解答
  18. 小米捐赠5亿助力教育普惠,“小米奖助学金”第二批高校报名启动
  19. 沙河之痛——河南鲁山县沙河非法采砂遥感影像分析
  20. RAID 5数据恢复图解

热门文章

  1. uni-app写微信小程序获取位置信息
  2. typeof(undefined) == undefined 成立吗?
  3. python类中的魔方方法
  4. C#调用DLL的几种方法
  5. 解决windows服务器装虚拟机windows系统无网络连接网络
  6. MongoDB查询集合中的文档
  7. 【CF#715C】Digit Tree 点分治+乘法逆元
  8. PMOS管用作电源开关注意事项
  9. 小程序获取oppenid时返回40125或者40029
  10. mysql绑定多个ip地址 (mysql给用户授权了, 还是无法远程连接)