本文档介绍在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
该文章对您有帮助吗?