Flask源码分析

本文环境python3.5.2,flask-1.0.2。

Flask的路由注册

此时编写的脚本内容如下,

from flask   import Flaskapp = Flask(__name__)@app.route('/')
def hello_world():return 'Hello, World!'

继续分析一下app.route是如何将该路由将hello_world函数对应起来的,

此时查看app.route函数,

def route(self, rule, **options):"""A decorator that is used to register a view function for agiven 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.  Flaskitself assumes the name of the view function asendpoint:param options: the options to be forwarded to the underlying:class:`~werkzeug.routing.Rule` object.  A changeto Werkzeug is handling of method options.  methodsis a list of methods this rule should be limitedto (``GET``, ``POST`` etc.).  By default a rulejust listens for ``GET`` (and implicitly ``HEAD``).Starting with Flask 0.6, ``OPTIONS`` is implicitlyadded and handled by the standard request handling."""def decorator(f):endpoint = options.pop('endpoint', None)            # 获取传入参数中endpoint字段值self.add_url_rule(rule, endpoint, f, **options)     # 添加路由return f                                            # 返回传入函数return decorator

由以上装饰器可知,该函数还是原函数直接返回,只是调用了add_url_rule方法将url添加,

@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None,provide_automatic_options=None, **options):"""Connects a URL rule.  Works exactly like the :meth:`route`decorator.  If a view_func is provided it will be registered with theendpoint.Basically this example::@app.route('/')def index():passIs equivalent to the following::def index():passapp.add_url_rule('/', 'index', index)If the view_func is not provided you will need to connect the endpointto a view function like so::app.view_functions['index'] = indexInternally :meth:`route` invokes :meth:`add_url_rule` so if you wantto customize the behavior via subclassing you only need to changethis method.For more information refer to :ref:`url-route-registrations`... versionchanged:: 0.2`view_func` parameter added... versionchanged:: 0.6``OPTIONS`` is added automatically as method.:param rule: the URL rule as string:param endpoint: the endpoint for the registered URL rule.  Flaskitself assumes the name of the view function asendpoint:param view_func: the function to call when serving a request to theprovided endpoint:param provide_automatic_options: controls whether the ``OPTIONS``method should be added automatically. This can also be controlledby setting the ``view_func.provide_automatic_options = False``before adding the rule.:param options: the options to be forwarded to the underlying:class:`~werkzeug.routing.Rule` object.  A changeto Werkzeug is handling of method options.  methodsis a list of methods this rule should be limitedto (``GET``, ``POST`` etc.).  By default a rulejust listens for ``GET`` (and implicitly ``HEAD``).Starting with Flask 0.6, ``OPTIONS`` is implicitlyadded and handled by the standard request handling."""if endpoint is None:                                                        # 传入是否为空endpoint = _endpoint_from_view_func(view_func)                          # 获取函数名称options['endpoint'] = endpoint                                              # 将该函数名称设置到选项中methods = options.pop('methods', None)                                      # 从传入值中获取methods,即该方法的访问方法GET,POST等# if the methods are not given and the view_func object knows its# methods we can use that instead.  If neither exists, we go with# a tuple of only ``GET`` as default.if methods is None:                                                         # 如果为空methods = getattr(view_func, 'methods', None) or ('GET',)               # 获取传入方法的属性methods如果为空则默认为get方法if isinstance(methods, string_types):                                       # 判断methods是否是可迭代的raise TypeError('Allowed methods have to be iterables of strings, ''for example: @app.route(..., methods=["POST"])')methods = set(item.upper() for item in methods)                             # 获取方法的大小集合# Methods that should always be addedrequired_methods = set(getattr(view_func, 'required_methods', ()))          # 获取函数的required_methods属性默认为空# starting with Flask 0.8 the view_func object can disable and# force-enable the automatic options handling.if provide_automatic_options is None:                                       # 如果该值为空provide_automatic_options = getattr(view_func,'provide_automatic_options', None)                                  # 获取该传入函数对应的provide_automatic_options属性if provide_automatic_options is None:                                       # 如果为空if 'OPTIONS' not in methods:                                            # 该方法不再methods中provide_automatic_options = True                                    # 设置成Truerequired_methods.add('OPTIONS')                                     # 添加该方法else:provide_automatic_options = False                                   # 设置为False# Add the required methods now.methods |= required_methods                                                 # 获取所有方法rule = self.url_rule_class(rule, methods=methods, **options)                # 初始化Rule实例 rule.provide_automatic_options = provide_automatic_options                  # 属性值self.url_map.add(rule)                                                      # 将该实例添加到url_map中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                               # 设置进字典中

