flask源码剖析

    这段时间想重新写个自己的博客系统,又正好在看一些框架源码,然后就想要不顺便写个小框架吧,既然想写框架,要不再顺便写个orm吧,再写个小的异步Server吧。。事实证明饭要一口一口吃

    先梳理一下flask工作的整个流程吧。

    首先flask是符合wsgi协议的,那么也就是说,flask要实现一个可以callable的object给web server。

    那么什么是wsgi? 简而言之,wsgi就是把框架和web Server协同工作的一个协议,规范。流程大概是:当有请求来的时候, web Server调用一个callable object(函数,类,实现__call__的实例等等), 传入(environ, start_response), 其中environ是一个字典,包含了请求相关的所有信息,比如请求路径,参数;start_response是一个函数。在这个callable object中会调用start_response,返回http状态码,headers等信息给web server, callable object本身则会返回具体的内容给web server, 返回的object必须是可迭代的。当有callable object本身返回的内容时候, 就会把start_response返回的内容和callable object返回的内容写入缓冲区并且刷新。

  那么flask 给web server的callable object是什么?

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

可以看到,是一个实现了__call__的实例,web server 调用Flask的实例,传入environ和start_response,那么这个self.wsgi_app是什么东西?

 1     def wsgi_app(self, environ, start_response):
 2         """The actual WSGI application.  This is not implemented in
 3         `__call__` so that middlewares can be applied without losing a
 4         reference to the class.  So instead of doing this::
 5 
 6             app = MyMiddleware(app)
 7 
 8         It's a better idea to do this instead::
 9 
10             app.wsgi_app = MyMiddleware(app.wsgi_app)
11 
12         Then you still have the original application object around and
13         can continue to call methods on it.
14 
15         .. versionchanged:: 0.7
16            The behavior of the before and after request callbacks was changed
17            under error conditions and a new callback was added that will
18            always execute at the end of the request, independent on if an
19            error ocurred or not.  See :ref:`callbacks-and-errors`.
20 
21         :param environ: a WSGI environment
22         :param start_response: a callable accepting a status code,
23                                a list of headers and an optional
24                                exception context to start the response
25         """
26         with self.request_context(environ):
27             try:
28                 response = self.full_dispatch_request()
29             except Exception, e:
30                 response = self.make_response(self.handle_exception(e))
31             return response(environ, start_response)

在文档里面第一句已经说明了“The actual WSGI application...”这个才是真正的callable object。那么这个真正的callable object会做什么事情?

首先,会打开self.request_context(envion),

class RequestContext(object):

    def __init__(self, app, environ):
        self.app = app
        self.request = app.request_class(environ)
        self.url_adapter = app.create_url_adapter(self.request)
        self.g = app.request_globals_class()
        self.flashes = None
        self.session = None


        self.preserved = False
        self._after_request_functions = []

        self.match_request()

        blueprint = self.request.blueprint
        if blueprint is not None:
            # better safe than sorry, we don't want to break code that
            # already worked
            bp = app.blueprints.get(blueprint)
            if bp is not None and blueprint_is_module(bp):
                self.request._is_old_module = True

    def match_request(self):
        try:
            url_rule, self.request.view_args = \
                self.url_adapter.match(return_rule=True)
            self.request.url_rule = url_rule
        except HTTPException, e:
            self.request.routing_exception = e

    def push(self):
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        _request_ctx_stack.push(self)
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()

    def pop(self, exc=None):
        app_ctx = self._implicit_app_ctx_stack.pop()

        clear_request = False
        if not self._implicit_app_ctx_stack:
            self.preserved = False
            if exc is None:
                exc = sys.exc_info()[1]
            self.app.do_teardown_request(exc)
            clear_request = True

        rv = _request_ctx_stack.pop()
        assert rv is self, 'Popped wrong request context.  (%r instead of %r)' \
            % (rv, self)

        # get rid of circular dependencies at the end of the request
        # so that we don't require the GC to be active.
        if clear_request:
            rv.request.environ['werkzeug.request'] = None

        # Get rid of the app as well if necessary.
        if app_ctx is not None:
            app_ctx.pop(exc)

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

    def __exit__(self, exc_type, exc_value, tb):
        if self.request.environ.get('flask._preserve_context') or \
           (tb is not None and self.app.preserve_context_on_exception):
            self.preserved = True
        else:
            self.pop(exc_value)

从__enter__可以看到,首先会把当前请求推入栈,这个是什么栈?有什么用呢?

  简而言之,这个栈就是flask根据每次请求时候,为每个请求所创建的线程/协程的id所创建的一个自己的threadlocal,在这个threadlocal中所实现的一个栈结构,会把不同的请求隔离开来,每次请求时候把当前请求所在的app,request推入栈,就可以在全局可用了。

 把current_app,request推入栈之后,就根据请求的方法,路径等参数分发请求,找到用@app.route('...')注册的函数,生成response。

但是这里有个问题,就是根据wsgi协议,返回的结果应该是个可迭代的结果,而且这时候start_response还没执行,状态码和其他头部信息还都没有。这个就是make_response的事情了,

    def full_dispatch_request(self):
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
        except Exception, e:
            rv = self.handle_user_exception(e)
        response = self.make_response(rv)
        response = self.process_response(response)
        request_finished.send(self, response=response)
        return response

make_response会把返回结果包装一下。

 def make_response(self, rv):
            if isinstance(rv, basestring):
                rv = self.response_class(rv, headers=headers, status=status)
                headers = status = None
            else:
                rv = self.response_class.force_type(rv, request.environ)

        if status is not None:
            if isinstance(status, basestring):
                rv.status = status
            else:
                rv.status_code = status
        if headers:
            rv.headers.extend(headers)

        return rv

make_response会利用class Response(BaseResponse)把view function返回的结果包装一下,

class Response(ResponseBase):
    default_mimetype = 'text/html'


class BaseResponse(object):
    automatically_set_content_length = True

    def __init__(self, response=None, status=None, headers=None,
         ....

       
    @classmethod
    def force_type(cls, response, environ=None):
         ...

    @classmethod
    def from_app(cls, app, environ, buffered=False):
        ...

    def _get_status_code(self):
        return self._status_code

    def __call__(self, environ, start_response):

        app_iter, status, headers = self.get_wsgi_response(environ)
        start_response(status, headers)
        return app_iter    

当执行Response的实例时候,在__call__里面会执行start_response, 同时返回一个可迭代的对象

这就是flask的执行逻辑

 

 

参考资料:

PEP 333 -- Python Web Server Gateway Interface v1.0

 

posted @ 2017-11-18 15:26  wilderness  阅读(250)  评论(0编辑  收藏  举报