NGINX升级至1.14后老Android/Java应用报ProtocolException问题求助
这个问题我之前帮好几个开发者排查解决过,核心原因其实是NGINX 1.14默认开启了HTTP/2,而你用的旧版okhttp(大概率是3.x及更早的版本)对HTTP/2的伪头处理有兼容性问题——HTTP/2用:status伪头传递状态码,但旧okhttp还在期望HTTP/1.x风格的Status头,找不到就直接抛出那个异常了。
不用改客户端也不用降级NGINX,有几个靠谱的配置层面解决方案:
如果你的业务不需要HTTP/2的特性,直接把NGINX配置里的http2参数去掉就行。找到对应server块的listen指令:
# 原来的配置 listen 443 ssl http2; # 修改后 listen 443 ssl;
重启NGINX后,所有客户端都会用HTTP/1.1通信,旧okhttp就能正常解析状态头了。
如果想给现代客户端保留HTTP/2的性能优势,可以通过用户代理(UA)识别旧版okhttp或旧Android设备,强制它们用HTTP/1.1。
步骤1:添加UA匹配规则
在NGINX的http全局块里添加map指令,用来匹配需要禁用HTTP/2的客户端:
http { # 其他全局配置... map $http_user_agent $force_http11 { default 0; # 匹配okhttp 1.x/2.x/3.x版本(可根据实际UA调整) "~*okhttp/[1-3]\." 1; # 匹配Android 4.x/5.x/6.x系统(旧系统通常搭配旧okhttp) "~*Android [4-6]\." 1; } }
步骤2:针对匹配到的客户端强制HTTP/1.1
在对应的server块里添加判断,当匹配到旧客户端时,通过重定向让NGINX用HTTP/1.1处理请求:
server { listen 443 ssl http2; server_name your-domain.com; # 其他配置... if ($force_http11) { # 重定向到同一个URL,NGINX会自动用HTTP/1.1处理新请求 return 302 https://$host$request_uri; } }
如果不想用重定向,也可以配置两个独立的server块分流:
# 给现代客户端的HTTP/2服务 server { listen 443 ssl http2; server_name your-domain.com; # 其他业务配置... } # 给旧客户端的HTTP/1.1服务 server { listen 443 ssl; server_name your-domain.com; # 仅允许旧UA进入这个server if ($force_http11 != 1) { return 302 https://$host$request_uri; } # 业务配置和上面的server保持一致 }
如果不想禁用HTTP/2也不想用重定向,可以让NGINX在HTTP/2响应中同时发送:status伪头和旧okhttp期望的status头。在server块中添加:
add_header status $status always;
always参数确保即使是4xx/5xx错误响应也会带上这个头。这样旧okhttp能找到它需要的status头,现代客户端会自动忽略重复的头,完全不影响使用。
内容的提问来源于stack exchange,提问作者Stavros_S