其中,在调用self.url_map.add(rule)方法时,会执行如下操作,

def add(self, rulefactory):"""Add a new rule or factory to the map and bind it.  Requires that therule is not bound to another map.:param rulefactory: a :class:`Rule` or :class:`RuleFactory`"""for rule in rulefactory.get_rules(self):                                    # 获取rulerule.bind(self)                                                         # 调用rule的bind方法self._rules.append(rule)                                                # 添加到_rules列表中self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)self._remap = True                                                          # 设置是否需要重新排序标志

其中调用的rule的bind方法,就是将传入的url进行正则匹配的相关处理,该函数的执行流程相对繁琐,大家可以自行分析。
此时路由的注册工作就已经完成,接下来就分析请求到来后的处理。

Flask的请求处理

在上文文末,已经分析到当请求到来后的处理流程会调用Flask实例的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 withoutlosing 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 andcan continue to call methods on it... versionchanged:: 0.7Teardown events for the request and app contexts are calledeven if an unhandled error occurs. Other events may not becalled 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 tostart the response."""ctx = self.request_context(environ)                     # 初始化请求上下文error = Nonetry:try:ctx.push()                                      response = self.full_dispatch_request()         # 处理请求except Exception as e:error = eresponse = self.handle_exception(e)             # 如果处理出错则包装该错误except:error = sys.exc_info()[1]raisereturn response(environ, start_response)            # 返回数据finally:if self.should_ignore_error(error):error = Nonectx.auto_pop(error)

此时,会先调用request_context方法,

def request_context(self, environ):"""Create a :class:`~flask.ctx.RequestContext` representing aWSGI environment. Use a ``with`` block to push the context,which will make :data:`request` point at this request.See :doc:`/reqcontext`.Typically you should not call this from your own code. A requestcontext is automatically pushed by the :meth:`wsgi_app` whenhandling a request. Use :meth:`test_request_context` to createan environment and context instead of this method.:param environ: a WSGI environment"""return RequestContext(self, environ)

返回一个RequestContext实例,此时查看该类的初始化过程,

def __init__(self, app, environ, request=None):self.app = app                                              # 传入的appif request is None:                                         # 如果传入的request为空request = app.request_class(environ)                    # 调用app的request_class将environ做参数传入并实例化self.request = request                                      # 设置请求self.url_adapter = app.create_url_adapter(self.request)     # 调用app的create_url_adapter方法self.flashes = Noneself.session = None# Request contexts can be pushed multiple times and interleaved with# other request contexts.  Now only if the last level is popped we# get rid of them.  Additionally if an application context is missing# one is created implicitly so for each level we add this informationself._implicit_app_ctx_stack = []# indicator if the context was preserved.  Next time another context# is pushed the preserved context is popped.self.preserved = False# remembers the exception for pop if there is one in case the context# preservation kicks in.self._preserved_exc = None# Functions that should be executed after the request on the response# object.  These will be called before the regular "after_request"# functions.self._after_request_functions = []self.match_request()                                        # 匹配请求

此时如果没有传入request则先初始化request,然后调用app的create_url_adapter方法,创建url_adapter来匹配request中的url,最后调用调用match_request方法去寻找最佳匹配的url,此时create_url_adapter如下,

def create_url_adapter(self, request):"""Creates a URL adapter for the given request. The URL adapteris created at a point where the request context is not yet setup so the request is passed explicitly... versionadded:: 0.6.. versionchanged:: 0.9This can now also be called without a request object when theURL adapter is created for the application context... versionchanged:: 1.0:data:`SERVER_NAME` no longer implicitly enables subdomainmatching. Use :attr:`subdomain_matching` instead."""if request is not None:                                             # 传入的request不为空空# If subdomain matching is disabled (the default), use the# default subdomain in all cases. This should be the default# in Werkzeug but it currently does not have that feature.subdomain = ((self.url_map.default_subdomain or None)if not self.subdomain_matching else None)          # 子域名return self.url_map.bind_to_environ(request.environ,server_name=self.config['SERVER_NAME'],subdomain=subdomain)                                        # 由于调用Map的bind_to_environ方法# We need at the very least the server name to be set for this# to work.if self.config['SERVER_NAME'] is not None:return self.url_map.bind(self.config['SERVER_NAME'],script_name=self.config['APPLICATION_ROOT'],url_scheme=self.config['PREFERRED_URL_SCHEME'])

由于url_map是一个Map实例,此时调用了Map的bind_to_environ方法,

def bind_to_environ(self, environ, server_name=None, subdomain=None):......return Map.bind(self, server_name, script_name,subdomain, environ['wsgi.url_scheme'],environ['REQUEST_METHOD'], path_info,query_args=query_args)

在进过对environ的传入参数进行相关处理后,就调用了Map的bind方法,

def bind(self, server_name, script_name=None, subdomain=None,url_scheme='http', default_method='GET', path_info=None,query_args=None):"""Return a new :class:`MapAdapter` with the details specified to thecall.  Note that `script_name` will default to ``'/'`` if not furtherspecified or `None`.  The `server_name` at least is a requirementbecause the HTTP RFC requires absolute URLs for redirects and so allredirect exceptions raised by Werkzeug will contain the full canonicalURL.If no path_info is passed to :meth:`match` it will use the default pathinfo passed to bind.  While this doesn't really make sense formanual bind calls, it's useful if you bind a map to a WSGIenvironment which already contains the path info.`subdomain` will default to the `default_subdomain` for this map ifno defined. If there is no `default_subdomain` you cannot use thesubdomain feature... versionadded:: 0.7`query_args` added.. versionadded:: 0.8`query_args` can now also be a string."""server_name = server_name.lower()if self.host_matching:if subdomain is not None:raise RuntimeError('host matching enabled and a ''subdomain was provided')elif subdomain is None:subdomain = self.default_subdomainif script_name is None:script_name = '/'try:server_name = _encode_idna(server_name)except UnicodeError:raise BadHost()return MapAdapter(self, server_name, script_name, subdomain,url_scheme, path_info, default_method, query_args)

该方法返回了一个MapAdapter实例,所以在RequestContext实例中的url_adapter就是一个MapAdapter实例。此时回到RequestContext在实例化的时候调用match_request方法,

def match_request(self):"""Can be overridden by a subclass to hook into the matchingof the request."""try:url_rule, self.request.view_args = \self.url_adapter.match(return_rule=True)            # 匹配请求self.request.url_rule = url_rule                        # 设置request属性except HTTPException as e:self.request.routing_exception = e                      # 如出错则设置匹配不到路由

此时调用了MapAdapter实例的match方法,

def match(self, path_info=None, method=None, return_rule=False,query_args=None):"""The usage is simple: you just pass the match method the currentpath info as well as the method (which defaults to `GET`).  Thefollowing things can then happen:- you receive a `NotFound` exception that indicates that no URL ismatching.  A `NotFound` exception is also a WSGI application youcan call to get a default page not found page (happens to be thesame object as `werkzeug.exceptions.NotFound`)- you receive a `MethodNotAllowed` exception that indicates that thereis a match for this URL but not for the current request method.This is useful for RESTful applications.- you receive a `RequestRedirect` exception with a `new_url`attribute.  This exception is used to notify you about a requestWerkzeug requests from your WSGI application.  This is for example thecase if you request ``/foo`` although the correct URL is ``/foo/``You can use the `RequestRedirect` instance as response-like objectsimilar to all other subclasses of `HTTPException`.- you get a tuple in the form ``(endpoint, arguments)`` if there isa match (unless `return_rule` is True, in which case you get a tuplein the form ``(rule, arguments)``)If the path info is not passed to the match method the default pathinfo of the map is used (defaults to the root URL if not definedexplicitly).All of the exceptions raised are subclasses of `HTTPException` so theycan be used as WSGI responses. They will all render generic error orredirect pages.Here is a small example for matching:>>> m = Map([...     Rule('/', endpoint='index'),...     Rule('/downloads/', endpoint='downloads/index'),...     Rule('/downloads/<int:id>', endpoint='downloads/show')... ])>>> urls = m.bind("example.com", "/")>>> urls.match("/", "GET")('index', {})>>> urls.match("/downloads/42")('downloads/show', {'id': 42})And here is what happens on redirect and missing URLs:>>> urls.match("/downloads")Traceback (most recent call last):...RequestRedirect: http://example.com/downloads/>>> urls.match("/missing")Traceback (most recent call last):...NotFound: 404 Not Found:param path_info: the path info to use for matching.  Overrides thepath info specified on binding.:param method: the HTTP method used for matching.  Overrides themethod specified on binding.:param return_rule: return the rule that matched instead of just theendpoint (defaults to `False`).:param query_args: optional query arguments that are used forautomatic redirects as string or dictionary.  It'scurrently not possible to use the query argumentsfor URL matching... versionadded:: 0.6`return_rule` was added... versionadded:: 0.7`query_args` was added... versionchanged:: 0.8`query_args` can now also be a string."""self.map.update()                                                       # 更新数据,重新排序传入rulesif path_info is None:                                                   # 如果传入为空path_info = self.path_info                                          # 则使用初始化时的path_infoelse:path_info = to_unicode(path_info, self.map.charset)if query_args is None:                                                  # 如果传入查询参数为空query_args = self.query_args                                        # 使用初始化值method = (method or self.default_method).upper()                        # 将方法转为大写path = u'%s|%s' % (self.map.host_matching and self.server_name or self.subdomain,path_info and '/%s' % path_info.lstrip('/'))have_match_for = set()for rule in self.map._rules:                                            # 遍历rulestry:rv = rule.match(path, method)                                   # 调用rule的match方法except RequestSlash:raise RequestRedirect(self.make_redirect_url(url_quote(path_info, self.map.charset,safe='/:|+') + '/', query_args))except RequestAliasRedirect as e:raise RequestRedirect(self.make_alias_redirect_url(path, rule.endpoint, e.matched_values, method, query_args))if rv is None:                                                      # 如果没有匹配上则直接下一个continueif rule.methods is not None and method not in rule.methods:         # 如果rule的方法不为空,并且当前使用的方法不再rule的方法列表中have_match_for.update(rule.methods)                             # 设置rule的方法列表continue                                                        # 继续下一个if self.map.redirect_defaults:                                      # 如果默认重定向有值redirect_url = self.get_default_redirect(rule, method, rv,query_args)if redirect_url is not None:raise RequestRedirect(redirect_url)                         # 重定向if rule.redirect_to is not None:                                    # 如果rule重定向不为空if isinstance(rule.redirect_to, string_types): def _handle_match(match):value = rv[match.group(1)]return rule._converters[match.group(1)].to_url(value)redirect_url = _simple_rule_re.sub(_handle_match,rule.redirect_to)else:redirect_url = rule.redirect_to(self, **rv)raise RequestRedirect(str(url_join('%s://%s%s%s' % (self.url_scheme or 'http',self.subdomain and self.subdomain + '.' or '',self.server_name,self.script_name), redirect_url)))                                              # 重定向if return_rule:                                                     # 如果需要返回rule则返回rule,和rvreturn rule, rvelse:return rule.endpoint, rv                                        # 否则返回rule的endpoint和rvif have_match_for:raise MethodNotAllowed(valid_methods=list(have_match_for))          # 如果匹配上了但是方法不允许则报错raise NotFound()                                                        # 报404not found

