iOS SDK接入

前言

本章节介绍HTTPDNS iOS SDK的接入方法。

  • 推荐工程使用cocoapods管理依赖。

  • 当前SDK最新版本支持iOS Deployment Target 10.0及以上。

  • 当前SDK打包方式为静态库。

  • 支持模拟器x86_64arm64架构以及真机arm64架构。

准备工作

第一步:将SDK添加到您的应用

我们提供了cocoapods引入依赖和本地依赖两种集成方式,方便您根据需要将SDK添加到您的应用中。

1. cocoapods引入依赖

1.1 指定Master仓库和阿里云仓库

HTTPDNS iOS SDK和其他EMAS产品的iOS SDK,都是发布到阿里云EMAS官方维护的github仓库中,因此,您需要在您的Podfile文件中包含该仓库地址。

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/aliyun/aliyun-specs.git'

1.2 添加依赖

为您需要依赖HTTPDNS iOS SDK的target添加如下依赖。

use_framework!

pod 'AlicloudHTTPDNS', 'x.x.x'
重要

示例依赖中的SDK版本号请以发布说明文档中的最新版本号为准。

1.3 安装依赖

在您的Terminal中进入Podfile所在目录,执行以下命令安装依赖。

pod install --repo-update
重要

安装完成后,注意使用.xcworkspace文件重新打开工程。

2. 本地手动集成依赖

2.1 下载依赖文件

EMAS SDK列表选择HTTPDNS iOS版本进行下载,解压得到多个framework文件,如图示。

image

2.2 将framework文件添加到工程中

Finder中选中上述xcframework文件,拖入需要使用HTTPDNS iOS SDK的target下,并在弹出框Action中选择Copy files to destination

image

2.3 添加系统库依赖

在工程项目中(Build Phases -> Link Binary With Libraries)添加以下库依赖。

libsqlite3.0.tbd
libresolv.tbd
CoreTelephony.framework
SystemConfiguration.framework

最终效果如图示。

image

2.4 ObjC配置

iOS端集成SDK时需要做-ObjC配置,即应用的 TARGETS -> Build Settings -> Linking -> Other Linker Flags ,需添加上 -ObjC 这个属性,如图示。

image

第二步:使用SDK

1. 引入头文件

在需要使用HTTPDNS的代码文件中引入头文件。

#import <AlicloudHttpDNS/AlicloudHttpDNS.h>
import AlicloudHttpDNS

2. 构造HTTPDNS实例并进行配置

建议在-[AppDelegate application:didFinishLaunchingWithOptions:]方法中构造HTTPDNS全局实例,并进行相关配置。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    // 使用阿里云HTTPDN控制台分配的AccountId构造全局实例
    // 全局只需要初始化一次
    HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:xxxxxx];

    // 若开启了鉴权访问,则需要到控制台获得鉴权密钥并在初始化时进行配置
    // HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:xxxxxx secretKey:@"your secret key"];

    // 打开日志,调试排查问题时使用
    [httpdns setLogEnabled:NO];

    // 设置httpdns域名解析网络请求是否需要走HTTPS方式
    [httpdns setHTTPSRequestEnabled:YES];

    // 设置开启持久化缓存,使得APP启动后可以复用上次活跃时缓存在本地的IP,提高启动后获取域名解析结果的速度
    [httpdns setPersistentCacheIPEnabled:YES];

    // 设置允许使用已经过期的IP,当域名的IP配置比较稳定时可以使用,提高解析效率
    [httpdns setReuseExpiredIPEnabled:YES];

    // 设置底层HTTPDNS网络请求超时时间,单位为秒
    [httpdns setTimeoutInterval:2];

    // 设置是否支持IPv6地址解析,只有开启这个开关,解析接口才有能力解析域名的IPv6地址并返回
    [httpdns setIPv6Enabled:YES];

    return YES;
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    // 使用HTTPDNS阿里云控制台分配的AccountId构造全局实例
    // 全局只需要初始化一次
    let httpdns = HttpDnsService(accountID: xxxxxx)!

    // 若开启了鉴权访问,则需要到控制台获得鉴权密钥并在初始化时进行配置
    // let httpdns = HttpDnsService(accountID: xxxxxx, secretKey: "your secret key")!

    // 打开日志,调试排查问题时使用
    httpdns.setLogEnabled(false)

    // 设置httpdns域名解析网络请求是否需要走HTTPS方式
    httpdns.setHTTPSRequestEnabled(true)

    // 设置开启持久化缓存,使得APP启动后可以复用上次活跃时缓存在本地的IP,提高启动后获取域名解析结果的速度
    httpdns.setPersistentCacheIPEnabled(true)

    // 设置允许使用已经过期的IP,当域名的IP配置比较稳定时可以使用,提高解析效率
    httpdns.setReuseExpiredIPEnabled(true)

    // 设置是否支持IPv6地址解析,只有开启这个开关,解析接口才有能力解析域名的IPv6地址并返回
    httpdns.setIPv6Enabled(true)

    return true
}

