Flask源碼剖析詳解

1. 前言

本文將基於flask 0.1版本(git checkout 8605cc3)來分析flask的實現,試圖理清flask中的一些概念,加深讀者對flask的理解,提升對flask的認識。從而,在使用flask過程當中,可以減小困惑,成竹在胸,遇bug而不驚。html

 

在試圖理解flask的設計以前,你知道應該知道如下幾個概念:python

  • flask(web框架)是什麼
  • WSGI是什麼
  • jinja2是什麼
  • Werkzeug是什麼

本文將首先回答這些問題,而後再分析flask源碼。git

2. 知識準備

2.1 WSGI

下面這張圖來自這裏,經過這張圖,讀者對web框架所處的位置和WSGI協議可以有一個感性的認識。github

WSGIweb

wikipedia上對WSGI的解釋就比較通俗易懂。爲了更好的理解WSGI,咱們來看一個例子:數據庫

from eventlet import wsgi import eventlet def hello_world(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return ['Hello, World!\r\n'] wsgi.server(eventlet.listen(('', 8090)), hello_world) 

咱們定義了一個hello_world函數,這個函數接受兩個參數。分別是environ和start_response,咱們將這個hello_world傳遞給eventlet.wsgi.server之後, eventlet.wsgi.server在調用hello_world時,會自動傳入environ和start_response這兩個參數,並接受hello_world的返回值。而這,就是WSGI的做用。django

也就是說,在python的世界裏,經過WSGI約定了web服務器怎麼調用web應用程序的代碼,web應用程序須要符合什麼樣的規範,只要web應用程序和web服務器都遵照WSGI 協議,那麼,web應用程序和web服務器就能夠隨意的組合。這也就是WSGI存在的緣由。flask

WSGI是一種協議,這裏,須要注意兩個相近的概念:設計模式

  • uwsgi同WSGI同樣是一種協議
  • 而uWSGI是實現了uwsgi和WSGI兩種協議的web服務器

2.2 jinja2與Werkzeug

flask依賴jinja2和Werkzeug,爲了徹底理解flask,咱們還須要簡單介紹一下這兩個依賴。瀏覽器

jinja2

Jinja2是一個功能齊全的模板引擎。它有完整的unicode支持,一個可選 的集成沙箱執行環境,被普遍使用。

jinja2的一個簡單示例以下:

>>> from jinja2 import Template >>> template = Template('Hello !') >>> template.render(name='John Doe') u'Hello John Doe!' 

Werkzeug

Werkzeug是一個WSGI工具包,它能夠做爲web框架的底層庫。

我發現Werkzeug的官方文檔介紹特別好,下面這一段摘錄自這裏

Werkzeug是一個WSGI工具包。WSGI是一個web應用和服務器通訊的協議,web應用能夠經過WSGI一塊兒工做。一個基本的」Hello World」WSGI應用看起來是這樣的:

def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return ['Hello World!'] 

上面這小段代碼就是WSGI協議的約定,它有一個可調用的start_response 。environ包含了全部進來的信息。 start_response用來代表已經收到一個響應。 經過Werkzeug,咱們能夠沒必要直接處理請求或者響應這些底層的東西,它已經爲咱們封裝好了這些。

請求數據須要environ對象,Werkzeug容許咱們以一個輕鬆的方式訪問數據。響應對象是一個WSGI應用,提供了更好的方法來建立響應。以下所示:

from werkzeug.wrappers import Response def application(environ, start_response): response = Response('Hello World!', mimetype='text/plain') return response(environ, start_response) 

2.3 如何理解wsgi, Werkzeug, flask之間的關係

Flask是一個基於Python開發而且依賴jinja2模板和Werkzeug WSGI服務的一個微型框架,對於Werkzeug,它只是工具包,其用於接收http請求並對請求進行預處理,而後觸發Flask框架,開發人員基於Flask框架提供的功能對請求進行相應的處理,並返回給用戶,若是要返回給用戶複雜的內容時,須要藉助jinja2模板來實現對模板的處理。將模板和數據進行渲染,將渲染後的字符串返回給用戶瀏覽器。

2.4 Flask是什麼,不是什麼

Flask永遠不會包含數據庫層,也不會有表單庫或是這個方面的其它東西。Flask自己只是Werkzeug和Jinja2的之間的橋樑,前者實現一個合適的WSGI應用,後者處理模板。固然,Flask也綁定了一些通用的標準庫包,好比logging。除此以外其它全部一切都交給擴展來實現。

爲何呢?由於人們有不一樣的偏好和需求,Flask不可能把全部的需求都囊括在覈內心。大多數web應用會須要一個模板引擎。然而不是每一個應用都須要一個SQL數據庫的。

Flask 的理念是爲全部應用創建一個良好的基礎,其他的一切都取決於你本身或者 擴展。

3. Flask源碼分析

Flask的使用很是簡單,官網的例子以下:

from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run() 

每當咱們須要建立一個flask應用時,咱們都會建立一個Flask對象:

app = Flask(__name__) 

下面看一下Flask對象的__init__方法,若是不考慮jinja2相關,核心成員就下面幾個:

class Flask: def __init__(self, package_name): 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() 

咱們把目光彙集到後面幾個成員,view_functions中保存了視圖函數(處理用戶請求的函數,如上面的hello()),error_handlers中保存了錯誤處理函數,before_request_funcs和after_request_funcs保存了請求的預處理函數和後處理函數。

self.url_map用以保存URI到視圖函數的映射,即保存app.route()這個裝飾器的信息,以下所示:

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

上面說到的是初始化部分,下面看一下執行部分,當咱們執行app.run()時,調用堆棧以下:

app.run() run_simple(host, port, self, **options) __call__(self, environ, start_response) wsgi_app(self, environ, start_response) 

wsgi_app是flask核心:

def wsgi_app(self, environ, start_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) 

能夠看到,wsgi_app這個函數的做用就是先調用全部的預處理函數,而後分發請求,再調用全部後處理函數,最後返回response。

看一下dispatch_request函數的實現,由於,這裏有flask的錯誤處理邏輯:

def dispatch_request(self): 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) 

