这篇博客会涉及一些 WSGI 的知识,不了解的可以看这篇博客,简单了解一下。
Python 的 WSGI 简单入门
flask__3">一、请求在 flask 中的处理过程
我们先来看一下 werkzeug.routing 包下 Map 和 Rule 方法的使用,这里给出一个官方的示例(我进行了一点修改并增加了简单的运行代码):
python">from werkzeug.routing import Map, Rule, Subdomain, NotFound, RequestRedirect
url_map = Map([
Rule('/', endpoint='blog/index'),
Rule('/<int:year>', endpoint='blog/archive'),
Rule('/<int:year>/<int:month>/', endpoint='blog/archive'),
Rule('/<int:year>/<int:month>/<int:day>', endpoint='blog/archive'),
Rule('/<int:year>/<int:month>/<int:day>/<slug>', endpoint='blog/show_post'),
Rule('/about', endpoint='blog/about_me'),
Rule('/feeds', endpoint='blog/feeds'),
Rule('/feeds/<feed_name>.rss', endpoint='blog/show_feed')
])
def application(environ, start_response):
urls = url_map.bind_to_environ(environ)
try:
endpoint, args = urls.match()
except HTTPException as e:
return e(environ, start_response)
start_response('200 OK', [('Content-Type', 'text/plain')])
rsp = f'Rule points to {endpoint!r} with arguments {args!r}'
return [rsp.encode('utf-8')] # 直接返回字符串会报错,这里进行一次转换
# provide a basic wsgi for test!
if __name__ == '__main__':
from wsgiref.simple_server import make_server
with make_server('', 8000, application) as httpd:
print("Listening on port 8000....")
httpd.serve_forever()
flask 的底层也是依赖于 Map 和 Rule,所以我们使用 @route
或者 add_url_rule
最终的目的也是构建类似上面的 url_map
对象,只不过它更加易用。有趣的是,这里并没有 view_func
函数,所以我们是统一返回了 200 OK
,不过 urls.match
的参数也表明了我们得到的是 endpoint
,这是我们通过它来查找到对应 view_func
的关键信息。
要注意这里的 urls.match()
的返回值中这个 args 是指 url 中的定义的参数,下面是几个示例:
python">urls = m.bind("example.com", "/")
urls.match("/", "GET")
# ('index', {})
urls.match("/downloads/42")
# ('downloads/show', {'id': 42})
1.1 add_url_rule
方法 和 @route
装饰器
add_url_rule
: Connects a URL rule. Works exactly like the :meth:route
decorator. If a view_func is provided it will be registered with the endpoint.
连接 URL 规则。其工作原理与 route
装饰器完全相同。如果提供了 view_func 函数,它会被用 endpoint 来注册。
基础示例:
python">@app.route('/')
def index():
pass
等价于以下:
python">def index():
pass
app.add_url_rule('/', 'index', index)
如果没有提供 view_func
函数,需要手动绑定 endpoint
和 view_func
函数。
python">app.view_function['index'] = index
在内部,route
方法会调用 add_url_rule
方法。
下面我们来看源码,这里我进行了删减,对于我认为不重要的部分去掉,我认为这样可以节约理解设计思路的脑力。
python">@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None,
provide_automatic_options=None, **options):
# 这里上下省略部分代码,只保留我认为关键的代码
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
rule = self.url_rule_class(rule, methods=methods, **options)
self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError('View function mapping is overwriting an '
'existing endpoint function: %s' % endpoint)
self.view_functions[endpoint] = view_func
说明:首先如果 endpoint
为空,则会使用 view_func
函数的名字,接着使用 add_url_rule
函数的参数创建 Rule
对象,将其加入 self.url_map
中,这是一个 Map
对象。然后会将 endpoint
作为键, view_func
作为值,存入 self.view_functions
中,它是一个 dict
对象。
也就是说我们最终得到了下面两个对象,它们是 Flask 类的两个实例属性。还记得上面的 urls.match
方法吗?当我们获取到 endpoint 后,就可以它为键在 slef.view_functions
中去索引对应的 view_func
函数,然后用它来执行对应路由的请求。
python">class Flask(_PackageBoundObject):
def __init__(
self,
import_name,
static_url_path=None,
static_folder='static',
static_host=None,
host_matching=False,
subdomain_matching=False,
template_folder='templates',
instance_path=None,
instance_relative_config=False,
root_path=None
):
#: The :class:`~werkzeug.routing.Map` for this instance.
self.url_map = Map()
#: A dictionary of all view functions registered. The keys will
#: be function names which are also used to generate URLs and
#: the values are the function objects themselves.
#: To register a view function, use the :meth:`route` decorator.
self.view_functions = {}
route 只是一个方便的装饰器函数,本质上还是调用 add_url_rule
函数。
python">def route(self, rule, **options):
"""A decorator that is used to register a view function for a
given URL rule. This does the same thing as :meth:`add_url_rule`
but is intended for decorator usage::
@app.route('/')
def index():
return 'Hello World'
"""
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
2.1 Flask 中的请求处理过程
我们创建的 Flask 的实例,最终也是类似于上面的 application 被 wsgi 服务调用,只是更加复杂一些,下面就来看看简化的流程:
python">class Flask(_PackageBoundObject):
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)
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.
"""
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 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)
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`.
"""
req = _request_ctx_stack.top.request # 获取当前请求的信息
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule # 获取到 url 对象
# 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() # 从 view_function 中找到endpoint对应的
# otherwise dispatch to the handler for that endpoint # view_func 函数,通过视图参数调用它并返回结果,
return self.view_functions[rule.endpoint](**req.view_args) # 注意这里返回的并非响应对象。
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 对象,这里就不往下继续了,下面
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
总结:flask 实例通过请求的 URL 来查找对应的 endpoint,再通过它来查找到对应的视图函数 view_func,然后传入视图函数的参数进行请求处理。在调用视图函数之前,它已经把请求上下文推入了,所以我们在视图函数中可以自由的使用它们,这就是 flask 处理一个请求的大致过程。
关于请求上下文中的全局变量,也就是 request
这些的使用,可以阅读这篇博客:
对 flask 框架中的全局变量 request 探究