gunicorn是如何和flask通信的
问题
flask是在对werkzeug简单封装的基础上形成的。作为一个轻量级的web框架,
flask容易上手,学习和进阶曲线不陡峭,是Python Web框架入门的首选(自己觉得)。
flask自带了一个HTTP Server用于开发和调试, 如:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello'
if __name__ == '__main__':
app.run()
但是,受制于安全、性能等问题,该Server不能用于生产环境。
生产环境中部署flask等WSGI常用的组合是:
nginx + gunicorn + application(flask应用等)
gunicorn是一款广受欢迎的WSGI服务器,可以用于部署flask应用。
那么, gunicorn收到请求之后,使如何对请求进行解析并发给flask应用的呢?
让我们从代码中一睹究竟。
代码跟踪
“柿子先捡软的捏”,flask代码比较简单,就先看flask的代码。
flask代码
既然是探究”gunicorn是如何和flask应用进行通信”, 那么我们就来看一下flask中
用于创建应用的Flask类。
class Flask(_PackageBoundObject):
...
def dispatch_request(self):
"""Does the request dispatching.
"""
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
# view_args是动态路由参数
return self.view_functions[rule.endpoint](**req.view_args)
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)
response = self.make_response(rv)
response = self.process_response(response)
request_finished.send(self, response=response)
return response
def wsgi_app(self, environ, start_response):
"""The actual WSGI application. This is not implemented in
`__call__` so that middlewares can be applied without losing a
reference to the class. So instead of doing this::
app = MyMiddleware(app)
It's a better idea to do this instead::
app.wsgi_app = MyMiddleware(app.wsgi_app)
Then you still have the original application object around and
can continue to call methods on it.
:param environ: a WSGI environment
:param start_response: a callable accepting a status code,
a list of headers and an optional
exception context to start the response
"""
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
# gunicorn会调用该方法
return self.wsgi_app(environ, start_response)
Flask的代码有1000+行,为了简单起见,我们只关注:
wsgi_app(self, environ, start_response)
full_dispatch_request(self)
dispatch_request(self)
__call__(self, environ, start_response)
这四个方法,这四个方法的用途分别是什么呢?
1) wsgi_app(…)
flask支持wsgi协议,该方法是gunicorn等中间件和flask通信的间接入口(直接入口是__call__(),下面会说)
2) full_dispatch_request(…)
用于分发请求,包括在请求被处理前执行的操作、处理请求、返回响应、处理响应等
3) dispatch_request(…)
分发请求并处理请求
4) __call__(…)
是gunicorn等中间件和flask通信的直接入口, 比如声明了: app = Flask(__name__), 那么gunicorn会通过
"app(...)"的方式来把请求的参数信息发给flask应用。
需要记住此处__call__的用法,下面去跟踪调试gunicorn代码。
gunicorn代码跟踪
如何跟踪gunicorn代码,请参考文章:运行Gunicorn源码
gunicorn的运行轨迹请参考文章: Gunicorn源码阅读与分析(一)——Gunicorn的整体运行轨迹
gunicorn和flask通信的代码
gunicorn是”主-从”模式,工作者进程用于接收用户请求并通知flask应用处理,
所以是工作者进程和flask通信的。
gunicorn支持多种工作者进程,这里以Sync工作者为例,介绍一下和flask的通信过程。
假设以:
# debug_flask是声明app = Flask(__name__)的.py文件
gunicorn debug_flask:app
启动flask应用,那么gunicorn会:
- 声明一个WSGIApplication对象,
1) 该对象包含一个init()方法,在该方法中,有一句代码很关键: self.app_uri = args[0]
此时, self.app_uri的值是debug_flask:app, 因为我们是通过gunicorn debug_flask:app
来启动应用的。也就是说,self.app_uri保存的是flask应用对象的位置。
2) 该对象包含一个load_wsgiapp(self)方法,该方法通过util.import_app(self.app_uri)来加载
flask应用对象。
3) WSGIApplication对象调用run()方法来启动服务器。由于WSGIApplication是Application类的子类,而run()是在Application中定义的,所以执行的是Application的run()方法。 - Application是BaseApplication的子类,在Application的run()方法会去调用BaseApplication的run()方法:
1) BaseApplication中定义了一个wsgi(self)方法, 定义如下:
def wsgi(self):也就是说,它会返回flask应用对象(很重要)。if self.callable is None: self.callable = self.load() return self.callable
2) 在BaseApplication的run()中,会通过Arbiter(self).run()启动gunicorn的主进程。也就是说,主进程对象包含了一个BaseApplication对象,而BaseApplication对象可以通过wsgi()方法来获得原始的flask应用对象。 - 主进程对象的run()方法是一个while循环,监听时间并管理工作者进程的数量。在创建工作者进程时,会把BaseApplication对象传递给工作者进程,并通过:
worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS, elf.app, self.timeout / 2.0, self.cfg, self.log)
初始化工作进程并启动工作者进程。worker.init_process()
- 在Worker的init_process()方法中,通过:工作者进程就获得了原始的Flask应用对象,并保存为self.wsgi。下面以Sync工作者进程为例。
self.wsgi = self.app.wsgi()
- Sync工作者监听socket, 使用handle_request方法来获取请求,并通过:
respiter = self.wsgi(environ, resp.start_response)
来调用Flask的__call__方法来让flask应用来处理请求。这就实现了gunicorn和Flask应用的通信过程