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

推荐订阅源

The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
T
Threatpost
WordPress大学
WordPress大学
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
PCI Perspectives
PCI Perspectives
T
The Exploit Database - CXSecurity.com
Y
Y Combinator Blog
雷峰网
雷峰网
爱范儿
爱范儿
The Hacker News
The Hacker News
Last Week in AI
Last Week in AI
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
S
Securelist
宝玉的分享
宝玉的分享
L
LangChain Blog
O
OpenAI News
AI
AI
P
Privacy International News Feed
L
LINUX DO - 最新话题
D
DataBreaches.Net
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Attack and Defense Labs
Attack and Defense Labs
罗磊的独立博客
M
MIT News - Artificial intelligence
Security Archives - TechRepublic
Security Archives - TechRepublic
月光博客
月光博客
博客园 - 【当耐特】
T
Tailwind CSS Blog
C
Cybersecurity and Infrastructure Security Agency CISA
H
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
Jina AI
Jina AI
The Last Watchdog
The Last Watchdog
K
Kaspersky official blog
Webroot Blog
Webroot Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Blog — PlanetScale
Blog — PlanetScale
MyScale Blog
MyScale Blog
MongoDB | Blog
MongoDB | Blog
P
Proofpoint News Feed
Recorded Future
Recorded Future
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 三生石上(FineUI控件)
The Cloudflare Blog

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是怎么做交并集运算的?
Web开发系列(四):Flask, Tornado和WSGI
Jiajun Huang · 2017-10-16 · via Jiajun的技术笔记

上篇 所讲,作为一个web服务器, 我们需要建立socket连接,并且监听在指定端口,当请求来到时我们要解析请求的内容,做出判断,给出响应, 然后关闭连接,进行下一个服务。

可是谁也不愿意需要做web开发的时候都从头开始,然后每次都处理这么多事情,那简直是太麻烦了。于是便有了框架, 今天我们讲两个框架,Flask,Tornado。

Flask

Flask似乎很受欢迎,emm。。。实际上我目前所在公司和上一家都是用Flask,不过我个人并不喜欢Flask的设计,最讨厌两点:

  • proxy
  • 借助proxy提供伪全局变量

那么,怎么写一个简单的Flask应用呢?在开始之前我们先来讲讲一个web框架大概需要哪些东西。

首先我们需要一个路由器,不是发射Wi-Fi的那个路由器,而是将URL里,不同的URL指向不同的函数或者其他能处理并且作出响应的 东西,我们叫做路由器,或者叫路由。此外我们需要一个东西,包含一些默认的配置,一般情况下都会叫做 “app”,一般都会把router 放在app里。这样我们就可以做出一个简单的web框架。

但是框架之所以叫做框架,是因为它规定了一系列流程,定义好了一系列接口,应用程序员只需要按照给定的接口写出符合接口的代码, 便可以做出web服务来。比如吧,我们决定我们的web框架有这样一系列动作:

def before_request(app, request):
    pass

def handle_request(app, request):
    pass

def after_request(app, request):
    pass

我们的应用将会从上至下依次调用函数,那么我们只要实现具体的函数,便可以完成指定的功能。

我们来看一个简单的Flask示例,来自官网:

from flask import Flask
app = Flask(__name__)


@app.route("/")
def hello():
    return "Hello World!"


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

保存并运行,就可以了。Flask的核心之一在于 @app.route 这个装饰器,他有一个概念叫做Blueprint,是什么呢?就是把一伙URL 集结在一起,比如,凡是 /api/say/v1 开头的URL都放在 say_bp_v1 下,那么便可以这样使用:

@say_bp_v1.route("/hello")
def foo():
    pass


@say_bp_v1.route("/world")
def bar():
    pass

其作用吧。。。其实是可以少些很多重复的代码,差不多就这样。我们来看看 @app.route 的源码:

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

所以我们应该追下去看 add_url_rule,这里我们暂不继续 展开。

