惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
S
SegmentFault 最新的问题
Project Zero
Project Zero
D
DataBreaches.Net
I
InfoQ
L
Lohrmann on Cybersecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
The Register - Security
The Register - Security
Recorded Future
Recorded Future
Vercel News
Vercel News
博客园 - 司徒正美
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
Intezer
The Hacker News
The Hacker News
F
Fortinet All Blogs
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
Help Net Security
Help Net Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Scott Helme
Scott Helme
T
Threatpost
爱范儿
爱范儿
N
Netflix TechBlog - Medium
D
Docker
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
K
Kaspersky official blog
H
Help Net Security
S
Secure Thoughts
T
Threat Research - Cisco Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security @ Cisco Blogs
Cyberwarzone
Cyberwarzone
N
News and Events Feed by Topic
G
Google Developers Blog
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
B
Blog
Google DeepMind News
Google DeepMind News
Recent Announcements
Recent Announcements
Simon Willison's Weblog
Simon Willison's Weblog
S
Securelist
P
Privacy International News Feed
Spread Privacy
Spread Privacy
The Last Watchdog
The Last Watchdog

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例) 读《为什么学生不喜欢上学》 OpenAI Prompt Engineering 摘录和总结 读《打造真正的新产品》 VueJS 总结 Linux 自动挂载 alist 提供的webdav FreeBSD 使用 vm-bhyve 安装Debian虚拟机 FreeBSD 和 Linux 网卡聚合实现提速 GPT 帮我搞定了时区转换问题 长任务系统如何处理? macOS/Linux 编译 InputLeap 使用开源软KVM - synergy-core 解决 macOS 终端hostname一直变化问题 KVM 共享 Intel 集成显卡 PromQL 备忘 读《格鲁夫给经理人的第一课》 读《打开心智》 为什么要把复杂的联表操作拆成多个单表查询? 红包系统的设计 MySQL Index Condition Pushdown Optimization Go mod 简明教程 OpenWRT 使用 Android/iOS USB 网络 搭建旁路由 Golang gRPC 错误处理 编写可维护的单元测试代码 OAuth 2 详解(六):Authorization Code Flow with PKCE OAuth 2 详解(五):Device Authorization Flow OAuth 2 详解(三):Resource Owner Password Credentials Grant OAuth 2 详解(四):Client Credentials Flow OAuth 2 详解(二):Implict Grant Flow OAuth 2 详解(一):简介及 Authorization Code 模式 ElasticSearch 学习笔记 三种git流程以及发版模型 错误处理实践 权限模型(RBAC/ABAC) OIDC(OpenID Connect) 简介 任务队列简介 PostgreSQL 操作笔记 使用Drone CI构建CI/CD系统 Golang migrate 做数据库变更管理 使用PostgreSQL做搜索引擎 Nginx 源码阅读(三): 连接池、内存池 Nginx 源码阅读(二): 请求处理 Nginx 源码阅读(一): 启动流程 Go 泛型简明教程 KVM 显卡穿透给 Windows 使用 HTTP Router 处理 Telegram Bot 按钮回调 使用反射(reflect)对结构体赋值 GIN 是如何绑定参数的 你好 2022(2021 年终总结) 用Go导入大型CSV到PostgreSQL 使用 OpenWRT 搭建软路由 使用软KVM切换器 barrier 共享键鼠 SQL 防注入及原理 使用 gomock 测试 Go 代码 gevent不是黑魔法(二): gevent 实现 gevent不是黑魔法(一): greenlet 实现 用 entgo 替代 gorm 应用内使用crontab不是那么方便 单测时要不要 mock 数据库? Sentry 自建指南 用selenium完成自动化任务 用闲置的安卓手机做垃圾电话短信过滤 推荐三个时间管理工具 一次事故反思 当JS遇到uint64:JS整数溢出问题 SQLite3 存储以及ACID原理 Redis源码阅读:pub/sub实现 Redis源码阅读:zset实现 Redis源码阅读:bitmap 位图的运算 Redis源码阅读:set是怎么做交并集运算的?
阅读Flask源码
2016-09-15 · via Jiajun的技术笔记

看到很多人推荐新手去阅读Flask,但其实个人并不推荐,因为单说Flask的话,代码量 确实很少,但是Flask完全建立在Werkzeug之上,如果把Werkzeug的代码加起来,代码量 可就不少了,而要完全弄懂Flask,就一定少不了看Werkzeug。

接下来,我们就一起来读一下Flask0.1的代码吧 :)

首先,我们把代码clone下来:

.. code:: bash

$ git clone https://github.com/pallets/flask
$ cd flask
$ git checkout 0.1

打开 flask.py 你就能看到,其实没多少行代码,按照惯例,找一下看有没有入口 函数,拉到最下面,没有 __main__ ,但是我们发现了这个:

.. code:: python

# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

LocalStack ??? LocalProxy ??? 这是什么东西。。。看一下文件,原来是从 Werkzeug导入的::

from werkzeug import Request as RequestBase, Response as ResponseBase, \
    LocalStack, LocalProxy, create_environ, cached_property, \
    SharedDataMiddleware

嗯,看来终究我们都逃脱不了Werkzeug的魔爪,不过,我们暂时先不理他,我们先来看看 flask的helloworld程序的调用栈是怎样的,这样有利于我们有看代码的目标,而不是拿着 代码毫无目的的随便看,首先我们新建一个virtualenv,然后装上jinja2和werkzeug,并 且把之前0.1版本的 flask.py 拷贝到那个目录,激活当前venv(这个我就不贴出来了):

.. code:: python

# coding: utf-8


from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello():
    import pdb; pdb.set_trace()  # TODO remove it
    return "hello world"


if __name__ == "__main__":
    app.run()

然后运行,并且新开一个终端或者浏览器的tab,来访问::

$ python2 main.py
$ # 另一个终端
$ http localhost:5000

然后就会执行到断点,此时我们再pdb里打印出当前调用栈:

.. code:: python

(py2kenv) ➜  src python main.py
* Running on http://localhost:5000/ (Press CTRL+C to quit)
> /home/jiajun/Code/python/py2kenv/src/main.py(12)hello()
-> return "hello world"
(Pdb) w
/home/jiajun/Code/python/py2kenv/src/main.py(16)<module>()
-> app.run()
/home/jiajun/Code/python/py2kenv/src/flask.py(331)run()
-> return run_simple(host, port, self, **options)
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(694)run_simple()
-> inner()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(659)inner()
-> srv.serve_forever()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(499)serve_forever()
-> HTTPServer.serve_forever(self)
/usr/lib64/python2.7/SocketServer.py(233)serve_forever()
-> self._handle_request_noblock()
/usr/lib64/python2.7/SocketServer.py(290)_handle_request_noblock()
-> self.process_request(request, client_address)
/usr/lib64/python2.7/SocketServer.py(318)process_request()
-> self.finish_request(request, client_address)
/usr/lib64/python2.7/SocketServer.py(331)finish_request()
-> self.RequestHandlerClass(request, client_address, self)
/usr/lib64/python2.7/SocketServer.py(652)__init__()
-> self.handle()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(216)handle()
-> rv = BaseHTTPRequestHandler.handle(self)
/usr/lib64/python2.7/BaseHTTPServer.py(340)handle()
-> self.handle_one_request()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(251)handle_one_request()
-> return self.run_wsgi()
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(193)run_wsgi()
-> execute(self.server.app)
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/serving.py(181)execute()
-> application_iter = app(environ, start_response)
/home/jiajun/Code/python/py2kenv/src/flask.py(655)__call__()
-> return self.wsgi_app(environ, start_response)
/home/jiajun/Code/python/py2kenv/lib/python2.7/site-packages/werkzeug/wsgi.py(599)__call__()
-> return self.app(environ, start_response)
/home/jiajun/Code/python/py2kenv/src/flask.py(626)wsgi_app()
-> rv = self.dispatch_request()
/home/jiajun/Code/python/py2kenv/src/flask.py(544)dispatch_request()
-> return self.view_functions[endpoint](**values)
> /home/jiajun/Code/python/py2kenv/src/main.py(12)hello()
-> return "hello world"

我们从下往上看,或者从上往下看,都能推理出我们的调用链::

app.run[main.py] -> run_simple[flask.py] -> srv.serve_forever[werkzeug] \
-> HTTPServer.serve_forever -> SocketServer._handle_request_noblock -> \
SocketServer.process_request -> werkzeug.run_wsgi -> flask.dispatch_request \
-> app.view_functions[endpoint](**values)[也就是我们的 `def hello`]

哇,好长,不行,如果一个一个深究,恐怕是要很长时间,无论是看代码还是写代码,我们 都要学会抽象,也就是说,我们要把某一部分东西当做一个模块,我们不管他里面是怎么实现 的,只管,他会完成这样的功能。所以为了顺利的看完 flask 的代码,我们不管 werkzeug 以下的调用链,也就是不管 SocketServer, HTTPServer 之类的。简化一下上面的调用链 (其实在这里我们所说的抽象,就是自动忽略细节)::

app.run[main.py] -> run_simple[flask.py] -> srv.serve_forever[werkzeug] \
-> werkzeug.run_wsgi -> flask.dispatch_request \
-> app.view_functions[endpoint](**values)[也就是我们的 `def hello`]

现在我们是不是已经站在一个更高的角度来看待flask了,werkzeug会自动调用好底层的 SocketServer,有请求来了,就会发到werkzeug上,然后就会处理请求。不过,我们说过, 暂时不管werkzeug,所以我们再来进行一次“抽象”::

app.run[main.py] -> run_simple[flask.py] -> flask.dispatch_request \
-> app.view_functions[endpoint](**values)[也就是我们的 `def hello`]

哦~原来flask这么简单,没错,因为我们忽略了werkzeug做了那么多事情嘛。 好,接下来我们照着调用链,深入到代码里去,首先我们来看 app.run :