3. 获取服务实例

HTTPDNS iOS SDK以全局service实例的方式提供域名解析服务,您可以通过以下方式获取实例。

HttpDnsService *httpdns = [HttpDnsService sharedInstance];
let httpdns = HttpDnsService.sharedInstance()!

4. 进行域名解析

HTTPDNS提供了多种域名解析方式,包括预解析/同步解析/异步解析/同步非阻塞解析。下面以同步非阻塞解析接口作为例子。

HttpDnsService *httpdns = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpdns resolveHostSyncNonBlocking:@"www.aliyun.com" byIpType:HttpdnsQueryIPTypeAuto];
if (result) {
    // 使用域名解析结果
} else {
    // 同步非阻塞接口,为了最快的解析速度,若缓存中无有效解析结果,会立即返回空值,同时在后台发起新的解析请求
    // 因此,要做好走LocalDNS解析,或者仍然直接给网络库传完整域名的方式降级
    // 可以使用强同步接口、或者回调形式的接口确保获得HTTPDNS解析的结果
}
let httpdns = HttpDnsService.sharedInstance()!
if let result = httpdns.resolveHostSyncNonBlocking("www.aliyun.com", by: HttpdnsQueryIPType.auto) {
    // 使用域名解析结果
} else {
    // 同步非阻塞接口,为了最快的解析速度,若缓存中无有效解析结果,会立即返回空值,同时在后台发起新的解析请求
    // 因此,要做好走LocalDNS解析,或者仍然直接给网络库传完整域名的方式降级
    // 可以使用强同步接口、或者回调形式的接口确保获得HTTPDNS解析的结果
}

请根据您的实际使用场景选择合适的域名解析接口。

重要
  • 如果返回的result一直为nil,请检查是否已经在阿里云HTTPDNS控制台上添加该域名。

  • 为了网络异常情况导致返回结果为nil时不影响业务流程,建议降级到LocalDNS解析作为兜底逻辑。

5. 使用域名解析结果

不同情况下,域名解析结果可能包含多种情况。

  • 空结果,如在使用同步非阻塞接口,或者网络异常时。

  • 只有ipv4的地址,在本地网络环境为ipv4单栈且指定包含ipv4的请求类型,或域名只配置了ipv4的地址。

  • 只有ipv6的地址,在启用ipv6且指定解析ipv6的地址时。考虑当前ipv6的推广程度,这种情况一般不会发生。

  • 同时拥有ipv4、ipv6的地址,在启用ipv6且指定解析双栈地址,或指定了自动判断网络类型且是双栈环境下,同时域名也配置了ipv4、ipv6地址的情况下。

本示例中,配置开启了ipv6解析,且请求IP类型设置为Both,若域名同时配置了ipv4、ipv6地址,则解析结果也会同时包含。因此,若需要优先选择ipv4地址,则可以按如下代码处理解析结果。