其中调用的rule.match方法就是将在url注册时候进行的正则等处理进行相关匹配回来,该函数大家可自行分析。当url匹配完成后,此时继续执行wsgi_app函数中的ctx.push()函数,

def push(self):"""Binds the request context to the current context."""# If an exception occurs in debug mode or if context preservation is# activated under exception situations exactly one context stays# on the stack.  The rationale is that you want to access that# information under debug situations.  However if someone forgets to# pop that context again we want to make sure that on the next push# it's invalidated, otherwise we run at risk that something leaks# memory.  This is usually only a problem in test suite since this# functionality is not active in production environments.top = _request_ctx_stack.top                                # _request_ctx_stack是个根据不同线程不同保存数据的栈if top is not None and top.preserved:                       # 如果不为空并且top.preserved为true则表示上一请求已经处理完成top.pop(top._preserved_exc)                             # 移除# Before we push the request context we have to ensure that there# is an application context.app_ctx = _app_ctx_stack.top                                # 获取_app_ctx_stack与线程相关的数据栈,获取栈顶if app_ctx is None or app_ctx.app != self.app:              # 如果为空或者当前的app上下文与当前上下文不同app_ctx = self.app.app_context()                        # 将app_ctx设置为当前app的上下文app_ctx.push()                                          # 将当前app_ctx入栈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)                               # 在_request_ctx_stack入栈# Open the session at the moment that the request context is available.# This allows a custom open_session method to use the request context.# Only open a new session if this is the first time the request was# pushed, otherwise stream_with_context loses the session.if self.session is None:                                    # 如果session为空session_interface = self.app.session_interface          # 获取SecureCookieSessionInterface实例self.session = session_interface.open_session(self.app, self.request)                                                       # 打开sessionif self.session is None:                                # 如果为空self.session = session_interface.make_null_session(self.app)  # 设置一个空的会话

