本文主要介绍iOS端App集成HTTPDNS时实现“IP直连”的解决方案。关于iOS上如何集成HTTPDNS,请先查看iOS SDK 开发手册。
1. 前言
在移动端网络环境中,DNS劫持、LocalDNS缓存污染等问题时常导致域名无法正确解析、网络请求失败。针对这些场景,阿里云HTTPDNS提供了可靠的域名递归解析服务,帮助移动App绕过本地DNS的潜在风险,提升网络请求的成功率和稳定性。
但是,在iOS平台上使用HTTPDNS,需要将请求原本的域名替换为实际解析出来的IP再直接发起请求,这就可能引发额外的问题,尤其是在HTTPS和SNI等复杂场景下。因此,在集成HTTPDNS之前,需要对可能出现的问题和可行的解决方案有一个全面的认识,以便在业务中安全且正确地使用HTTPDNS。
本文将介绍在iOS上使用HTTPDNS会遇到的主要问题,并给出在不同场景下的集成方案和各自的利弊,希望帮助开发者快速完成HTTPDNS的接入。
2. 在iOS上使用HTTPDNS会遇到哪些问题
在移动端应用中,如果我们将原始URL中的域名(如 example.com
)替换为HTTPDNS解析得到的IP,往往会遇到以下几个方面的问题。之所以会产生这些问题,与HTTPS协议在不同层次(TLS/SSL层和HTTP层)对Host字段的使用方式密切相关:
TLS/SSL层: 在HTTPS场景下,客户端会先进行TLS/SSL握手,其中会使用到URL中的Host来完成以下任务:
证书校验:验证服务器证书的域名(Common Name或Subject Alternative Name)是否与请求的Host一致。
SNI(Server Name Indication):在建立TLS连接时,客户端会将所请求的域名信息(即URL中的Host)发送给服务器,以便服务器返回对应的证书。
HTTP层: 在完成TLS握手后,客户端会在HTTP请求的Header中包含
Host
字段,用于告诉服务器此请求对应的具体站点或资源。如果将URL中的域名替换为IP,又未手动设置HTTP Header里的Host
,就可能让服务器无法识别要访问的实际域名,从而导致请求失败或返回异常内容。
根据上述Host在HTTPS协议栈各层的作用描述,可以看到,若只把URL中的域名直接替换为HTTPDNS解析出来的IP,则会引起以下技术问题:
域名与证书不匹配 对于HTTPS请求,如果URL里直接使用HTTPDNS解析出来的IP作为请求的Host,TLS层面无法匹配到正确的证书域名(Common Name或SAN扩展域名),会导致SSL握手失败。
SNI(Server Name Indication)问题 在SNI场景中,同一服务器IP可能对应多个域名的证书。如果客户端在SSL握手阶段没有传递正确的域名信息(只发送了IP),服务端就无法返回匹配该域名的证书,导致SSL握手失败。由于iOS的高层网络API(如
NSURLSession
)并未暴露直接配置SNI的接口,SNI问题常难以通过简单方式解决。Host头与业务寻址 如果仅把URL中的域名替换为IP,却忘记在HTTP请求头中显式设置
Host
为原始域名,那么在HTTP层服务器端可能无法识别具体的站点或资源。例如CDN场景下,服务器需要依赖Host字段来分发正确的内容,一旦Host为IP,将导致服务异常。底层网络库的选择 iOS自带的高层API(
NSURLSession
等)在定制SNI或手动证书校验方面扩展性较弱。如果开发者想自行处理SNI或修改TLS握手逻辑,就需要使用更底层的接口(如CFNetwork
、libcurl
等),但这会带来更高的开发和维护成本。
综上所述,直接将URL中的域名替换为HTTPDNS解析出来的IP,在HTTPS场景下会影响TLS层的证书校验与SNI传递,并在HTTP层可能导致Host头信息异常。因此在iOS端整合HTTPDNS,需要有针对性地解决这些问题,才能确保网络请求的可靠性与安全性。
3. 普通HTTP场景、HTTPS+非SNI场景接入方案
在针对普通HTTP或HTTPS + 非SNI这两种场景下,我们通常可以继续使用系统自带的NSURLSession
以及常规的网络请求逻辑,只需做一些相对简单的处理即可。需要注意的是,普通HTTP场景并不涉及TLS握手和证书校验;而HTTPS + 非SNI场景需要考虑到证书校验,但可以通过在NSURLSession
中Hook验证流程的方式来应对。
3.1 普通HTTP场景标题
对于普通HTTP请求,网络链路中不存在TLS/SSL握手,也无需证书校验,因此集成HTTPDNS的核心操作仅在HTTP层进行:
替换请求URL中的Host为HTTPDNS解析出的IP
例如原始请求URL为
http://example.com/api
,HTTPDNS解析后得到IP1.2.3.4
,则把URL修改为http://1.2.3.4/api
。
在HTTP Header中显式设置
Host
为原始域名若使用
NSMutableURLRequest
,可在请求头中添加request.allHTTPHeaderFields[@"Host"] = @"example.com";
确保服务器在应用层能够识别到正确的域名。
优点:实现简单;只需在现有HTTP请求中替换Host+设置Header,开发量较低。
缺点:仅适用于HTTP明文协议,无法解决HTTPS场景下的证书校验和SNI相关问题。
3.2 HTTPS + 非SNI场景标题
对于不使用SNI机制或仅包含少量固定域名的HTTPS站点(证书中只涵盖这些域名),可以在NSURLSession
层通过以下方式来完成HTTPDNS接入与证书校验:
替换请求URL中的Host为HTTPDNS解析出的IP
例如原始请求URL为
https://example.com/api
,HTTPDNS解析后得到IP1.2.3.4
,则把URL修改为https://1.2.3.4/api
。
在HTTP Header中显式设置
Host
为原始域名同理,可以在
NSMutableURLRequest
里设置request.allHTTPHeaderFields[@"Host"] = @"example.com";
。
Hook证书校验过程
由于这是HTTPS请求,需要在TLS握手阶段进行证书校验。此时,若直接拿IP作为Host检查,就会出现域名与证书不匹配的问题。
可以在
NSURLSessionDelegate
的回调方法URLSession:didReceiveChallenge:completionHandler:
中,将系统获取到的serverTrust
进行验证时,改用原始域名(example.com
)替换掉IP,从而通过证书校验。代码示例:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSString *originalHost = [self getOriginalHostFromRequest:task.originalRequest]; SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; if ([self evaluateServerTrust:serverTrust forDomain:originalHost]) { // 证书校验通过 NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential, credential); } else { // 证书校验失败,使用默认处理 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } }
优点:
不需要引入额外的第三方库,直接使用系统原生
NSURLSession
和证书校验逻辑.实现成本相对可控,适合不涉及SNI或只需少量域名证书的场景。
缺点:
无法处理SNI场景。若在同一IP上部署多个域名(如CDN场景),则仍然会因服务器返回错误的证书而握手失败。
4. HTTPS+SNI场景接入方案
针对SNI(单IP多HTTPS域名)的场景,简单的NSURLSession
方案中无法在SSL握手阶段发送正确的域名信息,导致握手失败。为了解决此问题,需要在更底层的Socket层级修改或指定SNI字段。常见的做法有以下三种:
4.1 自定义NSURLProtocol实现
iOS允许开发者通过继承NSURLProtocol
来拦截系统发起的网络请求,并在底层自行实现HTTP/HTTPS请求逻辑。可以基于CFNetwork
或者NSInputStream/NSOutputStream
等接口,手动完成所有网络操作:
拦截请求
在
canInitWithRequest:
方法中判断是否要拦截当前请求。在
startLoading
方法里,将原始请求URL中的域名替换为IP,并保留原始域名用于后续的证书校验和SNI设置。
设置SNI
通过
CFStream
相关API或者SecureTransport
接口,指定kCFStreamSSLPeerName
为原始域名,这样在SSL握手阶段,底层会带上正确的域名信息。
证书校验
手动执行证书验证流程,确保证书中包含的域名与原始域名匹配。
优点:不依赖第三方库,完全基于系统底层API,灵活度高。
缺点:实现成本非常高,需要开发者手动处理重定向、Cookie、缓存、编码、流量统计等;且维护风险大,升级系统或网络环境时需要额外适配。
如果需要参考示例,阿里云提供了httpdns_ios_demo中HttpDnsNSURLProtocolImpl.m
的示例实现,可根据业务需求进行修改或复用。
4.2 自行使用libcurl实现网络请求
libcurl
是C语言实现的跨平台网络库,支持手动设置SNI字段,从而在SSL握手阶段传递正确的域名信息,以完成多域名共享同一IP的证书校验场景。大致流程如下:
解析域名,得到对应IP
例如使用HTTPDNS提供的API
resolveHostSyncNonBlocking:
等方法,获取到目标域名的IP地址。
设置SNI和IP映射
通过
CURLOPT_RESOLVE
或其他API,把“域名:端口:解析到的IP”映射写入到curl内部DNS缓存。依然使用原始域名作为
CURLOPT_URL
的访问目标,保证TLS握手时会带上正确的域名信息。
证书校验
libcurl
默认开启证书校验,也可以根据需求使用相应的回调对证书进行更细粒度的检查。
下面是一个核心示例代码片段(伪代码),演示如何在iOS中通过libcurl使用HTTPDNS解析的结果并完成请求:
CURL *curl_handle = curl_easy_init();
if (curl_handle) {
// 例如从HTTPDNS得到IP = 1.2.3.4,目标域名 = example.com,端口 = 443
struct curl_slist *dnsResolve = NULL;
dnsResolve = curl_slist_append(dnsResolve, "example.com:443:1.2.3.4");
// 设置域名-IP映射
curl_easy_setopt(curl_handle, CURLOPT_RESOLVE, dnsResolve);
// 依然使用原始域名作为URL
curl_easy_setopt(curl_handle, CURLOPT_URL, "https://example.com");
// 开启SSL验证
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2L);
// 发起请求
CURLcode res = curl_easy_perform(curl_handle);
// 检查结果
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
// 清理
curl_easy_cleanup(curl_handle);
curl_slist_free_all(dnsResolve);
}
优点:
成熟、稳定,支持丰富的协议,能适应复杂的网络环境;对SNI场景有内置支持。
缺点:
需要将
libcurl
编译进iOS项目,使用纯C接口,对Objective-C/Swift项目的开发者有一定学习成本。同时也要处理自定义的请求流程或自行封装HTTP逻辑(如Cookie、重定向、缓存等)。
4.3 使用EMASCurl
为降低在iOS上直接使用libcurl
的接入门槛,阿里云EMAS团队提供了EMASCurl库,它对libcurl
进行封装,并支持与HTTPDNS的直接对接。
安装与拦截
提供了两种主要使用方式: 1)拦截指定
NSURLSessionConfiguration
创建的NSURLSession
; 2)拦截系统全局[NSURLSession sharedSession]
。API接口示例:
与HTTPDNS配合
实现
EMASCurlProtocolDNSResolver
协议即可将HTTPDNS解析结果交给EMASCurl。在
resolveDomain:
方法中调用[HttpDnsService resolveHostSyncNonBlocking:]
获取IP地址,然后返回给EMASCurl来完成后续的SNI设置和请求发送。
证书校验
EMASCurl依赖
libcurl
的证书校验机制,开发者也可通过相应接口扩展或自定义证书校验,以适配业务需求。
优点:
封装了
libcurl
的底层能力,API更符合iOS开发者的使用习惯。通过DNS Hook机制与HTTPDNS轻松对接。
同时解决了SNI场景的域名传递和证书校验问题,降低集成成本。
缺点:
依赖第三方库(EMASCurl+libcurl),需要注意兼容性、版本升级等。
对于非常复杂的HTTP特性或自定义需求,仍需要阅读和理解EMASCurl内部封装实现,以确保业务可用性。
在接入EMASCurl时,需要测试常见的HTTP/HTTPS特性(如重定向、Cookie、并发请求等)是否符合业务需求。
若业务对安全或网络性能有严格要求,需要评估EMASCurl在当前iOS系统版本下的表现。
确保在不同网络环境(Wi-Fi/蜂窝网络/代理等)下都能正常完成请求和握手。
5. 总结
根据业务中是否需要支持SNI、多域名及证书校验等需求,可结合以下方案进行选择。下表对各方案进行对比:
方案 | 适用场景 | 优点 | 缺点 |
仅设置Host和Header | 普通HTTP 场景 | - 集成成本最低 | -仅适用于HTTP明文协议 |
NSURLSession + Hook证书校验 (仍需设置Host和Header) | HTTPS+非SNI场景 | - 集成成本低 - 沿用系统API,无需额外库 | - 不支持SNI |
自定义NSURLProtocol | 全部场景 需要更灵活的底层控制 | - 完全基于系统底层API - 自由度高 | - 开发、维护成本高 - 需要自行处理重定向、Cookie、缓存、编码等 |
libcurl | 全部场景 跨平台或自定义HTTP流程 | - 成熟、稳定 - 可设置SNI字段、丰富协议支持 - 证书校验可灵活扩展 | - C接口对ObjC/Swift开发者有一定学习成本 - 需要自行封装Cookie、重定向、缓存等 |
EMASCurl | 全部场景 期望iOS端简单集成 | - 对libcurl封装较好 - 与HTTPDNS对接简单 - 实现SNI与证书校验 | - 依赖第三方库,需要注意兼容与升级 - 特殊需求需阅读源码进行二次开发 |
开发者应结合自身业务的多域名需求、网络安全要求、兼容性、维护成本以及对第三方库的接受度进行综合评估,选择合适的接入方案,并在上线前充分测试网络请求的可用性和安全性。
- 本页导读 (0)
- 1. 前言
- 2. 在iOS上使用HTTPDNS会遇到哪些问题
- 3. 普通HTTP场景、HTTPS+非SNI场景接入方案
- 3.1 普通HTTP场景标题
- 3.2 HTTPS + 非SNI场景标题
- 4. HTTPS+SNI场景接入方案
- 4.1 自定义NSURLProtocol实现
- 4.2 自行使用libcurl实现网络请求
- 4.3 使用EMASCurl
- 5. 总结