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é)核心的邏輯模塊,比如路由驹愚、request
和 response
封裝远搪、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í)行流程
- 每當(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)
- 調(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'