其中_request_ctx_stack和_app_ctx_stack,

class LocalStack(object):def __init__(self):self._local = Local()def __release_local__(self):self._local.__release_local__()def _get__ident_func__(self):return self._local.__ident_func__def _set__ident_func__(self, value):object.__setattr__(self._local, '__ident_func__', value)__ident_func__ = property(_get__ident_func__, _set__ident_func__)del _get__ident_func__, _set__ident_func__def __call__(self):def _lookup():rv = self.topif rv is None:raise RuntimeError('object unbound')return rvreturn LocalProxy(_lookup)def push(self, obj):"""Pushes a new item to the stack"""rv = getattr(self._local, 'stack', None)if rv is None:self._local.stack = rv = []rv.append(obj)return rvdef pop(self):"""Removes the topmost item from the stack, will return theold value or `None` if the stack was already empty."""stack = getattr(self._local, 'stack', None)if stack is None:return Noneelif len(stack) == 1:release_local(self._local)return stack[-1]else:return stack.pop()@propertydef top(self):"""The topmost item on the stack.  If the stack is empty,`None` is returned."""try:return self._local.stack[-1]except (AttributeError, IndexError):return None

从该类可看出,该类的操作都是基于初始化时传入的Local()来操作的,该类如下,

class Local(object):__slots__ = ('__storage__', '__ident_func__')def __init__(self):object.__setattr__(self, '__storage__', {})object.__setattr__(self, '__ident_func__', get_ident)def __iter__(self):return iter(self.__storage__.items())def __call__(self, proxy):"""Create a proxy for a name."""return LocalProxy(self, proxy)def __release_local__(self):self.__storage__.pop(self.__ident_func__(), None)def __getattr__(self, name):try:return self.__storage__[self.__ident_func__()][name]except KeyError:raise AttributeError(name)def __setattr__(self, name, value):ident = self.__ident_func__()storage = self.__storage__try:storage[ident][name] = valueexcept KeyError:storage[ident] = {name: value}def __delattr__(self, name):try:del self.__storage__[self.__ident_func__()][name]except KeyError:raise AttributeError(name)

