背景
任何一个移动APP项目都离不开网络接入功能,提升网络接入的质量几乎是所有移动项目的需求。很多项目都会引入HTTP DNS作为网络接入最基础也是最重要的优化之一。HTTP DNS的核心是后台下发某个域名对应的最优IP,基础点的可做到就近接入,即下发该域名终端就近地同运营商的IP,好一点的则根据线上用户实际测速数据下发最优的IP。而终端只需在HTTP接入时,将URL中的HOST从域名直接替换为后台下发的IP即可。
IP直连相对于域名接入的好处有:
- 省去DNS解析这一步,减少耗时
- 就近接入甚至就快接入,减少耗时
- 避免DNS劫持
- 当终端有多个IP接入选择时,有一定容灾能力
接入时后端常常都会存在转发层做一些诸如负载均衡的工作,由转发层将请求分发到真实服务器。HTTP接入的转发层一般是根据请求头中的HOST字段来转发到各个RS。所以对于HTTP接入,使用IP直连时需要设置一个HOST请求头,值为请求域名,在绝大部分情况下就可以获得上面提到的IP直连的好处。
但是对于HTTPS接入,情况会变得稍微复杂一些。本文主要是对Android平台使用HttpURLConnection(SDK开发由于包大小的限制,HTTP接入基本只能用这个了…)进行HTTPS IP直连时,遇到的一些问题及其解决方法的小结。
HTTPS IP直连问题与解决
首先先用一张图回顾下HTTPS接入的大致流程
本文描述的问题都发生在服务端公钥证书的校验这一步。
问题1. 证书HOST校验问题
终端在SSL握手过程会校验当前请求URL的HOST是否在服务端证书的可选域名列表里。举个例子,假设原本想要请求的URL为https://v.html5.qq.com
,而使用IP直连后实际请求的URL为:https://183.61.38.230:443
。此时服务端返回的证书可选域名列表如下
由于请求的HOST被替换成了IP,导致底层在进行证书的HOST校验时失败,最终请求失败。
这个问题的解决还比较简单。系统提供了接口,允许终端设置证书HOST校验实现。所以直接将底层默认实现中取终端传入URL的HOST(此处即IP)替换回,IP直连替换前的域名即可。
问题2. SNI问题
解决了问题1后,请求可以成功是“纯属意外”。事实上,183.61.38.230:443
这个转发层部署了多个域名的证书,除了问题1中的v.html5.qq.com
,还有https://ag.qq.com
等域名。此时如果用https://ag.qq.com
进行IP直连,请求会失败,因为当终端使用IP直连时,服务端SSL握手阶段获取到的域名为调度后的IP,服务端无法找到匹配的证书,只能返回默认的证书或者不返回。喵喵问题1中的图,默认返回的证书的拓展域名列表是不包含ag.qq.com
的,所以证书的HOST校验还是会失败,导致请求失败。
这个问题的解决也并不复杂:
- 系统提供接口,允许终端传入自定义SSLSocketFactory。SSLSocketFactory是创建SSLSocket的工厂,SSLSocket是Socket拓展,有SSL握手功能。
- 系统提供解决SNI问题的实现类SSLCertificateSocketFactory
所以设置一个自定义SSLSocketFactory并代理SSLCertificateSocketFactory即可解决SNI问题:
SSLSocketFactory接口有很多个创建Socket的方法,但是底层回调的就是Socket createSocket(Socket s, String host, int port, boolean autoClose)
,后文也会提到。
进行了这一步操作后,对