[python] pprika:基于werkzeug编写的web框架(3) ——错误处理

错误处理示例

from pprika import PPrika

app = PPrika()


@app.error_handler(404)
def not_found(e):
    print(e)
    return repr(e)


if __name__ == '__main__':
    app.run()

与flask一样,通过error_handler装饰器来注册错误处理函数。该装饰器接受一个HTTP错误码或异常类(Exception)作为参数,会将发生的具体错误对象传递给处理函数。

上述代码指当发生404错误时以not_found作为处理函数,其返回值跟视图函数一样会被处理成响应返回,而error_handler只是 register_error_handler 的装饰器版本,上方错误注册可改写成 app.register_error_handler(404, not_found) ,因此重点讲述register_error_handler。

 

register_error_handler

def register_error_handler(self, code_or_exception, func, field=None):
    if isinstance(code_or_exception, Exception):
        raise ValueError(f"""
            不可注册异常实例: {repr(code_or_exception)},
            只能是异常类或HTTP错误码
        """)

    exc_class, code = self._get_exc_class_and_code(code_or_exception)

field参数表示该错误处理规则的适用范围,为None时表示作用于全局(app);为str时是蓝图名,表示仅作用于该蓝图(blueprint)内,非None的情况之后讲蓝图的时候会再提及。

注意到其中调用了 _get_exc_class_and_code,它会根据错误码或者异常类其中一个尝试补出对应的另一个,比如本例中code_or_exception=404,经过该函数处理会补出 werkzeug.exceptions.NotFound。

依据错误码得到异常类主要通过 werkzeug.exceptions 包中的一个字典default_exceptions,它以{http_error_code: exception_class}的形式记录了错误码与对应异常类的映射,反过来若异常类为HTTPException子类则e.code即为错误码,其他异常就统一将错误码当做None。具体实现方法与flask同名函数几乎一样,不再赘述。

    handlers = self.error_handlers.setdefault(field, {}).setdefault(code, {})
    handlers[exc_class] = func

紧接上文,error_handlers是一个字典:{field: {status: {error: function}}},通过作用范围、错误码、具体错误类三层记录该错误类exc_class具体的处理函数func,这样一个错误处理函数就注册好了。

接下来会讲述错误发生时的处理方式。

 

 handle_user_exception

这是上一篇提到的dispatch_request内部的错误处理函数,也是内层错误处理

dispatch_request内部片段↓↓↓

    try:
        endpoint, args = request.rule.endpoint, request.view_args
        rv = self.view_functions[endpoint](**args)
    except Exception as e:
        rv = self.handle_user_exception(e)

当该过程(尤其指视图函数被调用时)产生的异常会被捕捉交给 handle_user_exception 处理。

def handle_user_exception(self, e):
    handler = self._find_error_handler(e)
    if handler is not None:
        return handler(e)
    raise e

该函数负责的是像本实例中通过error_handler装饰器或register_error_handler方法注册过的错误,若该错误未被注册将再次将其抛出。

其中 _find_error_handler 内部先通过python内置的type函数得到异常类exc_class

,再通过上述的 _get_exc_class_and_code方法获得错误码code。

由于一个错误可能在不同范围、以不同错误码注册过,因此查找时以错误码取code、None,在此基础上再以field取blueprint(表蓝图范围)、取None(表全局范围)的顺序在 self.error_handlers 中查找处理函数,若找不到则按None返回。具体实现方法同样与flask同名函数几乎一样,暂且略过,之后讲述restful部分时可能会有补充。

 

handle_exception

该函数作为外层错误处理器受 wsgi_app 调用,处理 handle_user_exception 无法处理的,或处理过程中再次发生的异常

wsgi_app内部片段(去掉了不相关的)↓↓↓

    try:
        rv = self.dispatch_request()
        response = make_response(rv)
    except Exception as e:
        response = self.handle_exception(e)

与handle_user_exception配合形成两层的错误处理,确保错误能正确响应

下方是其实现原理

def handle_exception(self, e):
    if isinstance(e, HTTPException):
        return e

首先判断如果是HTTPException或其子类实例则直接返回

    server_error = InternalServerError()
    server_error.original_exception = e

    handler = self._find_error_handler(e) or self._find_error_handler(server_error)

到了这一步说明该错误可能没有对应的错误处理器,也不是能直接作为response的HTTPException,那就统一当做 '500 InternalServerError' 响应,并在此尝试获取其handler(以原错误先尝试获取是考虑到可能是handle_user_exception处理中的再次发生的异常)

    if handler is not None:
        server_error = handler(server_error)
    else:
        print_exception(*exc_info())

    return make_response(server_error)

最后若能找到handler则将返回值生成make_response并返回,否则说明不能自行处理该错误,打印出Traceback并直接返回InternalServerError(make_response内部也会把HTTPException这类的直接返回)。

注意此处不能将无法处理的server_error再次raise并同时returnmake_response(server_error),即便使用try...finally也不行,raise与return不共存。但又需要显示错误信息(traceback),故通过exc_info得到traceback信息并由print_excption打印出来。

from sys import exc_info
from traceback import print_exception

这点与flask不同,它利用self.propagate_exceptions决定是否再次抛出,self.log_exception、self.logger对该错误进行记录,更加细致,具体的没有深入了...

 

结语

至此错误处理部分结束,不过这部分之后在将restful时会有进一步的补充,接下来先讲讲请求上下文。

[python] pprika:基于werkzeug编写的web框架(4) ——请求上下文与helpers

 

posted @ 2020-05-31 21:37  NoNoe  阅读(283)  评论(0编辑  收藏  举报