- 熟悉Django框架的应该都知道,Django为我们提供了非常良好的路由配置环境,只需在urls.py文件里添加url即可。
- 但在Flask框架并没有给我们提供这样一个文件,我们需要自己来写路由,当然,Flask的路由是超级简单的,只需给视图函数加一个装饰器即可。
- 接下来我们就启动一个最简单的Flask项目,直接上代码。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'OK'
if __name__ == '__main__':
app.run()
app.run()
方法的背后都执行了什么?
run
方法中调用了werkzeug.serving模块的run_simple
方法
run_simple
中首先执行这部分代码:
if use_reloader:
# If we're not running already in the subprocess that is the
# reloader we want to open up a socket early to make sure the
# port is actually available.
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
if port == 0 and not can_open_by_fd:
raise ValueError('Cannot bind to a random port with enabled '
'reloader if the Python interpreter does '
'not support socket opening by fd.')
# Create and destroy a socket so that any exceptions are
# raised before we spawn a separate Python interpreter and
# lose this ability.
address_family = select_ip_version(hostname, port)
s = socket.socket(address_family, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(get_sockaddr(hostname, port, address_family))
if hasattr(s, 'set_inheritable'):
s.set_inheritable(True)
# If we can open the socket by file descriptor, then we can just
# reuse this one and our socket will survive the restarts.
if can_open_by_fd:
os.environ['WERKZEUG_SERVER_FD'] = str(s.fileno())
s.listen(LISTEN_QUEUE)
log_startup(s)
else:
s.close()
# Do not use relative imports, otherwise "python -m werkzeug.serving"
# breaks.
from werkzeug._reloader import run_with_reloader
run_with_reloader(inner, extra_files, reloader_interval,
reloader_type)
else:
inner() # 通常会执行这个方法
再来看inner
方法,通过make_server
方法来创建WSGIServer的实例srv,然后调用server_forever
方法:
def inner():
try:
fd = int(os.environ['WERKZEUG_SERVER_FD'])
except (LookupError, ValueError):
fd = None
srv = make_server(hostname, port, application, threaded,
processes, request_handler,
passthrough_errors, ssl_context,
fd=fd)
if fd is None:
log_startup(srv.socket)
srv.serve_forever()
那么当请求进来时,代码流程是什么呢?
- 首先,当用户在浏览器输入url回车时,会调用
app
实例的__call__
方法:
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."""
# 请求每次进来时,先执行__call__方法,项目启动时不会执行
return self.wsgi_app(environ, start_response)
-
__call__
方法中又执行了wsgi_app
方法:
def wsgi_app(self, environ, start_response):
"""The actual WSGI application. This is not implemented in
:meth:`__call__` so that middlewares can be applied without
losing a reference to the app object. 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.
.. versionchanged:: 0.7
Teardown events for the request and app contexts are called
even if an unhandled error occurs. Other events may not be
called depending on when an error occurs during dispatch.
See :ref:`callbacks-and-errors`.
: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)
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)
- 在
full_dispatch_request
方法中又执行了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:
# 此方法为具体的路由分发逻辑,根据url执行对应的视图函数,rv为视图函数执行结果的返回值
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
# 最后执行finalize_request方法并返回
return self.finalize_request(rv)
- 切换到
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`.
"""
# 返回一个经过数据封装的Request对象
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
# 得到<class 'werkzeug.routing.Rule'>的对象rule
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_functions中获取endpoint对应的视图函数,加括号执行,并把相应的参数传进去
return self.view_functions[rule.endpoint](**req.view_args)
- 再来看最后执行的
finalize_request
方法,将返回数据进行封装:
def finalize_request(self, rv, from_error_handler=False):
"""Given the return value from a view function this finalizes
the request by converting it into a response and invoking the
postprocessing functions. This is invoked for both normal
request dispatching as well as error handlers.
Because this means that it might be called as a result of a
failure a special safe mode is available which can be enabled
with the `from_error_handler` flag. If enabled, failures in
response processing will be logged and otherwise ignored.
:internal:
"""
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
根据装饰器的特性,执行视图函数之前首先走装饰器
- 注意:我们在代码中添加的装饰器是有参数的
- 当访问根路径时,到
@app.route('/')
时,先执行app.route('/')
方法
代码中的装饰器便是一个路由,那么它是怎样一个原理呢?进入Flask路由源码:
- route方法返回一个函数体,
@app.route('/')
等价于@decorator
此时index
函数等价于:
@decorator
def index():
return 'OK'
- 这样一写就很清晰了,执行
index
前需执行decorator
- 那么问题来了,Flask为什么不直接这样写呢,还要多写一个route方法?想必大家应该都看出来了,这里用到了python的闭包,目的就是把参数传递给
decorator
。
接下来查看
decorator
方法
- 第一步是得到
endpoint
参数的值,这就不多说了 - 第二步执行了
add_url_rule
方法,并把路由,视图函数等当作参数传递进去 - 最后返回视图函数
进入
add_url_rule
- 第一步:获取endpoint和methods
当我们没有设置endpoint时,会执行_endpoint_from_view_func
方法,其内部返回值是: - 继续往下走:
到这里,装饰器逻辑完成后,就可以执行上面dispatch_request
里的self.view_functions[rule.endpoint](**req.view_args)
了