zoukankan      html  css  js  c++  java
  • flask route设计思路

    引言

    本文主要梳理了flask源码中route的设计思路。
    首先,从WSGI协议的角度介绍flask route的作用;
    其次,详细讲解如何借助werkzeug库的MapRule实现route
    最后,梳理了一次完整的http请求中route的完整流程。

    flask route 设计思路

    源码版本说明

    本文参考的是flask 0.5版本的代码。
    flask 0.1版本的代码非常短,只有600多行,但是这个版本缺少blueprint机制。
    因此,我参考的是0.5版本。

    flask route示例

    直接使用flask官方文档中的例子

    from flask import Flask
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        return 'Hello World!'
        
    @app.route('/post/<int:post_id>')
    def show_post(post_id):
        # show the post with the given id, the id is an integer
        return 'Post %d' % post_id
    
    if __name__ == '__main__':
        app.run()

    此例中,使用app.route装饰器,完成了以下两个url与处理函数的route:

    { 
        '/': hello_world, 
        '/post/<int:post_id>' : show_post
    }

    这样做的效果为:
    当http请求的url为'/'时,flask会调用hello_world函数;
    当http请求的url为'/post/<某整数值>'(例如/post/32)时,flask会调用show_post函数;

    flask route的作用

    从上面的示例中其实可以明白:flask route的作用就是建立url与处理函数的映射

    WSGI协议将处理请求的组件按照功能及调用关系分成了三种:server, middleware, application
    其中,server可以调用middleware和application,middleware可以调用application。

    符合WSGI的框架对于一次http请求的完整处理过程为:
    server读取解析请求,生成environ和start_response,然后调用middleware;
    middleware完成自己的处理部分后,可以继续调用下一个middleware或application,形成一个完整的请求链;
    application位于请求链的最后一级,其作用就是生成最终的响应。

     http服务器(比如,nginx)--> WSGI server(比如gunicorn,SimpleHttpServer)-->middleware-->
     middleware--> ... -->application

    如果接触过Java Web 开发的人可能会立刻发现,这与servlet中的middleware机制是完全一致的。

    特别重要的:

    在上一小节的示例中app = Flask(__name__)创建了一个middleware,
    而这个middleware的核心作用是进行请求转发(request dispatch)。

    上面这句话非常重要,请在心里重复一百遍。
    进行请求转发的前提就是能够建立url与处理函数之间的映射关系,即route功能。

    因此,在flask中,route是Flask类的一个装饰器。

    flask route的实现思路

    通过上一小节,我们知道以下两点:

    1. flask route 是url与处理函数的映射关系;

    2. 在http请求时,Flask这个middleware负责完成对url对应的处理函数的调用;

    那么,如果是我们自己来实现route,思路也很简单:

    1. 建立一个类Flask,这个类是一个middleware,并且有一个字典型的成员变量url_map

    2. url_map = {url : function}

    3. 当http请求时,进行request dispatch:根据url,从url_map中找到function,然后调用function;

    4. 调用后续的middleware或application,并把function的结果传递下去。

    flask的实现思路也是这样的。

    class Flask(object):
    
        def __init__(self):
            self.url_map = {}  # 此处定义保存url与处理函数的映射关系
            
        def __call__(self, environ, start_response):  # 根据WSGI协议,middleware必须是可调用对象
            self.dispatch_request()    # Flask的核心功能 request dispatch
            return application(environ, start_response)  #最后调用下一级的application
        
        def route(self, rule):  # Flask使用装饰器来完成url与处理函数的映射关系建立
            def decorator(f):   # 简单,侵入小,优雅
                self.url_map[rule] = f
                return f
            return decorator
        
        def dispath_request(self):
            url = get_url_from_environ() #解析environ获得url 
            return self.url_map[url]() #从url_map中找到对应的处理函数,并调用

    至此, 一个简单的Flaskmiddleware的骨架就完成了。
    上面的Flask类主要功能包括:

    1. 符合WSGI协议的middleware:可被调用,并且可以调用application

    2. 能够保存url与处理函数的映射信息

    3. 能够根据url找到处理函数并调用(即,request dispatch)

    当然,在实际中,不可能这么简单,但是基本思路是一致的。

    werkzeug库中的Map与Rule在Flask中的应用

    需要指出,上面实现的最简单的Flask类还是有很多问题的。
    比如,HTTP请求中相同的url,不同的请求方法,比如GET,POST如果对应不同的处理函数,该如何处理?

    flask使用了werkzeug库中的MapRule来管理url与处理函数映射关系。

    首先需要简单了解一下MapRule的作用:
    werkzeug中,Rule的主要作用是保存了一组urlendpointmethods关系:
    每个(url, endpoint, methods)都有一个对应的Rule对象:
    其实现如下:

    class Rule(object):
        def __init__(self, url, endpoint, methods):
            self.rule = url
            self.endpoint = endpoint
            self.methods = methods

    这里需要解释一下endpoint
    前面说过:url与其处理函数可以使用一个字典来实现:{url: function}

    flask在实现的时候,在中间加了一个中介endpoint,于是,url与处理函数的映射变成了这样:

    url-->endpoint-->function #一个url对应一个endpoint,一个endpoint对应一个function
    {url: endpoint} # 保存url与endpoint之间的关系
    {endpoint: function} #保存endpoint与function之间的关系

    于是,刚才我们实现的简单的flask骨架中{url: function}的字典,就变成了{endpoint: function}
    {url: endpoint}这个映射关系就需要借助MapRule这两个类来完成。

    可以发现:endpoint就是url和处理函数映射关系中的一个中介,所以,它可以是任何可以用作字典键的值,比如字符串。
    但是在实际使用中endpoint,一般endpoint均为字符串,并且默认情况下:

    1. 如果是通过Flask.route装饰器建立的映射关系,那么endpoint就是处理函数的函数名;

    2. 如果是通过blueprint建立的映射关系,那么endpoint是blueprint名.处理函数名;

    因为,每建立一个url-->endpoint-->function关系就会创建一个Rule对象,所以,会有很多Rule对象存在。
    Map的作用则是保存所有Rule对象。
    所以,一般情况下Map的用法如下:

        m = Map([
                Rule('/', endpoint='index'),
                Rule('/downloads/', endpoint='downloads/index'),
                Rule('/downloads/<int:id>', endpoint='downloads/show')
               ])

    在flask的源码中

    class Flask(object):
        def __init__(self):
            self.url_map = Map()  # url_map为保存所有Rule关系的容器Map
            self.view_functions = {} # view_functions保存endpoint-->function
    1. 成员变量url_map保存所有的(url, endpoint, method)关系

    2. 成员变量view_functions保存所有的{endpoint, function}关系

    所以,对于一个url,只要能找到(url,endpoint,method),就能根据endpoint找到对应的function

    route的完整流程

    首先,建立Flask对象:

    app = Flask(__name__)

    然后,建立urlfunction之间的映射关系:

    @app.route('/')
    def hello_world():
        return 'Hello World!'

    在装饰器route中,创建(url, endpoint, method){endpoint: function}两组映射关系:

    if endpoint is None:
        endpoint = view_func.__name__ # 默认使用响应函数名作为endpoint
    self.url_map.add(Rule(url, endpoint, method)) # 保存(url, endpoint, method)映射关系
    self.view_functions[endpoint] = view_func  # 保存{endpoint: function}映射关系

    这样,就完成了对url和响应函数的映射关系。

    下一步,调用WSGI server响应http请求,在文章开始的示例中使用:

    app.run()

    调用python标准库提供的WSGI server,在实际使用时,可能是gunicornuwsgi

    不论server是什么,最终都会调用Flask.__call__函数。这个函数完成request dispatch的任务。

    对于request dispatch而言,首先根据请求,解析environ,得到url,
    然后调用Map.match函数,这个函数会最终找到预先保存的(url, endpoint, method)映射,
    然后返回(endpoint, url请求参数),
    由于得到了endpoint,然后,可以从Flask.view_functions中直接取到对应的响应函数,
    所以,可以直接进行函数调用

    self.view_functions[endpoint](url请求参数)

    至此,就完成了完整的route

    总结

    1. flaskFlask类是WSGIdispatch middleware

    2. Flaskurl_map保存所有的(url, endpoint, method)映射关系;

    3. Flaskview_functions保存所有的{endpoint: function}映射关系;

    4. dispath request就是根据url找到endpoint,再根据endpoint找到function,最后调用function的过程

    https://segmentfault.com/a/1190000004213652

  • 相关阅读:
    一致性哈希算法
    Tcp 3次握手 4次挥手
    计算机字符编码编年史
    虚拟机字节码指令表 JVM
    计算机是如何计算的、运行时栈帧分析(神奇i++续)
    神奇的i++
    记一次 springboot 参数解析 bug调试 HandlerMethodArgumentResolver
    String+、intern()、字符串常量池
    签名和加密的区别(详细)
    java之设计模式汇总
  • 原文地址:https://www.cnblogs.com/leijiangtao/p/4529376.html
Copyright © 2011-2022 走看看