HTTP/2 协议于 2015 年 5 月 14 日正式版发布。随着 nginx 等流行 webserver 以及各大浏览器对 HTTP/2 的支持,越来越多的网站开始部署 HTTP/2 了。
HTTP/2 协议
HTTP/2 源自 SPDY/2。SPDY 系列协议由谷歌开发,于 2009 年公开。它的设计目标是降低 50% 的页面加载时间。
HTTP/2 协议由以下两个 RFC 组成:
RFC 7540 – Hypertext Transfer Protocol Version 2 (HTTP/2)
RFC 7541 – HPACK: Header Compression for HTTP/2
在 HTTP/2 官网 可以找到更多有关 HTTP/2 协议的资料。
HTTP/2 特点
HTTP/2 在底层传输上带来了大量的改动与优化,简单来讲,就是可以使得页面更快更持久。
二进制格式传输数据
HTTP/2 采用二进制格式传输数据。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。
消息头压缩传输
HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。头压缩能够很好的解决该问题。
多路复用
利用多路复用可以实现延迟削减。
每个 Frame Header 都有一个 Stream ID 就是被用于实现该特性。每次请求/响应使用不同的 Stream ID。就像同一个 TCP 链接上的数据包通过 IP:PORT来区分出数据包去往哪里一样。通过 Stream ID 标识,所有的请求和响应都可以欢快的同时跑在一条 TCP 链接上了。
当流并发时,就会涉及到流的优先级和依赖。优先级高的流会被优先发送。图片请求的优先级要低于 CSS 和 SCRIPT,这个设计可以确保重要的东西可以被优先加载完。
直白的说就是所有的请求都是通过一个 TCP 连接并发完成。HTTP/1.x 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求,同时,流还支持优先级和流量控制。
服务器推 (Server Push)
当服务端需要主动推送某个资源时,便会发送一个 Frame Type 为 PUSH_PROMISE 的 Frame,里面带了 PUSH 需要新建的 Stream ID。意思是告诉客户端:接下来我要用这个 ID 向你发送东西,客户端准备好接着。客户端解析 Frame 时,发现它是一个 PUSH_PROMISE 类型,便会准备接收服务端要推送的流。
这使得服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。
nginx 启用 HTTP/2 支持
nginx 在 1.9.5 以后开始支持 HTTP/2,并且移除了 SPDY 模块。只需将http2
添加到所有 listen
指令当中即可。示例:
server { server_name lzw.me; root /site/xxx; listen 443 ssl http2 fastopen=3 default_server; # 证书 ssl_certificate /etc/ssl_cert/lzw.me.crt; ssl_certificate_key /etc/ssl_cert/lzw.me.key; # 配置赫尔曼密钥 # openssl dhparam -out /etc/ssl/dhparam.pem 2048 //openssl 生成 2048 位的密钥 ssl_dhparam /etc/ssl_cert/dhparam.pem; # 禁止已经不安全的加密算法 ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4'; # 缓解 BEAST 攻击 ssl_prefer_server_ciphers on; # OCSP 缝合 ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/nginx/cert/trustchain.crt; resolver 114.114.114 8.8.8.8 valid=300s; # 禁止不安全的 SSL 协议,使用安全协议 ssl_protocols TLSv1.2 TLSv1.3; # 缓存连接凭据 ssl_session_cache shared:SSL:20m; ssl_session_timeout 60m; # 启用 HSTS,要求浏览器总是通过 https 访问 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload'; # 禁止被外部网站 iframe add_header X-Frame-Options SAMEORIGIN; # 严格的 MIME 类型响应,禁止 MIME-sniffing 猜测资源类型 add_header X-Content-Type-Options nosniff; # Public Key Pinning 对抗中间人攻击 # 生成方法参考:https://lzw.me/a/public-key-pins-hpkp.html add_header Public-Key-Pins 'pin-sha256="oYQpJ7NWxATnj4TXRpTIKMECkarA0lPcAblBBaV23Io="; pin-sha256="JGBhwHhyQ8RjdAiTim2KGnNegkNyBfISSuCL2YiSbTM="; max-age=2592000; includeSubDomains'; } # 80 端口跳转到 https server { listen 80; add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload'; return 301 https://lzw.me$request_uri; }
支持 ALPN
chrome 51 之后,要求对 HTTP/2 只支持 ALPN,否则还是会使用 HTTP/1.1。openssl 在 1.0.2 之后的版本才支持 ALPN。如果你的 nginx 是使用动态链接方式支持 openssl,需要更新系统的 openssl 版本;如果 nginx 是使用静态编译方式,则需要重新编译。验证是否支持 ALPN:
openssl s_client -alpn h2 -servername lzw.me -connect lzw.me:443 < /dev/null | grep 'ALPN'
看到输出 ALPN protocol: h2
则说明是支持的。
如不支持,请参考下面的编译 nginx 方法。
编译 nginx 支持 ALPN
安装依赖库和编译要用到的工具(选择没有的进行安装):
sudo yum install build-essential libpcre3 libpcre3-dev zlib1g-dev unzip git
下载 openssl
git clone --depth 1 https://github.com/cloudflare/sslconfig wget https://www.openssl.org/source/openssl-1.1.1s.tar.gz tar -zxvf openssl-1.1.1s.tar.gz mv openssl-1.1.1s openssl cd openssl patch -p1 < ../sslconfig/patches/openssl__chacha20_poly1305_draft_and_rfc_ossl102g.patch cd ../
下载 nginx-ct,以启用 Certificate Transparency
wget -O nginx-ct.zip -c https://github.com/grahamedgecombe/nginx-ct/archive/v1.2.0.zip unzip nginx-ct.zip mv nginx-ct-1.2.0 nginx-ct
下载 nginx 并解压,执行编译安装
# 备份已有的 nginx (如果是升级已有 nginx) cp -r /usr/local/nginx /usr/local/nginx-old # 下载 nginx-1.23.2 wget http://nginx.org/download/nginx-1.23.2.tar.gz tar -zxvf nginx-1.23.2.tar.gz cd nginx-1.23.2 ./configure --with-http_ssl_module --with-http_stub_status_module --with-http_v2_module --with-ipv6 --with-http_sub_module --with-stream --with-http_gzip_static_module --with-openssl=../openssl --prefix=/usr/local/nginx --add-module=../nginx-ct-1.2.0 # 应根据实际情况添加 user 和 group 参数 # --prefix=/usr/local/nginx --user=www --group=www make # 全新安装 make install # or 升级 make upgrade ps -ef | grep nginx
验证网站对 HTTP/2 的支持
在 chrome 浏览器控制台中,可以通过查看 Protocol
列进行验证。示例:
更多参考:https://www.nginx.com/blog/nginx-1-9-5/
也可以通过 ssl 在线测试网站来查看网站 ssl 支持情况:
注意点
HSTS preload 与子域名相关问题
谨慎设置 HSTS
信息。它会导致所有基于 http
的访问都被自动提升为 https
。如果设置了 includeSubDomains
,子域名也会如此。
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload';
如果遇到因为这个问题导致 Chrome 无法访问站点的情况,注释去掉这个设置,并且到 Chrome 相关设置中删除域名缓存:
chrome://net-internals/#hsts
https 后端 API 代理使用 http 问题
用户访问的域名使用了 https
,但后端不同服务可能会转发到不同的后端服务上,这些服务使用 http
作代理时,在获取使用的协议时就变成了 http
。此时需要添加一个代理转发header:X-Forwarded-Proto
代理转发配置示例:
location /api { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_pass http://localhost:1337; }
相关参考
HTTP/2 gitbook
http://http2.github.io/
HTTP,HTTP2.0,SPDY,HTTPS你应该知道的一些事
https://segmentfault.com/a/1190000002765886
https://imququ.com/post/enable-alpn-asap.html
https://imququ.com/post/my-nginx-conf.html