ALPN 让 TLS 握手顺便协商应用层协议(h2 还是 http/1.1)。谁的偏好优先?没有交集会怎样?一方没有设置会怎样?这篇用 JDK 的 SSLSocket、真实的 nginx、openssl s_client、curl 和 Java HttpClient 逐项验证。
[TLSv1.3]
客户端 [h2, http/1.1] 服务器 [http/1.1] → 客户端 getApplicationProtocol() = "http/1.1",协议 TLSv1.3;服务器 getApplicationProtocol() = "http/1.1"
客户端 [h2, http/1.1] 服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "h2",协议 TLSv1.3;服务器 getApplicationProtocol() = "h2"
客户端 [http/1.1, h2] 服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "h2",协议 TLSv1.3;服务器 getApplicationProtocol() = "h2"
客户端 [h2, http/1.1] 服务器 [http/1.1, h2] → 客户端 getApplicationProtocol() = "http/1.1",协议 TLSv1.3;服务器 getApplicationProtocol() = "http/1.1"
[TLSv1.2]
客户端 [h2, http/1.1] 服务器 [http/1.1] → 客户端 getApplicationProtocol() = "http/1.1",协议 TLSv1.2;服务器 getApplicationProtocol() = "http/1.1"
客户端 [h2, http/1.1] 服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "h2",协议 TLSv1.2;服务器 getApplicationProtocol() = "h2"
客户端 [http/1.1, h2] 服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "h2",协议 TLSv1.2;服务器 getApplicationProtocol() = "h2"
客户端 [h2, http/1.1] 服务器 [http/1.1, h2] → 客户端 getApplicationProtocol() = "http/1.1",协议 TLSv1.2;服务器 getApplicationProtocol() = "http/1.1"客户端 [h2] 服务器 [http/1.1](没有交集)→ 客户端异常:SSLHandshakeException: (no_application_protocol) Received fatal alert: no_application_protocol;服务器端异常:SSLHandshakeException: (no_application_protocol) No matching application layer protocol values 客户端没有设置 ALPN 服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "",协议 TLSv1.3;服务器 getApplicationProtocol() = "" 客户端 [h2, http/1.1] 服务器没有设置 ALPN → 客户端 getApplicationProtocol() = "",协议 TLSv1.3;服务器 getApplicationProtocol() = "" 双方都没有设置 ALPN → 客户端 getApplicationProtocol() = "",协议 TLSv1.3;服务器 getApplicationProtocol() = "" TLS 1.2,客户端 [h2] 服务器 [http/1.1](没有交集) → 客户端异常:SSLHandshakeException: (no_application_protocol) Received fatal alert: no_application_protocol;服务器端异常:SSLHandshakeException: (no_application_protocol) No matching application layer protocol values 服务器用 selector,偏爱客户端列表的最后一个 → 客户端 getApplicationProtocol() = "spdy/3",协议 TLSv1.3;服务器 getApplicationProtocol() = "spdy/3" 服务器 selector 返回 null → 客户端异常:SSLHandshakeException: (no_application_protocol) Received fatal alert: no_application_protocol;服务器端异常:SSLHandshakeException: (no_application_protocol) No matching application layer protocol values 服务器 selector 返回空字符串 "" → 客户端 getApplicationProtocol() = "",协议 TLSv1.3;服务器 getApplicationProtocol() = "" 服务器 selector 返回客户端没有提供的 "foo" → 客户端异常:SSLHandshakeException: (no_application_protocol) Received fatal alert: no_application_protocol;服务器端异常:SSLHandshakeException: (no_application_protocol) No matching application layer protocol values selector 拿到的客户端提供的列表 = [h2, http/1.1, acme-tls/1]
nginx(http2 on;) openssl s_client -alpn h2,http/1.1 → h2 nginx(http2 on;) openssl s_client -alpn http/1.1,h2 → h2 nginx(http2 on;) openssl s_client -alpn h2 → h2 nginx(http2 on;) openssl s_client -alpn http/1.1 → http/1.1 nginx(http2 on;) openssl s_client -alpn spdy/3.1 → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120 nginx(http2 on;) openssl s_client -alpn acme-tls/1 → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120 nginx(没有开 http2) openssl s_client -alpn h2,http/1.1 → http/1.1 nginx(没有开 http2) openssl s_client -alpn http/1.1,h2 → http/1.1 nginx(没有开 http2) openssl s_client -alpn h2 → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120 nginx(没有开 http2) openssl s_client -alpn http/1.1 → http/1.1 nginx(没有开 http2) openssl s_client -alpn spdy/3.1 → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120 nginx(没有开 http2) openssl s_client -alpn acme-tls/1 → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120 nginx(http2 on;) curl --http2 实际使用 HTTP/2,curl --http1.1 实际使用 HTTP/1.1 nginx(没有开 http2) curl --http2 实际使用 HTTP/1.1,curl --http1.1 实际使用 HTTP/1.1 nginx(http2 on;): Java HttpClient(请求版本 HTTP_2)→ 响应版本 HTTP_2,body = ok Java HttpClient(请求版本 HTTP_1_1)→ 响应版本 HTTP_1_1,body = ok nginx(没有开 http2): Java HttpClient(请求版本 HTTP_2)→ 响应版本 HTTP_1_1,body = ok Java HttpClient(请求版本 HTTP_1_1)→ 响应版本 HTTP_1_1,body = ok