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

推荐订阅源

F
Fortinet All Blogs
宝玉的分享
宝玉的分享
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Exploit Database - CXSecurity.com
Help Net Security
Help Net Security
腾讯CDC
Project Zero
Project Zero
C
CXSECURITY Database RSS Feed - CXSecurity.com
IT之家
IT之家
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tailwind CSS Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Threatpost
N
News | PayPal Newsroom
C
Cybersecurity and Infrastructure Security Agency CISA
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
SegmentFault 最新的问题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Proofpoint News Feed
A
Arctic Wolf
B
Blog RSS Feed
Forbes - Security
Forbes - Security
P
Privacy & Cybersecurity Law Blog
Attack and Defense Labs
Attack and Defense Labs
V2EX - 技术
V2EX - 技术
P
Proofpoint News Feed
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
阮一峰的网络日志
阮一峰的网络日志
aimingoo的专栏
aimingoo的专栏
T
Tenable Blog
MyScale Blog
MyScale Blog
U
Unit 42
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
WordPress大学
WordPress大学
W
WeLiveSecurity
D
DataBreaches.Net
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
罗磊的独立博客
The Last Watchdog
The Last Watchdog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Vulnerabilities – Threatpost
美团技术团队
Microsoft Security Blog
Microsoft Security Blog

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
用 whitenoise 提供静态文件服务(Python)
laixintao · 2018-11-10 · via 卡瓦邦噶!

Whitenoise 这个项目是一个符合 WSGI 标准的静态文件服务器,因为 WSGI 是可以嵌套的,所以 Whitenoise 可以和你原来的 WSGI 应用配合的很好,迁移成本很小,或者像 Django 这种项目有某种 middleware 机制,迁移就更方便了。本文介绍这个库的使用、为什么要用这个库(尽量说服你 ),以及买二赠一的源代码导读。

从文档掏出来的一个 QuickStart 如下:

Python

1

2

3

4

5

6

7

from whitenoise import WhiteNoise

from my_project import MyWSGIApp

application = MyWSGIApp()

application = WhiteNoise(application, root='/path/to/static/files')

application.add_files('/path/to/more/static/files', prefix='more-files/')

可以看到,其实就是将你的 WSGI app 外面再包一层 WSGI app,即 WhiteNoise。

Django 可以不通过这种方式,因为 Whitenoise 对 Django 做了一些额外的适配,可以使用 Django 原生的中间件机制。

静态文件服务其实就是对于 HTTP 请求,发送对应的文件给用户。这件事情为什么要用 Whitenoise 来做呢?这个项目存在的意义究竟是什么呢?当你没听说过这个项目之前,一般的做法是用 Python 写的 web 应用来处理动态内容,用 Nginx/Apache2 这种专业的 HTTP 服务器处理静态文件;或者将静态文件都放在 S3 这种对象存储上。

为什么用 Whitenoise 比这两种做法要好,官方的 FAQ 写的很好,我这里捧哏转述一下。

首先使用 S3 这种方案,只是可以 work 的方式但是不是最佳的。第一,对压缩的支持不好。HTTP 的大多数时间都花在了网络上,而现在大多数个人电脑的 CPU 都是闲置的,所以假如服务器要传给客户端文件,那么服务器这边压缩一遍,通过网络传给客户端,客户端解压,虽然多了一次压缩/解压,但是总体上时间还是快的。S3 目前不支持压缩。支持压缩的话就要读 Accept-Encoding 这个 Header 看客户端支持哪种压缩方式(古老的 Gzip 压缩,或者现代的 brotli)。还要设置好 Vary 这些 Header 告诉 CDN 怎么处理缓存(什么是 Vary?)。而 Whitenoise 都帮你处理好了。另一个不便是配置 S3 比较麻烦,要用客户端上传,要有 Key,Secret 这些东西。CDN 的话,CDN 知道你的地址,你知道 CDN 的地址(互相确认过眼神)就好了。

其次,为什么不用 Nginx 呢?用 Nginx 是可以的,Whitenoise 使用的场景是 Heroku 这种 PaaS 平台,这种场景下 Nginx 不太好搞,所以用了 Whitenoise 你可以脱离 Nginx 了,直接将 WSGI app 平滑地部署到 PaaS 上。而且用 Nginx 你得仔细的设置很多东西,比如 CORS Header,cache Header,Nginx 原生不支持 brotli ,你还得去装 module 。

最后的一个问题是,Python 的性能问题是否意味着 Whitenoise 是一个效率很低的静态文件服务器?

如果你关心性能的话一定要使用 CDN,而对于 CDN 后面的真实服务器来说,最重要的事情是正确设置 HTTP 的 Header,最大限度的、正确的使用 CDN。所以这个问题更是一个逻辑是否正确的问题,而不是效率的问题。一个 静态文件 Request 的处理过程,其实主要是根据 PATH 来找到对应的文件并返回而已。对大多输的 WSGI 服务器(比如 Gunicorn)来说,发送文件的部分是对内核的 sendfile 的系统调用,效率是很高的,用 Python 还是 C,区别并不大。

