flask源碼分析

WSGI、uWSGI栖秕、uwsgi三者是什么

  • WSGI:python應(yīng)用程序與web服務(wù)器之間進行交互的一種協(xié)議
  • uWSGI:uWSGI是一個Web服務(wù)器春塌,它實現(xiàn)了WSGI協(xié)議、uwsgi簇捍、http等協(xié)議
  • uwsgi:也是一種協(xié)議只壳,規(guī)定了怎么把請求轉(zhuǎn)發(fā)給應(yīng)用程序和返回; ,在此常用于在uWSGI服務(wù)器與其他網(wǎng)絡(luò)服務(wù)器的數(shù)據(jù)通信暑塑。

flask 簡介

flask是一個python語言編寫的web輕量級框架吼句。其 WSGI工具箱采用 Werkzeug ,模板引擎則使用 Jinja2事格。這兩個庫是flask的核心依賴惕艳,werkzeug 負責(zé)核心的邏輯模塊,比如路由驹愚、requestresponse 封裝远搪、WSGI 相關(guān)的函數(shù)等;jinja2 負責(zé)模板的渲染逢捺。

一個簡單的例子

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

調(diào)用app.run后谁鳍,這個web服務(wù)就啟動了,可在瀏覽器訪問之蒸甜。
當(dāng)調(diào)用run方法時棠耕,主要是調(diào)用了Werkzeug 的run_simple,監(jiān)聽指定端口

        from werkzeug.serving import run_simple

        try:
            run_simple(host, port, self, **options)
        finally:
            # reset the first request information if the development server
            # reset normally.  This makes it possible to restart the server
            # without reloader and that stuff from an interactive shell.
            self._got_first_request = False

之后每次請求過來都會調(diào)用對對象app進行調(diào)用柠新,也就是app()

執(zhí)行流程

  1. 每當(dāng)請求過來窍荧,會調(diào)用app對象的的__call__()方法
    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)
  1. 調(diào)用wsgi_app方法,而這個方法如上源碼所示:
  • 關(guān)于ctx = self.request_context(environ)請求上下文恨憎,我們稍后單獨分析蕊退,現(xiàn)在先把整體整個流程梳理下來。
  • response = self.full_dispatch_request()主要通過這個進行消息的分發(fā)處理憔恳,full_dispatch_request源碼如下:
    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """
        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 as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)
  • full_dispatch_request方法中瓤荔,實現(xiàn)了一個請求,請求前钥组、請求過程输硝、請求結(jié)束需要調(diào)用的處理方法,通過方法dispatch_request進行路由,找到對應(yīng)的視圖函數(shù)程梦。
    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`.

        .. versionchanged:: 0.7
           This no longer does the exception handling, this code was
           moved to the new :meth:`full_dispatch_request`.
        """
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        # if we provide automatic options for this URL and the
        # request came with the OPTIONS method, reply automatically
        if getattr(rule, 'provide_automatic_options', False) \
           and req.method == 'OPTIONS':
            return self.make_default_options_response()
        # otherwise dispatch to the handler for that endpoint
        return self.view_functions[rule.endpoint](**req.view_args)

完整流程

當(dāng)一個請求到來時点把,這個請求經(jīng)封裝放到一個線程隔離的棧_request_ctx_stack中橘荠,current_app對象也會放入到另一個線程隔離的棧_app_ctx_stack中,請求結(jié)束后郎逃,會彈出哥童。因此只在請求過程中才能使用request對象和current_app對象。

請求上下文

我們使用flask寫我們的應(yīng)用時褒翰,經(jīng)常會使用到from flask import request贮懈,那么這個request對象是什么呢,每次請求的request對象為什么都不同呢优训,下面我們就來分析一下朵你。這個request定義在flask的globals.py下。

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

線程隔離

我們先了解下線程隔離型宙,在多線程編程中有一種線程隔離技術(shù)local撬呢,也就是在一個對象中伦吠,每個線程有單獨的數(shù)據(jù)妆兑,對自己的數(shù)據(jù)操作不會影響到其他線程,實現(xiàn)也很簡單毛仪,定義一個字典用于保存數(shù)據(jù)搁嗓,字典的key是線程id。而在flask中箱靴,同樣有線程隔離腺逛。參考werkzeug的local.py文件,源碼:

class Local(object):
    __slots__ = ("__storage__", "__ident_func__")

    def __init__(self):
        object.__setattr__(self, "__storage__", {})
        object.__setattr__(self, "__ident_func__", get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    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}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

這個local對象與多線程庫的local不同之處在于衡怀,這個local不光是線程隔離棍矛,也是協(xié)程隔離的。LocalStack是基于Local的抛杨,實現(xiàn)了基于棧的線程隔離够委,這個對象也就是我們前面說的用于存儲當(dāng)前應(yīng)用與當(dāng)前請求的。
因此怖现,每次請求的request的對象都是棧_request_ctx_stack的最上面元素茁帽,并且實現(xiàn)了線程隔離,每次請求的request都不是同一個屈嗤。current_app同理潘拨,屬于應(yīng)用上下文,應(yīng)用上下文除了current_app還有g(shù)饶号。而請求上下文除了request還有session铁追。另外這幾個全局變量都是通過代理模式實現(xiàn)的,看下LocalProxy關(guān)鍵部分源碼:

class LocalProxy(object):
    __slots__ = ("__local", "__dict__", "__name__", "__wrapped__")

    def __init__(self, local, name=None):
        object.__setattr__(self, "_LocalProxy__local", local)
        object.__setattr__(self, "__name__", name)
        if callable(local) and not hasattr(local, "__release_local__"):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, "__wrapped__", local)

    def _get_current_object(self):
        """Return the current object.  This is useful if you want the real
        object behind the proxy at a time for performance reasons or because
        you want to pass the object into a different context.
        """
        if not hasattr(self.__local, "__release_local__"):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError("no object bound to %s" % self.__name__)

    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError("__dict__")

  • 主要看方法_get_current_object茫船,返回當(dāng)前對象琅束,里面幾乎所有的魔術(shù)方法都實現(xiàn)了對對象的代理操作癣蟋。其實這個代理類也實現(xiàn)了request和current_app的動態(tài)獲取,因為這兩個對象都是有上下文的狰闪,請求過程才存在疯搅,每次都可能不同,因此我們對這些對象進行操作時埋泵,每次都會通過_get_current_object重新獲取最新的request和current_app對象幔欧。

鉤子函數(shù)

在看源碼的過程中,發(fā)現(xiàn)一些鉤子函數(shù)

# 服務(wù)器被第一次訪問執(zhí)行的鉤子函數(shù)
@app.before_first_request
def first_request():
    print("Hello World")


# 服務(wù)器被第一次訪問執(zhí)行的鉤子函數(shù)
@app.before_first_request
def first_request():
    print("Hello World")
等等

被裝飾的函數(shù)會在對應(yīng)的時候調(diào)用

信號

Flask框架中的信號基于blinker丽声,其主要就是讓開發(fā)者可是在flask請求過程中定制一些用戶行為礁蔗。
內(nèi)置信號:

request_started = _signals.signal('request-started')                # 請求到來前執(zhí)行
request_finished = _signals.signal('request-finished')              # 請求結(jié)束后執(zhí)行
  
before_render_template = _signals.signal('before-render-template')  # 模板渲染前執(zhí)行
template_rendered = _signals.signal('template-rendered')            # 模板渲染后執(zhí)行
  
got_request_exception = _signals.signal('got-request-exception')    # 請求執(zhí)行出現(xiàn)異常時執(zhí)行
  
request_tearing_down = _signals.signal('request-tearing-down')      # 請求執(zhí)行完畢后自動執(zhí)行(無論成功與否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 請求上下文執(zhí)行完畢后自動執(zhí)行(無論成功與否)
  
appcontext_pushed = _signals.signal('appcontext-pushed')            # 請求上下文push時執(zhí)行
appcontext_popped = _signals.signal('appcontext-popped')            # 請求上下文pop時執(zhí)行
message_flashed = _signals.signal('message-flashed')                # 調(diào)用flask在其中添加數(shù)據(jù)時,自動觸發(fā)

也可以自定義信號

# 自定義信號
xxxxx = _signals.signal('xxxxx')
  
def func(sender, *args, **kwargs):
    print(sender)
  
# 自定義信號中注冊函數(shù)
xxxxx.connect(func)
  
@app.route("/x")
def index():
    # 觸發(fā)信號
    xxxxx.send('1', k1='v1')
    return '123'
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雁社,一起剝皮案震驚了整個濱河市浴井,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌霉撵,老刑警劉巖磺浙,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異徒坡,居然都是意外死亡撕氧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門喇完,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伦泥,“玉大人,你說我怎么就攤上這事锦溪〔桓” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵刻诊,是天一觀的道長防楷。 經(jīng)常有香客問我,道長坏逢,這世上最難降的妖魔是什么域帐? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮是整,結(jié)果婚禮上肖揣,老公的妹妹穿的比我還像新娘。我一直安慰自己浮入,他們只是感情好龙优,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著事秀,像睡著了一般彤断。 火紅的嫁衣襯著肌膚如雪野舶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天宰衙,我揣著相機與錄音平道,去河邊找鬼。 笑死供炼,一個胖子當(dāng)著我的面吹牛一屋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播袋哼,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼冀墨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涛贯?” 一聲冷哼從身側(cè)響起诽嘉,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弟翘,沒想到半個月后虫腋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡衅胀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年岔乔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滚躯。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嘿歌,靈堂內(nèi)的尸體忽然破棺而出掸掏,到底是詐尸還是另有隱情,我是刑警寧澤宙帝,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布丧凤,位于F島的核電站,受9級特大地震影響步脓,放射性物質(zhì)發(fā)生泄漏愿待。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一靴患、第九天 我趴在偏房一處隱蔽的房頂上張望仍侥。 院中可真熱鬧,春花似錦鸳君、人聲如沸农渊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砸紊。三九已至传于,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間醉顽,已是汗流浹背沼溜。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留游添,地道東北人盛末。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像否淤,于是被迫代替她去往敵國和親悄但。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容