如何在 Apache 中啟用 HTTP/2

7 贊 回覆發表於2015-12-11

剛釋出的 Apache httpd 2.4.17 終於支援 HTTP/2 了。這個頁面給出了一些如何構建/部署/配置的建議。目的是為了大家發現 bugs 時能升級它,或者給一些能更好工作的建議。

最後,這會歸併回到官方 Apache 文件,這裡只會留下一個到那裡的連結。暫時我們還沒做到。

如何在 Apache 中啟用 HTTP/2

原始碼

你可以從這裡得到 Apache 版本。Apache 2.4.17 及其更高版本都支援 HTTP/2。我不會再重複介紹如何構建該伺服器的指令。在很多地方有很好的指南,例如這裡

(有任何這個試驗性軟體包的相關連結?在 Twitter 上告訴我吧 @icing)

編譯支援 HTTP/2

在你編譯版本之前,你要進行一些配置。這裡有成千上萬的選項。和 HTTP/2 相關的是:

  • –enable-http2啟用在 Apache 伺服器內部實現該協議的 ‘http2’ 模組。
  • –with-nghttp2=<dir>指定 http2 模組需要的 libnghttp2 模組的非預設位置。如果 nghttp2 是在預設的位置,配置過程會自動採用。
  • –enable-nghttp2-staticlib-deps很少用到的選項,你可能想將 nghttp2 庫靜態連結到伺服器裡。在大部分平臺上,只有在找不到共享 nghttp2 庫時才有用。

如果你想自己編譯 nghttp2,你可以到 nghttp2.org 檢視文件。最新的 Fedora 以及其它版本已經附帶了這個庫。

TLS 支援