HttpDnsService *httpdns = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpdns resolveHostSyncNonBlocking:@"www.aliyun.com" byIpType:HttpdnsQueryIPTypeAuto];
if (!result) {
    // 无有效ip,走兜底逻辑
}

if (result.hasIpv4Address) {
    NSString *ip = result.firstIpv4Address;
    // 使用ip

    NSArray<NSString *> *ips = result.ips;
    // 使用ip列表
} else if (result.hasIpv6Address) {
    NSString *ip = result.firstIpv6Address;
    // 使用ip

    NSArray<NSString *> *ips = result.ipv6s;
    // 使用ip列表
} else {
    // 无有效ip,走兜底逻辑
}
let httpdns = HttpDnsService.sharedInstance()!
if let result = httpdns.resolveHostSyncNonBlocking("www.aliyun.com", by: HttpdnsQueryIPType.auto) {
    if (result.hasIpv4Address()) {
        let ip = result.firstIpv4Address()
        // 使用ip

        let ipList = result.ips
        // 使用ip列表
    } else if (result.hasIpv6Address()) {
        let ip = result.firstIpv6Address()
        // 使用ip

        let ipList = result.ipv6s
        // 使用ip列表
    } else {
        // 无有效ip,走兜底逻辑
    }
} else {
    // 无有效ip,走兜底逻辑
}

样例代码

HTTPDNS iOS SDK接入工程样例参见HTTPDNS iOS Demo

