前言
本章节介绍HTTPDNS iOS SDK的接入方法。
推荐工程使用cocoapods管理依赖。
当前SDK最新版本支持
iOS Deployment Target 10.0
及以上。当前SDK打包方式为静态库。
支持模拟器
x86_64
、arm64
架构以及真机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
文件,如图示。
2.2 将framework文件添加到工程中
在Finder
中选中上述xcframework
文件,拖入需要使用HTTPDNS iOS SDK的target下,并在弹出框中勾选Copy items if needed
。
2.3 添加系统库依赖
在工程项目中(Build Phases -> Link Binary With Libraries)添加以下库依赖。
libsqlite3.0.tbd
libresolv.tbd
CoreTelephony.framework
SystemConfiguration.framework
最终效果如图示。
2.4 ObjC配置
iOS端集成SDK时需要做-ObjC
配置,即应用的 TARGETS -> Build Settings -> Linking -> Other Linker Flags ,需添加上 -ObjC
这个属性,如图示。
第二步:使用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。
注意事项
务必编写降级代码
降级代码指的是HTTPDNS未获取到期望结果时的处理代码。通常您可以降级到使用LocalDNS进行解析,即,为网络库传入原始域名,让网络库自行走本地LocalDNS解析。
记录从HTTPDNS获取的IP及sessinId
我们提供了用于解析问题排查的解决方案,需要您将从HTTPDNS获取的IP及sessionId记录到日志中,详情请参考如何使用“会话追踪方案”排查解析异常。
设置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场景指导。
Cookie字段
部分网络库支持COOKIE的自动存储管理,当您使用HTTPDNS进行IP URL请求时,部分网络库会将您URL中的IP信息作为COOKIE对应的域名信息进行存储管理(而非HTTP请求头HOST字段信息),进而造成COOKIE管理与使用上的困扰,因此您需要关闭COOKIE的自动管理功能(默认关闭)。
HTTPS/WebView/SNI场景
HTTPS场景,请参考iOS端HTTPS场景使用HTTPDNS。
WebView场景,请参考iOS端WebView " IP直连 " 如何处理 Cookie。
代理情况下的使用
当存在中间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冲突解决方案