该这的操作都是根据线程id设置相关的操作值,保证了各个线程之间的数据独立不干扰。
在push函数中上下文处理完成后,然后就是session的相关操作,flask默认通过加密的形式将所有的数据添加到cookie中,然后再下次请求的时候将cookie中的数据进行解密获取上次的数据,session的具体分析如有机会后续会分析,

此刻,回到wsgi_app函数中,接下将要执行response = self.full_dispatch_request() ,

def full_dispatch_request(self):"""Dispatches the request and on top of that performs requestpre and postprocessing as well as HTTP exception catching anderror handling... versionadded:: 0.7"""self.try_trigger_before_first_request_functions()                   # 根据_got_first_request标志判断执行相关配置函数try:request_started.send(self) rv = self.preprocess_request()                                  # 处理请求urlif rv is None:                                                  # 如果返回值为空rv = self.dispatch_request()                                # 调用该方法处理except Exception as e:rv = self.handle_user_exception(e)                              # 如果执行出错则带哦用该方法return self.finalize_request(rv)                                    # 结束请求

此时首先查看self.try_trigger_before_first_request_functions方法的执行过程,

def try_trigger_before_first_request_functions(self):"""Called before each request and will ensure that it triggersthe :attr:`before_first_request_funcs` and only exactly once perapplication instance (which means process usually).:internal:"""if self._got_first_request:                             # 检查该标志returnwith self._before_request_lock:                         # 加锁if self._got_first_request:                         # 再次检查returnfor func in self.before_first_request_funcs:        # 获取在列表中的函数并依次执行func()self._got_first_request = True                      # 将该标志置为True,确保只执行依次