下面是源码导读。

通过本文开头的那个例子可以看出,Whitenoise 实例化出来的对象其实是一个 WSGI app,它的 __call__() 方法如下:

Python

1

2

3

4

5

6

7

8

9

10

11

12

class WhilteNoise(object):

    ...

    def __call__(self, environ, start_response):

        path = decode_path_info(environ.get('PATH_INFO', ''))

        if self.autorefresh:

            static_file = self.find_file(path)

        else:

            static_file = self.files.get(path)

        if static_file is None:

            return self.application(environ, start_response)

        else:

            return self.serve(static_file, environ, start_response)

HTTP 请求到了 WSGI 应用的时候会首先进入这个方法,然后尝试根据 PATH 寻找对应的静态文件,如果找到的话,通过 self.serve() 处理返回内容;如果找不到的话,就调用进入 self.application ,就是后端 Python 应用。Whitenoise 只提供了这一种配置方法,不像 uWSGI,有 4 种 serve 静态文件的配置方法。这里我有一个担忧是每一个进来的请求都要经过查找对应静态文件的逻辑,不知道对性能有没有影响。

serve 方法如下:

Python

1

2

3

4

5

6

7

8

9

10

@staticmethod

def serve(static_file, environ, start_response):

    response = static_file.get_response(environ['REQUEST_METHOD'], environ)

    status_line = '{} {}'.format(response.status, response.status.phrase)

    start_response(status_line, list(response.headers))

    if response.file is not None:

        file_wrapper = environ.get('wsgi.file_wrapper', FileWrapper)

        return file_wrapper(response.file)

    else:

        return []

通过 static_file.get_response 方法拿到 Response,start_response 这一行是 WSGI 标准,返回 200 OK 这样的状态信息以及 Headers,最后以 list 形式返回 body。后面的 FileWrapper 是从 wsgiref.util 导入的,这个 FileWrapper 在 CPython 库里面的代码其实就是实现了迭代器,每次读 8k 的内容返回。如果用了其他的 WSGI 服务器,会拿到对应的 file_wrapper。从这里可以看出,Whitenoise 本身不处理 socket 发送文件的部分,真正负责这个的是 WSGI 服务器,比如 uWSGI 这种。

通过上面的介绍,我们知道这个库最重要的就是正确返回 Header,所以核心的逻辑都在 static_file.get_response 里面。

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

def get_response(self, method, request_headers):

    if method not in ('GET', 'HEAD'):

        return NOT_ALLOWED_RESPONSE

    if self.is_not_modified(request_headers):

        return self.not_modified_response

    path, headers = self.get_path_and_headers(request_headers)

    if method != 'HEAD':

        file_handle = open(path, 'rb')

    else:

        file_handle = None

    range_header = request_headers.get('HTTP_RANGE')

    if range_header:

        try:

            return self.get_range_response(range_header, headers, file_handle)

        except ValueError:

            # If we can't interpret the Range request for any reason then

            # just ignore it and return the standard response (this

            # behaviour is allowed by the spec)

            pass

     return Response(HTTPStatus.OK, headers, file_handle)

首先检查 method 必须是 GET 或 HEAD,否则就返回 403.

然后根据 etag 和 HTTP_IF_MODIFIED_SINCE 这个 Header 检查文件是否有修改。

接着解析 path 和 headers,注意这时候还没有打开文件,如果是 HEAD 方法,是不需要打开文件的,直接将 file 变量设置为 None 即可,后面发送的时候会自动不发送 body。

最后看是否是 HTTP_RANGE 请求,如果是,只读相应部分的文件。

主要的逻辑就到这里了,这个库的代码只有几千行,主要的逻辑就是上面介绍的这些。其他的一些包括 compress.py 有压缩相关的代码,middleware.py 和 storage.py 这些处理 Django 相关的一些 migration,scantree.py 处理查找文件的部分等等。

压缩的部分有一个值得注意的,这个变量将不需要压缩的文件的后缀列出来了。因为这些文件格式本身是经过高质量压缩的,如果启用压缩,会徒增计算成本,但是实际并不会减少很多文件体积。

Python

1

2

3

4

5

6

7

8

9

SKIP_COMPRESS_EXTENSIONS = (

    # Images

    'jpg', 'jpeg', 'png', 'gif', 'webp',

    # Compressed files

    'zip', 'gz', 'tgz', 'bz2', 'tbz', 'xz', 'br',

    # Flash

    'swf', 'flv',

    # Fonts

    'woff', 'woff2')

总之,这是一个很小的库,但是对 HTTP 文件服务是一个比较值得参考的实现。Worth to read.