我们再看一下Flask中最最核心的东西,proxy:

https://github.com/pallets/flask/blob/master/flask/globals.py#L14

继续追到Werkzeug的代码中看 LocalProxyLocalStack:

class LocalStack(object):
    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    def _get__ident_func__(self):
        return self._local.__ident_func__

    def _set__ident_func__(self, value):
        object.__setattr__(self._local, '__ident_func__', value)
    __ident_func__ = property(_get__ident_func__, _set__ident_func__)
    del _get__ident_func__, _set__ident_func__

    def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError('object unbound')
            return rv
        return LocalProxy(_lookup)

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

@implements_bool
class LocalProxy(object):
    __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')

    def __init__(self, local, name=None):
        object.__setattr__(self, '_LocalProxy__local', local)
        object.__setattr__(self, '__name__', name)
        if callable(local) and not hasattr(local, '__release_local__'):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, '__wrapped__', local)

    def _get_current_object(self):
        """Return the current object.  This is useful if you want the real
        object behind the proxy at a time for performance reasons or because
        you want to pass the object into a different context.
        """
        if not hasattr(self.__local, '__release_local__'):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError('no object bound to %s' % self.__name__)

    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    # 略略略

可以看出(当然,肯定不是这样随随便便看两眼,其实Flask的源码还是可以研究研究的),Flask的本质就是,如果你执行以下代码:

from flask import request

@app.route("/")
def foo():
    print(request.args.get("hello"))

request本来是一个导入的object,但实际上从中获取属性或者值时,会从栈顶的ctx里,再取出来,所以他是个代理,哎,不多说了, 等你看过Flask源码之后你就知道这个设计有多么不科学了(虽然很多人都似乎比较喜欢Flask。。。)。

对Flask有兴趣的可以看看我的这篇博客:https://jiajunhuang.com/articles/2016_09_15-flask_source_code.rst.html

Tornado

Tornado我还是比较喜欢,可惜除了web框架之外,数据库或者其他几乎都是阻塞的。Tornado与Flask的函数形式的写法不一样,Tornado 属于class形式的写法,我认为这个设计比较科学,举个例子:

import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

    def post(self):
        self.write("post :)")


def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

于是乎,对同一个URL的get,post,put等请求,我们都可以只要实现对应方法便可以,有人要说了,Flask也有class based view,emm, 是有,你去看看源码看看你会想用吗?

Tornado比我们最上面所说的框架所需要的东西还多了什么呢?还多了一个IOLoop,I/O多路复用,此外借助yield,用同步的方式写异步代码, 当然,前提是带上病毒式传播的decorator—-只要想写非阻塞代码,那么这个decorator便一加到底。

关于yield是如何把本来回调式的代码连接起来编程同步式的代码,可以看这篇博客:https://jiajunhuang.com/articles/2016_11_29-python_yield.md.html

此外我写了一个类似的Tornado的代码,基于Cython和UVLoop: https://github.com/jiajunhuang/storm 有兴趣的话可以看看。

既然Tornado这么好用,性能又高,为什么好像还没有Flask受欢迎呢?因为Web开发虽然看起来就是分析一下请求,给一下响应,但是远 不是这么简单,还需要和数据库打交道,Tornado自身可以写出非阻塞的代码,但是连数据库,想用ORM的时候却不行,所以也不是特别方便。

因此很多人选择使用Flask或者是Django,然后Gunicorn挡在前面,加上Gevent加持,于是又可以愉快的用写同步的方式写异步。说起Gunicorn, 我们就得说说WSGI了。

WSGI

WSGI全名Web Server Gateway Interface,Python界web框架百家争鸣,怎么统一一下呢?于是便有了WSGI这种,定义接口,而非定义实现的方式。 具体需要看看这里:https://www.python.org/dev/peps/pep-3333/#specification-details 实现了对应的接口,便可以接入针对WSGI的 应用例如Gunicorn。

讲完,收工 :)