iOS端Native场景使用HTTPDNS

更新时间:2025-03-06 03:19:40

本文主要介绍iOSApp集成HTTPDNS时实现“IP直连”的解决方案。关于iOS上如何集成HTTPDNS,请先查看iOS SDK 开发手册

1. 前言

在移动端网络环境中,DNS劫持、LocalDNS缓存污染等问题时常导致域名无法正确解析、网络请求失败。针对这些场景,阿里云HTTPDNS提供了可靠的域名递归解析服务,帮助移动App绕过本地DNS的潜在风险,提升网络请求的成功率和稳定性。

但是,在iOS平台上使用HTTPDNS,需要将请求原本的域名替换为实际解析出来的IP再直接发起请求,这就可能引发额外的问题,尤其是在HTTPSSNI等复杂场景下。因此,在集成HTTPDNS之前,需要对可能出现的问题和可行的解决方案有一个全面的认识,以便在业务中安全且正确地使用HTTPDNS。

image.png

本文将介绍在iOS上使用HTTPDNS会遇到的主要问题,并给出在不同场景下的集成方案和各自的利弊,希望帮助开发者快速完成HTTPDNS的接入。

2. 在iOS上使用HTTPDNS会遇到哪些问题

在移动端应用中,如果我们将原始URL中的域名(如 example.com)替换为HTTPDNS解析得到的IP,往往会遇到以下几个方面的问题。之所以会产生这些问题,与HTTPS协议在不同层次(TLS/SSL层和HTTP层)对Host字段的使用方式密切相关:

  • TLS/SSL层: HTTPS场景下,客户端会先进行TLS/SSL握手,其中会使用到URL中的Host来完成以下任务:

    1. 证书校验:验证服务器证书的域名(Common NameSubject Alternative Name)是否与请求的Host一致。

    2. SNI(Server Name Indication):在建立TLS连接时,客户端会将所请求的域名信息(即URL中的Host)发送给服务器,以便服务器返回对应的证书。

  • HTTP层: 在完成TLS握手后,客户端会在HTTP请求的Header中包含Host字段,用于告诉服务器此请求对应的具体站点或资源。如果将URL中的域名替换为IP,又未手动设置HTTP Header里的Host,就可能让服务器无法识别要访问的实际域名,从而导致请求失败或返回异常内容。

根据上述HostHTTPS协议栈各层的作用描述,可以看到,若只把URL中的域名直接替换为HTTPDNS解析出来的IP,则会引起以下技术问题:

  1. 域名与证书不匹配 对于HTTPS请求,如果URL里直接使用HTTPDNS解析出来的IP作为请求的Host,TLS层面无法匹配到正确的证书域名(Common NameSAN扩展域名),会导致SSL握手失败。

  2. SNI(Server Name Indication)问题 SNI场景中,同一服务器IP可能对应多个域名的证书。如果客户端在SSL握手阶段没有传递正确的域名信息(只发送了IP),服务端就无法返回匹配该域名的证书,导致SSL握手失败。由于iOS的高层网络API(如 NSURLSession)并未暴露直接配置SNI的接口,SNI问题常难以通过简单方式解决。

  3. Host头与业务寻址 如果仅把URL中的域名替换为IP,却忘记在HTTP请求头中显式设置Host为原始域名,那么在HTTP层服务器端可能无法识别具体的站点或资源。例如CDN场景下,服务器需要依赖Host字段来分发正确的内容,一旦HostIP,将导致服务异常。

  4. 底层网络库的选择 iOS自带的高层API(NSURLSession等)在定制SNI或手动证书校验方面扩展性较弱。如果开发者想自行处理SNI或修改TLS握手逻辑,就需要使用更底层的接口(如 CFNetworklibcurl 等),但这会带来更高的开发和维护成本。

综上所述,直接将URL中的域名替换为HTTPDNS解析出来的IP,在HTTPS场景下会影响TLS层的证书校验与SNI传递,并在HTTP层可能导致Host头信息异常。因此在iOS端整合HTTPDNS,需要有针对性地解决这些问题,才能确保网络请求的可靠性与安全性。

3. 普通HTTP场景、HTTPS+非SNI场景接入方案

在针对普通HTTPHTTPS + 非SNI这两种场景下,我们通常可以继续使用系统自带的NSURLSession以及常规的网络请求逻辑,只需做一些相对简单的处理即可。需要注意的是,普通HTTP场景并不涉及TLS握手和证书校验;而HTTPS + 非SNI场景需要考虑到证书校验,但可以通过在NSURLSessionHook验证流程的方式来应对。

3.1 普通HTTP场景标题

对于普通HTTP请求,网络链路中不存在TLS/SSL握手,也无需证书校验,因此集成HTTPDNS的核心操作仅在HTTP层进行:

  1. 替换请求URL中的HostHTTPDNS解析出的IP

    • 例如原始请求URLhttp://example.com/api,HTTPDNS解析后得到IP 1.2.3.4,则把URL修改为 http://1.2.3.4/api

  2. HTTP Header中显式设置Host为原始域名

    • 若使用NSMutableURLRequest,可在请求头中添加 request.allHTTPHeaderFields[@"Host"] = @"example.com"; 确保服务器在应用层能够识别到正确的域名。

