Apache HTTP Server 版本 2.4
HTTP/2 是世界上最成功的应用层协议 HTTP 的演进。它专注于更有效地利用网络资源。它不会改变 HTTP 的基本原理,即语义。仍然存在请求和响应以及标头等等。因此,如果您已经了解 HTTP/1,那么您也了解 HTTP/2 的 95%。
关于 HTTP/2 及其工作原理,已经有很多文章。最规范的当然是它的 RFC 7540 (也以更易读的格式提供,YMMV)。因此,您将在其中找到所有细节。
但是,正如 RFC 所做的那样,它并不是真正适合首先阅读的东西。最好先了解什么东西想要做,然后阅读关于如何做它的 RFC。一个更好的入门文档是 http2 explained,由 curl 的作者 Daniel Stenberg 撰写。它也以不断增长的语言列表提供!
太长,没看:阅读本文档时,需要牢记一些新的术语和注意事项。
HTTP/2 协议由其自己的 httpd 模块实现,恰如其分地命名为 mod_http2
。它实现了 RFC 7540 描述的完整功能集,并支持通过明文 (http:) 以及安全 (https:) 连接的 HTTP/2。明文变体名为“h2c
”,安全变体名为“h2
”。对于 h2c
,它允许直接模式和通过初始 HTTP/1 请求的 Upgrade:
。
HTTP/2 的一项提供新功能的特性是 服务器推送。请参阅该部分了解您的 Web 应用程序如何利用它。
mod_http2
使用 nghttp2 的库作为其实现基础。为了构建 mod_http2
,您需要在系统上安装至少 1.2.1 版本的 libnghttp2
。
当您./configure
Apache httpd 源代码树时,您需要提供“--enable-http2
”作为附加参数来触发模块的构建。如果您的 libnghttp2
位于非寻常位置(无论它在您的操作系统上是什么),您可以使用“--with-nghttp2=<path>
”向 configure
宣布其位置。
虽然这对于大多数人来说应该足够了,但有些人可能更喜欢在这个模块中静态链接 nghttp2
。对于这些人,存在选项 --enable-nghttp2-staticlib-deps
。它的工作原理与将 openssl 静态链接到 mod_ssl
非常类似。
说到 SSL,您需要知道大多数浏览器只会在 https:
URL 上使用 HTTP/2,因此您需要一个支持 SSL 的服务器。但不仅如此,您还需要一个支持 ALPN
扩展的 SSL 库。如果您使用 OpenSSL 作为库,则需要至少 1.0.2 版本。
当您拥有使用 mod_http2
构建的 httpd
时,您需要一些基本配置才能使其生效。首先,与每个 Apache 模块一样,您需要加载它
LoadModule http2_module modules/mod_http2.so
您需要添加到服务器配置中的第二个指令是
Protocols h2 http/1.1
这允许 h2(安全变体)成为您服务器连接上的首选协议。当您想要启用所有 HTTP/2 变体时,只需编写
Protocols h2 h2c http/1.1
根据您放置此指令的位置,它会影响所有连接或仅影响特定虚拟主机的连接。您可以嵌套它,例如
Protocols http/1.1 <VirtualHost ...> ServerName test.example.org Protocols h2 http/1.1 </VirtualHost>
这允许在连接上仅使用 HTTP/1,除了对 test.example.org
的 SSL 连接,这些连接提供 HTTP/2。
需要使用强大的 TLS 密码套件配置 SSLCipherSuite
。当前版本的 mod_http2
不会强制执行任何密码,但大多数客户端都会这样做。将浏览器指向具有不适当密码套件的启用了 h2 的服务器将迫使它拒绝并回退到 HTTP 1.1。这是在第一次为 HTTP/2 配置 httpd 时经常犯的错误,因此请牢记这一点,以避免长时间的调试会话!如果您想确定要选择的密码套件,请避免 HTTP/2 TLS 拒绝列表 中列出的密码套件。
提到的协议的顺序也很重要。默认情况下,第一个协议是最优先的协议。当客户端提供多个选择时,最左边的协议将被选中。在
Protocols http/1.1 h2
中最优先的协议是 HTTP/1,它将始终被选中,除非客户端只支持 h2。由于我们希望与支持它的客户端进行 HTTP/2 通信,因此更好的顺序是
Protocols h2 h2c http/1.1
还有一件事与排序有关:客户端也有自己的偏好。如果您愿意,您可以配置您的服务器以选择客户端最喜欢的协议
ProtocolsHonorOrder Off
使您编写的协议顺序无关紧要,只有客户端的排序将决定。
最后一点:您配置的协议不会检查其正确性或拼写。您可以提及不存在的协议,因此无需使用任何 <IfModule>
检查来保护 Protocols
。
有关配置的更多高级技巧,请参阅 关于尺寸调整的模块部分 和 如何使用同一个证书管理多个主机。
HTTP/2 在所有与 httpd 附带的多处理模块中受支持。但是,如果您使用 prefork
mpm,将存在严重限制。
在 prefork
中,mod_http2
每次连接只会处理一个请求。但客户端(如浏览器)会同时发送多个请求。如果其中一个请求处理时间过长(或是一个长时间轮询请求),其他请求将被阻塞。
mod_http2
默认情况下不会解决此限制。原因是 prefork
今天只在您运行未准备好进行多线程处理的处理引擎时才会选择,例如,如果有多个请求,它将崩溃。
如果您的设置可以处理,配置 event
mpm 现在是最好的选择(如果您的平台支持)。
如果您真的被困在 prefork
中,并且想要多个请求,您可以调整 H2MinWorkers
来实现这一点。但是,如果它崩溃了,您将承担所有责任。
几乎所有现代浏览器都支持 HTTP/2,但仅限于通过 SSL 连接:Firefox (v43)、Chrome (v45)、Safari (自 v9 起)、iOS Safari (v9)、Opera (v35)、Chrome for Android (v49) 和 Internet Explorer (Windows10 上的 v11) (来源)。
其他客户端以及服务器都列在 Implementations wiki 上,其中包括 c、c++、common lisp、dart、erlang、haskell、java、nodejs、php、python、perl、ruby、rust、scala 和 swift 的实现。
几个非浏览器客户端实现支持通过明文 h2c 的 HTTP/2。其中最通用的就是 curl。
第一个要提到的工具当然是 curl。请确保您的版本支持 HTTP/2,检查其 Features
$ curl -V curl 7.45.0 (x86_64-apple-darwin15.0.0) libcurl/7.45.0 OpenSSL/1.0.2d zlib/1.2.8 nghttp2/1.3.4 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 [...] Features: IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2
对于真正深入的检查,可以使用 wireshark。
nghttp2 包还包括客户端,例如
Chrome 通过 特殊的 net-internals 页面 提供其连接的详细 HTTP/2 日志。还有一个有趣的扩展程序,用于 Chrome 和 Firefox,用于可视化您的浏览器何时使用 HTTP/2。
HTTP/2 协议允许服务器将客户端从未请求过的响应推送给客户端。对话的语气是:“这里有一个您从未发送过的请求,它的响应很快就会到达……”
但存在限制:客户端可以禁用此功能,并且服务器只能在来自客户端的请求上推送。
目的是允许服务器将客户端很可能需要的资源发送给客户端:客户端请求的 html 页面所属的 css 或 javascript 资源。css 引用的图像集等等。
对客户端来说,这样做的好处是节省了发送请求的时间,这可能从几毫秒到半秒不等,具体取决于双方在地球上的位置。缺点是客户端可能会收到它缓存中已经存在的内容。当然,HTTP/2 允许提前取消此类请求,但仍然会浪费资源。
总结一下:没有一个最佳策略来充分利用 HTTP/2 的这一特性,每个人都在不断尝试。那么,如何在 Apache httpd 中进行尝试呢?
mod_http2
检查响应头中的 Link
头,这些头采用特定格式
Link </xxx.css>;rel=preload, </xxx.js>; rel=preload
如果连接支持 PUSH,这两个资源将被发送到客户端。作为 Web 开发人员,您可以直接在应用程序响应中设置这些头,也可以通过以下方式配置服务器:
<Location /xxx.html> Header add Link "</xxx.css>;rel=preload" Header add Link "</xxx.js>;rel=preload" </Location>
如果您想使用 preload
链接而不触发 PUSH,可以使用 nopush
参数,例如:
Link </xxx.css>;rel=preload;nopush
或者您可以使用以下指令完全禁用服务器的 PUSH:
H2Push Off
还有更多内容
该模块将记录每个连接已推送的内容(基本上是 URL 的哈希值),并且不会重复推送相同的资源。连接关闭时,这些信息将被丢弃。
有些人正在考虑如何让客户端告诉服务器它已经拥有哪些内容,以便避免推送这些内容,但这目前还处于高度实验阶段。
另一个在 mod_http2
中实现的实验性草案是 Accept-Push-Policy 头字段,客户端可以在每个请求中定义它接受哪些类型的 PUSH。
PUSH 并不总是能触发预期或希望的请求/响应/性能。网络上有很多关于这个主题的研究,解释了优点和缺点,以及客户端和网络的不同特性如何影响结果。例如:即使服务器推送了资源,也不意味着浏览器会实际使用这些数据。
影响推送响应的主要因素是模拟的请求。PUSH 的请求 URL 由应用程序提供,但请求头来自哪里?例如,PUSH 请求会包含 accept-language
头吗?如果是,其值是什么?
Apache 将查看原始请求(触发 PUSH 的请求),并将以下头复制到 PUSH 请求中:user-agent
、accept
、accept-encoding
、accept-language
、cache-control
。
所有其他头将被忽略。Cookie 也不会被复制。推送需要 cookie 才能存在的资源将无法正常工作。这可能是一个争论点。但除非与浏览器更明确地讨论这个问题,否则我们应该谨慎行事,不要在 cookie 应该不可见的地方公开它。
推送资源的另一种方法是在响应准备好之前向客户端发送 Link
头。这利用了 HTTP 的“早期提示”功能,在 RFC 8297 中有描述。
要使用此功能,您需要通过以下方式在服务器上显式启用它:
H2EarlyHints on
(默认情况下它没有启用,因为一些旧的浏览器会遇到此类响应的问题。)
如果此功能已启用,您可以使用指令 H2PushResource
来触发早期提示和资源推送
<Location /xxx.html> H2PushResource /xxx.css H2PushResource /xxx.js </Location>
这将在服务器开始处理请求后立即向客户端发送 "103 Early Hints"
响应。这可能比确定第一个响应头的时间早得多,具体取决于您的 Web 应用程序。
如果 H2Push
已启用,这也会在 103 响应之后立即开始 PUSH。但是,如果 H2Push
已禁用,则仍会向客户端发送 103 响应。