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, modifications to AFNetworking are necessary to ensure requests use IP.
The general process is to integrate the SDK -> register the SDK -> obtain IP -> store -> use. Depending on personal circumstances, SDK registration can be done at startup, and there are two ways to obtain IP: one is to get it once when the app starts and store it, without needing updates during app use; 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 match and second-level domain methods
- Refer to iOS SDK Integration for integration
- Integrate using CocoaPods
This is where it gets frustrating; the SDK installed via CocoaPods according to 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 initializes using a dictionary 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;
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 AFURLSessionManager.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 validation policy to server's certificate
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// Evaluate whether the current serverTrust is trusted,
// The official recommendation is that serverTrust can be validated when 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, requiring further modifications to the AFSecurityPolicy
class.
+ (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, retesting showed 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 of dependencies 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
AFURLSessionManager.m
class in AFNetworking. - Modify the
AFSecurityPolicy
class.
- Modify the