11.3 HttpException

1.first_ot_404的异常抛出流程

我们来接着看Aborter类中的firstot_404流程,在first_ot_404调用流程中,由于不满足\_call__的前两个判断条件,最终会抛出self.mapping,其在构造函数中进行了声明

class Aborter(object):

    """
    When passed a dict of code -> exception items it can be used as
    callable that raises exceptions.  If the first argument to the
    callable is an integer it will be looked up in the mapping, if it's
    a WSGI application it will be raised in a proxy exception.

    The rest of the arguments are forwarded to the exception constructor.
    """

    def __init__(self, mapping=None, extra=None):
        if mapping is None:
            mapping = default_exceptions
        self.mapping = dict(mapping)
        if extra is not None:
            self.mapping.update(extra)

    def __call__(self, code, *args, **kwargs):
        if not args and not kwargs and not isinstance(code, integer_types):
            raise HTTPException(response=code)
        if code not in self.mapping:
            raise LookupError('no exception for %r' % code)
        raise self.mapping[code](*args, **kwargs)

self.mapping是一个dict,其封装了default_exceptions,下面来看一下default_exceptions的装载,在_find_exceptions中完成。他的作用是扫描当前模块下所有HTTPException的子类对象,并装载到default_exceptions

default_exceptions = {}
__all__ = ['HTTPException']


def _find_exceptions():
    for name, obj in iteritems(globals()):
        try:
            is_http_exception = issubclass(obj, HTTPException)
        except TypeError:
            is_http_exception = False
        if not is_http_exception or obj.code is None:
            continue
        __all__.append(obj.__name__)
        old_obj = default_exceptions.get(obj.code, None)
        if old_obj is not None and issubclass(obj, old_obj):
            continue
        default_exceptions[obj.code] = obj
_find_exceptions()
del _find_exceptions

最终Aborter函数的__call__方法拿着封装好的self.mapping(实质是default_exceptions)通过参数传来的code去匹配相应的异常,并进行抛出。

2.抛出异常后到浏览器异常界面的显示流程

因为first_or_404()后面传入Aborter的code为404,所以其对应抛出的异常就是 NotFound对象,而其中的description描述文本,就是异常页面的显示文本

class NotFound(HTTPException):

    """*404* `Not Found`

    Raise if a resource does not exist and never existed.
    """
    code = 404
    description = (
        'The requested URL was not found on the server.  '
        'If you entered the URL manually please check your spelling and '
        'try again.'
    )

而所有的页面的显示文本,都是由response来做的,我们的试图函数在调用first_or_404()函数时,由于结果不存在,就抛出了上面的NotFound异常而终止了,后面的异常流程直到界面显示,都是由HttpException完成的,来看下其对应源码

    # get_response最终返回了一个Response对象
    def get_response(self, environ=None):
        """Get a response object.  If one was passed to the exception
        it's returned directly.

        :param environ: the optional environ for the request.  This
                        can be used to modify the response depending
                        on how the request looked like.
        :return: a :class:`Response` object or a subclass thereof.
        """
        if self.response is not None:
            return self.response
        if environ is not None:
            environ = _get_environ(environ)
        headers = self.get_headers(environ)
        # get_body获取页面文本内容
        return Response(self.get_body(environ), self.code, headers)

get_body()

    def get_body(self, environ=None):
        """Get the HTML body."""
        return text_type((
            u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
            u'<title>%(code)s %(name)s</title>\n'
            u'<h1>%(name)s</h1>\n'
            u'%(description)s\n'
        ) % {
            'code':         self.code,
            'name':         escape(self.name),
            # self.get_description获取异常的description信息
            'description':  self.get_description(environ)
        })

至此,first_or_404,从调用,到界面显示流程,就剖析完成了

3.基于AOP统一处理异常

如果我们不想返回默认的404界面,而想自己定制,那么直接想到的方法就是在代码中捕获异常,返回我们自定义的试图,但是这样代码太啰嗦了。我们可以采用AOP面向切面的设计思想,定义一个装饰器,用来统一处理某类异常,好在Flask已经为我们提供了这种方法,使用方法如下

# web是蓝图对象,当然也可以使用app对象
# app_errorhandler接受一个状态码,代表当前方法处理的异常
# not_found函数不是只能返回视图,他可以做任何你想做的事情
@web.app_errorhandler(404)
def not_found(e):
    return render_template('404.html'), 404

Last updated