若是出現錯誤,則根據相應的error code,調用不一樣的錯誤處理函數。

上面這段簡單的源碼分析,就已經將Flask幾個核心變量和核心函數串聯起來了。其實,咱們這裏扣出來的幾段代碼,也就是Flask的核心代碼。畢竟,Flask的0.1版本包含大量註釋之後,也才六百行代碼。

4. flask的魔法

若是讀者打開flask.py文件,將看到我前面的源碼分析幾乎已經覆蓋了全部重要的代碼。可是,細心的讀者會看到,在Flask.py文件的末尾處,有如下幾行代碼:

# context locals _request_ctx_stack = LocalStack() current_app = LocalProxy(lambda: _request_ctx_stack.top.app) request = LocalProxy(lambda: _request_ctx_stack.top.request) session = LocalProxy(lambda: _request_ctx_stack.top.session) g = LocalProxy(lambda: _request_ctx_stack.top.g) 

這是咱們得以方便的使用flask開發的魔法,也是flask源碼中的難點。在分析以前,咱們先看一下它們的做用。

在flask的開發過程當中,咱們能夠經過以下方式訪問url中的參數:

from flask import request @app.route('/') def hello(): name = request.args.get('name', None) 

看起來request像是一個全局變量,那麼,一個全局變量爲何能夠在一個多線程環境中隨意使用呢,下面就隨我來一探究竟吧!

先看一下全局變量_request_ctx_stack的定義:

_request_ctx_stack = LocalStack() 

正如它LocalStack()的名字所暗示的那樣,_request_ctx_stack是一個棧。顯然,一個棧確定會有push 、pop和top函數,以下所示:

class LocalStack(object): def __init__(self): self._local = Local() def push(self, obj): rv = getattr(self._local, 'stack', None) if rv is None: self._local.stack = rv = [] rv.append(obj) return rv def pop(self): stack = getattr(self._local, 'stack', None) if stack is None: return None elif len(stack) == 1: release_local(self._local) return stack[-1] else: return stack.pop() 

按照咱們的理解,要實現一個棧,那麼LocalStack類應該有一個成員變量,是一個list,而後經過 這個list來保存棧的元素。然而,LocalStack並無一個類型是list的成員變量, LocalStack僅有一個成員變量self._local = Local()。

順藤摸瓜,咱們來到了Werkzeug的源碼中,到達了Local類的定義處:

class Local(object): def __init__(self): object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident) def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value} 

須要注意的是,Local類有兩個成員變量,分別是__storage__和__ident_func__,其中,前者 是一個字典,後者是一個函數。這個函數的含義是,獲取當前線程的id(或協程的id)。

此外,咱們注意到,Local類自定義了__getattr__和__setattr__這兩個方法,也就是說,咱們在操做self.local.stack時, 會調用__setattr__和__getattr__方法。

_request_ctx_stack = LocalStack() _request_ctx_stack.push(item) # 注意,這裏賦值的時候,會調用__setattr__方法 self._local.stack = rv = [] ==> __setattr__(self, name, value) 

而__setattr的定義以下:

def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value} 

在__setattr__中,經過__ident_func__獲取到了一個key,而後進行賦值。自此,咱們能夠知道, LocalStack是一個全局字典,或者說是一個名字空間。這個名字空間是全部線程共享的。 當咱們訪問字典中的某個元素的時候,會經過__getattr__進行訪問,__getattr__先經過線程id, 找當前這個線程的數據,而後進行訪問。

字段的內容以下:

{'thread_id':{'stack':[]}} {'thread_id1':{'stack':[_RequestContext()]}, 'thread_id2':{'stack':[_RequestContext()]}} 

最後,咱們來看一下其餘幾個全局變量:

current_app = LocalProxy(lambda: _request_ctx_stack.top.app) request = LocalProxy(lambda: _request_ctx_stack.top.request) session = LocalProxy(lambda: _request_ctx_stack.top.session) g = LocalProxy(lambda: _request_ctx_stack.top.g) 

讀者能夠自行看一下LocalProxy的源碼,LocalProxy僅僅是一個代理(能夠想象設計模式中的代理模式)。

經過LocalStack和LocalProxy這樣的Python魔法,每一個線程訪問當前請求中的數據(request, session)時, 都好像都在訪問一個全局變量,可是,互相之間又互不影響。這就是Flask爲咱們提供的便利,也是咱們 選擇Flask的理由!

5. 總結

在這篇文章中,咱們簡單地介紹了WSGI, jinja2和Werkzeug,詳細介紹了Flask在web開發中所處的位置和發揮的做用。最後,深刻Flask的源碼,瞭解了Flask的實現。

相關文章
相關標籤/搜索