执行了第一次执行时,需要执行的相关的回调方法,然后继续调用self.preprocess_request函数,

def preprocess_request(self):"""Called before the request is dispatched. Calls:attr:`url_value_preprocessors` registered with the app and thecurrent 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, thevalue is handled as if it was the return value from the view, andfurther request handling is stopped."""bp = _request_ctx_stack.top.request.blueprint                       # 获取request属性的blueprintfuncs = self.url_value_preprocessors.get(None, ())                  # 获取注册在dispatched之前的函数if bp is not None and bp in self.url_value_preprocessors:           # 检查bp是否为空,并且该函数对应的key是否存在字典中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, ())                     # 获取在执行request之前的函数列表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                                                   # 则直接返回执行结果

该函数处理的类似于Django框架中的中间件功能类似,在处理请求之前执行相关注册的函数,此时继续分析self.dispatch_request()函数,

def dispatch_request(self):"""Does the request dispatching.  Matches the URL and returns thereturn value of the view or error handler.  This does not have tobe a response object.  In order to convert the return value to aproper response object, call :func:`make_response`... versionchanged:: 0.7This no longer does the exception handling, this code wasmoved to the new :meth:`full_dispatch_request`."""req = _request_ctx_stack.top.request                                # 获取requestif req.routing_exception is not None:                               # 如果路由匹配出错self.raise_routing_exception(req)                               # 抛出错误rule = req.url_rule                                                 # 获取匹配成功的rule实例# if we provide automatic options for this URL and the# request came with the OPTIONS method, reply automaticallyif getattr(rule, 'provide_automatic_options', False) \and req.method == 'OPTIONS':                                     # 如果provide_automatic_options设置为True并且方法为OPTIONSreturn self.make_default_options_response()                     # 调用默认回复# otherwise dispatch to the handler for that endpointreturn self.view_functions[rule.endpoint](**req.view_args)          # 调用在路由注册时候添加的函数

此时就执行了,hello_world函数并返回相应结果,然后回到full_dispatch_request函数继续执行self.finalize_request(rv) 函数,

