前言
http2可以承載在TCP上或者TLS上,分別簡稱h2和h2c
當訪問一個服務的時候,到底用http1.1還是http2,client和server是可以協商的。
[https://www.cnblogs.com/hugetong/p/13410276.html]
TLS承載HTTP2
RFC規定,TLS承載HTTP2時,TLS必須實現ALPN功能。
implementations that support HTTP/2 over TLS MUST use protocol negotiation in TLS
ALPN同時配置上http1和http2讓server選. 見wiki: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation
Handshake Type: Client Hello (1) Length: 141 Version: TLS 1.2 (0x0303) Random: dd67b5943e5efd0740519f38071008b59efbd68ab3114587... Session ID Length: 0 Cipher Suites Length: 10 Cipher Suites (5 suites) Compression Methods Length: 1 Compression Methods (1 method) Extensions Length: 90 [other extensions omitted] Extension: application_layer_protocol_negotiation (len=14) Type: application_layer_protocol_negotiation (16) Length: 14 ALPN Extension Length: 12 ALPN Protocol ALPN string length: 2 ALPN Next Protocol: h2 ALPN string length: 8 ALPN Next Protocol: http/1.1
nginx里可以這樣配置:這樣配置之后,5000端口可以同時為http2與http1.1提供服務。
server { listen 0.0.0.0:5000 http2 ssl; ssl_certificate /data/sni/sni_test3.cer; ssl_certificate_key /data/sni/sni_test3.key; location / { proxy_pass http://httpt7/; } }
使用curl測試如下:
http2
╰─>$ curl -v -k https://t9:5000/ * Trying 192.168.7.9:5000... * Connected to t9 (192.168.7.9) port 5000 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: none * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (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, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 * ALPN, server accepted to use h2 * Server certificate: * subject: C=CN; ST=BeiJing; L=BeiJing; O=tong.com; OU=tong; CN=caotong_test3; emailAddress=tong@local * start date: Sep 24 10:00:10 2019 GMT * expire date: Sep 21 10:00:10 2029 GMT * issuer: C=CN; ST=BeiJing; L=BeiJing; O=Tartaglia; CN=TTTrust; emailAddress=ca@tartaglia.org * SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. * 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 0x5635fa55b8b0) > GET / HTTP/2 > Host: t9:5000 > user-agent: curl/7.70.0 > accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS == 128)! < HTTP/2 200 < server: tong.localhost < date: Fri, 31 Jul 2020 07:41:11 GMT < content-type: text/html < content-length: 660 < last-modified: Thu, 02 Apr 2020 08:45:48 GMT < etag: "5e85a63c-294" < accept-ranges: bytes < <!DOCTYPE html> <html> xxx </html> * Connection #0 to host t9 left intact
http1
╰─>$ curl -v -k --http1.1 https://t9:5000/ * Trying 192.168.7.9:5000... * Connected to t9 (192.168.7.9) port 5000 (#0) * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: none * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (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, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 * ALPN, server accepted to use http/1.1 * Server certificate: * subject: C=CN; ST=BeiJing; L=BeiJing; O=tong.com; OU=tong; CN=caotong_test3; emailAddress=tong@local * start date: Sep 24 10:00:10 2019 GMT * expire date: Sep 21 10:00:10 2029 GMT * issuer: C=CN; ST=BeiJing; L=BeiJing; O=Tartaglia; CN=TTTrust; emailAddress=ca@tartaglia.org * SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. > GET / HTTP/1.1 > Host: t9:5000 > User-Agent: curl/7.70.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Server: TONG < Date: Fri, 31 Jul 2020 07:45:34 GMT < Content-Type: text/html < Content-Length: 660 < Connection: keep-alive < Last-Modified: Thu, 02 Apr 2020 08:45:48 GMT < ETag: "5e85a63c-294" < Accept-Ranges: bytes < <!DOCTYPE html> <html> xxx </html> * Connection #0 to host t9 left intact
TCP承載HTTP2
nginx配置:
server { listen 0.0.0.0:5000 http2; location / { proxy_pass http://httpt7/; } }
curl訪問,必須采用顯示約定的方式進行訪問。顯示約定是指client事先知道了對方是http2,curl的話,要參數 http2-prior-knowledge指定
╰─>$ curl -v -k --http2-prior-knowledge http://t9:5000/ * Trying 192.168.7.9:5000... * Connected to t9 (192.168.7.9) port 5000 (#0) * 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 0x55f343ece8b0) > GET / HTTP/2 > Host: t9:5000 > user-agent: curl/7.70.0 > accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS == 128)! < HTTP/2 200 < server: TONG < date: Fri, 31 Jul 2020 07:48:36 GMT < content-type: text/html < content-length: 660 < last-modified: Thu, 02 Apr 2020 08:45:48 GMT < etag: "5e85a63c-294" < accept-ranges: bytes < <!DOCTYPE html> <html> xxx </html> * Connection #0 to host t9 left intact
一般來說,server應該有能力與client協商版本。但是很遺憾目前nginx還不支持。這個feature已經被提出,還沒有被開發。見:https://trac.nginx.org/nginx/ticket/816
h2c的時候怎么協商?
首先client發一個http1.1的請求過去,並攜帶着header 希望upgrade到http2,如果server願意,將回一個101的response
如下例子: (這個例子是nignx不支持,拒絕的情況)
╰─>$ curl -v -k --http2 http://t9:5000/ * Trying 192.168.7.9:5000... * Connected to t9 (192.168.7.9) port 5000 (#0) > GET / HTTP/1.1 > Host: t9:5000 > User-Agent: curl/7.70.0 > Accept: */* > Connection: Upgrade, HTTP2-Settings > Upgrade: h2c > HTTP2-Settings: AAMAAABkAAQCAAAAAAIAAAAA > * Received HTTP/0.9 when not allowed * Closing connection 0 curl: (1) Received HTTP/0.9 when not allowed
(假如不拒絕,將回復如下:)
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c [ HTTP/2 connection ...
當server只支持http1時,試圖升到http2的client會回落至http1正常工作,如下:
╰─>$ curl -v -k --http2 http://t9:5000/ * Trying 192.168.7.9:5000... * Connected to t9 (192.168.7.9) port 5000 (#0) > GET / HTTP/1.1 > Host: t9:5000 > User-Agent: curl/7.70.0 > Accept: */* > Connection: Upgrade, HTTP2-Settings > Upgrade: h2c > HTTP2-Settings: AAMAAABkAAQCAAAAAAIAAAAA > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Server: TONG < Date: Fri, 31 Jul 2020 08:01:12 GMT < Content-Type: text/html < Content-Length: 660 < Connection: keep-alive < Last-Modified: Thu, 02 Apr 2020 08:45:48 GMT < ETag: "5e85a63c-294" < Accept-Ranges: bytes < <!DOCTYPE html> <html> xxx </html> * Connection #0 to host t9 left intact
總結一下
client訪問http2 server的時候,分兩種情況,一種是自適應版本的協商方式。一種是顯式指定版本為http2的方式。
自適應的方式分兩種情況tls承載與tcp承載。
tls承載時,使用ALPN。tcp承載時,client使用Upgrade header嘗試http2,serveri接受便回復101開始http2,server不接受就正常用http1答復。