flask源码解析
flask源码解析
flask启动时如何收集所有的路由
1.收集静态资源路由
初始化时收集
flask init时
if self.has_static_folder:
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
self.add_url_rule(
self.static_url_path + '/<path:filename>',
endpoint='static',
host=static_host,
view_func=self.send_static_file
)
2.收集@app.route()路由
初始化时读到@app.route()就添加
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'
For more information refer to :ref:`url-route-registrations`.
:param rule: the URL rule as string
:param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
endpoint
:param options: the options to be forwarded to the underlying
:class:`~werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods
is a list of methods this rule should be limited
to (``GET``, ``POST`` etc.). By default a rule
just listens for ``GET`` (and implicitly ``HEAD``).
Starting with Flask 0.6, ``OPTIONS`` is implicitly
added and handled by the standard request handling.
"""
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
3.收集蓝图下的路由
读到@index_bp.route()即添加
self.url_adapter = app.create_url_adapter(self.request)
实际调用 添加Rule对象
return Map.bind(
self,
server_name,
script_name,
subdomain,
environ["wsgi.url_scheme"],
environ["REQUEST_METHOD"],
path_info,
query_args=query_args,
)
无论是哪种定义方式 均是由Flask的add_url_rule()
先定义endpoint 若没有则 获取view_func的.name
再获取所有的methods 未设置则给默认值GET
rule = self.url_rule_class(rule, methods=methods, **options)中的rule是一个Rule对象 rule入参是route中的参数
添加到rule_map中 同时去view_functions中根据endpoint去获取值 以此来去重 如为获取到值 就新增
当请求进入flask时 同样 根据请求url来做endpoint从view_functions取值来分发请求

请求在flask中的处理流程
生成request对象把请求的method args 包装起来供在视图函数中使用 同时先生成app上下文 再生成request上下文 并开启session 并把根据正则 match出 请求对应的view_func
在分发请求前 chain()调用请求钩子 再到视图函数
视图函数业务逻辑处理完成之后 经由flask的finalize_request的make_response 包装成一个可返回的Response对象 基本就是补充默认的 headers 和 status


此处的app是一个DispatchingApp 加上()之后触发了他的__call__方法

https://img-blog.csdnimg.cn/3d845ea96788491e9bf9481c03756538.png

1.ctx = self.request_context(environ)
生成request对象

1.flask在wsgi_app中生成一个ctx对象 ctx是一个RequestContext

RequestContext的主要方法 push pop
push方法主要是生成一个request app上下文 并从此开启session

RequestContext对象 init时 生成self.request = app.request_class(environ)
app.request_class就是Request

request因此有了json的相关方法 并带有自身的endpoint blueprint的属性


init时还有一个match_request方法


最终返回值 rule.endpoint, rv rv应是request.view_args
并在match_request方法中给request.url_rule赋值 即endpoint

2.ctx.push()
_app_ctx_stack是一个LocalStack对象
LocalStack 包含push pop方法及top属性 因此 app_ctx =self._local.stack[-1]


此时是未取到stack的值 获取到了None的
当app_ctx是None或者 app_ctx.app != self.app时 调用app_ctx.push()

push 实际就是 self._local.stack在此处被赋值
3.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)
1.preprocess_request 中处理了hook
1.self.url_value_preprocessors
处理bp的钩子函数
def preprocess_request(self):
"""Called before the request is dispatched. Calls
:attr:`url_value_preprocessors` registered with the app and the
current blueprint (if any). Then calls :attr:`before_request_funcs`
registered with the app and the blueprint.
If any :meth:`before_request` handler returns a non-None value, the
value is handled as if it was the return value from the view, and
further request handling is stopped.
"""
bp = _request_ctx_stack.top.request.blueprint
funcs = self.url_value_preprocessors.get(None, ())
if bp is not None and bp in self.url_value_preprocessors:
funcs = chain(funcs, self.url_value_preprocessors[bp])
for func in funcs:
func(request.endpoint, request.view_args)
funcs = self.before_request_funcs.get(None, ())
if bp is not None and bp in self.before_request_funcs:
funcs = chain(funcs, self.before_request_funcs[bp])
for func in funcs:
rv = func()
if rv is not None:
return rv
2.self.before_request_funcs
处理请求的钩子函数
2.dispatch_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
3.finalize_request(rv)
1.make_response
仅返回str时 flask帮我们生成一个可返回的Response对象 并添加默认status headers
# make sure the body is an instance of the response class
if not isinstance(rv, self.response_class):
if isinstance(rv, (text_type, bytes, bytearray)):
# let the response class set the status and headers instead of
# waiting to do it manually, so that the class can handle any
# special logic
rv = self.response_class(rv, status=status, headers=headers)
status = headers = None
else:
# evaluate a WSGI callable, or coerce a different response
# class to the correct type
try:
rv = self.response_class.force_type(rv, request.environ)
except TypeError as e:
new_error = TypeError(
'{e}\nThe view function did not return a valid'
' response. The return type must be a string, tuple,'
' Response instance, or WSGI callable, but it was a'
' {rv.__class__.__name__}.'.format(e=e, rv=rv)
)
reraise(TypeError, new_error, sys.exc_info()[2])
# prefer the status if it was provided
if status is not None:
if isinstance(status, (text_type, bytes, bytearray)):
rv.status = status
else:
rv.status_code = status
# extend existing headers with provided headers
if headers:
rv.headers.extend(headers)
return rv
2.process_response
1.再次调用hook
def process_response(self, response):
"""Can be overridden in order to modify the response object
before it's sent to the WSGI server. By default this will
call all the :meth:`after_request` decorated functions.
.. versionchanged:: 0.5
As of Flask 0.5 the functions registered for after request
execution are called in reverse order of registration.
:param response: a :attr:`response_class` object.
:return: a new response object or the same, has to be an
instance of :attr:`response_class`.
"""
ctx = _request_ctx_stack.top
bp = ctx.request.blueprint
funcs = ctx._after_request_functions
if bp is not None and bp in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
if None in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs:
response = handler(response)
if not self.session_interface.is_null_session(ctx.session):
self.session_interface.save_session(self, ctx.session, response)
return response
2.并设置响应的cookie
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
# If the session is modified to be empty, remove the cookie.
# If the session is empty, return without setting the cookie.
if not session:
if session.modified:
response.delete_cookie(
app.session_cookie_name,
domain=domain,
path=path
)
return
# Add a "Vary: Cookie" header if the session was accessed at all.
if session.accessed:
response.vary.add('Cookie')
if not self.should_set_cookie(app, session):
return
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session))
response.set_cookie(
app.session_cookie_name,
val,
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite
)
full_dispatch_request调用完成 如果出现异常就是捕获异常
再调用BaseResponse 包装成get_wsgi_response返回
调用ctx.auto_pop(error)时 do_teardown_request被调用
def do_teardown_request(self, exc=_sentinel):
"""Called after the request is dispatched and the response is
returned, right before the request context is popped.
This calls all functions decorated with
:meth:`teardown_request`, and :meth:`Blueprint.teardown_request`
if a blueprint handled the request. Finally, the
:data:`request_tearing_down` signal is sent.
This is called by
:meth:`RequestContext.pop() <flask.ctx.RequestContext.pop>`,
which may be delayed during testing to maintain access to
resources.
:param exc: An unhandled exception raised while dispatching the
request. Detected from the current exception information if
not passed. Passed to each teardown function.
.. versionchanged:: 0.9
Added the ``exc`` argument.
"""
if exc is _sentinel:
exc = sys.exc_info()[1]
funcs = reversed(self.teardown_request_funcs.get(None, ()))
bp = _request_ctx_stack.top.request.blueprint
if bp is not None and bp in self.teardown_request_funcs:
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
for func in funcs:
func(exc)
request_tearing_down.send(self, exc=exc)
再调用LocalStack的pop 将request上下文清除 再清除app上下文
def pop(self):
"""Removes the topmost item from the stack, will return the
old value or `None` if the stack was already empty.
"""
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
4. ctx.auto_pop(error)
实际调用了RequestContext.pop(exc)
finally:
rv = _request_ctx_stack.pop()
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
if clear_request:
rv.request.environ['werkzeug.request'] = None
# Get rid of the app as well if necessary.
if app_ctx is not None:
app_ctx.pop(exc)
该方法又会调用app_ctx.pop(exc) 通过对引用计数 -= 1来回收app_cxt的回收
def pop(self, exc=_sentinel):
"""Pops the app context."""
try:
self._refcnt -= 1
if self._refcnt <= 0:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
finally:
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
appcontext_popped.send(self.app)

780

被折叠的 条评论
为什么被折叠?



