本文檔介紹在iOS用戶端上使用AFNetworking接入HTTPDNS的方案。
1. 概述
AFNetworking是iOS開發中廣泛使用的網路請求架構,提供了簡潔的API和強大的功能。本文檔介紹如何在使用AFNetworking的專案中整合HTTPDNS。
關於HTTPDNS的基礎知識和在iOS上使用時遇到的技術挑戰,請先參考:iOS端Native情境使用HTTPDNS。
2. 普通HTTP情境、HTTPS+非SNI情境接入方案
適用於普通HTTP或HTTPS + 非SNI這兩種情境。
2.1 建立AFHTTPSessionManager
+ (AFHTTPSessionManager *)sharedAfnManager {
static AFHTTPSessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
// 配置安全性原則
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
manager.securityPolicy = securityPolicy;
// 配置序列化器
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:
@"application/json", @"text/json", @"text/javascript", @"text/plain", nil];
manager.requestSerializer.timeoutInterval = 10.0f;
});
return manager;
}
2.2 HTTPDNS網域名稱解析
+ (NSString *)resolveAvailableIp:(NSString *)host {
HttpDnsService *httpDnsService = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpDnsService resolveHostSyncNonBlocking:host
byIpType:HttpdnsQueryIPTypeAuto];
if (!result) return nil;
if (result.hasIpv4Address) {
return result.firstIpv4Address;
} else if (result.hasIpv6Address) {
return [NSString stringWithFormat:@"[%@]", result.firstIpv6Address];
}
return nil;
}
2.3 發送網路請求並處理認證校正
+ (void)httpDnsQueryWithURL:(NSString *)originalUrl
completionHandler:(void(^)(NSString *message))completionHandler {
NSURL *url = [NSURL URLWithString:originalUrl];
NSString *resolvedIpAddress = [self resolveAvailableIp:url.host];
NSString *requestUrl = originalUrl;
if (resolvedIpAddress) {
// 將網域名稱替換為解析得到的IP
requestUrl = [originalUrl stringByReplacingOccurrencesOfString:url.host
withString:resolvedIpAddress];
NSLog(@"HTTPDNS解析成功,網域名稱: %@, IP: %@", url.host, resolvedIpAddress);
} else {
NSLog(@"HTTPDNS解析失敗,使用原始URL: %@", url.host);
}
AFHTTPSessionManager *manager = [self sharedAfnManager];
// 關鍵:設定Host頭,確保伺服器能正確識別網域名稱
[manager.requestSerializer setValue:url.host forHTTPHeaderField:@"host"];
// 關鍵:配置HTTPS認證校正,使用原始網域名稱進行驗證
[manager setSessionDidReceiveAuthenticationChallengeBlock:
^NSURLSessionAuthChallengeDisposition(NSURLSession *session,
NSURLAuthenticationChallenge *challenge,
NSURLCredential **credential) {
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust
forDomain:url.host]) {
*credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
return NSURLSessionAuthChallengeUseCredential;
}
}
return NSURLSessionAuthChallengePerformDefaultHandling;
}];
// 發送請求
[manager GET:requestUrl parameters:nil headers:nil progress:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSData *data = [[NSData alloc] initWithData:responseObject];
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (completionHandler) {
completionHandler([NSString stringWithFormat:@"請求成功: %@", dataStr]);
}
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completionHandler) {
completionHandler([NSString stringWithFormat:@"請求失敗: %@", error.localizedDescription]);
}
}];
}
+ (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
// 建立認證校正策略
NSMutableArray *policies = [NSMutableArray array];
if (domain) {
[policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
} else {
[policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
}
// 綁定校正策略到服務端認證
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies);
// 評估認證信任度
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}
3. HTTPS+SNI情境接入方案
適用於需要SNI支援的情境,如CDN或多網域名稱共用IP的情況。
3.1 配置支援SNI的AFHTTPSessionManager
+ (AFHTTPSessionManager *)sharedAfnManagerWithSNI {
static AFHTTPSessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 關鍵:註冊自訂NSURLProtocol來處理SNI
NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:configuration.protocolClasses];
[protocolsArray insertObject:[HttpDnsNSURLProtocolImpl class] atIndex:0];
[configuration setProtocolClasses:protocolsArray];
manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
// 配置安全性原則
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
manager.securityPolicy = securityPolicy;
// 配置序列化器
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:
@"application/json", @"text/json", @"text/javascript", @"text/plain", nil];
manager.requestSerializer.timeoutInterval = 10.0f;
});
return manager;
}
3.2 HTTPDNS網域名稱解析
+ (NSString *)resolveAvailableIp:(NSString *)host {
HttpDnsService *httpDnsService = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpDnsService resolveHostSyncNonBlocking:host
byIpType:HttpdnsQueryIPTypeAuto];
if (!result) return nil;
if (result.hasIpv4Address) {
return result.firstIpv4Address;
} else if (result.hasIpv6Address) {
return [NSString stringWithFormat:@"[%@]", result.firstIpv6Address];
}
return nil;
}
3.3 發送SNI情境的網路請求
+ (void)httpDnsQueryWithSNIURL:(NSString *)originalUrl
completionHandler:(void(^)(NSString *message))completionHandler {
NSURL *url = [NSURL URLWithString:originalUrl];
NSString *resolvedIpAddress = [self resolveAvailableIp:url.host];
NSString *requestUrl = originalUrl;
if (resolvedIpAddress) {
requestUrl = [originalUrl stringByReplacingOccurrencesOfString:url.host
withString:resolvedIpAddress];
NSLog(@"HTTPDNS解析成功,網域名稱: %@, IP: %@", url.host, resolvedIpAddress);
} else {
NSLog(@"HTTPDNS解析失敗,使用原始URL: %@", url.host);
}
AFHTTPSessionManager *manager = [self sharedAfnManagerWithSNI];
// 設定Host頭部
[manager.requestSerializer setValue:url.host forHTTPHeaderField:@"host"];
// 注意:由於使用了自訂NSURLProtocol,SNI和認證驗證已在Protocol層處理
// 直接發送請求即可
[manager GET:requestUrl parameters:nil headers:nil progress:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSData *data = [[NSData alloc] initWithData:responseObject];
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (completionHandler) {
completionHandler([NSString stringWithFormat:@"請求成功: %@", dataStr]);
}
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completionHandler) {
completionHandler([NSString stringWithFormat:@"請求失敗: %@", error.localizedDescription]);
}
}];
}
說明:SNI情境下,認證校正和網域名稱處理由NSURLProtocol層自動處理,無需額外配置認證校正回調。
如果需要參考樣本,阿里雲提供了httpdns_ios_demo中HttpDnsNSURLProtocolImpl.m的樣本實現,可根據業務需求進行修改或複用。
4. 使用樣本
4.1 基礎HTTPS請求
// 普通HTTP請求(非SNI情境)
[AFNHttpsScenario httpDnsQueryWithURL:@"http://example.com/api/data"
completionHandler:^(NSString *message) {
NSLog(@"請求結果: %@", message);
}];
4.2 SNI情境請求
// SNI情境請求
[AFNHttpsWithSNIScenario httpDnsQueryWithSNIURL:@"https://example.com/api/data"
completionHandler:^(NSString *message) {
NSLog(@"請求結果: %@", message);
}];
5. 總結
AFNetworking整合HTTPDNS的核心步驟:
初始化AFHTTPSessionManager - 配置基礎網路參數
HTTPDNS網域名稱解析 - 擷取IP地址替換網域名稱
發送網路請求 - 設定Host頭並處理HTTPS認證校正
關鍵要點
Host頭設定:確保伺服器能正確識別請求的網域名稱
認證校正:使用原始網域名稱而非IP進行認證驗證
SNI處理:複雜情境使用NSURLProtocol自動處理
降級策略:HTTPDNS解析失敗時回退到系統DNS
完整的範例程式碼請參考:HTTPDNS iOS Demo