httpdns实战感受(ios开发篇章)
近期项目中为了解决域名问题在项目中集成了阿里云的第三方域名解析服务HttpDns,现在描述一下我的实战感受
首先提出几个问题,本文就围绕着这几个问题来进行编辑。
1. 为什么要集成HttpDns?
2. 集成HttpDns的步骤?
3. 集成过程中容易遇到哪些问题?
第一个问题:
客户端进行网络请求,首先会经过运营商(移动,联通,电信)的Local DNS。因为我们进行网络请求的路径通常都是使用域名拼接的,所以需要经过运营商根据域名找到对应的ip,然后再访问到公司服务器。而经过运营商的Local Dns的时候,有可能存在劫持的情况,导致访问的结果并不是我们想要的。或者在我们访问的网页上打入一些广告之类的。为了解决类似情况,我们在进行网络请求之前需要对域名进行解析,解析的结果是对应的ip地址。那么发送网络请求的时候,运营商发现你的路径是ip,就会直接"放行"。这样可以防止域名劫持,间接的也提高了访问的速度,因为我们中间跳过了运营商这一步。那么怎样对域名进行解析呢?这就涉及到第三方服务了。阿里云,腾讯都有域名解析的第三方服务,毕竟他们的技术比较成熟。我们公司集成的是阿里云的HttpDns。
第二个问题:
集成HttpDns步骤(其实文档上也有)
第一步:需要到登陆阿里云账号(没有的注册),然后找到HttpDns,并且开通。开通之后,就能获取到一个AccountId,这个AccountId在项目中需要使用。然后再添加你项目中需要解析的域名,控制台就可以看到每天域名解析的次数(当天解析的要到第二天才能看到)。
第二步:添加包含HttpDns解析功能的frameWork,并添加相应的依赖库。然后照着文档在appDelegate中添加相应代码(设置AccountId,设置降级代理,预解析域名...),这个文档上都写的比较清楚。接下来就涉及到域名解析了,解析分两种情况:http、https请求。 但是不管是http还是https请求都有一些共同步骤,首先将域名成ip,由于通常情况下都使用异步解析,所以有可能不能立即拿到解析后的ip(httpdns有缓存机制,首先查看缓存,缓存没有返回nil,然后异步解析域名,解析成功之后更新缓存),所以我在程序启动的时候就手动调用了异步解析的接口,解析项目需要使用到的域名。那么在做网络请求之前再解析的时候就能获取到解析之后的iP了。然后将请求路径中的域名替换成ip。将域名替换成ip之后直接进行网络请求,会导致网络请求不通过,原因是因为在我们将网络请求中的域名替换成ip后,网络请求中的head中host字段也会更换成ip,因为一台服务器我们会有很多接口服务同时存在,服务器接收到请求后无法根据域名去判断我们访问的是哪个服务。所以就会导致网络请求不通过。所以我们在将域名替换成ip之后需要将请求头(header)中的host字段设置回域名。做完这些操作之后,http请求就能正常解析,能够正常访问了。但是https请求光做这些还不够,由于https要做证书校验,所以针对https请求还需要绑定证书策略,将ip重新替换成域名在执行证书校验。以NSURLSession进行的网络请求为例:代码如下
- (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);
/*
* 评估当前serverTrust是否可信任,
* 官方建议在result = kSecTrustResultUnspecified 或 kSecTrustResultProceed
* 的情况下serverTrust可以被验证通过,https://developer.apple.com/library/ios/technotes/tn2232/_index.html
* 关于SecTrustResultType的详细信息请参考SecTrust.h
*/
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler {
if (!challenge) {
return;
}
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
/*
* 获取原始域名信息。
*/
NSString *host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
if (!host) {
host = self.request.URL.host;
}
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
// 对于其他的challenges直接使用默认的验证方案
completionHandler(disposition, credential);
}
https请求加上这些代码之后,也就能通过证书校验了,就能正常访问了。
第三个问题:
容易踩的两个坑就是 1. 将域名替换成ip之后,没有将header中的host字段设置成域名 2. https请求没有重新将ip替换成域名之后在做证书校验
在我们的项目中,我遇到的两个问题
1. 我们项目中使用的是公共的网络请求工具类,然后使用的域名却不止一个,所以就导致当从一个域名的网络请求切换到另一个域名的网络请求的时候,网络请求不通过,这是因为header中的 host字段使用的还是上一个域名,导致header中的host字段与真正请求的ip不匹配
解决办法:在每一个网络请求之前都根据当前网络请求的域名解析ip,将ip替换成域名,并且每一个请求都将header中的host字段设置成当前域名。不做统一设置(如果项目中使用的域名只有 一个则可以统一设置)
2. 我们公司解析出来的ip在某些地区受运营商的限制,网络请求无法通过。在相同的网络状态下,不管是使用运营商的Local DNS还是使用第三方的HttpDns,解析的都是同一个域名,所以即 使使用了HttpDns解析,我们的项目在受限制的地区任然无法访问公司服务
解决办法:后台增加域名,当网络请求失败之后,进行域名切换,这样就能解决在受限地区无法访问的问题
总而言之,集成HttpDns有很多好处。1. 防止域名劫持 2. 优化网络,提高网络请求的访问速度