flask运行流程原理
作者:向前的步伐 / 发表: 2020年11月7日 19:21 / 更新: 2020年11月9日 20:21 / flask / 阅读量:740
一、WSGI网关接口
WSGI的主要作用是在Web服务器和Web应用程序承担“翻译官”的角色。用于Web服务器和Web应用解析对方给与的信息。主要的作用如下:
- Web服务器的责任在于监听和接收请求。在处理请求的时候调用WSGI提供的标准化接口,将请求的信息转给WSGI;
- WSGI的责任在于“中转”请求和响应信息。WSGI接收到Web服务器提供的请求信息后可以做一些处理,之后通过标准化接口调用Web应用,并将请求信息传递给Web应用。同时,WSGI还将会处理Web应用返回的响应信息,并通过服务器返回给客户端;
- Web应用的责任在于接收请求信息,并且生成响应。
Flask框架也是基于WSGI网关进行的开发,所以这个应用也可以叫WSGI应用,依赖于底层库werkzeug。
服务器、Web应用的设计应该遵循网关接口的一些规范。对于WSGI网关,要求Web应用实现一个函数或者一个可调用对象webapp(environ, start_response)。服务器或网关中要定义start_response函数并且调用Web应用。
二、Werkzeug库
Werkzeug是一个Python写成的WSGI工具集。它遵循WSGI规范,对服务器和Web应用之间的“中间层”进行了开发,衍生出一系列非常有用的Web服务底层模块。关于Werkzeug功能的最简单的一个例子如下:
from werkzeug.wrappers import Request, Response
def application(environ, start_response):
request = Request(environ)
response = Response("Hello %s!" % request.args.get('name', 'World!'))
return response(environ, start_response)
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
运行上面的例子,当在浏览器输入http://127.0.0.1:4000/就会向本地的服务器发出一个请求。在请求的过程中,werkzeug主要做了下面几件事情:
- 根据服务器和WSGI服务器产生的environ环境信息,封装一个Request实例,这个实例包含请求的所有信息;
- Web应用根据封装的Request实例信息,产生一个Response实例(上述例子只是输出一段字符串)。这个Response实例是一个可调用的WSGI应用;
- 上一步骤产生的可调用应用对象response调用response(environ, start_response)生成响应信息并发回客户端。调用函数是由WSGI规范规定的。
以上过程很好地将服务器和web应用分离开来:服务器不用考虑请求信息怎么被解析给web应用,以及后续怎么和web应用通信;web应用也不用考虑怎么将响应信息返回给服务器。服务器要做的只是提供web应用所需的请求信息,web应用提供的也只是响应信息,中间的处理过程werkzeug可以帮助完成。
三、flask应用的要点或特性
使用app = Flask(name),可以实例化一个Flask应用。运行起来后的Flask应用有一些要点或特性:
1,对于请求和响应的处理,Flask使用werkzeug库中的Request类和Response类。
Werkzeug库中的wrappers模块主要对request和response进行封装。request包含了客户端发往服务器的所有请求信息,response包含了web应用返回给客户端的所有信息。wrappers模块对请求和响应的封装简化了客户端、服务器和web应用通信的流程。
2,对于URL模式的处理,Flask应用使用werkzeug库中的Map类和Rule类,每一个URL模式对应一个Rule实例,这些Rule实例最终会作为参数传递给Map类构造包含所有URL模式的一个“地图”。
Werkzeug库的routing模块的主要功能在于URL解析。对于WSGI应用来讲,不同的URL对应不同的视图函数,routing模块则会对请求信息的URL进行解析并匹配,触发URL对应的视图函数,以此生成一个响应信息。routing模块的解析和匹配功能主要体现在三个类上:Rule、Map和MapAdapter。
3,当实例化一个Flask应用app之后,Flask采取装饰器的方法,将URL规则和视图函数结合在一起写,其中主要的函数是route,如:
@app.route('/')
def index():
pass
这样写视图函数,会将'/'这条URL规则和视图函数index()联系起来,并且会形成一个Rule实例,再添加进Map实例中去。当访问'/'时,会执行index()。
4,实例化Flask应用时,会创造一个Jinja环境,这是Flask自带的一种模板引擎。
5,实例化的Flask应用是一个可调用对象要遵循WSGI规范,就要实现一个函数或者一个可调用对象webapp(environ, start_response),以方便服务器或网关调用。Flask应用通过__call__(environ, start_response)方法可以让它被服务器或网关调用。
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`"""
return self.wsgi_app(environ, start_response)
四、flask应用运行和使用
当实例化完一个Flask应用之后,可以通过调用app.run()方法运行这个应用。
Flask应用的run()方法会调用werkzeug.serving模块中的run_simple方法。这个方法会创建一个本地的测试服务器,并且在这个服务器中运行Flask应用。
def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
# 省略其他代码
_host = "127.0.0.1"
_port = 5000
server_name = self.config.get("SERVER_NAME")
sn_host, sn_port = None, None
if server_name:
sn_host, _, sn_port = server_name.partition(":")
host = host or sn_host or _host
# pick the first value that's not None (0 is allowed)
port = int(next((p for p in (port, sn_port) if p is not None), _port))
options.setdefault("use_reloader", self.debug)
options.setdefault("use_debugger", self.debug)
options.setdefault("threaded", True)
cli.show_server_banner(self.env, self.debug, self.name, False)
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
当服务器开始调用Flask应用后,便会触发Flask应用的__call__(environ, start_response)方法。其中environ由服务器产生,start_response在服务器中定义。
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)
从上面的代码看,Flask应用被调用时会执行wsgi_app(environ, start_response)方法。可以看出,wsgi_app是真正被调用的WSGI应用,wsgi_app可以被一些“中间件”装饰,以便先行处理一些操作。
当Flask应用真正处理请求时,wsgi_app(environ, start_response)被调用,这个方法会构造一个上下文对象,所有的请求处理过程,都会在这个上下文对象中进行。这个上下文对象是RequestContext类的实例。
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: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
-------------------------------------
class RequestContext(object):
def __init__(self, app, environ, request=None, session=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = None
try:
self.url_adapter = app.create_url_adapter(self.request)
except HTTPException as e:
self.request.routing_exception = e
self.flashes = None
self.session = session
self._implicit_app_ctx_stack = []
self.preserved = False
self._preserved_exc = None
self._after_request_functions = []
def push(self):
"""Binds the request context to the current context."""
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
app_ctx = self.app.app_context()
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)
if hasattr(sys, "exc_clear"):
sys.exc_clear()
_request_ctx_stack.push(self)
if self.session is None:
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
self.session = session_interface.make_null_session(self.app)
if self.url_adapter is not None:
self.match_request()
根据RequestContext对象的构造方法,可以发现一些和flask应用相关的属性:
- app:上下文对象的app属性是当前的Flask应用;
- url_adapter:上下文对象的url_adapter属性是通过Flask应用中的Map实例构造成一个MapAdapter实例,主要功能是将请求中的URL和Map实例中的URL规则进行匹配;
- request:上下文对象的request属性是通过Request类构造的实例,反映请求的信息;
- session:上下文对象的session属性存储请求的会话信息;
- flashes:消息闪现的信息。
处理请求的过程定义在wsgi_app方法中,主要实现看full_dispatch_request方法:
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: # noqa: B001
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 full_dispatch_request(self):
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)
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv)
try:
response = self.process_response(response)
request_finished.send(self, response=response)
except Exception:
if not from_error_handler:
raise
self.logger.exception(
"Request finalizing failed with an error while handling an error"
)
return response
主要分为下面的步骤:
-
1,在请求正式被处理之前的一些操作,调用preprocess_request()方法,例如打开一个数据库连接等操作;
-
2,正式处理请求。这个过程调用dispatch_request()方法,这个方法会根据URL匹配的情况调用相关的视图函数;
-
3,将从视图函数返回的值转变为一个Response对象;
-
4,在响应被发送到WSGI服务器之前,调用process_response(response)做一些后续处理过程;
-
5,调用response(environ, start_response)方法将响应发送回WSGI服务器。