Flask源碼剖析詳解


1. 前言

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

 

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

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

本文將首先回答這些問題,然后再分析flask源碼。

2. 知識准備

2.1 WSGI

下面這張圖來自這里,通過這張圖,讀者對web框架所處的位置和WSGI協議能夠有一個感性的認識。

WSGI

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的作用。

也就是說,在python的世界里,通過WSGI約定了web服務器怎么調用web應用程序的代碼,web應用程序需要符合什么樣的規范,只要web應用程序和web服務器都遵守WSGI 協議,那么,web應用程序和web服務器就可以隨意的組合。這也就是WSGI存在的原因。

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的實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM