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

推荐订阅源

N
News and Events Feed by Topic
V
V2EX
博客园 - 【当耐特】
Vercel News
Vercel News
雷峰网
雷峰网
爱范儿
爱范儿
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
F
Full Disclosure
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog
NISL@THU
NISL@THU
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Microsoft Security Blog
Microsoft Security Blog
腾讯CDC
P
Proofpoint News Feed
B
Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
K
Kaspersky official blog
I
InfoQ
Google Online Security Blog
Google Online Security Blog
L
LINUX DO - 最新话题
Project Zero
Project Zero
Engineering at Meta
Engineering at Meta
V
Visual Studio Blog
AI
AI
Schneier on Security
Schneier on Security
B
Blog RSS Feed
T
Tor Project blog
H
Help Net Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LINUX DO - 热门话题
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
T
Threat Research - Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Cyber Attacks, Cyber Crime and Cyber Security
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
V2EX - 技术
V2EX - 技术
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
Arctic Wolf
Webroot Blog
Webroot Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main

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是怎么做交并集运算的?
Tornado 源码阅读
2016-03-14 · via Jiajun的技术笔记

准备开始学习django的文档和源代码了,先总结一下看tornado源码的收获,防止忘记。 这里写的是tornadov1.2的代码,因为我看了一遍4.3版本,和1.2思路相差不大,只是 utils更多了,1.2版本代码量少,更有利于理清思路。列出我们将会涉及到的文件:

.. code:: bash

# tree
.
├── httpserver.py
├── httputil.py
├── __init__.py
├── ioloop.py
├── iostream.py
├── web.py

自上而下 - 总体概览

首先,我们来一张图,表明tornado整体的框架,但是并不涉及tornado中所有文件和类, 只是其中一部分而已。

.. image:: img/tornado.png

不太会画图,描述一下图片的意思吧:

  • tornado是以IOLoop为核心的(虽然没有画在中间), IOLoop <https://github.com/tornadoweb/tornado/blob/branch1.2/tornado/ioloop.py#L43>__ 是对epoll, kqueue, select的一层包装,是异步的核心,相当于 python3.4 引入的 DefaultSelector <https://docs.python.org/3/library/selectors.html#selectors.DefaultSelector>__

  • Application <https://github.com/tornadoweb/tornado/blob/branch1.2/tornado/web.py#L987>__ 是整个web应用的配置中心,配置一些东西比如 secret_cookie 等,其中还包括了 URL正则和对应的Handler 列表。

  • HTTPServer 或者是 WSGI 负责解析HTTP请求

  • HTTPConnection是一次处理的包装,在HTTPServer初始化的时候,会把Application的实例 传到HTTPServer中,当请求来了,就调用application,从而执行 __call__ 方法

.. code:: python

def __call__(self, request):
    """Called by HTTPServer to execute the request."""
    transforms = [t(request) for t in self.transforms]
    handler = None
    args = []
    kwargs = {}
    handlers = self._get_host_handlers(request)
    if not handlers:
        handler = RedirectHandler(
            self, request, url="http://" + self.default_host + "/")
    else:
        for spec in handlers:
            match = spec.regex.match(request.path)
            if match:
                # None-safe wrapper around urllib.unquote to handle
                # unmatched optional groups correctly
                def unquote(s):
                    if s is None: return s
                    return urllib.unquote(s)
                handler = spec.handler_class(self, request, **spec.kwargs)
                # Pass matched groups to the handler.  Since
                # match.groups() includes both named and unnamed groups,
                # we want to use either groups or groupdict but not both.
                kwargs = dict((k, unquote(v))
                              for (k, v) in match.groupdict().iteritems())
                if kwargs:
                    args = []
                else:
                    args = [unquote(s) for s in match.groups()]
                break
        if not handler:
            handler = ErrorHandler(self, request, status_code=404)

    # In debug mode, re-compile templates and reload static files on every
    # request so you don't need to restart to see changes
    if self.settings.get("debug"):
        if getattr(RequestHandler, "_templates", None):
            for loader in RequestHandler._templates.values():
                loader.reset()
        RequestHandler._static_hashes = {}

    handler._execute(transforms, *args, **kwargs)
    return handler
  • IOStream是socket上的一层包装,也会传到HTTPConnection中。

从socket开始

好了,概览看完了,接下来我们从服务器socket开始过一遍。

.. code:: python

application = web.Application([
    (r"/", MainPageHandler),
])
http_server = httpserver.HTTPServer(application)
http_server.listen(8080)
ioloop.IOLoop.instance().start()

这里我们看到了,首先实例化Application,里边设置了路由表。

看看HTTPServer的demo:

.. code:: python

import httpserver
import ioloop
def handle_request(request):
    message = "You requested %s\n" % request.uri
    request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (
                    len(message), message))
    request.finish()
http_server = httpserver.HTTPServer(handle_request)
http_server.listen(8888)
ioloop.IOLoop.instance().start()

实例化HTTPServer的时候,我们就要给它传一个处理http请求的东西,在项目中,也就是我们 上面经过实例化过的application。调用 http_server.listen 方法就会调用 http_server.start ::

def listen(self, port, address=""):
    self.bind(port, address)
    self.start(1)