大部分人想在瀏覽器上使用 HTTP/2, 而瀏覽器只在使用 TLS 連線(https:// 開頭的 url)時才支援 HTTP/2。你需要一些我下面介紹的配置。但首先你需要的是支援 ALPN 擴充套件的 TLS 庫。

ALPN 用來協商(negotiate)伺服器和客戶端之間的協議。如果你伺服器上 TLS 庫還沒有實現 ALPN,客戶端只能通過 HTTP/1.1 通訊。那麼,可以和 Apache 連結並支援它的是什麼庫呢?

  • OpenSSL 1.0.2 及其以後。
  • ??? (別的我也不知道了)

如果你的 OpenSSL 庫是 Linux 版本自帶的,這裡使用的版本號可能和官方 OpenSSL 版本的不同。如果不確定的話檢查一下你的 Linux 版本吧。

配置

另一個給伺服器的好建議是為 http2 模組設定合適的日誌等級。新增下面的配置:

# 放在某個地方的這樣一行
LoadModule http2_module modules/mod_http2.so

<IfModule http2_module>
    LogLevel http2:info
</IfModule>

當你啟動伺服器的時候,你可以在錯誤日誌中看來類似的一行:

[timestamp] [http2:info] [pid XXXXX:tid numbers] 
  mod_http2 (v1.0.0, nghttp2 1.3.4), initializing...

協議

那麼,假設你已經編譯部署好了伺服器, TLS 庫也是最新的,你啟動了你的伺服器,開啟了瀏覽器。。。你怎麼知道它在工作呢?

如果除此之外你沒有新增其它的伺服器配置,很可能它沒有工作。

你需要告訴伺服器在哪裡使用該協議。預設情況下,你的伺服器並沒有啟動 HTTP/2 協議。因為這樣比較安全,也許才能讓你已有的部署可以繼續工作。

你可以用新的 Protocols 指令啟用 HTTP/2 協議:

# 對於 https 伺服器
Protocols h2 http/1.1
...

# 對於 http 伺服器
Protocols h2c http/1.1

你可以給整個伺服器或者指定的 vhosts 新增這個配置。

SSL 引數

對於 TLS (SSL),HTTP/2 有一些特殊的要求。閱讀下面的“ https:// 連線”一節瞭解更詳細的資訊。

http:// 連線 (h2c)

儘管現在還沒有瀏覽器支援,但是 HTTP/2 協議也工作在 http:// 這樣的 url 上, 而且 mod_h[ttp]2 也支援。啟用它你唯一所要做的是在 Protocols 配置中啟用它:

# 對於 http 伺服器
Protocols h2c http/1.1

這裡有一些支援 h2c 的客戶端(和客戶端庫)。我會在下面介紹:

curl

Daniel Stenberg 維護的用於訪問網路資源的命令列客戶端 curl 當然支援。如果你的系統上有 curl,有一個簡單的方法檢查它是否支援 http/2:

sh> curl -V
curl 7.43.0 (x86_64-apple-darwin15.0) libcurl/7.43.0 SecureTransport zlib/1.2.5
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz UnixSockets

不好了。這些功能中沒有 ‘HTTP2′。你想要的是下面這樣:

sh> curl -V
url 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 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets

如果你的 curl 支援 HTTP2 功能,你可以用一些簡單的命令檢查你的伺服器:

sh> curl -v --http2 http://<yourserver>/
...
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
> 
< HTTP/1.1 101 Switching Protocols
< Upgrade: h2c
< Connection: Upgrade
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
...
<the resource>

恭喜,如果看到了有 …101 Switching… 的行就表示它正在工作!

有一些情況不會發生 HTTP/2 的升級切換(Upgrade)。如果你的第一個請求有請求資料(body),例如你上傳一個檔案時,就不會觸發升級切換。h2c 限制部分有詳細的解釋。

nghttp

nghttp2 可以一同編譯它自己的客戶端和伺服器。如果你的系統中有該客戶端,你可以簡單地通過獲取一個資源來驗證你的安裝:

sh> nghttp -uv http://<yourserver>/
[  0.001] Connected
[  0.001] HTTP Upgrade request
...
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: AAMAAABkAAQAAP__
...
[  0.005] HTTP Upgrade response
HTTP/1.1 101 Switching Protocols
Upgrade: h2c
Connection: Upgrade

[  0.006] HTTP Upgrade success
...

這和我們上面 curl 例子中看到的 Upgrade 輸出很相似。

有另外一種在命令列引數中不用 -u 引數而使用 h2c 的方法。這個引數會指示 nghttp 進行 HTTP/1 升級切換過程。但如果我們不使用呢?

sh> nghttp -v http://<yourserver>/
[  0.002] Connected
[  0.002] send SETTINGS frame 
...
[  0.002] send HEADERS frame 
          ; END_STREAM | END_HEADERS | PRIORITY
          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
          ; Open new stream
          :method: GET
          :path: /
          :scheme: http
...

連線馬上使用了 HTTP/2!這就是協議中所謂的直接(direct)模式,當客戶端傳送一些特殊的 24 位元組到伺服器時就會發生:

0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a

用 ASCII 表示是:

PRI * HTTP/2.0/r/n/r/nSM/r/n/r/n

支援 h2c 的伺服器在一個新的連線中看到這些資訊就會馬上切換到 HTTP/2。HTTP/1.1 伺服器則認為是一個可笑的請求,響應並關閉連線。

因此,直接模式只適合於那些確定伺服器支援 HTTP/2 的客戶端。例如,當前一個升級切換過程成功了的時候。

直接模式的魅力是零開銷,它支援所有請求,即使帶有請求資料部分(檢視h2c 限制)。

對於 2.4.17 版本,明文連線時預設啟用 H2Direct 。但是有一些模組和這不相容。因此,在下一版本中,預設會設定為off,如果你希望你的伺服器支援它,你需要設定它為:

H2Direct on

https:// 連線 (h2)

當你的 mod_h[ttp]2 可以支援 h2c 連線時,那就可以一同啟用 h2 兄弟了,現在的瀏覽器僅支援它和 https: 一同使用。

HTTP/2 標準對 https:(TLS)連線增加了一些額外的要求。上面已經提到了 ALNP 擴充套件。另外的一個要求是不能使用特定黑名單中的加密演算法。

儘管現在版本的 mod_h[ttp]2 不增強這些演算法(以後可能會),但大部分客戶端會這麼做。如果讓你的瀏覽器使用不恰當的演算法開啟 h2 伺服器,你會看到不明確的警告 INADEQUATE_SECURITY,瀏覽器會拒接連線。

一個可行的 Apache SSL 配置類似:

SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
SSLProtocol All -SSLv2 -SSLv3
...

(是的,這確實很長。)

這裡還有一些應該調整,但不是必須調整的 SSL 配置引數:SSLSessionCacheSSLUseStapling 等,其它地方也有介紹這些。例如 Ilya Grigorik 寫的一篇超讚的部落格: 高效能瀏覽器網路

curl

再次回到 shell 使用 curl(檢視上面的“curl h2c”章節瞭解要求),你也可以通過 curl 用簡單的命令檢測你的伺服器:

sh> curl -v --http2 https://<yourserver>/
...
* ALPN, offering h2
* ALPN, offering http/1.1
...
* ALPN, server accepted to use h2
...
<the resource>

恭喜你,能正常工作啦!如果還不能,可能原因是:

  • 你的 curl 不支援 HTTP/2。檢視上面的“檢測 curl”一節。
  • 你的 openssl 版本太低不支援 ALPN。
  • 不能驗證你的證書,或者不接受你的演算法配置。嘗試新增命令列選項 -k 停用 curl 中的這些檢查。如果可以工作,就重新配置你的 SSL 和證書。

nghttp

我們已經在 h2c 討論過 nghttp。如果你用它來進行 https: 連線,你會看到類似下面的資訊:

sh> nghttp https://<yourserver>/
[ERROR] HTTP/2 protocol was not selected. (nghttp2 expects h2)

這有兩種可能,你可以通過新增 -v 來檢查。如果是:

sh> nghttp -v https://<yourserver>/
[  0.034] Connected
[ERROR] HTTP/2 protocol was not selected. (nghttp2 expects h2)

這意味著你伺服器使用的 TLS 庫沒有實現 ALPN。有時候正確安裝有點困難。多看看 Stackoverflow 吧。

你看到的也可能是:

sh> nghttp -v https://<yourserver>/
[  0.034] Connected
The negotiated protocol: http/1.1
[ERROR] HTTP/2 protocol was not selected. (nghttp2 expects h2)

這表示 ALPN 能正常工作,但並沒有用 h2 協議。你需要像上面介紹的那樣檢查你伺服器上的 Protocols 配置。如果一開始在 vhost 部分設定不能正常工作,試著在通用部分設定它。

Firefox

更新: Apache Lounge 的 Steffen Land 告訴我 Firefox 上有個 HTTP/2 指示外掛。你可以看到有多少地方用到了 h2(提示:Apache Lounge 用 h2 已經有一段時間了…)

你可以在 Firefox 瀏覽器中開啟開發者工具,在那裡的網路標籤頁檢視 HTTP/2 連線。當你開啟了 HTTP/2 並重新重新整理 html 頁面時,你會看到類似下面的東西:

如何在 Apache 中啟用 HTTP/2

在響應頭中,你可以看到奇怪的 X-Firefox-Spdy 條目中列出了 “h2”。這表示在這個 https: 連線中使用了 HTTP/2。

Google Chrome

在 Google Chrome 中,你在開發者工具中看不到 HTTP/2 指示器。相反,Chrome 用特殊的地址 chrome://net-internals/#http2 給出了相關資訊。(LCTT 譯註:Chrome 已經有一個 “HTTP/2 and SPDY indicator” 可以很好的在位址列識別 HTTP/2 連線)

如果你開啟了一個伺服器的頁面,可以在 Chrome 中檢視那個 net-internals 頁面,你可以看到類似下面這樣:

如何在 Apache 中啟用 HTTP/2

如果你的伺服器在上面的列表中,就表示它正在工作。

Microsoft Edge

Windows 10 中 Internet Explorer 的繼任者 Edge 也支援 HTTP/2。你也可以在開發者工具的網路標籤頁看到 HTTP/2 協議。

如何在 Apache 中啟用 HTTP/2

Safari

在 Apple 的 Safari 中,開啟開發者工具,那裡有個網路標籤頁。重新載入你的伺服器上的頁面,並在開發者工具中選擇顯示了載入的那行。如果你啟用了在右邊顯示詳細檢視,看 Status 部分。那裡顯示了 HTTP/2.0 200,像這樣:

如何在 Apache 中啟用 HTTP/2

重新協商

https: 連線重新協商是指正在執行的連線中特定的 TLS 引數會發生變化。在 Apache httpd 中,你可以在 directory 配置中改變 TLS 引數。如果進來一個獲取特定位置資源的請求,配置的 TLS 引數會和當前的 TLS 引數進行對比。如果它們不相同,就會觸發重新協商。

這種最常見的情形是演算法變化和客戶端證書。你可以要求客戶訪問特定位置時需要通過驗證,或者對於特定資源,你可以使用更安全的、對 CPU 壓力更大的演算法。

但不管你的想法有多麼好,HTTP/2 中都不可以發生重新協商。在同一個連線上會有 100 多個請求,那麼重新協商該什麼時候做呢?

對於這種配置,現有的 mod_h[ttp]2 還沒有辦法。如果你有一個站點使用了 TLS 重新協商,別在上面啟用 h2!

當然,我們會在後面的版本中解決這個問題,然後你就可以安全地啟用了。

限制

非 HTTP 協議

實現除 HTTP 之外協議的模組可能和 mod_http2 不相容。這在其它協議要求伺服器首先傳送資料時無疑會發生。

NNTP 就是這種協議的一個例子。如果你在伺服器中配置了 mod_nntp_like_ssl,那麼就不要載入 mod_http2。等待下一個版本。

h2c 限制

h2c 的實現還有一些限制,你應該注意:

在虛擬主機中拒絕 h2c

你不能對指定的虛擬主機拒絕 h2c 直連。連線建立而沒有看到請求時會觸發直連,這使得不可能預先知道 Apache 需要查詢哪個虛擬主機。

有請求資料時的升級切換

對於有資料的請求,h2c 升級切換不能正常工作。那些是 PUT 和 POST 請求(用於提交和上傳)。如果你寫了一個客戶端,你可能會用一個簡單的 GET 或者 OPTIONS * 來處理那些請求以觸發升級切換。

原因從技術層面來看顯而易見,但如果你想知道:在升級切換過程中,連線處於半瘋狀態。請求按照 HTTP/1.1 的格式,而響應使用 HTTP/2 幀。如果請求有一個資料部分,伺服器在傳送響應之前需要讀取整個資料。因為響應可能需要從客戶端處得到應答用於流控制及其它東西。但如果仍在傳送 HTTP/1.1 請求,客戶端就仍然不能以 HTTP/2 連線。

為了使行為可預測,幾個伺服器在實現上決定不在任何帶有請求資料的請求中進行升級切換,即使請求資料很小。

302 時的升級切換

有重定向發生時,當前的 h2c 升級切換也不能工作。看起來 mod_http2 之前的重寫有可能發生。這當然不會導致斷路,但你測試這樣的站點也許會讓你迷惑。

h2 限制

這裡有一些你應該意識到的 h2 實現限制:

連線重用

HTTP/2 協議允許在特定條件下重用 TLS 連線:如果你有帶萬用字元的證書或者多個 AltSubject 名稱,瀏覽器可能會重用現有的連線。例如:

你有一個 a.example.org 的證書,它還有另外一個名稱 b.example.org。你在瀏覽器中開啟 URL https://a.example.org/,用另一個標籤頁載入 https://b.example.org/

在重新開啟一個新的連線之前,瀏覽器看到它有一個到 a.example.org 的連線並且證書對於 b.example.org 也可用。因此,它在第一個連線上面傳送第二個標籤頁的請求。

這種連線重用是刻意設計的,它使得使用了 HTTP/1切分(sharding)來提高效率的站點能夠不需要太多變化就能利用 HTTP/2。

Apache mod_h[ttp]2 還沒有完全實現這點。如果 a.example.orgb.example.org 是不同的虛擬主機, Apache 不會允許這樣的連線重用,並會告知瀏覽器狀態碼 421 Misdirected Request。瀏覽器會意識到它需要重新開啟一個到 b.example.org 的連線。這仍然能工作,只是會降低一些效率。

我們期望下一次的釋出中能有合適的檢查。

Münster, 12.10.2015,

Stefan Eissing, greenbytes GmbH

Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without warranty of any kind. See LICENSE for details.

該專案由 icing 維護。

相關文章