Flask0.1源码阅读——请求处理和响应

用flask做了几个项目了,它的各个模块的功能也都基本了解,有时候为了解决某个bug会溯源到对应的源码里一看究竟,但一直都没有从整体上理解flask的工作流程。最近发现flask最初版本代码不过300多行,于是赶紧找出来阅读了一遍。
这篇笔记主要记录flask对于请求的处理和响应过程,忽略了关于模版渲染的内容。

数据结构

werkzeug

稍微浏览一遍源码你就会发现,werkzeug才是爸爸。flask中关键的几个模块全是从werkzeug中继承下来的。在这里就不溯源到werkzeug去分析了,只是了解werkzeug中的这些重要模块在flask中起的作用。

Request

Request类处理从environ变量中获取的变量,将其封装好供其他模块使用,我们经常使用到的Flask.request就是它的实例。

Response

Response类将响应函数的返回值包装为http响应

LocalStack

LocalStack类是一个线程隔离栈,被用于应用上下文和请求上下文。这是非常重要的一个概念,这篇文章讲的很清楚:Flask 的 Context 机制

Map、Rule

Map、Rule类将路由和视图函数及其参数绑定

Flask

class Flask(object):
    request_class = Request   
    response_class = Response
    static_path = '/static'  
    session_cookie_name = 'session'  
    secret_key = None   
    def __init__(self, package_name):
        self.debug = False  
        self.package_name = package_name
        self.root_path = _get_package_path(self.package_name)
        self.view_functions = {}
        self.error_handlers = {} 
        self.before_request_funcs = []
        self.after_request_funcs = [] 
    
        self.url_map = Map()
    def open_session(self, request):
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request, self.session_cookie_name,
                                            secret_key=key)
    def request_context(self, environ):
        return _RequestContext(self, environ) 

上面节选了些Flask类中定义成员变量的部分,可以看到,Flask直接利用了werkzeug的Request、Response、Map作为请求、响应、路由的成员变量。open_session将secret_key作为密钥,生成保存在cookie中的加密session。view_functions和error_handlers保存视图函数和错误处理函数与对应的路由。before_request_funcs和after_request_funcs分别保存请求预处理函数和响应的后续处理函数。

def add_url_rule(self, rule, endpoint, **options):
    options['endpoint'] = endpoint
    options.setdefault('methods', ('GET',))

    self.url_map.add(Rule(rule, **options))

def route(self, rule, **options):
    def decorator(f):
        self.add_url_rule(rule, f.__name__, **options)    
        self.view_functions[f.__name__] = f       
        return f
    return decorator

def errorhandler(self, code):
    def decorator(f):
        self.error_handlers[code] = f 
        return f
    return decorator

上面三个函数处理视图函数与路由的绑定,add_url_rule利用Rule类将路由规则、视图函数、参数绑定到一起,存储到url_map中。
而route就是那个刚接触flask时觉得非常神器的装饰器。它的作用就是把被装饰的函数和路由规则作为参数调用了add_url_rule,并放入view_functions字典。下面两段代码效果一样。

@app.route('/')
def index():
    pass
def index():
    pass
app.add_url_rule('index', '/')
app.view_functions['index'] = index

_RequestContext

class _RequestContext(object):  
    """The request context contains all request relevant information.  It is created at the beginning of the request and pushed to the `_request_ctx_stack` and removed at the end of it.  It will create the URL adapter and request object for the WSGI environment provided.
    """

    def __init__(self, app, environ):
        self.app = app
        self.url_adapter = app.url_map.bind_to_environ(environ)
        self.request = app.request_class(environ)
        self.session = app.open_session(self.request)
        self.g = _RequestGlobals()    
        self.flashes = None

    def __enter__(self):
        _request_ctx_stack.push(self)

    def __exit__(self, exc_type, exc_value, tb):
        # do not pop the request stack if we are in debug mode and an
        # exception happened.  This will allow the debugger to still
        # access the request object in the interactive shell.
        if tb is None or not self.app.debug:
            _request_ctx_stack.pop()

我觉得这是一个非常重要的辅助类。它把werkzeug中的Request、Map、SecureCookie等依赖实例化为自己的成员函数,再使用python的上下文机制,将自己作为flask的请求上下文,压入LocalStack栈中。它传入的参数environ即是WSGI的environ参数,在WSGI中,environ变量包含了一个请求的所有信息,_RequestContext将environ解析请求的数据信息——request,和请求的路由信息——url_adapter,具体的解析过程都是在werkzeug中完成的。所以,在flask中,一个请求的所有信息都可以在request和url_adapter中找到。

处理流程