start做了一件最重要的事情:把自己加入IOLoop。这样子,每当有请求来,epoll就会把 当前进程拉起来,然后开始执行,当然,首先,我们要找到 poll 才能说服你嘛。别着急, 我们先看看 ioloop.IOLoop.instance().start() 做了什么。 在 这里 <https://github.com/tornadoweb/tornado/blob/branch1.2/tornado/ioloop.py#L211>__ 我们找到了事件循环,是吧,我就说一定是这样的^_^:

.. code:: python

while True:

并且在 这里 <https://github.com/tornadoweb/tornado/blob/branch1.2/tornado/ioloop.py#L243>__ 我们看到了 poll。

.. code:: python

event_pairs = self._impl.poll(poll_timeout)

然后就在 self._handlers 里找到对应的handler,去处理。对于已经打开的socket, 那自然就是读写数据了,那么对于服务器起的这个socket,注册的handler是个啥呢? 玄机还藏在 httpserver.py 里, 点这里 <https://github.com/tornadoweb/tornado/blob/branch1.2/tornado/httpserver.py#L175>__ :

.. code:: python

self.io_loop.add_handler(self._socket.fileno(),
        self._handle_events,
        ioloop.IOLoop.READ)

当服务器socket可读时,调用 self._handle_events 方法,ok,我们继续看::

# 省略了一部分代码,故不用Python高亮
while True:
    try:
        connection, address = self._socket.accept()
    except ...

    stream = iostream.IOStream(connection, io_loop=self.io_loop)
    HTTPConnection(stream, address, self.request_callback,
            self.no_keep_alive, self.xheaders)

实例化了一个 HTTPConnection, 并且还设置了 callback,这里的callback就是我们 实际项目中的 application. HTTPConnection的 __init__ 函数最后::

self._header_callback = stack_context.wrap(self._on_headers)
self.stream.read_until("\r\n\r\n", self._header_callback)

嗯~parse HTTP的头部,然后工厂模式调用不同的方法是不是?我猜是这样的,继续看 下去,越来越带劲了,哈哈哈哈::

def _on_headers(self, data):
    # blablabla, parse...
    method, uri, version = start_line.split(" ")  # GET /index HTTP/1.1
    # blablabla, ...
    self._request = HTTPRequest(
        connection=self, method=method, uri=uri, version=version,
        headers=headers, remote_ip=self.address[0]
    )
    # blablabla, ...
    self.request_callback(self._request)

到这里,我还记得,最后一句,就是调用 application.__call__ 方法,你还记得吗? 希望还没晕。千万别转晕了,现在我们要转回去,跳到 Application 里了哦~

.. code:: python

def __call__(self, request):
    transforms = [t(request) for t in self.transforms]
    handler = None
    args = []
    kwargs = {}
    handlers = self._get_host_handlers(request)
    if not handlers:
        pass  # 略

咱们继续跳,现在该看 self._get_host_handlers 干了什么了!

.. code:: python

def _get_host_handlers(self, request):
    host = request.host.lower().split(':')[0]
    for pattern, handlers in self.handlers:
        if pattern.match(host):
            return handlers
    return None

看到没,在application的 handlers 里找到匹配的 handler,然后返回,所以文档上说, 如果一个URL匹配到了n个handler,调用的是第一个被找到的(根据你放列表的顺序)。 我们先继续往下看,初始化完 handler 以后,代码执行了 handler的 _execute 方法, 以及在 这里 <https://github.com/tornadoweb/tornado/blob/branch1.2/tornado/web.py#L1182>__ 我们看到了执行代码的操作::

handler = spec.handler_class(self, request, **spec.kwargs)  # 找 Handler
handler._execute(transforms, *args, **kwargs)  # 执行

然后我们再回去看 __call__ 里的代码,首先我们看到 __init__ ::

def __init__(self, application, request, **kwargs):

然后,我们再看 _execute :

.. code:: python

def _execute(self, transforms, *args, **kwargs):
    """Executes this request with the given output transforms."""
    self._transforms = transforms
    with stack_context.ExceptionStackContext(
        self._stack_context_handle_exception):
        if self.request.method not in self.SUPPORTED_METHODS:
            raise HTTPError(405)
        # If XSRF cookies are turned on, reject form submissions without
        # the proper cookie
        if self.request.method not in ("GET", "HEAD") and \
        self.application.settings.get("xsrf_cookies"):
            self.check_xsrf_cookie()
        self.prepare()
        if not self._finished:
            getattr(self, self.request.method.lower())(*args, **kwargs)
            if self._auto_finish and not self._finished:
                self.finish()

看到没,工厂在此!::

getattr(self, self.request.method.lower())(*args, **kwargs)

干!终于找到你了!这里前后调用了两个钩子,首先 self.prepare 然后 self.finish 现在你知道可以用他们来做一些类似中间件的工作了吧~

好了,这之后就是 finish, flush 等等了,我就不继续讲下去了。总结一下,一开始我以为 工厂会在很早出现,没想到一直到 _execute 才出现-。-。

估计这样做是为了代码不太散乱?

如果你对最新的tornado代码有兴趣,也可以去看,不过看完之后可能会一头雾水,因为 对比1.2来说,代码量实在是大多了~