flask源碼剖析

    這段時間想從新寫個本身的博客系統,又正好在看一些框架源碼,而後就想要不順便寫個小框架吧,既然想寫框架,要再也不順便寫個orm吧,再寫個小的異步Server吧。。事實證實飯要一口一口吃html

    先梳理一下flask工做的整個流程吧。python

    首先flask是符合wsgi協議的,那麼也就是說,flask要實現一個能夠callable的object給web server。web

    那麼什麼是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

  那麼flask 給web server的callable object是什麼?session

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是什麼東西?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__能夠看到,首先會把當前請求推入棧,這個是什麼棧?有什麼用呢?ide

  簡而言之,這個棧就是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

相關文章
相關標籤/搜索