iOS SDK接入
前言
本章節(jié)介紹HTTPDNS iOS SDK的接入方法。
推薦工程使用cocoapods管理依賴。
當(dāng)前SDK最新版本支持
iOS Deployment Target 10.0
及以上。當(dāng)前SDK打包方式為靜態(tài)庫(kù)。
支持模擬器
x86_64
、arm64
架構(gòu)以及真機(jī)arm64
架構(gòu)。
準(zhǔn)備工作
已創(chuàng)建項(xiàng)目和應(yīng)用。具體操作請(qǐng)參見創(chuàng)建項(xiàng)目和應(yīng)用。
第一步:將SDK添加到您的應(yīng)用
我們提供了cocoapods引入依賴和本地依賴兩種集成方式,方便您根據(jù)需要將SDK添加到您的應(yīng)用中。
1. cocoapods引入依賴
1.1 指定Master倉(cāng)庫(kù)和阿里云倉(cāng)庫(kù)
HTTPDNS iOS SDK和其他EMAS產(chǎn)品的iOS SDK,都是發(fā)布到阿里云EMAS官方維護(hù)的github倉(cāng)庫(kù)中,因此,您需要在您的Podfile文件中包含該倉(cāng)庫(kù)地址。
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版本號(hào)請(qǐng)以發(fā)布說明文檔中的最新版本號(hào)為準(zhǔn)。
1.3 安裝依賴
在您的Terminal中進(jìn)入Podfile所在目錄,執(zhí)行以下命令安裝依賴。
pod install --repo-update
安裝完成后,注意使用.xcworkspace
文件重新打開工程。
2. 本地手動(dòng)集成依賴
2.1 下載依賴文件
從EMAS SDK列表選擇HTTPDNS iOS版本進(jìn)行下載,解壓得到多個(gè)framework
文件,如圖示。
2.2 將framework文件添加到工程中
在Finder
中選中上述xcframework
文件,拖入需要使用HTTPDNS iOS SDK的target下,并在彈出框中勾選Copy items if needed
。
2.3 添加系統(tǒng)庫(kù)依賴
在工程項(xiàng)目中(Build Phases -> Link Binary With Libraries)添加以下庫(kù)依賴。
libsqlite3.0.tbd
libresolv.tbd
CoreTelephony.framework
SystemConfiguration.framework
最終效果如圖示。
2.4 ObjC配置
iOS端集成SDK時(shí)需要做-ObjC
配置,即應(yīng)用的 TARGETS -> Build Settings -> Linking -> Other Linker Flags ,需添加上 -ObjC
這個(gè)屬性,如圖示。
第二步:使用SDK
1. 引入頭文件
在需要使用HTTPDNS的代碼文件中引入頭文件。
#import <AlicloudHttpDNS/AlicloudHttpDNS.h>
import AlicloudHttpDNS
2. 構(gòu)造HTTPDNS實(shí)例并進(jìn)行配置
建議在-[AppDelegate application:didFinishLaunchingWithOptions:]
方法中構(gòu)造HTTPDNS全局實(shí)例,并進(jìn)行相關(guān)配置。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 使用阿里云HTTPDN控制臺(tái)分配的AccountId構(gòu)造全局實(shí)例
// 全局只需要初始化一次
HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:xxxxxx];
// 若開啟了鑒權(quán)訪問,則需要到控制臺(tái)獲得鑒權(quán)密鑰并在初始化時(shí)進(jìn)行配置
// HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:xxxxxx secretKey:@"your secret key"];
// 打開日志,調(diào)試排查問題時(shí)使用
[httpdns setLogEnabled:NO];
// 設(shè)置httpdns域名解析網(wǎng)絡(luò)請(qǐng)求是否需要走HTTPS方式
[httpdns setHTTPSRequestEnabled:YES];
// 設(shè)置開啟持久化緩存,使得APP啟動(dòng)后可以復(fù)用上次活躍時(shí)緩存在本地的IP,提高啟動(dòng)后獲取域名解析結(jié)果的速度
[httpdns setPersistentCacheIPEnabled:YES];
// 設(shè)置允許使用已經(jīng)過期的IP,當(dāng)域名的IP配置比較穩(wěn)定時(shí)可以使用,提高解析效率
[httpdns setReuseExpiredIPEnabled:YES];
// 設(shè)置底層HTTPDNS網(wǎng)絡(luò)請(qǐng)求超時(shí)時(shí)間,單位為秒
[httpdns setTimeoutInterval:2];
// 設(shè)置是否支持IPv6地址解析,只有開啟這個(gè)開關(guān),解析接口才有能力解析域名的IPv6地址并返回
[httpdns setIPv6Enabled:YES];
return YES;
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// 使用HTTPDNS阿里云控制臺(tái)分配的AccountId構(gòu)造全局實(shí)例
// 全局只需要初始化一次
let httpdns = HttpDnsService(accountID: xxxxxx)!
// 若開啟了鑒權(quán)訪問,則需要到控制臺(tái)獲得鑒權(quán)密鑰并在初始化時(shí)進(jìn)行配置
// let httpdns = HttpDnsService(accountID: xxxxxx, secretKey: "your secret key")!
// 打開日志,調(diào)試排查問題時(shí)使用
httpdns.setLogEnabled(false)
// 設(shè)置httpdns域名解析網(wǎng)絡(luò)請(qǐng)求是否需要走HTTPS方式
httpdns.setHTTPSRequestEnabled(true)
// 設(shè)置開啟持久化緩存,使得APP啟動(dòng)后可以復(fù)用上次活躍時(shí)緩存在本地的IP,提高啟動(dòng)后獲取域名解析結(jié)果的速度
httpdns.setPersistentCacheIPEnabled(true)
// 設(shè)置允許使用已經(jīng)過期的IP,當(dāng)域名的IP配置比較穩(wěn)定時(shí)可以使用,提高解析效率
httpdns.setReuseExpiredIPEnabled(true)
// 設(shè)置是否支持IPv6地址解析,只有開啟這個(gè)開關(guān),解析接口才有能力解析域名的IPv6地址并返回
httpdns.setIPv6Enabled(true)
return true
}
3. 獲取服務(wù)實(shí)例
HTTPDNS iOS SDK以全局service實(shí)例的方式提供域名解析服務(wù),您可以通過以下方式獲取實(shí)例。
HttpDnsService *httpdns = [HttpDnsService sharedInstance];
let httpdns = HttpDnsService.sharedInstance()!
4. 進(jìn)行域名解析
HTTPDNS提供了多種域名解析方式,包括預(yù)解析/同步解析/異步解析/同步非阻塞解析。下面以同步非阻塞解析接口作為例子。
HttpDnsService *httpdns = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpdns resolveHostSyncNonBlocking:@"www.aliyun.com" byIpType:HttpdnsQueryIPTypeAuto];
if (result) {
// 使用域名解析結(jié)果
} else {
// 同步非阻塞接口,為了最快的解析速度,若緩存中無有效解析結(jié)果,會(huì)立即返回空值,同時(shí)在后臺(tái)發(fā)起新的解析請(qǐng)求
// 因此,要做好走LocalDNS解析,或者仍然直接給網(wǎng)絡(luò)庫(kù)傳完整域名的方式降級(jí)
// 可以使用強(qiáng)同步接口、或者回調(diào)形式的接口確保獲得HTTPDNS解析的結(jié)果
}
let httpdns = HttpDnsService.sharedInstance()!
if let result = httpdns.resolveHostSyncNonBlocking("www.aliyun.com", by: HttpdnsQueryIPType.auto) {
// 使用域名解析結(jié)果
} else {
// 同步非阻塞接口,為了最快的解析速度,若緩存中無有效解析結(jié)果,會(huì)立即返回空值,同時(shí)在后臺(tái)發(fā)起新的解析請(qǐng)求
// 因此,要做好走LocalDNS解析,或者仍然直接給網(wǎng)絡(luò)庫(kù)傳完整域名的方式降級(jí)
// 可以使用強(qiáng)同步接口、或者回調(diào)形式的接口確保獲得HTTPDNS解析的結(jié)果
}
請(qǐng)根據(jù)您的實(shí)際使用場(chǎng)景選擇合適的域名解析接口。
如果返回的result一直為
nil
,請(qǐng)檢查是否已經(jīng)在阿里云HTTPDNS控制臺(tái)上添加該域名。為了網(wǎng)絡(luò)異常情況導(dǎo)致返回結(jié)果為
nil
時(shí)不影響業(yè)務(wù)流程,建議降級(jí)到LocalDNS
解析作為兜底邏輯。
5. 使用域名解析結(jié)果
不同情況下,域名解析結(jié)果可能包含多種情況。
空結(jié)果,如在使用同步非阻塞接口,或者網(wǎng)絡(luò)異常時(shí)。
只有ipv4的地址,在本地網(wǎng)絡(luò)環(huán)境為ipv4單棧且指定包含ipv4的請(qǐng)求類型,或域名只配置了ipv4的地址。
只有ipv6的地址,在啟用ipv6且指定解析ipv6的地址時(shí)。考慮當(dāng)前ipv6的推廣程度,這種情況一般不會(huì)發(fā)生。
同時(shí)擁有ipv4、ipv6的地址,在啟用ipv6且指定解析雙棧地址,或指定了自動(dòng)判斷網(wǎng)絡(luò)類型且是雙棧環(huán)境下,同時(shí)域名也配置了ipv4、ipv6地址的情況下。
本示例中,配置開啟了ipv6解析,且請(qǐng)求IP類型設(shè)置為Both
,若域名同時(shí)配置了ipv4、ipv6地址,則解析結(jié)果也會(huì)同時(shí)包含。因此,若需要優(yōu)先選擇ipv4地址,則可以按如下代碼處理解析結(jié)果。
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。
注意事項(xiàng)
務(wù)必編寫降級(jí)代碼
降級(jí)代碼指的是HTTPDNS未獲取到期望結(jié)果時(shí)的處理代碼。通常您可以降級(jí)到使用LocalDNS進(jìn)行解析,即,為網(wǎng)絡(luò)庫(kù)傳入原始域名,讓網(wǎng)絡(luò)庫(kù)自行走本地LocalDNS解析。
記錄從HTTPDNS獲取的IP及sessinId
我們提供了用于解析問題排查的解決方案,需要您將從HTTPDNS獲取的IP及sessionId記錄到日志中,詳情請(qǐng)參考如何使用“會(huì)話追蹤方案”排查解析異常。
設(shè)置HTTP請(qǐng)求頭HOST字段
標(biāo)準(zhǔn)的HTTP協(xié)議中服務(wù)端會(huì)將HTTP請(qǐng)求頭HOST字段的值作為請(qǐng)求的域名信息進(jìn)行解析。使用HTTPDNS后,您可能需要將HTTP請(qǐng)求URL中的HOST字段替換為HTTPDNS解析獲得的IP,這時(shí)標(biāo)準(zhǔn)的網(wǎng)絡(luò)庫(kù)會(huì)將您的IP賦值給HTTP請(qǐng)求頭的HOST字段,進(jìn)而導(dǎo)致服務(wù)端的解析異常(服務(wù)端認(rèn)可的是您的域名信息,而非IP信息)。
為了解決這個(gè)問題,您可以主動(dòng)設(shè)置HTTP請(qǐng)求HOST字段的值,如以下這個(gè)簡(jiǎn)單示例:
- (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成功,進(jìn)行URL替換和HOST頭設(shè)置 NSRange hostFirstRange = [originalUrlStr rangeOfString:url.host]; NSString* newUrl = [originalUrlStr stringByReplacingCharactersInRange:hostFirstRange withString:validIp]; request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:newUrl]]; // 設(shè)置請(qǐng)求HOST字段 [request setValue:url.host forHTTPHeaderField:@"host"]; } else { // 本處演示如何做好降級(jí)處理 // 通過HTTPDNS無法獲取IP,直接使用原有的URL進(jìn)行網(wǎng)絡(luò)請(qǐng)求 request = [[NSMutableURLRequest alloc] initWithURL:url]; } // 發(fā)送請(qǐng)求 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成功,進(jìn)行URL替換和HOST頭設(shè)置 let hostFirstRange = originalUrlStr.firstRange(of: url.host!)! let newUrl = originalUrlStr.replacingCharacters(in: hostFirstRange, with: validIp) request = URLRequest(url: URL(string: newUrl)!) // 設(shè)置請(qǐng)求HOST字段 request.setValue(url.host!, forHTTPHeaderField: "host") } else { // 本處演示如何做好降級(jí)處理 // 通過HTTPDNS無法獲取IP,直接使用原有的URL進(jìn)行網(wǎng)絡(luò)請(qǐng)求 request = URLRequest(url: url) } // 發(fā)送請(qǐng)求 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() }
重要這個(gè)簡(jiǎn)單示例中,也要注意這些細(xì)節(jié):
該示例僅展示了一定解析成功的情況,未考慮兜底邏輯。
簡(jiǎn)單起見,該示例請(qǐng)求的是HTTP的地址,因此要在
plist.info
中配置NSAllowsArbitraryLoads
才能訪問。如果請(qǐng)求地址為HTTPS類型,則需要參考后文的HTTPS場(chǎng)景指導(dǎo)。
Cookie字段
部分網(wǎng)絡(luò)庫(kù)支持COOKIE的自動(dòng)存儲(chǔ)管理,當(dāng)您使用HTTPDNS進(jìn)行IP URL請(qǐng)求時(shí),部分網(wǎng)絡(luò)庫(kù)會(huì)將您URL中的IP信息作為COOKIE對(duì)應(yīng)的域名信息進(jìn)行存儲(chǔ)管理(而非HTTP請(qǐng)求頭HOST字段信息),進(jìn)而造成COOKIE管理與使用上的困擾,因此您需要關(guān)閉COOKIE的自動(dòng)管理功能(默認(rèn)關(guān)閉)。
HTTPS/WebView/SNI場(chǎng)景
HTTPS場(chǎng)景,請(qǐng)參考iOS端HTTPS場(chǎng)景使用HTTPDNS。
WebView場(chǎng)景,請(qǐng)參考iOS端WebView " IP直連 " 如何處理 Cookie。
代理情況下的使用
當(dāng)存在中間HTTP代理時(shí),客戶端發(fā)起的請(qǐng)求中請(qǐng)求行會(huì)使用絕對(duì)路徑的URL,在您開啟HTTPDNS并采用IP URL進(jìn)行訪問時(shí),中間代理將識(shí)別您的IP信息并將其作為真實(shí)訪問的HOST信息傳遞給目標(biāo)服務(wù)器,這時(shí)目標(biāo)服務(wù)器將無法處理這類無真實(shí)HOST信息的HTTP請(qǐng)求。移動(dòng)網(wǎng)關(guān)提供了
X-Online-Host
的私有協(xié)議字段來解決這個(gè)問題,比如:目標(biāo) URL:http://www.aliyun.com/product/oss/ 通過 HTTPDNS 解析出來的www.aliyun.com的IP:X.X.X.X 代理:10.0.0.172:80 您的HTTP請(qǐng)求頭: GET http://X.X.X.X/product/oss/ HTTP/1.1 # 通過代理發(fā)起的HTTP請(qǐng)求頭,請(qǐng)求行是一個(gè)絕對(duì)路徑 Host: www.aliyun.com # 這個(gè)Header會(huì)被代理網(wǎng)關(guān)忽略,代理網(wǎng)關(guān)會(huì)使用請(qǐng)求行絕對(duì)路徑中的host字段作為源站的host,即1.1.X.X X-Online-Host: www.aliyun.com # 這個(gè)Header就是移動(dòng)網(wǎng)關(guān)為了傳遞真實(shí)Host添加的私有頭部,源站需要配置識(shí)別該私有頭部以獲取真實(shí)的Host信息
同樣您可以通過下述方法進(jìn)行
X-Online-Host
請(qǐng)求頭域的設(shè)置,并在服務(wù)端設(shè)置對(duì)該私有頭域的解析。[request setValue:url.host forHTTPHeaderField:@"X-Online-Host"];
說明在絕大多數(shù)場(chǎng)景下,我們建議您檢測(cè)當(dāng)前設(shè)備是否開啟了網(wǎng)絡(luò)代理,然后在代理模式下不使用HTTPDNS進(jìn)行域名解析。
集成常見問題
UTDID沖突,可參考:阿里云-云產(chǎn)品SDK UTDID沖突解決方案