资讯专栏INFORMATION COLUMN

深入理解flask框架(1):WSGI与路由

xiaolinbang / 412人阅读

摘要:是一个小而美的微框架,主要依赖于和,只建立和的桥梁,前者实现一个合适的应用,后者处理模板。本文主要分析了是在基础上如何构建接口与路由系统的。网关协议的本质是为了解耦,实现服务器和应用程序的分离,就是一个支持的服务器与应用程序之间的约定。

flask是一个小而美的微框架,主要依赖于Werkezug 和 Jinja2, Flask 只建立 Werkezug 和 Jinja2 的桥梁,前者实现一个合适的 WSGI 应用,后者处理模板。 Flask 也绑定了一些通用的标准库包,比如 logging 。其它所有一切取决于扩展。
本文主要分析了flask是在Werkezug基础上如何构建WSGI接口与路由系统的。

WSGI是什么?

WSGI的本质是一种约定,是Python web开发中 web服务器与web应用程序之间数据交互的约定。
网关协议的本质是为了解耦,实现web服务器和web应用程序的分离,WSGI就是一个支持WSGI的web服务器与Python web应用程序之间的约定。

为了支持WSGI,服务器需要做什么?

在一起写一个 Web 服务器(2)
中给出了一个支持WSGI的服务器实现,下面的代码以他为例。
一个WSGI服务器需要实现两个函数:
1.解析http请求,为应用程序提供environ字典

    def get_environ(self):
        env = {}
        env["wsgi.version"]      = (1, 0)
        env["wsgi.url_scheme"]   = "http"
        env["wsgi.input"]        = StringIO.StringIO(self.request_data)
        env["wsgi.errors"]       = sys.stderr
        env["wsgi.multithread"]  = False
        env["wsgi.multiprocess"] = False
        env["wsgi.run_once"]     = False
        env["REQUEST_METHOD"]    = self.request_method    # GET
        env["PATH_INFO"]         = self.path              # /hello
        env["SERVER_NAME"]       = self.server_name       # localhost
        env["SERVER_PORT"]       = str(self.server_port)  # 8888
        return env

2.实现start response函数

    def start_response(self, status, response_headers, exc_info=None):
        # Add necessary server headers
        server_headers = [
            ("Date", "Tue, 31 Mar 2015 12:54:48 GMT"),
            ("Server", "WSGIServer 0.2"),
        ]
        self.headers_set = [status, response_headers + server_headers]
        # To adhere to WSGI specification the start_response must return
        # a "write" callable. We simplicity"s sake we"ll ignore that detail
        # for now.
        # return self.finish_response
WSGI服务器调用Python应用程序

这里展示了服务器与应用程序交互的过程:
1.从客户端获取到请求
2.通过get_env获得envir变量
3.调用应用程序,传入env和start_response函数,并获得响应
4.将响应返回给客户端

    def handle_one_request(self):
        self.request_data = request_data = self.client_connection.recv(1024)
        print("".join(
            "< {line}
".format(line=line)
            for line in request_data.splitlines()
        ))
 
        self.parse_request(request_data)
        env = self.get_environ()
        result = self.application(env, self.start_response)//调用应用程序
        self.finish_response(result)
Python 应用程序需要做什么?

在上述这个过程中,Python应用程序需要做什么呢?
主要工作就是根据输入的environ字典信息生成相应的http报文返回给服务器。

from wsgiref.simple_server import make_server

def simple_app(environ, start_response):
    status = "200 OK"
    response_headers = [("Content-type", "text/plain")]
    start_response(status, response_headers)
    return [u"This is hello wsgi app".encode("utf8")]

httpd = make_server("", 8000, simple_app)
print "Serving on port 8000..."
httpd.serve_forever()
flask中如何实现WSGI接口

1.通过__call__方法将Flask对象变为可调用

    def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)

2.实现wsgi_app函数处理web服务器转发的请求

    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:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)
路由是什么?

在web开发中,路由是指根据url分配到对应的处理程序。
在上面的应用中,一个包含了url路径信息的environ对应一个视图函数,问题就在于他只能处理一个固定的路由,如果我们需要根据我们的需求自由的绑定url和视图函数,这就需要我们自己建立一个新的间接层,这就是web框架的路由系统。

flask的路由是如何实现的?

Flask类中支持路由功能的数据结构,在__init__函数中初始化:

url_rule_class = Rule
self.url_map = Map()
self.view_functions = {}

