Django中间件,在视图运行前自动发送preload头,启用更快的HTTP2服务器推送(支持CSP)
项目描述
Django HTTP2
<script src="{% http2static 'js/jquery.min.js' %}"></script>
<!-- Preload header for js/jquery.min.js will be automatically attached to response -->
这是一个针对Django v2.0+的小型中间件,用于自动从模板渲染中使用的静态文件生成preload头,支持使用 StreamingHttpResponse
在实际响应生成之前发送缓存的preload头。仅preload头本身就能提供巨大的速度提升,但预先在视图执行前发送缓存的头是此库提供的真正优势。
它还构建来支持现代安全功能,如使用 Content Security Policy 的 django-csp
,它正确地发送 request.csp_nonce
到preload头,以确保如果需要nonce,preload不会被您的CSP策略拒绝。还计划在不久的将来支持自动生成和附加静态文件和内联块CSP散列的功能。
它的工作原理
它通过提供 {% http2static %}
模板标签来实现,该标签作为 {% static %}
的直接替换,除了它记录在渲染模板时使用的所有URL到 request.to_preload
。
http2中间件随后将to_preload
URL列表转换为完整的HTTP预加载头,并将其附加到响应中。当settings.HTTP2_PRESEND_CACHED_HEADERS = True
时,第一个响应的预加载头将被缓存,并在后续请求中自动提前发送(使用StreamingHttpResponse
在视图执行之前发送它们)。上游服务器如Nginx和CloudFlare可以使用这些头来执行HTTP2服务器推送,在浏览器解析和渲染之前将资源发送给客户端。借助TCP快速打开、TLS 1.3和HTTP2服务器推送,现在可以实现整个页面加载只需一次往返,我们需要的只是缓存摘要和QUIC,然后我们就会达到网络极乐状态 🎂。
HTTP2服务器推送
当预加载头快速发送,并在settings.py
中启用HTTP2_SERVER_PUSH = True
时,上游服务器如Nginx或Cloudflare的HTTP2通常会在浏览器请求它们之前,甚至在视图完成执行之前完成所有页面资源的推送,在某些情况下为静态文件加载提供100ms以上的先发优势。当启用时,查看网络瀑布图并看到您的页面静态文件一起加载完成,比HTML从Django返回早50ms以上,非常酷!
不幸的是,虽然很闪亮且令人兴奋,但这并不一定能使您的网站在真实用户中更快。实际上,它有时会使网站变慢,因为第一次访问后,用户已经缓存了大部分资源,并且每次请求推送不需要的文件可能会浪费网络带宽,并使用IO和CPU容量,否则这些容量本可以用来加载实际内容。您应该在测试项目时切换配置选项,以查看服务器推送是否提供了实际速度提升,或者使用以下推荐设置,在大多数情况下提供速度提升,而不会浪费带宽推送不需要的资源。尽管如此,有些情况下HTTP2推送仍然值得,例如,如果您必须推送一小组用于首次绘制的静态文件,并且大多数用户都是第一次访问您的网站而没有缓存。
一旦缓存摘要发布,HTTP2服务器推送将成为页面交付的最佳方法(提高延迟和带宽使用)。阅读这些文章以及其中的链接,了解更多关于HTTP2、服务器推送以及为什么缓存摘要是一个重要的功能,以使服务器推送变得值得
- https://http2.github.io/faq/#whats-the-benefit-of-server-push
- https://calendar.perfplanet.com/2016/cache-digests-http2-server-push/
- https://httpwg.org/http-extensions/cache-digest.html#introduction
尽管如此,即使没有启用服务器推送,这个库仍然有用,因为其主要功能是收集静态文件,并在Django视图完成执行之前并行地将它们作为<Link>
预加载头发送,这在许多情况下可以为浏览器开始加载页面内容提供100ms以上的先发优势。截至2019年7月,最佳推荐设置是发送预加载头、缓存它们并提前发送,但不要启用HTTP2_SERVER_PUSH
,直到大多数浏览器发布缓存摘要功能。
安装
- 使用您选择的包管理器安装
django-http2
包
pip install django-http2
- 将
http2.middleware.HTTP2Middleware
添加到settings.py
中的MIDDLEWARE
列表中
MIDDLEWARE = [
...
'csp.middleware.CSPMiddleware', # (optional if you use django-csp, it must be above the http2 middleware)
'django_http2.middleware.HTTP2Middleware', # (add the middleware at the end, but before gzip)
]
# (adding "http2" to INSTALLED_APPS is not needed)
- 将所需的配置选项添加到
settings.py
中
HTTP2_PRELOAD_HEADERS = True
HTTP2_PRESEND_CACHED_HEADERS = True
HTTP2_SERVER_PUSH = False
- (如果使用
django-csp
则可选)在settings.py
中包含任何所需资源类型的nonces:生成的预加载头将自动使用{{request.csp_nonce}}
包含此nonce。
# add any types you want to use with nonce-validation (or just add it to the fallback default-src)
CSP_DEFAULT_SRC = ("'self'", ...)
CSP_INCLUDE_NONCE_IN = ('default-src', ...)
用法
只要在想要预加载资源的任何时候使用{% http2static '...' %}
标签代替{% static '...' %}
即可。
<!-- It's still a good idea to put normal html preload link tags at the top of your templates in addition to using the auto-generated HTTP headers, though it's not strictly necessary -->
<link rel="preload" as="style" href="{% http2static 'css/base.css' %}" crossorigin nonce="{{request.csp_nonce}}">
<link rel="preload" as="script" href="{% http2static 'vendor/jquery-3.4.1/jquery.min.js' %}" crossorigin nonce="{{request.csp_nonce}}">
...
<!-- Place the actual tags anywhere on the page, they will likely already be pushed and downloaded by time the browser parses them. -->
<link rel="stylesheet" href="{% http2static 'css/base.css' %}" type="text/css" crossorigin nonce="{{request.csp_nonce}}">
<script src="{% http2static 'vendor/jquery-3.4.1/jquery.min.js' %}" type="text/javascript" crossorigin nonce="{{request.cscp_nonce}}"></script>
不要将{% http2static %}
用于所有内容,仅将其用于需要初始页面加载的关键渲染路径中的内容。它最好用于渲染页面所需的CSS、JS、字体和图标,但通常不应用于非关键页脚脚本和样式、异步页面内容、图片、视频、音频或其他媒体。
配置
推荐设置
这些设置可为90%的网站提供最大的速度提升,尽管测试所有可能性以查看您项目的实际结果是有价值的。
HTTP2_PRELOAD_HEADERS = True
HTTP2_PRESEND_CACHED_HEADERS = True
HTTP2_SERVER_PUSH = False
django-http2-middleware
配置
HTTP2_PRELOAD_HEADERS
值: [True
]/False
在响应中附加在模板中使用的任何{% http2static %}
URL,并在自动生成的HTTP预加载头中。禁用此选项可关闭预加载头并完全禁用中间件,这还将防止头缓存和http2服务器推送。
HTTP2_PRESEND_CACHED_HEADERS
值: [True
]/False
缓存第一次请求的预加载URL,并在后续请求中提前发送。启用此选项以缓存第一次请求生成的预加载头,并在后续请求中使用StreamingHttpResponse
在视图开始执行之前提前发送头。禁用此选项以使用带有预加载头附加在视图执行结束后的正常HTTPResponses。
HTTP2_SERVER_PUSH
值: True
/[False
]
允许上游服务器在预加载头中推送任何文件。禁用此选项可在所有预加载头中添加; nopush
以防止上游服务器提前推送资源。建议在大多数浏览器发送缓存摘要之前将此设置为False
。
django-csp
配置
使用Django实现内容安全策略(CSP)头和非ces有多种方法,对于Django来说最流行的是由Mozilla维护的django-csp
库。这个库是为了与Mozilla的django-csp
兼容而构建的,但使用这两个库不是必需的。您可以在以下位置找到有关配置Django以进行CSP验证的更多信息
- https://django-csp.readthedocs.io/en/latest/configuration.html#policy-settings
- https://content-security-policy.com/
- https://mdn.org.cn/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
Web服务器配置
为了使用HTTP2服务器推送,您需要一个位于Django前面的web服务器来读取预加载头并推送文件。Cloudflare有一个控制面板选项来启用服务器推送,nginx只需一行额外的配置即可完成
server {
listen 443 ssl http2;
http2_push_preload on; # nginx will automatically server-push anything specified in preload headers
...
}
有关更多信息及nginx http2选项,请参阅
- https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/
- https://nginx.ac.cn/en/docs/http/ngx_http_v2_module.html
验证其工作情况
当使用django-http2-middleware
时,可以以三种不同的方式提供响应。您可以通过查看附加到响应的x-http2-preload
头来检查给定响应使用的方式。如果所有选项都启用,则在中启用了中间件并启动Django后,需要两个初始请求才能使缓存预热,一个用于检测内容类型,另一个用于构建模板使用的资源URL列表
- 对给定URL的第一个请求不会提前发送预加载头(
x-http2-preload: off
)。它用于确认请求和响应是Content-Type: text/html
而不是JSON API请求、文件下载或其他不应附加预加载头的非HTML类型。 - 第二个请求有预加载头,但仅在生成响应后附加(
x-http2-preload: late
)。它用于通过在模板渲染期间收集{% http2static %}
标签使用的URL来构建给定request.path
的初始预加载URL缓存。 - 如果
HTTP2_PRESEND_CACHED_HEADERS = True
,则第三个请求(以及之后的所有请求)将在生成响应之前立即发送缓存的头部(x-http2-preload: early
)。如果禁用预先发送缓存的头部,则不会使用StreamingHttpResponse
在视图之前预先发送头部,并且预加载头部将在x-http2-preload: late
模式下作为通常的操作附加到响应之后。
在nginx后面启动runserver,并在观察开发控制台的同时刷新你的页面4次,以确认缓存已正确预热,并且后续请求接收服务器推送的资源。如果一切正常,第3次页面加载以及所有后续用户的所有加载应显示带有 x-http2-preload: early
响应头的响应,并且推送的资源应在网络时间瀑布视图中显著更早出现。
您可以使用Chrome/Firefox/Safari开发工具中的网络请求瀑布图检查给定页面的预加载性能,并确认它与您期望的 x-http2-preload
模式相匹配。
x-http2-preload: 关闭 |
x-http2-preload: 晚 |
x-http2-preload: 早 |
---|---|---|
需求 | 需求 | 需求 |
HTTP2_PRELOAD_HEADERS = True |
HTTP2_PRELOAD_HEADERS = True |
HTTP2_PRELOAD_HEADERS = True |
HTTP2_PRESEND_CACHED_HEADERS = True |
HTTP2_PRESEND_CACHED_HEADERS = True |
|
HTTP2_SERVER_PUSH = True |
如果您设置 HTTP2_PRESEND_CACHED_HEADERS = True
和 HTTP2_SERVER_PUSH = False
,则所有响应都将发送在 x-http2-preload: late
模式下,这是推荐的模式,直到大多数浏览器可用缓存摘要。
进一步阅读
文档与文章
- https://dexecure.com/blog/http2-push-vs-http-preload/
- https://www.keycdn.com/blog/http-preload-vs-http2-push
- https://symfony.ac.cn/doc/current/web_link.html
- https://www.smashingmagazine.com/2017/04/guide-http2-server-push/
- http2.github.io/faq/#whats-the-benefit-of-server-push
- https://calendar.perfplanet.com/2016/cache-digests-http2-server-push
- https://httpwg.org/http-extensions/cache-digest.html#introduction
- https://django-csp.readthedocs.io/en/latest/configuration.html#policy-settings
- https://content-security-policy.com
- htts://mdn.org.cn/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
类似项目
在我自己的解决方案之后,我发现伟大的思想是相似的,有些人已经在我之前做了完全相同的事情!我们选择实施它的方式如此相似,每个人都使用了 {% static %}
的直接替换,我想这表明Django在这个领域特别设计得很好,因为有一个明显的方法来做事情,每个人都独立地找到了并实现了在 <200LOC 中健壮的解决方案。
- https://github.com/ricardochaves/django_http2_push
- https://github.com/fladi/django-static-push
- https://github.com/DistPub/nginx-http2-django-server-push
然而,这些都没有支持CSP策略(需要在预加载头部中添加nonces),或使用 StreamingHttpResponse
在视图执行之前发送推送头部,所以在这个项目中,以某种方式充分利用了可用的HTTP2加速方法中的4种。
项目状态
请将此库视为“beta”软件,某些区域仍很粗糙,但已在几个项目中用于生产6+个月。它尚未在PyPi上发布,一旦它变得更友好并且有更多的测试,我就会发布它。目前,它应克隆到您的Django文件夹中,或作为您自己的代码的启发式使用。
一旦HTTP2 缓存摘要 最终确定,服务器推送将成为传递资源最快的方式,并且随着我们将其整合到所有生产项目中,我将投入更多的时间到 @Monadical-SAS。要了解更多关于为什么缓存摘要对于HTTP2服务器推送实际上非常有用的原因,这篇文章是一个很好的资源
"缓存摘要:解决HTTP/2服务器推送的缓存失效问题,以减少延迟和带宽" by Sebastiaan Deckers
额外材料
你知道吗?你可以在Django视图返回响应后运行代码,无需使用Celery、Dramatiq或任何其他后台工作系统?结果发现这非常简单,但很少有人知道。
def my_view(request):
...
return HttpResponseWithCallback(..., callback=some_expensive_function)
class HttpResponseWithCallback(HttpResponse):
def __init__(self, *args, **kwargs):
self.callback = kwargs.pop('callback', None)
super().__init__(*args, **kwargs)
def close(self):
super().close()
self.callback and self.callback(response=self)
在小型项目中,这非常适合发送注册电子邮件、跟踪分析事件、写入文件或任何其他CPU/IO密集型任务,你不想让它阻塞用户。在大项目中,这是一种反模式,因为它鼓励将这些“假”异步请求回调放入大的阻塞IO或CPU操作。这些回调实际上并不异步运行(如Celery),它们不会在主服务器线程上提供任何免费性能提升,它们只是将一些操作隐藏在正常的请求/响应生命周期之外,这使得跟踪延迟问题变得困难。你可能不希望用那些更适合在后台处理的事情来阻塞主Django工作线程,因为这会大大减少服务器可以处理的并发用户数量。
要查看这个库的完整示例,请参阅这个gist: django_turbo_response.py。
项目详情
下载文件
下载适合您平台的应用程序。如果您不确定选择哪个,请了解更多关于安装包的信息。
源代码分布
构建版本
django_http2-0.0.1.tar.gz 的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 8d5fed6af9ac2d20e7824d48ab38aeaf16b709c71845709b51870d04d3940fae |
|
MD5 | 816f843d5d8f274fd0f025f08606f3d4 |
|
BLAKE2b-256 | 92f219ed8e7269b61b54d0e67746b5f889a101cb9ca13a62953f47d8aae097df |
django_http2-0.0.1-py3-none-any.whl 的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | a3421837eca77db669ef0739157dc9f6fa19307ae635be75008e8644eb34f939 |
|
MD5 | cff8e96c99e521aa30187bdef0b81bae |
|
BLAKE2b-256 | d6e24bb6288ed4cdf1a655b4a116f1085ad6badbbe19282f03676312d1b28e5a |