Flask进击篇(1)——Flask运行流程

微信公众号:战渣渣
欢迎大家关注,一起分享。

一次完整的请求

在我们了解Flask运行流程之前,先看一下我们在浏览器一次请求中所经历的过程,下面这张是结合Flask的源码绘制的一张流程图

Python-Web开发中Flask运行流程

能看到Flask在一次请求中实际上只是做了最后一部分功能,这里没有将Flask的具体处理流程列出来,我们在下面会继续讲解。
在上图中出现WSGIRequestHandler,WSGI协议是在Python Web开发中很核心的部分,如果想继续进击的话,需要对这部分有深刻的理解。
这部分我在另一篇文章中有写到,如有需要可以点击WEB开发——Python WSGI协议查看

Flask处理流程

我所理解Flask要做的事情,是根据请求的HTTP协议中url和method映射相应的处理函数,处理完并返回。这是基础的功能,Flask在这基础上又增加了一些其他功能。下面我们就通过Flask的源码中一些属性来进行分析。

Flask部分重要属性

Flask部分属性.png
Flask启动时构建属性

Flask在启动时已将各属性根据需求配置好,但实际映射函数的属性就是view_functions,此属性类型为字典,key是endpoint。

endpoint可自定义,若不指定将会根据函数名生成,若出现重复的endpoint将会提示错误。

endpoint会与url和method统一封装成到rule放入到url_map中,在请求过来时会根据url和和method生成reuqest到url_map中匹配,如果匹配到则根据endpoint获取到相应的函数去执行,并将结果返回。这部分可以看Flask源码部分。

添加到url_map

# flask/app.py
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)
    options["endpoint"] = endpoint
    methods = options.pop("methods", None)

    if methods is None:
        methods = getattr(view_func, "methods", None) or ("GET",)
    if isinstance(methods, string_types):
        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 added
    required_methods = set(getattr(view_func, "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
        )

    if provide_automatic_options is None:
        if "OPTIONS" not in methods:
            provide_automatic_options = True
            required_methods.add("OPTIONS")
        else:
            provide_automatic_options = False

    # Add the required methods now.
    methods |= required_methods

    rule = self.url_rule_class(rule, methods=methods, **options)
    rule.provide_automatic_options = provide_automatic_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

请求时匹配请求

  1. 生成请求
# flask/app.py
def create_url_adapter(self, request):
    if request is not None:
        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,
        )
    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"],
        )

# flask/ctx.py
def match_request(self):
    try:
        result = self.url_adapter.match(return_rule=True)
        self.request.url_rule, self.request.view_args = result
    except HTTPException as e:
        self.request.routing_exception = e  

此处是在生成上下文的push中执行会执行match_request,这里没有贴出来。
实质就是请求过来了,根据url和method匹配启动时的url_map,如果没有的话则返回匹配不到

  1. 匹配请求

# flask/app.py
def dispatch_request(self):
    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    rule = req.url_rule
    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
    return self.view_functions[rule.endpoint](**req.view_args)

根据上面从url_map得到的rule,然后根据endpoint取得要执行的函数。

Flask另外几个属性,则表示在请求之前和请求之后做一些处理,并且可以针对不同的blueprints来进行处理,关于blueprints我们等几个章节再细分析。

Flask的处理流程

Flask实际的处理流程是什么样子,先看一下Flask的源码

# flask/app.py

# 1. 先通过wsgi协议到这个函数
def __call__(self, environ, start_response):
    return self.wsgi_app(environ, start_response)

# 2. 然后调用这个函数,处理上下文
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)
# 3. 请求处理流程
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)

基本流程可以看的比较清晰,至于每个函数列表的来源以及作用,我在开始的属性图上已将其标识出来。


Flask执行流程.

至此可以大体知道请求过来之后Flask是如何处理及前期Flask会构建哪些内容。

但Flask还有很多东西。例如我们经常使用request,current_app对象和常用的blueprints是怎么个原理。

下章节会针对Flask的上下文处理再做深入的理解。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容