Map和Rule是werkzeug中实现的映射类和路由类。

    >>> m = Map([
    ...     # Static URLs
    ...     Rule("/", endpoint="static/index"),
    ...     Rule("/about", endpoint="static/about"),
    ...     Rule("/help", endpoint="static/help"),
    ...     # Knowledge Base
    ...     Subdomain("kb", [
    ...         Rule("/", endpoint="kb/index"),
    ...         Rule("/browse/", endpoint="kb/browse"),
    ...         Rule("/browse//", endpoint="kb/browse"),
    ...         Rule("/browse//", endpoint="kb/browse")
    ...     ])
    ... ], default_subdomain="www")

这里我们注意到Map类先建立了url到endpoint的映射,这样做的目的是为了实现动态路由功能。

而view_functions是一个字典,它负责建立endpoint和视图函数之间的映射关系。
下面是一个小实验,证明我们所说的映射关系

>>> from flask import Flask
>>> app = Flask(__name__)
>>> @app.route("/")
... def index():
...     return "hello world"
... 
>>> app.url_map
Map([ index>,
 " (HEAD, GET, OPTIONS) -> static>])
>>> app.view_functions
{"index": , "static": >}

这里我们可以看到从 index>,"index": ,通过endpoint这个中间量,我们让把路由和函数建立了映射关系。
要注意一下,为什么会有"/static/"这个路由呢,这是应为在初始化时flask调用了add_url_rule函数做了如下绑定:

        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 + "/",
                endpoint="static",
                host=static_host,
                view_func=self.send_static_file
            )
注册路由

在flask中注册路由有两种方式,一种是用route装饰器,如上所示,另一种是直接调用add_url_rule函数绑定视图类,但是本质上二者都是调用add_url_rule函数,下面我们来看一下add_url_rule函数的实现。
在Flask的add_url_rule函数很长,但是核心的代码为以下几行:

self.url_map.add(rule)
rule = self.url_rule_class(rule, methods=methods, **options)
self.view_functions[endpoint] = view_func

1.装饰器

def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

2.视图类

        class CounterAPI(MethodView):
            def get(self):
                return session.get("counter", 0)
            def post(self):
                session["counter"] = session.get("counter", 0) + 1
                return "OK"
        app.add_url_rule("/counter", view_func=CounterAPI.as_view("counter"))

注册路由之后,flask就需要分发路由,调用相应的视图函数。

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()
        return self.view_functions[rule.endpoint](**req.view_args)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41115.html

相关文章

  • flask 源码解析:简介

    摘要:简介官网上对它的定位是一个微开发框架。另外一个必须理解的概念是,简单来说就是一套和框架应用之间的协议。功能比较丰富,支持解析自动防止攻击继承变量过滤器流程逻辑支持代码逻辑集成等等。那么,从下一篇文章,我们就正式开始源码之旅了 文章属于作者原创,原文发布在个人博客。 flask 简介 Flask 官网上对它的定位是一个微 python web 开发框架。 Flask is a micro...

    megatron 评论0 收藏0
  • flask源码走读

    摘要:另外,如果你对模板渲染部分的内容感兴趣,也可以考虑阅读文档文档文档源码阅读,可以参考下面的函数打断点,再测试一个请求,理清过程。 Flask-Origin 源码版本 一直想好好理一下flask的实现,这个项目有Flask 0.1版本源码并加了注解,挺清晰明了的,我在其基础上完成了对Werkzeug的理解部分,大家如果想深入学习的话,可以参考werkzeug_flow.md. 阅读前 为...

    Coly 评论0 收藏0
  • 如何理解Nginx, WSGI, Flask之间的关系

    摘要:通过查阅了些资料,总算把它们的关系理清了。在这个过程中,服务器的作用是接收请求处理请求返回响应服务器是一类特殊的服务器,其作用是主要是接收请求并返回响应。正是为了替代而出现的。三结语最后以,,之间的对话结束本文。 刚转行互联网行业,听到了许多名词:Flask、Django、WSGI、 Nginx、Apache等等,一直无法搞清楚这些开源项目之间的关系,直至看到这篇文章后感觉醍醐灌顶,以...

    魏明 评论0 收藏0
  • flask 源码解析:应用启动流程

    摘要:中有一个非常重要的概念每个应用都是一个可调用的对象。它规定了的接口,会调用,并传给它两个参数包含了请求的所有信息,是处理完之后需要调用的函数,参数是状态码响应头部还有错误信息。一般来说,嵌套的最后一层是业务应用,中间就是。 文章属于作者原创,原文发布在个人博客。 WSGI 所有的 python web 框架都要遵循 WSGI 协议,如果对 WSGI 不清楚,可以查看我之前的介绍文章。 ...

    whatsns 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<