def finalize_request(self, rv, from_error_handler=False):"""Given the return value from a view function this finalizesthe request by converting it into a response and invoking thepostprocessing functions.  This is invoked for both normalrequest dispatching as well as error handlers.Because this means that it might be called as a result of afailure a special safe mode is available which can be enabledwith the `from_error_handler` flag.  If enabled, failures inresponse 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:raiseself.logger.exception('Request finalizing failed with an ''error while handling an error')return response                                                     # 返回结果

继续查看make_response函数,

def make_response(self, rv):"""Convert the return value from a view function to an instance of:attr:`response_class`.:param rv: the return value from the view function. The view functionmust return a response. Returning ``None``, or the view endingwithout returning, is not allowed. The following types are allowedfor ``view_rv``:``str`` (``unicode`` in Python 2)A response object is created with the string encoded to UTF-8as the body.``bytes`` (``str`` in Python 2)A response object is created with the bytes as the body.``tuple``Either ``(body, status, headers)``, ``(body, status)``, or``(body, headers)``, where ``body`` is any of the other typesallowed here, ``status`` is a string or an integer, and``headers`` is a dictionary or a list of ``(key, value)``tuples. If ``body`` is a :attr:`response_class` instance,``status`` overwrites the exiting value and ``headers`` areextended.:attr:`response_class`The object is returned unchanged.other :class:`~werkzeug.wrappers.Response` classThe object is coerced to :attr:`response_class`.:func:`callable`The function is called as a WSGI application. The result isused to create a response object... versionchanged:: 0.9Previously a tuple was interpreted as the arguments for theresponse object."""status = headers = None                                                     # 状态码和头部信息置空# unpack tuple returnsif isinstance(rv, tuple):                                                   # 检查是否是元组len_rv = len(rv)                                                        # 获取元组长度# a 3-tuple is unpacked directlyif len_rv == 3:                                                         # 如果是三个值rv, status, headers = rv                                            # 直接解析为返回结果,状态码,头部信息# decide if a 2-tuple has status or headerselif len_rv == 2:                                                       # 如果长度是2if isinstance(rv[1], (Headers, dict, tuple, list)):                 # 检查rv[1]的类型rv, headers = rv                                                # 将rv解析为返回值,头部数据else:rv, status = rv                                                 # 否则解析成返回值,状态码# other sized tuples are not allowedelse:raise TypeError('The view function did not return a valid response tuple.'' The tuple must have the form (body, status, headers),'' (body, status), or (body, headers).')# the body must not be Noneif rv is None:                                                              # 如果返回数据为空则报错raise TypeError('The view function did not return a valid response. The'' function either returned None or ended without a return'' statement.')# make sure the body is an instance of the response classif not isinstance(rv, self.response_class):                                 # 如果rv不是Response类if isinstance(rv, (text_type, bytes, bytearray)):                       # 判断rv类型# let the response class set the status and headers instead of# waiting to do it manually, so that the class can handle any# special logicrv = self.response_class(rv, status=status, headers=headers)        # 传入参数获得Response实例status = headers = None                                             # 将头部和返回码置空else:# evaluate a WSGI callable, or coerce a different response# class to the correct typetry:rv = self.response_class.force_type(rv, request.environ)        # 强制将相关数据转换为Response实例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 providedif status is not None:                                                      # 如果状态码不为空if isinstance(status, (text_type, bytes, bytearray)):                   # 判断状态码类型rv.status = status                                                  # 直接设置状态码else:rv.status_code = status                                             # 设置状态值# extend existing headers with provided headersif headers:rv.headers.extend(headers)                                              # 如果headers还有值则直接添加到rv.headers中return rv                                                                   # 返回response实例

该函数就是根据传入的需要返回的数据的类型,包装成一个Response的实例,然后返回该实例,该函数执行完成后,返回finalize_request函数继续执行process_response函数,

def process_response(self, response):"""Can be overridden in order to modify the response objectbefore it's sent to the WSGI server.  By default this willcall all the :meth:`after_request` decorated functions... versionchanged:: 0.5As of Flask 0.5 the functions registered for after requestexecution 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 aninstance of :attr:`response_class`."""ctx = _request_ctx_stack.top                                        # 获取当前请求上下文bp = ctx.request.blueprint                                          # 获取设置的blueprintfuncs = 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]))    # 添加到funcs中if None in self.after_request_funcs:funcs = chain(funcs, reversed(self.after_request_funcs[None]))for handler in funcs:                                               # 依次遍历函数列表response = handler(response)                                    # 将response作为参数,依次执行函数if not self.session_interface.is_null_session(ctx.session):         # 判断是否是null的sessionself.session_interface.save_session(self, ctx.session, response)    # 如果不是则保存相应数据到cookie中,等待下次访问时带上来return response

在返回response之前,获取在返回结果之前需要执行的相关函数列表,然后依次执行,然后判断session是否需要保存,如要保存则存在cookie中,然后返回response,此刻full_dispatch_request函数就执行完。

返回到wsgi_app函数中执行,response(environ, start_response) 将处理返回的数据返回回去。当数据返回成功后,还将目前的上下文自动弹出,至此,一个响应的完成请求就完成了。

本文总结

本文主要讲述了flask框架的helloworld程序的路由的注册过程,并分析了在请求到来后的路由的匹配,相关注册函数的注册的流程,执行完成后并将相关结果返回的大概流程。

