做爲新一代的HTTP協議,HTTP/2能夠提升網站性能,優化用戶體驗,Fundebug也是時候升級HTTP/2了,雖然已經有點晚了。css
升級HTTP/2是一件很簡單的事情,改1行Nginx配置就行了,可是,工程師只知道How是遠遠不夠的,還須要理解Why,這就要求咱們須要足夠的事先調研(1. 什麼是HTTP/2?)以及過後分析(4. 升級HTTP/2真的提升性能了嗎?)。html
HTTP/2是新一代的HTTP協議,於2015正式發佈。前端
與其餘衆多Web技術標準同樣,推進HTTP/2標準的依然是Google。發佈Chrome的時候Google說過要推進Web技術的發展,而後它真的作到了。(JavaScript深刻淺出第5課:Chrome是如何成功的?)nginx
根據W3Techs的統計,截止2019年10月26日,全世界41.3%的網站已經使用了HTTP/2。git
根據Can I use,絕大多數瀏覽器都支持了HTTP/2:github
HTTP/2主要有如下幾個特性:算法
由上圖可知,HTTP/1.1傳輸的是文本數據,而HTTP/2傳輸的是二進制數據,提升了數據傳輸效率。chrome
由上圖可知,HTTP 1.1須要爲不一樣的HTTP請求創建單獨的TCP鏈接,而HTTP/2的多個HTTP請求能夠複用同一個TCP鏈接。shell
要知道,創建TCP鏈接時須要3次握手,再加上TLS的4次握手,加起來就是7次握手,若是能夠複用TCP鏈接的話,則能夠減小這些多餘的開銷。後端
如上圖所示,第2個請求的Header只有:path不同,所以壓縮空間很是可觀。
Headers壓縮的算法HPACK自己彷佛很複雜(其實也不難),可是算法思想其實很是簡單的,假設咱們在瀏覽器發起100個請求,它們的user-agent是不會變的,那咱們爲何須要重複傳輸這個長長的字符串呢?用dictionary記錄一次不就好了!
圖片來源:lujjjh
由上圖可知,當客服端向服務端請求HTML時,Server Push服務端能夠提早返回HTML所依賴的css、js等資源,這樣能夠節省解析HTML以及請求資源的時間,從而縮短頁面的加載時間。
咱們使用了Nginx做爲前端頁面與後端接口的反向代理服務器(Reverse Proxy),只須要修改一下Nginx配置文件就能夠升級HTTP/2了,很是簡單。
注意,在 Nginx 上 開啓 HTTP/2 須要 Nginx 1.9.5 以上版本(包括1.9.5),而且須要 OpenSSL 1.0.2 以上版本(包括1.0.2)。使用nginx -V
命令能夠查看Nginx的版本信息:
nginx -V nginx version: nginx/1.12.1 built by gcc 6.3.0 20170516 (Debian 6.3.0-18) built with OpenSSL 1.1.0f 25 May 2017 TLS SNI support enabled configure arguments: --prefix=/etc/nginx --sbin3-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.12.1/debian/debuild-base/nginx-1.12.1=. -specs=/usr/share/dpkg/no-pie-compile.specs -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-specs=/usr/share/dpkg/no-pie-link.specs -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' 複製代碼
可知,咱們使用的Nginx版本爲1.12.1,OpenSSL版本爲1.1.0f,符合要求。
還有一點,雖然HTTP/2標準並無要求加密,可是全部瀏覽器都要求HTTP/2必須加密,這樣的話,只有HTTPS才能升級HTTP/2。
若是你還沒用過HTTPS的話,不妨看看個人博客:教你快速擼一個免費HTTPS證書,其實也很簡單。
一切前提沒問題的話(Nginx>=1.9.5,OpenSSL>=1.0.2,HTTPS),只須要修改1行配置,在listen指令後面添加http2:
server { listen 443 ssl http2; server_name www.fundebug.com; } 複製代碼
重啓Nginx,升級HTTP/2就成功了,可使用curl命令檢查:
curl -sI https://www.fundebug.com HTTP/2 200 server: nginx/1.12.1 date: Mon, 07 Oct 2019 00:12:53 GMT content-type: text/html; charset=UTF-8 content-length: 4892 x-powered-by: Express accept-ranges: bytes cache-control: public, max-age=0 last-modified: Sun, 06 Oct 2019 23:07:25 GMT etag: W/"131c-16da353dbc8" vary: Accept-Encoding strict-transport-security: max-age=15768001 複製代碼
升級HTTP/2以後,使用Safari的用戶發現沒法登錄Fundebug了:
咱們的前端異常監控插件捕獲了這個報錯:
可知,是**/api/members/login**接口出錯了。
通過排查發現是OPTIONS請求失敗了:
curl -X OPTIONS https://api.fundebug.com/api/members/login -v * Trying 120.77.45.162... * TCP_NODELAY set * Connected to api.fundebug.com (120.77.45.162) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem CApath: none * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Client hello (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Client hello (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 * ALPN, server accepted to use h2 * Server certificate: * subject: CN=api.fundebug.com * start date: Sep 15 16:38:43 2019 GMT * expire date: Dec 14 16:38:43 2019 GMT * subjectAltName: host "api.fundebug.com" matched cert's "api.fundebug.com" * issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3 * SSL certificate verify ok. * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x7fcfbb80ce00) > OPTIONS /api/members/login HTTP/2 > Host: api.fundebug.com > User-Agent: curl/7.54.0 > Accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS updated)! * http2 error: Invalid HTTP header field was received: frame type: 1, stream: 1, name: [content-length], value: [0] * HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1) * Closing connection 0 * TLSv1.2 (OUT), TLS alert, Client hello (1): curl: (92) HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1) 複製代碼
根據curl的報錯信息,可知是Header中content-length有問題:
* http2 error: Invalid HTTP header field was received: frame type: 1, stream: 1, name: [content-length], value: [0] 複製代碼
將Nginx配置文件中OPTIONS請求的Content-Length配置註釋掉,問題就解決了:
if ($request_method = "OPTIONS") { add_header Access-Control-Allow-Origin *; add_header 'Access-Control-Max-Age' 86400; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, OPTIONS, DELETE'; add_header 'Access-Control-Allow-Headers' 'token, reqid, nid, host, x-real-ip, x-forwarded-ip, event-type, event-id, accept, content-type'; # add_header 'Content-Length' 0; // 必須註釋,不然HTTP/2會報錯 add_header 'Content-Type' 'text/plain, charset=utf-8'; return 200; } 複製代碼
HTTP/2中對於Header有特殊處理,這應該是致使出錯的根本緣由,關於這一個問題,我會在下一篇博客中詳細介紹。
理論上來講,HTTP/2應該能夠提升網站性能,可是實際狀況是怎樣呢?HTTP/2真的能夠提升性能了嗎?若是有的話,究竟提升了多少呢?
因而,我使用Chrome記錄了升級HTTP/2先後Fundebug首頁的加載時間,計算了5次加載的平均時間(單位爲妙),以下表:
HTTP版本 | DOMContentLoaded | Load | Finish |
---|---|---|---|
HTTP/1.1 | 1.572 | 4.342 | 5.138 |
HTTP/2 | 1.0004 | 4.102 | 4.288 |
可知,HTTP/2明顯提升了首頁加載時間,DOMContentLoaded、Load與Finish時間均有明顯提升。
一共也就改了2行Nginx配置,就能夠提升頁面訪問性能,多好啊!