一、簡介
flask的藍圖可以實現url的分發,當有多個app時也可以利用app進行url分發,這里介紹下使用方式和內部原理以及棧的應用。
二、多app使用
使用示例
from werkzeug.wsgi import DispatcherMiddleware from werkzeug.serving import run_simple from flask import Flask app01 = Flask('app01') app02 = Flask('app02') @app01.route('/index') def index(): return "app01" @app02.route('/index') def index2(): return "app02" app = DispatcherMiddleware(app01, { '/app01': app01, '/app02': app02, }) #默認使用app01的路由,也就是訪問 http://127.0.0.1:5000/index 返回app01 #當以app01開頭時候使用app01的路由,也就是http://127.0.0.1:5000/app01/index 返回app01 #當以app02開頭時候使用app02的路由,也就是http://127.0.0.1:5000/app02/index 返回app02 if __name__ == "__main__": run_simple('127.0.0.1', 5000, app)
實現原理
多app使用借助於DispatcherMiddleware,讓我們看看其源碼類的定義:
class DispatcherMiddleware(object): """Allows one to mount middlewares or applications in a WSGI application. This is useful if you want to combine multiple WSGI applications:: app = DispatcherMiddleware(app, { '/app2': app2, '/app3': app3 }) """ def __init__(self, app, mounts=None): self.app = app self.mounts = mounts or {} def __call__(self, environ, start_response): script = environ.get('PATH_INFO', '') path_info = '' while '/' in script: if script in self.mounts: app = self.mounts[script] break script, last_item = script.rsplit('/', 1) path_info = '/%s%s' % (last_item, path_info) else: app = self.mounts.get(script, self.app) original_script_name = environ.get('SCRIPT_NAME', '') environ['SCRIPT_NAME'] = original_script_name + script environ['PATH_INFO'] = path_info return app(environ, start_response)
def __call__(self, environ, start_response): script = environ.get('PATH_INFO', '') path_info = '' while '/' in script: if script in self.mounts: app = self.mounts[script] break script, last_item = script.rsplit('/', 1) path_info = '/%s%s' % (last_item, path_info) else: app = self.mounts.get(script, self.app) original_script_name = environ.get('SCRIPT_NAME', '') environ['SCRIPT_NAME'] = original_script_name + script environ['PATH_INFO'] = path_info return app(environ, start_response)
當script = /index是,while條件成立,同時對判斷 /index是否在self.mounts,顯然此時不在,分割/index,修改script為空,此時while不成立,執行app = self.mounts.get(script, self.app),返回self.app也就是app01,接着運行app01(),這也就是和在上下文中流程一樣了。究其本質,就是通過url匹配出是哪個app,在運行該app的__call__方法。
三、棧是使用
flask中的請求數據存放實際利用列表構造的棧來存儲的,每次pop都從最后pop出棧。當我們在進行測試或者寫flask離線腳本時候可能會使用到上下文嵌套,例如:
from flask import Flask, current_app, _app_ctx_stack app1 = Flask('app01') app1.debug = False app2 = Flask('app02') app2.debug = True with app1.app_context(): print(_app_ctx_stack._local.__storage__) #{<greenlet.greenlet object at 0x10dc79e88>: {'stack': [<flask.ctx.AppContext object at 0x10dda7b00>]}} print(current_app.config['DEBUG']) # False with app2.app_context(): print(_app_ctx_stack._local.__storage__) #{<greenlet.greenlet object at 0x10dc79e88>: {'stack': [<flask.ctx.AppContext object at 0x10dda7b00>, <flask.ctx.AppContext object at 0x10dda7c18>]}} print(current_app.config['DEBUG']) # True print(current_app.config['DEBUG']) # False
在以上示例中在app01的上下文中嵌套了app02的上下文,所以在棧中會有兩個app_ctx,但是在各自取上下文的時候都不會沖突,因為app02的上下文在最后,也就是第二個with中top是app02的app_ctx。
四、關於with
class Foo(object): def __init__(self,name): self.name=name def __enter__(self): print('run __enter__') return self.name def __exit__(self, exc_type, exc_val, exc_tb): print('run __exit__') with Foo('wd') as myname: print("vars:",myname) 結果: run __enter__ vars: wd run __exit__
可能你會注意在到,在以上的demo中__exit__方法中多了三個參數,即exc_type、exc_val、exc_tb這是對應着在with語句中代碼出現異常時也會執行__exit__並接受異常,分別對應着:異常類型、異常值、以及異常的traceback。示例:
class Foo(object): def __init__(self,name): self.name=name def __enter__(self): print('run __enter__') return self.name def __exit__(self, exc_type, exc_val, exc_tb): print('run __exit__') print('exc_type:',exc_type) print('exc_val:',exc_val) print('exc_tb:',exc_tb) with Foo('wd') as myname: print("vars:", myname) a=[] v=a[1] print(v) 結果: run __enter__ vars: wd run __exit__ exc_type: <class 'IndexError'> exc_val: list index out of range exc_tb: <traceback object at 0x10ec98508> Traceback (most recent call last): File "dbapi.py", line 21, in <module> v=a[1] IndexError: list index out of range
最后我們回過頭來看看app_context()對象中的__enter__方法和__exit__方法:
def __enter__(self): self.push() return self def __exit__(self, exc_type, exc_value, tb): self.pop(exc_value) if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None: reraise(exc_type, exc_value, tb)
實際上非常簡單,執行__enter__時候調用push壓棧,執行__exit__時pop出棧,這樣就使得每個with里面都是當前app的上下文,而不會沖突。