背景
最近开发水印相机,遇到了用户网络正常,但是上传超时、上传失败的问题。通过听云后台看到接口错误记录中,用户的 localDNS 为空,于是就有了接入 HTTPDNS 的需求。
实践
由于项目中网络请求使用的 AFNetworking 框架,接入第三方 HTTPDNS 后,需要修改 AFNetworking 中的内容,才能让请求走IP。
大致流程是接入 SDK——>注册 SDK——>获取 IP——>存储——>使用。这里可依据个人情况,在启动时进行 SDK注册,获取 IP 有两种方式,一是只在 APP 启动时获取一次,然后存储起来,APP使用过程中不需要更新。二是在 每次某个接口使用时都获取。
下面详细来看看接入的过程
- 按照快速入门中的步骤进行配置
- 添加域名,注意阿里的添加域名,可以添加全匹配和二级域名的方式
- 参考iOS SDK 接入进行接入
- 使用 CocoaPods 接入
这里到要骂人的地方了,按照阿里自己的官方文档上面写的 CocoaPod 安装的SDK不是最新的 1 2 3 4 5
| source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/aliyun/aliyun-specs.git'
pod 'AlicloudHTTPDNS'
|
- 项目中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| - (void)registerAliDNS{ HttpDnsService *httpdns = [[HttpDnsService alloc]initWithAccountID:@"Your Account ID" secretKey:@"Your Secret Key"]; [httpdns setCachedIPEnabled:NO]; [httpdns setHTTPSRequestEnabled:YES]; [httpdns setPreResolveHosts:@[ @"baidu.com", ]]; [httpdns setLogEnabled:YES]; [httpdns setPreResolveAfterNetworkChanged:YES];
self.httpdns = httpdns; NSUInteger delaySeconds = 1; dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC)); dispatch_after(when, dispatch_get_main_queue(), ^{ [self getUpFileHostIp]; }); }
- (void)getUpFileHostIp { NSString *originalUrl = @"https://www.baidu.com"; NSURL *url = [NSURL URLWithString:originalUrl]; NSArray *ipsArray = [self.httpdns getIpsByHostAsync:url.host]; if (!IsNilArray(ipsArray)) { self.hostIpStr = ipsArray.firstObject; } }
|
- 按照入门指南中的步骤进行配置
- 注册/登录账号
- 开通服务
- 在开发配置中申请应用
- 在域名管理中添加域名,注意添加腾讯设置域名,只能添加 xxx.com,不能添加 xxx.yyy.com 这种
- 参考iOS SDK 文档进行接入
- 使用 CocoaPods 接入
- 项目中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #import <MSDKDns/MSDKDns.h> // 腾讯 HTTP DNS
- (void)registerMSDKDns { [[MSDKDns sharedInstance] initConfigWithDictionary:@{ @"dnsIp": @"119.29.29.98", @"dnsId": @"Your AppID", @"dnsKey": @"Your SecretKey", @"encryptType": @0, @"debug": @1, }];
NSString *hostStr = @"baidu.com"; [[MSDKDns sharedInstance] WGGetHostByNameAsync:hostStr returnIps:^(NSArray *ipsArray) { NSLog(@"解析 IP:%@", ipsArray); if (ipsArray) { self.hostIpStr = ipsArray.firstObject; } }]; }
|
使用
检测本地是否使用 HTTP、HTTPS 的代理,如果有代理,建议不要使用 HTTPDNS —— iOS SDK 文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
- (BOOL)isUseHTTPProxy { CFDictionaryRef dicRef = CFNetworkCopySystemProxySettings(); const CFStringRef proxyCFstr = (const CFStringRef)CFDictionaryGetValue(dicRef, (const void*)kCFNetworkProxiesHTTPProxy); NSString *proxy = (__bridge NSString *)proxyCFstr; if (proxy) { return YES; } else { return NO; } }
- (BOOL)isUseHTTPSProxy { CFDictionaryRef dicRef = CFNetworkCopySystemProxySettings(); const CFStringRef proxyCFstr = (const CFStringRef)CFDictionaryGetValue(dicRef, (const void*)kCFNetworkProxiesHTTPSProxy); NSString *proxy = (__bridge NSString *)proxyCFstr; if (proxy) { return YES; } else { return NO; } }
|
用 HTTPDNS 返回的 IP 替换掉 URL 中的域名,指定下 HTTP 头的 host 字段。
1 2 3 4 5 6 7 8 9 10
| NSURL *httpDnsURL = [NSURL URLWithString:@"使用解析结果ip拼接的URL"]; float timeOut = 设置的超时时间; NSMutableURLRequest *mutableReq = [NSMutableURLRequest requestWithURL:httpDnsURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval: timeOut]; [mutableReq setValue:@"原域名" forHTTPHeaderField:@"host"]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue currentQueue]]; NSURLSessionTask *task = [session dataTaskWithRequest:mutableReq]; [task resume];
|
项目中使用的是 AFNetworking,修改 AFNetworking 中 AFURLSessionMananger.m类:
- 添加
evaluateServerTrust:forDmain:方法
- 修改
URLSession:task:didReceiveChallenge:completionHandler:方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| - (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); }
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { BOOL evaluateServerTrust = NO; NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; NSURLCredential *credential = nil;
if (self.authenticationChallengeHandler) { id result = self.authenticationChallengeHandler(session, task, challenge, completionHandler); if (result == nil) { return; } else if ([result isKindOfClass:NSError.class]) { objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, result, OBJC_ASSOCIATION_RETAIN); disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } else if ([result isKindOfClass:NSURLCredential.class]) { credential = result; disposition = NSURLSessionAuthChallengeUseCredential; } else if ([result isKindOfClass:NSNumber.class]) { disposition = [result integerValue]; NSAssert(disposition == NSURLSessionAuthChallengePerformDefaultHandling || disposition == NSURLSessionAuthChallengeCancelAuthenticationChallenge || disposition == NSURLSessionAuthChallengeRejectProtectionSpace, @""); evaluateServerTrust = disposition == NSURLSessionAuthChallengePerformDefaultHandling && [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; } else { @throw [NSException exceptionWithName:@"Invalid Return Value" reason:@"The return value from the authentication challenge handler must be nil, an NSError, an NSURLCredential or an NSNumber." userInfo:nil]; } } else { evaluateServerTrust = [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; }
if (evaluateServerTrust) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, [self serverTrustErrorForServerTrust:challenge.protectionSpace.serverTrust url:task.currentRequest.URL], OBJC_ASSOCIATION_RETAIN); disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } }
NSURLRequest *request = task.currentRequest; NSString *host = [[request allHTTPHeaderFields] objectForKey:@"host"]; if (!host) { host = challenge.protectionSpace.host; } if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) { disposition = NSURLSessionAuthChallengeUseCredential; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } } if (completionHandler) { completionHandler(disposition, credential); } }
|
测试
按照上面的接入步骤接入完成后进行测试,上传图片出现了:首次失败、再次成功的情况,失败的原因是,疑似不受信任服务器xxx.xx.xxx.xx,还需要再修改AFSecurityPolicy类
1 2 3 4 5 6 7 8 9 10 11 12
| + (instancetype)defaultPolicy { AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
securityPolicy.allowInvalidCertificates = YES; securityPolicy.validatesDomainName = NO;
return securityPolicy; }
|
加入后再次测试,完美运行
总结
- 接入前,先考虑考虑要接入的方式,即:刷新 HTTPDNS 的逻辑
- 接入时:
- 阿里 HTTPDNS 需要注意 Pod 中依赖不要按照官方文档设置版本;
- 腾讯的 HTTPDNS 则要注意初始化的方法;
- 注意两家平台上设置域名时的不同;
- 接入后:
- 修改AFNetworking 中
AFURLSessionMananger.m类
- 修改
AFSecurityPolicy类
参考