Background#
Recently, while developing a watermark camera, we encountered issues where users had a normal network connection, but uploads timed out or failed. By checking the error logs in the backend, we found that the user's localDNS was empty, leading to the need for integrating HTTPDNS.
Practice#
Since the project uses the AFNetworking framework for network requests, after integrating a third-party HTTPDNS, we need to modify the content in AFNetworking to allow requests to use IP.
The general process is to integrate the SDK -> register the SDK -> obtain IP -> store it -> use it. Depending on personal circumstances, the SDK registration can be done at startup, and there are two ways to obtain the IP: one is to get it once when the app starts and store it, without needing to update during the app's usage; the other is to obtain it each time a specific interface is used.
Let's take a detailed look at the integration process.
Ali HTTPDNS#
- Configure according to the steps in the Quick Start
- Add a domain name, note that Ali's domain name addition can include both full matches and second-level domains
- Refer to the iOS SDK Integration for integration
- Integrate using CocoaPods
This is where it gets frustrating; the SDK installed via CocoaPods as per Ali's official documentation is not the latestsource 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/aliyun/aliyun-specs.git' pod 'AlicloudHTTPDNS' # Note, do not add a specified version as per the official documentation
- Use in the project
- (void)registerAliDNS{ HttpDnsService *httpdns = [[HttpDnsService alloc]initWithAccountID:@"Your Account ID" secretKey:@"Your Secret Key"]; [httpdns setCachedIPEnabled:NO]; // [httpdns cleanHostCache:nil]; [httpdns setHTTPSRequestEnabled:YES]; [httpdns setPreResolveHosts:@[ @"baidu.com", ]]; [httpdns setLogEnabled:YES]; [httpdns setPreResolveAfterNetworkChanged:YES]; // [httpdns setExpiredIPEnabled:YES]; // [httpdns setDelegateForDegradationFilter:self]; 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; } }
- Integrate using CocoaPods
Tencent Cloud HTTPDNS#
- Configure according to the steps in the Getting Started Guide
- Register/login to your account
- Activate the service
- Apply for an application in the development configuration
- Add a domain name in domain management, note that Tencent only allows adding xxx.com, not xxx.yyy.com
- Refer to the iOS SDK Documentation for integration
- Integrate using CocoaPods
pod 'MSDKDns'
- Use in the project
#import <MSDKDns/MSDKDns.h> // Tencent HTTP DNS - (void)registerMSDKDns { // Note the usage method below, it uses a dictionary method for initialization because using class methods does not compile... [[MSDKDns sharedInstance] initConfigWithDictionary:@{ @"dnsIp": @"119.29.29.98", @"dnsId": @"Your AppID", @"dnsKey": @"Your SecretKey", // Note that different encryption methods have different SecretKeys @"encryptType": @0, // Encryption method @"debug": @1, // @"routeIp": @"", }]; NSString *hostStr = @"baidu.com"; [[MSDKDns sharedInstance] WGGetHostByNameAsync:hostStr returnIps:^(NSArray *ipsArray) { NSLog(@"Resolved IP: %@", ipsArray); if (ipsArray) { self.hostIpStr = ipsArray.firstObject; } }]; }
- Integrate using CocoaPods
Usage#
Check if there is an HTTP or HTTPS proxy in use; if there is a proxy, it is recommended not to use HTTPDNS —— iOS SDK Documentation
// Check if there is an HTTP proxy
- (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;
}
}
// Check if there is an HTTPS proxy
- (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;
}
}
Replace the domain name in the URL with the IP returned by HTTPDNS, and specify the host field in the HTTP header.
NSURL *httpDnsURL = [NSURL URLWithString:@"URL constructed using the resolved IP"];
float timeOut = set timeout duration;
NSMutableURLRequest *mutableReq = [NSMutableURLRequest requestWithURL:httpDnsURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval: timeOut];
[mutableReq setValue:@"original domain name" forHTTPHeaderField:@"host"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue currentQueue]];
NSURLSessionTask *task = [session dataTaskWithRequest:mutableReq];
[task resume];
The project uses AFNetworking, modifying the AFURLSessionMananger.m
class in AFNetworking:
- Add the
evaluateServerTrust:forDomain:
method - Modify the
URLSession:task:didReceiveChallenge:completionHandler:
method
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
// Create certificate validation policy
NSMutableArray *policies = [NSMutableArray array];
if (domain) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// Bind the validation policy to the server's certificate
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// Evaluate whether the current serverTrust is trusted,
// Officially recommended that serverTrust can be validated if result = kSecTrustResultUnspecified or kSecTrustResultProceed,
// https://developer.apple.com/library/ios/technotes/tn2232/_index.html
// For detailed information about SecTrustResultType, please refer to SecTrust.h
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;
}
}
// Modification----------------------------part
// Get original domain information
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);
}
}
Testing#
After completing the integration steps above, testing was conducted, and the image upload showed: first failure, then success. The reason for the failure was suspected to be an untrusted server xxx.xx.xxx.xx, and further modifications to the AFSecurityPolicy
class were needed.
+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
// Add the following two lines of code
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
return securityPolicy;
}
After adding this, testing again resulted in perfect operation.
Summary#
- Before integration, consider the method of integration, i.e., refreshing the logic of HTTPDNS.
- During integration:
- For Ali HTTPDNS, be careful not to set the version in the Pod according to the official documentation;
- For Tencent's HTTPDNS, pay attention to the initialization method;
- Note the differences in domain name settings between the two platforms;
- After integration:
- Modify the
AFURLSessionMananger.m
class in AFNetworking - Modify the
AFSecurityPolicy
class
- Modify the