说明

优点:实现简单;只需在现有HTTP请求中替换Host+设置Header,开发量较低。

缺点:仅适用于HTTP明文协议,无法解决HTTPS场景下的证书校验和SNI相关问题。

3.2 HTTPS + 非SNI场景标题

对于不使用SNI机制或仅包含少量固定域名的HTTPS站点(证书中只涵盖这些域名),可以在NSURLSession层通过以下方式来完成HTTPDNS接入与证书校验:

  1. 替换请求URL中的HostHTTPDNS解析出的IP

    • 例如原始请求URLhttps://example.com/api,HTTPDNS解析后得到IP 1.2.3.4,则把URL修改为 https://1.2.3.4/api

  2. HTTP Header中显式设置Host为原始域名

    • 同理,可以在NSMutableURLRequest里设置 request.allHTTPHeaderFields[@"Host"] = @"example.com";

  3. 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(单IPHTTPS域名)的场景,简单的NSURLSession方案中无法在SSL握手阶段发送正确的域名信息,导致握手失败。为了解决此问题,需要在更底层的Socket层级修改或指定SNI字段。常见的做法有以下三种:

4.1 自定义NSURLProtocol实现

iOS允许开发者通过继承NSURLProtocol来拦截系统发起的网络请求,并在底层自行实现HTTP/HTTPS请求逻辑。可以基于CFNetwork或者NSInputStream/NSOutputStream等接口,手动完成所有网络操作:

  1. 拦截请求

    • canInitWithRequest:方法中判断是否要拦截当前请求。

    • startLoading方法里,将原始请求URL中的域名替换为IP,并保留原始域名用于后续的证书校验和SNI设置。

  2. 设置SNI

    • 通过CFStream相关API或者SecureTransport接口,指定kCFStreamSSLPeerName为原始域名,这样在SSL握手阶段,底层会带上正确的域名信息。

  3. 证书校验

    • 手动执行证书验证流程,确保证书中包含的域名与原始域名匹配。

说明

优点:不依赖第三方库,完全基于系统底层API,灵活度高。

缺点:实现成本非常高,需要开发者手动处理重定向、Cookie、缓存、编码、流量统计等;且维护风险大,升级系统或网络环境时需要额外适配。

如果需要参考示例,阿里云提供了httpdns_ios_demoHttpDnsNSURLProtocolImpl.m的示例实现,可根据业务需求进行修改或复用。

4.2 自行使用libcurl实现网络请求

libcurlC语言实现的跨平台网络库,支持手动设置SNI字段,从而在SSL握手阶段传递正确的域名信息,以完成多域名共享同一IP的证书校验场景。大致流程如下:

  1. 解析域名,得到对应IP

    • 例如使用HTTPDNS提供的API resolveHostSyncNonBlocking:等方法,获取到目标域名的IP地址。

  2. 设置SNIIP映射

    • 通过CURLOPT_RESOLVE或其他API,把“域名:端口:解析到的IP”映射写入到curl内部DNS缓存。

    • 依然使用原始域名作为CURLOPT_URL的访问目标,保证TLS握手时会带上正确的域名信息。

  3. 证书校验

    • 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. 安装与拦截

    • 提供了两种主要使用方式: 1)拦截指定NSURLSessionConfiguration创建的NSURLSession; 2)拦截系统全局[NSURLSession sharedSession]

    • API接口示例:

  2. HTTPDNS配合

    • 实现EMASCurlProtocolDNSResolver协议即可将HTTPDNS解析结果交给EMASCurl。

    • resolveDomain:方法中调用[HttpDnsService resolveHostSyncNonBlocking:]获取IP地址,然后返回给EMASCurl来完成后续的SNI设置和请求发送。

  3. 证书校验

    • EMASCurl依赖libcurl的证书校验机制,开发者也可通过相应接口扩展或自定义证书校验,以适配业务需求。

说明

优点:

  • 封装了libcurl的底层能力,API更符合iOS开发者的使用习惯。

  • 通过DNS Hook机制与HTTPDNS轻松对接。

  • 同时解决了SNI场景的域名传递和证书校验问题,降低集成成本。

缺点

  • 依赖第三方库(EMASCurl+libcurl),需要注意兼容性、版本升级等。

  • 对于非常复杂的HTTP特性或自定义需求,仍需要阅读和理解EMASCurl内部封装实现,以确保业务可用性。

重要
  • 在接入EMASCurl时,需要测试常见的HTTP/HTTPS特性(如重定向、Cookie、并发请求等)是否符合业务需求。

  • 若业务对安全或网络性能有严格要求,需要评估EMASCurl在当前iOS系统版本下的表现。

  • 确保在不同网络环境(Wi-Fi/蜂窝网络/代理等)下都能正常完成请求和握手。

5. 总结

根据业务中是否需要支持SNI、多域名及证书校验等需求,可结合以下方案进行选择。下表对各方案进行对比:

方案

适用场景

优点

缺点

仅设置HostHeader

普通HTTP 场景

- 集成成本最低

-仅适用于HTTP明文协议

NSURLSession + Hook证书校验

(仍需设置HostHeader)

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. 总结
AI助理

点击开启售前

在线咨询服务

你好,我是AI助理

可以解答问题、推荐解决方案等