.. code:: python

def run(self, host='localhost', port=5000, **options):
    from werkzeug import run_simple
    if 'debug' in options:
        self.debug = options.pop('debug')
    options.setdefault('use_reloader', self.debug)
    options.setdefault('use_debugger', self.debug)
    return run_simple(host, port, self, **options)

哦,又是调用werkzeug。。。哎,反正是监听服务器就对了,好,接下来我们看下一个:

.. code:: python

def dispatch_request(self):
    """Does the request dispatching.  Matches the URL and returns the
    return value of the view or error handler.  This does not have to
    be a response object.  In order to convert the return value to a
    proper response object, call :func:`make_response`.
    """
    try:
        endpoint, values = self.match_request()
        return self.view_functions[endpoint](**values)
    except HTTPException, e:
        handler = self.error_handlers.get(e.code)
        if handler is None:
            return e
        return handler(e)
    except Exception, e:
        handler = self.error_handlers.get(500)
        if self.debug or handler is None:
            raise
        return handler(e)

嗯,这个函数我没有把注释去掉,是因为注释还是很有说明性的,一般读代码,有注释 都要先看看注释,就像上面的run函数其实也有注释,但是为了篇幅,我把注释删掉了。 真正读代码的时候,请优先读注释。

这个函数,作用就是,看请求是否有匹配到,如果没有的话,就报错。这一步,同时看完了 调用链的后两部分,但是同时为我们引入了新的问题,那就是,WTF is endpoint???

为了调查真相,再一次进入pdb,然后看看这些都是啥。

.. code:: python

(Pdb) app.view_functions
{'hello': <function hello at 0x7fb9a3f5ce60>}

也就是说,endpoint是一个字符串,然后对应了一个函数,那endpoint是在哪里设置的呢?

.. code:: python

def route(self, rule, **options):
    """很多注释,值得一读"""
    def decorator(f):
        self.add_url_rule(rule, f.__name__, **options)
        self.view_functions[f.__name__] = f
        return f
    return decorator

也就是说,flask的url匹配是这样的: url -> endpoint -> func 要问我为什么? 我也不清楚,可以看看 [#]_ 和 [#]_ ,分别是stackoverflow的一个解释,和我提出的 关于endpoint的作用的问题。如果你能找到endpoint的存在必要性,请务必告知我,谢谢。

看到这里,好像已经看完了flask是怎么工作的了?no,刚刚开始而已。提个问题, 我们知道flask里读取参数是这样的::

from flask import request
test = request.args.get("test", None)

如果有多线程存在,他是怎么做到线程安全的?暂且不说线程,我们是怎么从一个导入的 模块里读取我们当前请求的参数的?

还记得上面我们最开始贴的那段代码嘛?

.. code:: python

# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

原来 flask.request 是这样一个东西,看来我们有必要深入到werkzeug里看看 LocalProxy 是如何工作的了。不过在此之前我猜我们需要先看看 LocalStack 这个东西,因为上面 的代码显示,他是作为一个参数传到 LocalProxy 里的。

好吧,接下来我们把werkzeug的代码搞下来,然后搜一下这货在哪:

.. code:: bash

(py2kenv) ➜  werkzeug git:(master) ack 'class LocalProxy'
werkzeug/local.py
254:class LocalProxy(object):
(py2kenv) ➜  werkzeug git:(master) ack 'class LocalStack'
werkzeug/local.py
89:class LocalStack(object):
(py2kenv) ➜  werkzeug git:(master)

所以接下来我们打开 local.py 来看看,看到 LocalStack 的注释,这就是一个 栈(名字其实就说明了它是栈)。看到 __init__ 里最终存储数据的还是 Local 类, 所以接下来我们来看 Local 类。

.. code:: python

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] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

这个类的作用就是,存东西的时候,实际上存在 __storage__ 里,而它是一个字典。 字典的key是线程id,value是另外的dict。而我们的 LocalStack 就是,之前说的value 里,以 “stack” 为key,list为value的一个键值对,我们来验证一下:

.. code:: python

(Pdb) from flask import _request_ctx_stack as test
(Pdb) test._local
<werkzeug.local.Local object at 0x7fec126179d0>
(Pdb) for i in test._local: print i
(140651991053184, {'stack': [<flask._RequestContext object at 0x7fec11d2d2d0>]})
(Pdb)

通过 LocalProxy ,我们执行如下代码的时候::

from flask import request

@app.route("/")
def hello():
    name = request.args.get("name", None)
    if name:
        return "Hello %s" % name
    else:
        return "Hello World"

request总是能正确的指向当前所压入的请求。

小结

好了,Flask的代码我们暂时看到这里。下一篇,我准备探索一下, Gunicorn + Gevent的工作原理(也就是,他们俩使怎样让Flask的代码能够异步执行的)。

.. [#] http://stackoverflow.com/questions/19261833/what-is-an-endpoint-in-flask/19262349#19262349 .. [#] https://www.v2ex.com/t/304941#reply11