注意事项

  1. 务必编写降级代码

    降级代码指的是HTTPDNS未获取到期望结果时的处理代码。通常您可以降级到使用LocalDNS进行解析,即,为网络库传入原始域名,让网络库自行走本地LocalDNS解析。

  2. 记录从HTTPDNS获取的IP及sessinId

    我们提供了用于解析问题排查的解决方案,需要您将从HTTPDNS获取的IP及sessionId记录到日志中,详情请参考如何使用“会话追踪方案”排查解析异常

  3. 设置HTTP请求头HOST字段

    标准的HTTP协议中服务端会将HTTP请求头HOST字段的值作为请求的域名信息进行解析。使用HTTPDNS后,您可能需要将HTTP请求URL中的HOST字段替换为HTTPDNS解析获得的IP,这时标准的网络库会将您的IP赋值给HTTP请求头的HOST字段,进而导致服务端的解析异常(服务端认可的是您的域名信息,而非IP信息)。

    为了解决这个问题,您可以主动设置HTTP请求HOST字段的值,如以下这个简单示例:

    - (void)sampleRequestUsingHttpdns {
        HttpDnsService *httpdns = [HttpDnsService sharedInstance];
    
        NSString *originalUrlStr = @"http://www.aliyun.com/";
        NSURL* url = [NSURL URLWithString:originalUrlStr];
    
        // 同步接口获取IP
        HttpdnsResult* result = [httpdns resolveHostSyncNonBlocking:url.host byIpType:HttpdnsQueryIPTypeAuto];
        NSLog(@"resolve result: %@", result);
        NSString *validIp = nil;
        if (result) {
            if (result.hasIpv4Address) {
                validIp = result.firstIpv4Address;
            }
        }
    
        NSMutableURLRequest *request;
    
        if (validIp) {
            // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
            NSRange hostFirstRange = [originalUrlStr rangeOfString:url.host];
            NSString* newUrl = [originalUrlStr stringByReplacingCharactersInRange:hostFirstRange withString:validIp];
            request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:newUrl]];
            // 设置请求HOST字段
            [request setValue:url.host forHTTPHeaderField:@"host"];
        } else {
            // 本处演示如何做好降级处理
            // 通过HTTPDNS无法获取IP,直接使用原有的URL进行网络请求
            request = [[NSMutableURLRequest alloc] initWithURL:url];
        }
    
        // 发送请求
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"error: %@", error);
            } else {
                NSLog(@"response: %@", response);
            }
        }];
        [task resume];
    }
    func sampleRequestUsingHttpdns() {
        let httpdns = HttpDnsService.sharedInstance()!
        let originalUrlStr = "http://www.aliyun.com/"
        let url = URL(string: originalUrlStr)!
    
        // 同步接口获取IP
        let result = httpdns.resolveHostSyncNonBlocking(url.host!, by: HttpdnsQueryIPType.auto)
        print("resolve result: \(result?.description ?? "")")
    
        var validIp: String?
        if let result = result {
            if result.hasIpv4Address() {
                validIp = result.firstIpv4Address()
            }
        }
    
        var request: URLRequest
    
        if let validIp = validIp {
            // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
            let hostFirstRange = originalUrlStr.firstRange(of: url.host!)!
            let newUrl = originalUrlStr.replacingCharacters(in: hostFirstRange, with: validIp)
            request = URLRequest(url: URL(string: newUrl)!)
            // 设置请求HOST字段
            request.setValue(url.host!, forHTTPHeaderField: "host")
        } else {
            // 本处演示如何做好降级处理
            // 通过HTTPDNS无法获取IP,直接使用原有的URL进行网络请求
            request = URLRequest(url: url)
        }
    
        // 发送请求
        let session = URLSession.shared
        let task = session.dataTask(with: request) { (data, response, error) in
            if let error = error {
                print("error: \(error)")
            } else {
                print("response: \(response?.description ?? "")")
            }
        }
        task.resume()
    }
    重要

    这个简单示例中,也要注意这些细节:

    • 该示例仅展示了一定解析成功的情况,未考虑兜底逻辑。

    • 简单起见,该示例请求的是HTTP的地址,因此要在plist.info中配置NSAllowsArbitraryLoads才能访问。

    • 如果请求地址为HTTPS类型,则需要参考后文的HTTPS场景指导。

  4. Cookie字段

    部分网络库支持Cookie的自动存储管理,当您使用HTTPDNS进行IP URL请求时,部分网络库会将您URL中的IP信息作为Cookie对应的域名信息进行存储管理(而非HTTP请求头HOST字段信息),进而造成Cookie管理与使用上的困扰,因此您需要关闭Cookie的自动管理功能(默认关闭)。

  5. HTTPS/WebView/SNI场景

    HTTPS场景,请参考iOS端HTTPS场景使用HTTPDNS

    WebView场景,请参考iOS端WebView " IP直连 " 如何处理 Cookie

  6. 代理情况下的使用

    当存在中间HTTP代理时,客户端发起的请求中请求行会使用绝对路径的URL,在您开启HTTPDNS并采用IP URL进行访问时,中间代理将识别您的IP信息并将其作为真实访问的HOST信息传递给目标服务器,这时目标服务器将无法处理这类无真实HOST信息的HTTP请求。移动网关提供了X-Online-Host的私有协议字段来解决这个问题,比如:

    目标 URL:http://www.aliyun.com/product/oss/
    通过 HTTPDNS 解析出来的www.aliyun.com的IP:X.X.X.X
    代理:10.0.0.172:80
    
    您的HTTP请求头:
    
    GET http://X.X.X.X/product/oss/ HTTP/1.1     # 通过代理发起的HTTP请求头,请求行是一个绝对路径
    Host: www.aliyun.com   # 这个Header会被代理网关忽略,代理网关会使用请求行绝对路径中的host字段作为源站的host,即1.1.X.X
    X-Online-Host: www.aliyun.com    # 这个Header就是移动网关为了传递真实Host添加的私有头部,源站需要配置识别该私有头部以获取真实的Host信息

    同样您可以通过下述方法进行X-Online-Host请求头域的设置,并在服务端设置对该私有头域的解析。

    [request setValue:url.host forHTTPHeaderField:@"X-Online-Host"];
    说明

    在绝大多数场景下,我们建议您检测当前设备是否开启了网络代理,然后在代理模式下不使用HTTPDNS进行域名解析。

集成常见问题

UTDID冲突,可参考:阿里云-云产品SDK UTDID冲突解决方案