跳转到主要内容

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 Policydjango-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、服务器推送以及为什么缓存摘要是一个重要的功能,以使服务器推送变得值得

尽管如此,即使没有启用服务器推送,这个库仍然有用,因为其主要功能是收集静态文件,并在Django视图完成执行之前并行地将它们作为<Link>预加载头发送,这在许多情况下可以为浏览器开始加载页面内容提供100ms以上的先发优势。截至2019年7月,最佳推荐设置是发送预加载头、缓存它们并提前发送,但不要启用HTTP2_SERVER_PUSH,直到大多数浏览器发布缓存摘要功能。

安装

  1. 使用您选择的包管理器安装django-http2
pip install django-http2
  1. 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)
  1. 将所需的配置选项添加到settings.py
HTTP2_PRELOAD_HEADERS = True
HTTP2_PRESEND_CACHED_HEADERS = True
HTTP2_SERVER_PUSH = False
  1. (如果使用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验证的更多信息

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选项,请参阅

验证其工作情况

当使用django-http2-middleware时,可以以三种不同的方式提供响应。您可以通过查看附加到响应的x-http2-preload头来检查给定响应使用的方式。如果所有选项都启用,则在中启用了中间件并启动Django后,需要两个初始请求才能使缓存预热,一个用于检测内容类型,另一个用于构建模板使用的资源URL列表

  1. 对给定URL的第一个请求不会提前发送预加载头(x-http2-preload: off)。它用于确认请求和响应是Content-Type: text/html而不是JSON API请求、文件下载或其他不应附加预加载头的非HTML类型。
  2. 第二个请求有预加载头,但仅在生成响应后附加(x-http2-preload: late)。它用于通过在模板渲染期间收集{% http2static %}标签使用的URL来构建给定request.path的初始预加载URL缓存。
  3. 如果 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 = TrueHTTP2_SERVER_PUSH = False,则所有响应都将发送在 x-http2-preload: late 模式下,这是推荐的模式,直到大多数浏览器可用缓存摘要。

进一步阅读

文档与文章

类似项目

在我自己的解决方案之后,我发现伟大的思想是相似的,有些人已经在我之前做了完全相同的事情!我们选择实施它的方式如此相似,每个人都使用了 {% static %} 的直接替换,我想这表明Django在这个领域特别设计得很好,因为有一个明显的方法来做事情,每个人都独立地找到了并实现了在 <200LOC 中健壮的解决方案。

然而,这些都没有支持CSP策略(需要在预加载头部中添加nonces),或使用 StreamingHttpResponse 在视图执行之前发送推送头部,所以在这个项目中,以某种方式充分利用了可用的HTTP2加速方法中的4种。

项目状态

请将此库视为“beta”软件,某些区域仍很粗糙,但已在几个项目中用于生产6+个月。它尚未在PyPi上发布,一旦它变得更友好并且有更多的测试,我就会发布它。目前,它应克隆到您的Django文件夹中,或作为您自己的代码的启发式使用。

一旦HTTP2 缓存摘要 最终确定,服务器推送将成为传递资源最快的方式,并且随着我们将其整合到所有生产项目中,我将投入更多的时间到 @Monadical-SAS。要了解更多关于为什么缓存摘要对于HTTP2服务器推送实际上非常有用的原因,这篇文章是一个很好的资源

额外材料

你知道吗?你可以在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 (17.0 kB 查看哈希)

上传时间

构建版本

django_http2-0.0.1-py3-none-any.whl (10.9 kB 查看哈希)

上传时间 Python 3

支持者