WSGI、uWSGI、uwsgi三者是什么
- WSGI:python应用程序与web服务器之间进行交互的一种协议
- uWSGI:uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议
- uwsgi:也是一种协议,规定了怎么把请求转发给应用程序和返回; ,在此常用于在uWSGI服务器与其他网络服务器的数据通信。
flask 简介
flask是一个python语言编写的web轻量级框架。其 WSGI工具箱采用 Werkzeug ,模板引擎则使用 Jinja2。这两个库是flask的核心依赖,werkzeug 负责核心的逻辑模块,比如路由、request
和 response
封装、WSGI 相关的函数等;jinja2 负责模板的渲染。
一个简单的例子
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run()
调用app.run后,这个web服务就启动了,可在浏览器访问之。
当调用run方法时,主要是调用了Werkzeug 的run_simple,监听指定端口
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
之后每次请求过来都会调用对对象app进行调用,也就是app()
执行流程
- 每当请求过来,会调用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)
- 调用
wsgi_app
方法,而这个方法如上源码所示:
- 关于
ctx = self.request_context(environ)
请求上下文,我们稍后单独分析,现在先把整体整个流程梳理下来。 -
response = self.full_dispatch_request()
主要通过这个进行消息的分发处理,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
方法中,实现了一个请求,请求前、请求过程、请求结束需要调用的处理方法,通过方法dispatch_request
进行路由,找到对应的视图函数。
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)
完整流程
当一个请求到来时,这个请求经封装放到一个线程隔离的栈_request_ctx_stack
中,current_app对象也会放入到另一个线程隔离的栈_app_ctx_stack
中,请求结束后,会弹出。因此只在请求过程中才能使用request对象和current_app对象。
请求上下文
我们使用flask写我们的应用时,经常会使用到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'))
线程隔离
我们先了解下线程隔离,在多线程编程中有一种线程隔离技术local
,也就是在一个对象中,每个线程有单独的数据,对自己的数据操作不会影响到其他线程,实现也很简单,定义一个字典用于保存数据,字典的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不光是线程隔离,也是协程隔离的。LocalStack
是基于Local
的,实现了基于栈的线程隔离,这个对象也就是我们前面说的用于存储当前应用与当前请求的。
因此,每次请求的request的对象都是栈_request_ctx_stack
的最上面元素,并且实现了线程隔离,每次请求的request都不是同一个。current_app同理,属于应用上下文,应用上下文除了current_app还有g。而请求上下文除了request还有session。另外这几个全局变量都是通过代理模式实现的,看下LocalProxy
关键部分源码:
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
,返回当前对象,里面几乎所有的魔术方法都实现了对对象的代理操作。其实这个代理类也实现了request和current_app的动态获取,因为这两个对象都是有上下文的,请求过程才存在,每次都可能不同,因此我们对这些对象进行操作时,每次都会通过_get_current_object重新获取最新的request和current_app对象
。
钩子函数
在看源码的过程中,发现一些钩子函数
# 服务器被第一次访问执行的钩子函数
@app.before_first_request
def first_request():
print("Hello World")
# 服务器被第一次访问执行的钩子函数
@app.before_first_request
def first_request():
print("Hello World")
等等
被装饰的函数会在对应的时候调用
信号
Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为。
内置信号:
request_started = _signals.signal('request-started') # 请求到来前执行
request_finished = _signals.signal('request-finished') # 请求结束后执行
before_render_template = _signals.signal('before-render-template') # 模板渲染前执行
template_rendered = _signals.signal('template-rendered') # 模板渲染后执行
got_request_exception = _signals.signal('got-request-exception') # 请求执行出现异常时执行
request_tearing_down = _signals.signal('request-tearing-down') # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 请求上下文执行完毕后自动执行(无论成功与否)
appcontext_pushed = _signals.signal('appcontext-pushed') # 请求上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped') # 请求上下文pop时执行
message_flashed = _signals.signal('message-flashed') # 调用flask在其中添加数据时,自动触发
也可以自定义信号
# 自定义信号
xxxxx = _signals.signal('xxxxx')
def func(sender, *args, **kwargs):
print(sender)
# 自定义信号中注册函数
xxxxx.connect(func)
@app.route("/x")
def index():
# 触发信号
xxxxx.send('1', k1='v1')
return '123'