flask源码学习-路由的注册与请求处理的过程相关推荐

  1. Flask源码学习【个人自学记录】-应用准备阶段

    0. 前情提要: 掌握基础的flask语法知识与python语法知识. 参考资料:https://www.bilibili.com/video/BV11Y411h71J 0.1 flask版本 本文所 ...

  2. Spring源码学习:BeanPostProcessor注册和执行时机

    目录 前言 1 BeanPostProcessors作用 2 源码分析 2.1 BeanPostProcessors注册时机 2.1.1 注册beanPostProcessorChecker 2.1. ...

  3. flask源码学习-helloworld与本地启动流程

    Flask源码分析 本文环境python3.5.2,flask-1.0.2. Flask的初探 首先,在项目文件夹下建立flask_run.py文件,然后写入如下, from flask import ...

  4. Go框架 gin 源码学习--路由的实现原理剖析

    往期回顾: gin源码解析 - gin 与 net/http 的关系 gin 源码解析 - 详解http请求在gin中的流转过程 上面两篇文章基本讲清楚了 Web Server 如何接收客户端请求,以 ...

  5. nginx源码学习资源

    nginx源码学习是一个痛苦又快乐的过程,下面列出了一些nginx的学习资源. 首先要做的当然是下载一份nginx源码,可以从nginx官方网站下载一份最新的. 看了nginx源码,发现这是一份完全没 ...

  6. python flask源码解析_用尽洪荒之力学习Flask源码

    [TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...

  7. Spark-Core源码学习记录 3 SparkContext、SchedulerBackend、TaskScheduler初始化及应用的注册流程

    Spark-Core源码学习记录 该系列作为Spark源码回顾学习的记录,旨在捋清Spark分发程序运行的机制和流程,对部分关键源码进行追踪,争取做到知其所以然,对枝节部分源码仅进行文字说明,不深入下 ...

  8. python flask源码解析_浅谈flask源码之请求过程

    Flask Flask是什么? Flask是一个使用 Python 编写的轻量级 Web 应用框架, 让我们可以使用Python语言快速搭建Web服务, Flask也被称为 "microfr ...

  9. DotText源码学习——ASP.NET的工作机制

    --本文是<项目驱动学习--DotText源码学习>系列的第一篇文章,在这之后会持续发表相关的文章. 概论 在阅读DotText源码之前,让我们首先了解一下ASP.NET的工作机制,可以使 ...

最新文章

  1. 使用Python和OpenCV实现超快速,简单的伽玛校正功能
  2. delphi ScriptGate 调用JS
  3. Web应用漏洞评估工具Paros
  4. SQL Server 2008中的hierarchyid
  5. 2021-06-12
  6. 10自带sftp服务器_WinSCP v5.15.3 免费的 开源图形化 SFTP 客户端
  7. PHP 逆转字符串与逆转句子
  8. OpenTelemetry-可观察性的新时代
  9. 密钥库证书的SHA-1指纹
  10. 【Python游戏】Python各大游戏合集:超级玛丽、天天酷跑、我的世界、魔塔、雷霆战机 | 附带源码
  11. 虚拟机安装黑苹果【虚拟机安装,黑苹果安装,黑苹果无法全屏问题】(这应该全网最全的资源了吧~)
  12. Django操作views(一)
  13. 计算机专业职称入深户,深圳市人才引进入深户新政策
  14. JSF 的el表达式语言
  15. 获取高德POI(关键词搜索法 多边形搜索法 周边搜索法)综合运用 2022新版高德poi下载
  16. Kubernetes详解
  17. OC和Swift中循环引用的问题
  18. 64位Win7 安装配置 64位Oracle11g + 32位InstantClient + PLSQL10
  19. c语言中int和void,关于指针:void(*)void和int(*)int在C中的含义是什么?
  20. 百度“算盘”logo引领国风来袭

热门文章

  1. 网红 AI 高仿坎爷发布说唱情歌,歌迷:堪比真人原声
  2. 官方抓虫,PyTorch 新版本修复 13 项 Bug
  3. 百万人学AI:CSDN重磅共建人工智能技术新生态
  4. 写给Python开发者:机器学习十大必备技能
  5. 程序员在地铁写代码遭疯狂吐槽!网友:装什么装
  6. 从对ML一窍不通到斩获AT等special offer,拿下大厂算法岗就靠它了
  7. 暴雪游戏遭遇AI“实力”坑队友:四处游走,还不参与战斗
  8. 算法和编程面试题精选TOP50!(附代码+解题思路+答案)
  9. 不被邀请又怎样!马云都快买下中国AI芯片的半壁江山了,直怼腾讯和百度
  10. 注意,免费的 CentOS 落幕,将于本月底终止维护!