def wsgi_app(self, environ, start_response):
    """The actual WSGI application.  This is not implemented in
    `__call__` so that middlewares can be applied:
        app.wsgi_app = MyMiddleware(app.wsgi_app)
    :param environ: a WSGI environment
    :param start_response: a callable accepting a status code,
                           a list of headers and an optional
                           exception context to start the response
    """
    with self.request_context(environ):     # 请求上下文
        rv = self.preprocess_request()      # 请求前, 预处理
        if rv is None:
            rv = self.dispatch_request()    # 处理请求

        response = self.make_response(rv)            # 产生响应
        response = self.process_response(response)   # 返回响应前, 作清理工作

        return response(environ, start_response)
        
def __call__(self, environ, start_response):
    """Shortcut for :attr:`wsgi_app`"""
    return self.wsgi_app(environ, start_response)

整个响应的处理流程在wsgi_app函数中描述的非常清除,依次来看:

请求上下文压入栈

Flask被调用时,会执行wsgi_app函数,首先把请求上下文,即之前提到的_RequestContext压入LocalStack栈。

请求预处理

def preprocess_request(self):
        """Called before the actual request dispatching and will
        call every as :meth:`before_request` decorated function.
        If any of these function returns a value it's handled as
        if it was the return value from the view and further
        request handling is stopped.
        """
        for func in self.before_request_funcs:
            rv = func()    # 执行预处理函数
            if rv is not None:
                return rv

请求预处理阶段,通常是从请求中提取出一些信息放到请求上下文中,方便视图函数使用。比如从cookie中提取用户身份验证信息,得到对应的用户实例,保存在全局变量g中。
如果before_request_funcs列表中的某个函数有返回值,也就是在预处理阶段就产生了响应,那么就立即响应生成阶段。比如用户身份验证未通过,立即返回403错误,不再交给试图函数处理。

请求处理

def match_request(self):
    """Matches the current request against the URL map and also
    stores the endpoint and view arguments on the request object
    is successful, otherwise the exception is stored.
    """
    rv = _request_ctx_stack.top.url_adapter.match()
    request.endpoint, request.view_args = rv
    return rv
    
def dispatch_request(self):
    """Does the request dispatching.  Matches the URL and returns the
    return value of the view or error handler.  This does not have to
    be a response object.  In order to convert the return value to a
    proper response object, call :func:`make_response`.
    """
    try:
        endpoint, values = self.match_request()    # 请求匹配
        return self.view_functions[endpoint](**values)
    except HTTPException, e:
        handler = self.error_handlers.get(e.code)
        if handler is None:
            return e
        return handler(e)
    except Exception, e:
        handler = self.error_handlers.get(500)
        if self.debug or handler is None:
            raise
        return handler(e)

处理请求时,首先调用match_request函数,从请求上下文的url_adapter中匹配到对应的路由规则和参数。再从view_functions字典中找出规则对应的请求函数,然后执行这个视图,并返回它的值。如果抛出了错误,先看看错误有没有在error_handlers字典中定义,有的话就调用相应的错误处理函数,没有就抛出500错误。

产生响应

def make_response(self, rv):
    """Converts the return value from a view function to a real
    response object that is an instance of :attr:`response_class`.
    The following types are allowd for `rv`:
    ======================= ===========================================
    :attr:`response_class`  the object is returned unchanged
    :class:`str`            a response object is created with the
                            string as body
    :class:`unicode`        a response object is created with the
                            string encoded to utf-8 as body
    :class:`tuple`          the response object is created with the
                            contents of the tuple as arguments
    a WSGI function         the function is called as WSGI application
                            and buffered as response object
    ======================= ===========================================
    :param rv: the return value from the view function
    """
    if isinstance(rv, self.response_class):
        return rv
    if isinstance(rv, basestring):
        return self.response_class(rv)
    if isinstance(rv, tuple):
        return self.response_class(*rv)
    return self.response_class.force_type(rv, request.environ)

make_response将请求处理阶段返回的值转换为Response对象

响应返回前的处理

def process_response(self, response):
    """Can be overridden in order to modify the response object
    before it's sent to the WSGI server.  By default this will
    call all the :meth:`after_request` decorated functions.
    :param response: a :attr:`response_class` object.
    :return: a new response object or the same, has to be an
             instance of :attr:`response_class`.
    """
    session = _request_ctx_stack.top.session
    if session is not None:
        self.save_session(session, response)     # 保存 session

    for handler in self.after_request_funcs:     # 请求结束后, 清理工作
        response = handler(response)
    return response

这里首先检查请求上下文中有没有session,如果有就调用save_session将session存到cookie中。然后把after_request_funcs列表中的函数取出,依次执行。

返回响应

这里的response(environ, start_response)看接收的参数就很像WSGI标准的HTTP处理函数:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'

而response实例已经在make_response函数中,携带上了需要返回的值。

Comments
Write a Comment