646 lines
26 KiB
Objective-C
646 lines
26 KiB
Objective-C
/*
|
||
* Licensed to the Apache Software Foundation (ASF) under one
|
||
* or more contributor license agreements. See the NOTICE file
|
||
* distributed with this work for additional information
|
||
* regarding copyright ownership. The ASF licenses this file
|
||
* to you under the Apache License, Version 2.0 (the
|
||
* "License"); you may not use this file except in compliance
|
||
* with the License. You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing,
|
||
* software distributed under the License is distributed on an
|
||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
* KIND, either express or implied. See the License for the
|
||
* specific language governing permissions and limitations
|
||
* under the License.
|
||
*/
|
||
|
||
#import "HttpdnsRequestManager.h"
|
||
#import "HttpdnsHostObject.h"
|
||
#import "HttpdnsRemoteResolver.h"
|
||
#import "HttpdnsLocalResolver.h"
|
||
#import "HttpdnsInternalConstant.h"
|
||
#import "HttpdnsUtil.h"
|
||
#import "HttpdnsLog_Internal.h"
|
||
#import "HttpdnsPersistenceUtils.h"
|
||
#import "HttpdnsService_Internal.h"
|
||
#import "HttpdnsScheduleCenter.h"
|
||
#import "HttpdnsService_Internal.h"
|
||
#import "HttpdnsReachability.h"
|
||
#import "HttpdnsHostRecord.h"
|
||
#import "HttpdnsUtil.h"
|
||
#import "HttpDnsLocker.h"
|
||
#import "HttpdnsRequest_Internal.h"
|
||
#import "HttpdnsHostObjectInMemoryCache.h"
|
||
#import "HttpdnsIPQualityDetector.h"
|
||
#import "HttpdnsIpStackDetector.h"
|
||
#import "HttpdnsDB.h"
|
||
|
||
|
||
static dispatch_queue_t _persistentCacheConcurrentQueue = NULL;
|
||
static dispatch_queue_t _asyncResolveHostQueue = NULL;
|
||
|
||
typedef struct {
|
||
BOOL isResultUsable;
|
||
BOOL isResolvingRequired;
|
||
} HostObjectExamingResult;
|
||
|
||
@interface HttpdnsRequestManager()
|
||
|
||
@property (nonatomic, strong) dispatch_queue_t cacheQueue;
|
||
@property (nonatomic, assign, readwrite) NSInteger accountId;
|
||
@property (nonatomic, weak) HttpDnsService *ownerService;
|
||
|
||
@property (atomic, setter=setPersistentCacheIpEnabled:, assign) BOOL persistentCacheIpEnabled;
|
||
@property (atomic, setter=setDegradeToLocalDNSEnabled:, assign) BOOL degradeToLocalDNSEnabled;
|
||
@property (atomic, assign) BOOL atomicExpiredIPEnabled;
|
||
@property (atomic, assign) BOOL atomicPreResolveAfterNetworkChanged;
|
||
|
||
@property (atomic, assign) NSTimeInterval lastUpdateTimestamp;
|
||
@property (atomic, assign) HttpdnsNetworkStatus lastNetworkStatus;
|
||
|
||
@end
|
||
|
||
@implementation HttpdnsRequestManager {
|
||
HttpdnsHostObjectInMemoryCache *_hostObjectInMemoryCache;
|
||
HttpdnsDB *_httpdnsDB;
|
||
}
|
||
|
||
+ (void)initialize {
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
_persistentCacheConcurrentQueue = dispatch_queue_create("com.New.sdk.httpdns.persistentCacheOperationQueue", DISPATCH_QUEUE_CONCURRENT);
|
||
_asyncResolveHostQueue = dispatch_queue_create("com.New.sdk.httpdns.asyncResolveHostQueue", DISPATCH_QUEUE_CONCURRENT);
|
||
});
|
||
}
|
||
|
||
- (instancetype)initWithAccountId:(NSInteger)accountId ownerService:(HttpDnsService *)service {
|
||
if (self = [super init]) {
|
||
_accountId = accountId;
|
||
_ownerService = service;
|
||
|
||
HttpdnsReachability *reachability = [HttpdnsReachability sharedInstance];
|
||
self.atomicExpiredIPEnabled = NO;
|
||
self.atomicPreResolveAfterNetworkChanged = NO;
|
||
_hostObjectInMemoryCache = [[HttpdnsHostObjectInMemoryCache alloc] init];
|
||
_httpdnsDB = [[HttpdnsDB alloc] initWithAccountId:accountId];
|
||
[[HttpdnsIpStackDetector sharedInstance] redetectIpStack];
|
||
|
||
_lastNetworkStatus = reachability.currentReachabilityStatus;
|
||
_lastUpdateTimestamp = [NSDate date].timeIntervalSince1970;
|
||
|
||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||
selector:@selector(handleReachabilityNotification:)
|
||
name:kHttpdnsReachabilityChangedNotification
|
||
object:reachability];
|
||
[reachability startNotifier];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void)setExpiredIPEnabled:(BOOL)enable {
|
||
self.atomicExpiredIPEnabled = enable;
|
||
}
|
||
|
||
- (void)setCachedIPEnabled:(BOOL)enable discardRecordsHasExpiredFor:(NSTimeInterval)duration {
|
||
// 开启允许持久化缓存
|
||
[self setPersistentCacheIpEnabled:enable];
|
||
|
||
if (enable) {
|
||
dispatch_async(_persistentCacheConcurrentQueue, ^{
|
||
// 先清理过期时间超过阈值的缓存结果
|
||
[self->_httpdnsDB cleanRecordAlreadExpiredAt:[[NSDate date] timeIntervalSince1970] - duration];
|
||
|
||
// 再读取持久化缓存中的历史记录,加载到内存缓存<E7BC93><E5AD98>?
|
||
[self loadCacheFromDbToMemory];
|
||
});
|
||
}
|
||
}
|
||
|
||
- (void)setPreResolveAfterNetworkChanged:(BOOL)enable {
|
||
self.atomicPreResolveAfterNetworkChanged = enable;
|
||
}
|
||
|
||
- (void)preResolveHosts:(NSArray *)hosts queryType:(HttpdnsQueryIPType)queryType {
|
||
if (![HttpdnsUtil isNotEmptyArray:hosts]) {
|
||
return;
|
||
}
|
||
|
||
__weak typeof(self) weakSelf = self;
|
||
dispatch_async(_asyncResolveHostQueue, ^{
|
||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||
if (!strongSelf) {
|
||
return;
|
||
}
|
||
|
||
if ([strongSelf isHostsNumberLimitReached]) {
|
||
return;
|
||
}
|
||
|
||
// 分批处理,每批最<E689B9><E69C80>?个域<E4B8AA><E59F9F>?
|
||
NSUInteger totalCount = hosts.count;
|
||
for (NSUInteger i = 0; i < totalCount; i += HTTPDNS_PRE_RESOLVE_BATCH_SIZE) {
|
||
NSUInteger length = MIN(HTTPDNS_PRE_RESOLVE_BATCH_SIZE, totalCount - i);
|
||
NSArray *batch = [hosts subarrayWithRange:NSMakeRange(i, length)];
|
||
|
||
NSString *combinedHostString = [batch componentsJoinedByString:@","];
|
||
HttpdnsLogDebug("Pre resolve host by async lookup, hosts: %@", combinedHostString);
|
||
|
||
HttpdnsRequest *request = [[HttpdnsRequest alloc] initWithHost:combinedHostString queryIpType:queryType];
|
||
request.accountId = strongSelf.accountId;
|
||
[request becomeNonBlockingRequest];
|
||
|
||
dispatch_async(_asyncResolveHostQueue, ^{
|
||
[strongSelf executePreResolveRequest:request retryCount:0];
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
#pragma mark - core method for all public query API
|
||
- (HttpdnsHostObject *)resolveHost:(HttpdnsRequest *)request {
|
||
HttpdnsLogDebug("resolveHost, request: %@", request);
|
||
|
||
NSString *host = request.host;
|
||
NSString *cacheKey = request.cacheKey;
|
||
|
||
if (request.accountId == 0 || request.accountId != self.accountId) {
|
||
request.accountId = self.accountId;
|
||
}
|
||
|
||
if ([HttpdnsUtil isEmptyString:host]) {
|
||
return nil;
|
||
}
|
||
|
||
HttpdnsHostObject *result = [_hostObjectInMemoryCache getHostObjectByCacheKey:cacheKey createIfNotExists:^id _Nonnull {
|
||
HttpdnsLogDebug("No cache for cacheKey: %@", cacheKey);
|
||
HttpdnsHostObject *newObject = [HttpdnsHostObject new];
|
||
newObject.hostName = host;
|
||
newObject.v4Ips = @[];
|
||
newObject.v6Ips = @[];
|
||
return newObject;
|
||
}];
|
||
|
||
HostObjectExamingResult examingResult = [self examineHttpdnsHostObject:result underQueryType:request.queryIpType];
|
||
BOOL isCachedResultUsable = examingResult.isResultUsable;
|
||
BOOL isResolvingRequired = examingResult.isResolvingRequired;
|
||
|
||
if (isCachedResultUsable) {
|
||
if (isResolvingRequired) {
|
||
// 缓存结果可用,但是需要请求,因为缓存结果已经过期
|
||
// 这种情况异步去解析就可以<E58FAF><E4BBA5>?
|
||
[self determineResolvingHostNonBlocking:request];
|
||
}
|
||
// 缓存是以cacheKey为准,这里返回前,要把host替换成用户请求的这个
|
||
result.hostName = host;
|
||
HttpdnsLogDebug("Reuse available cache for cacheKey: %@, result: %@", cacheKey, result);
|
||
// 因为缓存结果可用,可以立即返<E58DB3><E8BF94>?
|
||
return result;
|
||
}
|
||
|
||
if (request.isBlockingRequest) {
|
||
// 缓存结果不可用,且是同步请求,需要等待结<E5BE85><E7BB93>?
|
||
return [self determineResolveHostBlocking:request];
|
||
} else {
|
||
// 缓存结果不可用,且是异步请求,不需要等待结<E5BE85><E7BB93>?
|
||
[self determineResolvingHostNonBlocking:request];
|
||
return nil;
|
||
}
|
||
}
|
||
|
||
- (void)determineResolvingHostNonBlocking:(HttpdnsRequest *)request {
|
||
dispatch_async(_asyncResolveHostQueue, ^{
|
||
HttpDnsLocker *locker = [HttpDnsLocker sharedInstance];
|
||
if ([locker tryLock:request.cacheKey queryType:request.queryIpType]) {
|
||
@try {
|
||
[self executeRequest:request retryCount:0];
|
||
} @catch (NSException *exception) {
|
||
HttpdnsLogDebug("determineResolvingHostNonBlocking host: %@, exception: %@", request.host, exception);
|
||
} @finally {
|
||
[locker unlock:request.cacheKey queryType:request.queryIpType];
|
||
}
|
||
} else {
|
||
HttpdnsLogDebug("determineResolvingHostNonBlocking skipped due to concurrent limitation, host: %@", request.host);
|
||
}
|
||
});
|
||
}
|
||
|
||
- (HttpdnsHostObject *)determineResolveHostBlocking:(HttpdnsRequest *)request {
|
||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||
__block HttpdnsHostObject *result = nil;
|
||
dispatch_async(_asyncResolveHostQueue, ^{
|
||
HttpDnsLocker *locker = [HttpDnsLocker sharedInstance];
|
||
@try {
|
||
[locker lock:request.cacheKey queryType:request.queryIpType];
|
||
|
||
result = [self->_hostObjectInMemoryCache getHostObjectByCacheKey:request.cacheKey];
|
||
if (result && ![result isExpiredUnderQueryIpType:request.queryIpType]) {
|
||
// 存在且未过期,意味着其他线程已经解析到了新的结果
|
||
return;
|
||
}
|
||
|
||
result = [self executeRequest:request retryCount:0];
|
||
} @catch (NSException *exception) {
|
||
HttpdnsLogDebug("determineResolveHostBlocking host: %@, exception: %@", request.host, exception);
|
||
} @finally {
|
||
[locker unlock:request.cacheKey queryType:request.queryIpType];
|
||
dispatch_semaphore_signal(semaphore);
|
||
}
|
||
});
|
||
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(request.resolveTimeoutInSecond * NSEC_PER_SEC)));
|
||
return result;
|
||
}
|
||
|
||
- (HostObjectExamingResult)examineHttpdnsHostObject:(HttpdnsHostObject *)hostObject underQueryType:(HttpdnsQueryIPType)queryType {
|
||
if (!hostObject) {
|
||
return (HostObjectExamingResult){NO, YES};
|
||
}
|
||
|
||
if ([hostObject isIpEmptyUnderQueryIpType:queryType]) {
|
||
return (HostObjectExamingResult){NO, YES};
|
||
}
|
||
|
||
if ([hostObject isExpiredUnderQueryIpType:queryType]) {
|
||
if (self.atomicExpiredIPEnabled || [hostObject isLoadFromDB]) {
|
||
// 只有开启了允许过期缓存,和开启持久化缓存情况下启动后加载到内存中的缓存,才可以直接复用过期结<E69C9F><E7BB93>?
|
||
HttpdnsLogDebug("The ips is expired, but we accept it, host: %@, queryType: %ld, expiredIpEnabled: %d, isLoadFromDB: %d",
|
||
hostObject.hostName, queryType, self.atomicExpiredIPEnabled, [hostObject isLoadFromDB]);
|
||
// 复用过期结果,同时也需要发起新的解析请<E69E90><E8AFB7>?
|
||
return (HostObjectExamingResult){YES, YES};
|
||
}
|
||
|
||
// 只要过期了就肯定需要请<E8A681><E8AFB7>?
|
||
return (HostObjectExamingResult){NO, YES};
|
||
}
|
||
|
||
return (HostObjectExamingResult){YES, NO};
|
||
}
|
||
|
||
- (HttpdnsHostObject *)executeRequest:(HttpdnsRequest *)request retryCount:(int)hasRetryedCount {
|
||
NSString *host = request.host;
|
||
NSString *cacheKey = request.cacheKey;
|
||
HttpdnsQueryIPType queryIPType = request.queryIpType;
|
||
HttpdnsHostObject *result = nil;
|
||
|
||
BOOL isDegradationResult = NO;
|
||
|
||
if (hasRetryedCount <= HTTPDNS_MAX_REQUEST_RETRY_TIME) {
|
||
HttpdnsLogDebug("Internal request starts, host: %@, request: %@", host, request);
|
||
|
||
NSError *error = nil;
|
||
NSArray<HttpdnsHostObject *> *resultArray = [[HttpdnsRemoteResolver new] resolve:request error:&error];
|
||
|
||
if (error) {
|
||
HttpdnsLogDebug("Internal request error, host: %@, error: %@", host, error);
|
||
|
||
HttpdnsScheduleCenter *scheduleCenter = self.ownerService.scheduleCenter;
|
||
[scheduleCenter rotateServiceServerHost];
|
||
|
||
// 确保一定的重试间隔
|
||
hasRetryedCount++;
|
||
[NSThread sleepForTimeInterval:hasRetryedCount * 0.25];
|
||
|
||
return [self executeRequest:request retryCount:hasRetryedCount];
|
||
}
|
||
|
||
if ([HttpdnsUtil isEmptyArray:resultArray]) {
|
||
HttpdnsLogDebug("Internal request get empty result array, host: %@", host);
|
||
return nil;
|
||
}
|
||
|
||
// 这个路径里,host只会有一个,所以直接取第一个处理就<E79086><E5B0B1>?
|
||
result = resultArray.firstObject;
|
||
} else {
|
||
if (!self.degradeToLocalDNSEnabled) {
|
||
HttpdnsLogDebug("Internal remote request retry count exceed limit, host: %@", host);
|
||
return nil;
|
||
}
|
||
|
||
result = [[HttpdnsLocalResolver new] resolve:request];
|
||
if (!result) {
|
||
HttpdnsLogDebug("Fallback to local dns resolver, but still get no result, host: %@", host);
|
||
return nil;
|
||
}
|
||
|
||
isDegradationResult = YES;
|
||
}
|
||
|
||
HttpdnsLogDebug("Internal request finished, host: %@, cacheKey: %@, isDegradationResult: %d, result: %@ ",
|
||
host, cacheKey, isDegradationResult, result);
|
||
|
||
// merge之后,返回的应当是存储在缓存中的实际对象,而非请求过程中构造出来的对象
|
||
HttpdnsHostObject *lookupResult = [self mergeLookupResultToManager:result host:host cacheKey:cacheKey underQueryIpType:queryIPType];
|
||
// 返回一个快照,避免进行中的一些缓存调整影响返回去的结<E79A84><E7BB93>?
|
||
return [lookupResult copy];
|
||
}
|
||
|
||
- (void)executePreResolveRequest:(HttpdnsRequest *)request retryCount:(int)hasRetryedCount {
|
||
NSString *host = request.host;
|
||
HttpdnsQueryIPType queryIPType = request.queryIpType;
|
||
|
||
BOOL isDegradationResult = NO;
|
||
|
||
if (hasRetryedCount > HTTPDNS_MAX_REQUEST_RETRY_TIME) {
|
||
HttpdnsLogDebug("PreResolve remote request retry count exceed limit, host: %@", host);
|
||
return;
|
||
}
|
||
|
||
HttpdnsLogDebug("PreResolve request starts, host: %@, request: %@", host, request);
|
||
|
||
NSError *error = nil;
|
||
NSArray<HttpdnsHostObject *> *resultArray = [[HttpdnsRemoteResolver new] resolve:request error:&error];
|
||
|
||
if (error) {
|
||
HttpdnsLogDebug("PreResolve request error, host: %@, error: %@", host, error);
|
||
|
||
HttpdnsScheduleCenter *scheduleCenter = self.ownerService.scheduleCenter;
|
||
[scheduleCenter rotateServiceServerHost];
|
||
|
||
// 确保一定的重试间隔
|
||
hasRetryedCount++;
|
||
[NSThread sleepForTimeInterval:hasRetryedCount * 0.25];
|
||
|
||
// 预解析重试需保持“多域名预解析”的语义,不能误用单域名执行路径
|
||
[self executePreResolveRequest:request retryCount:hasRetryedCount];
|
||
|
||
return;
|
||
}
|
||
|
||
if ([HttpdnsUtil isEmptyArray:resultArray]) {
|
||
HttpdnsLogDebug("PreResolve request get empty result array, host: %@", host);
|
||
return;
|
||
}
|
||
|
||
HttpdnsLogDebug("PreResolve request finished, host: %@, isDegradationResult: %d, result: %@ ",
|
||
host, isDegradationResult, resultArray);
|
||
|
||
for (HttpdnsHostObject *result in resultArray) {
|
||
// merge之后,返回的应当是存储在缓存中的实际对象,而非请求过程中构造出来的对象
|
||
// 预解析不支持SDNS,所以cacheKey只能是单独的每一个hostName
|
||
[self mergeLookupResultToManager:result host:result.hostName cacheKey:result.hostName underQueryIpType:queryIPType];
|
||
}
|
||
}
|
||
|
||
- (HttpdnsHostObject *)mergeLookupResultToManager:(HttpdnsHostObject *)result host:host cacheKey:(NSString *)cacheKey underQueryIpType:(HttpdnsQueryIPType)queryIpType {
|
||
if (!result) {
|
||
return nil;
|
||
}
|
||
|
||
NSArray<HttpdnsIpObject *> *v4IpObjects = [result getV4Ips];
|
||
NSArray<HttpdnsIpObject *> *v6IpObjects = [result getV6Ips];
|
||
NSString* extra = [result getExtra];
|
||
|
||
BOOL hasNoIpv4Record = NO;
|
||
BOOL hasNoIpv6Record = NO;
|
||
if (queryIpType & HttpdnsQueryIPTypeIpv4 && [HttpdnsUtil isEmptyArray:v4IpObjects]) {
|
||
hasNoIpv4Record = YES;
|
||
}
|
||
if (queryIpType & HttpdnsQueryIPTypeIpv6 && [HttpdnsUtil isEmptyArray:v6IpObjects]) {
|
||
hasNoIpv6Record = YES;
|
||
}
|
||
|
||
HttpdnsHostObject *cachedHostObject = [_hostObjectInMemoryCache getHostObjectByCacheKey:cacheKey];
|
||
if (!cachedHostObject) {
|
||
HttpdnsLogDebug("Create new hostObject for cache, cacheKey: %@, host: %@", cacheKey, host);
|
||
cachedHostObject = [[HttpdnsHostObject alloc] init];
|
||
}
|
||
|
||
[cachedHostObject setCacheKey:cacheKey];
|
||
[cachedHostObject setClientIp:result.clientIp];
|
||
|
||
[cachedHostObject setHostName:host];
|
||
[cachedHostObject setIsLoadFromDB:NO];
|
||
[cachedHostObject setHasNoIpv4Record:hasNoIpv4Record];
|
||
[cachedHostObject setHasNoIpv6Record:hasNoIpv6Record];
|
||
|
||
if ([HttpdnsUtil isNotEmptyArray:v4IpObjects]) {
|
||
[cachedHostObject setV4Ips:v4IpObjects];
|
||
[cachedHostObject setV4TTL:result.getV4TTL];
|
||
[cachedHostObject setLastIPv4LookupTime:result.lastIPv4LookupTime];
|
||
}
|
||
|
||
if ([HttpdnsUtil isNotEmptyArray:v6IpObjects]) {
|
||
[cachedHostObject setV6Ips:v6IpObjects];
|
||
[cachedHostObject setV6TTL:result.getV6TTL];
|
||
[cachedHostObject setLastIPv6LookupTime:result.lastIPv6LookupTime];
|
||
}
|
||
|
||
if ([HttpdnsUtil isNotEmptyString:extra]) {
|
||
[cachedHostObject setExtra:extra];
|
||
}
|
||
|
||
HttpdnsLogDebug("Updated hostObject to cached, cacheKey: %@, host: %@", cacheKey, host);
|
||
|
||
// 由于从缓存中读取到的是拷贝出来的新对象,字段赋值不会影响缓存中的值对象,因此这里无论如何都要放回缓存
|
||
[_hostObjectInMemoryCache setHostObject:cachedHostObject forCacheKey:cacheKey];
|
||
|
||
[self persistToDB:cacheKey hostObject:cachedHostObject];
|
||
|
||
NSArray *ipv4StrArray = [cachedHostObject getV4IpStrings];
|
||
if ([HttpdnsUtil isNotEmptyArray:ipv4StrArray]) {
|
||
[self initiateQualityDetectionForIP:ipv4StrArray forHost:host cacheKey:cacheKey];
|
||
}
|
||
|
||
NSArray *ipv6StrArray = [cachedHostObject getV6IpStrings];
|
||
if ([HttpdnsUtil isNotEmptyArray:ipv6StrArray]) {
|
||
[self initiateQualityDetectionForIP:ipv6StrArray forHost:host cacheKey:cacheKey];
|
||
}
|
||
return cachedHostObject;
|
||
}
|
||
|
||
- (void)initiateQualityDetectionForIP:(NSArray *)ipArray forHost:(NSString *)host cacheKey:(NSString *)cacheKey {
|
||
HttpDnsService *service = self.ownerService ?: [HttpDnsService sharedInstance];
|
||
NSDictionary<NSString *, NSNumber *> *dataSource = [service getIPRankingDatasource];
|
||
if (!dataSource || ![dataSource objectForKey:host]) {
|
||
return;
|
||
}
|
||
NSNumber *port = [dataSource objectForKey:host];
|
||
for (NSString *ip in ipArray) {
|
||
[[HttpdnsIPQualityDetector sharedInstance] scheduleIPQualityDetection:cacheKey
|
||
ip:ip
|
||
port:port
|
||
callback:^(NSString * _Nonnull cacheKey, NSString * _Nonnull ip, NSInteger costTime) {
|
||
[self->_hostObjectInMemoryCache updateQualityForCacheKey:cacheKey forIp:ip withConnectedRT:costTime];
|
||
}];
|
||
}
|
||
}
|
||
|
||
- (BOOL)isHostsNumberLimitReached {
|
||
if ([_hostObjectInMemoryCache count] >= HTTPDNS_MAX_MANAGE_HOST_NUM) {
|
||
HttpdnsLogDebug("Can't handle more than %d hosts due to the software configuration.", HTTPDNS_MAX_MANAGE_HOST_NUM);
|
||
return YES;
|
||
}
|
||
return NO;
|
||
}
|
||
|
||
- (void)handleReachabilityNotification:(NSNotification *)notification {
|
||
[self networkChanged];
|
||
}
|
||
|
||
- (void)networkChanged {
|
||
HttpdnsNetworkStatus currentStatus = [[HttpdnsReachability sharedInstance] currentReachabilityStatus];
|
||
NSString *currentStatusString = [[HttpdnsReachability sharedInstance] currentReachabilityString];
|
||
|
||
// 重新检测协议栈代价小,所以只要网络切换就发起检<E8B5B7><E6A380>?
|
||
// 但考虑到网络切换后不稳定,还是延迟1秒才发起
|
||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
|
||
[[HttpdnsIpStackDetector sharedInstance] redetectIpStack];
|
||
});
|
||
|
||
NSTimeInterval currentTimestamp = [NSDate date].timeIntervalSince1970;
|
||
BOOL statusChanged = (_lastNetworkStatus != currentStatus);
|
||
|
||
// 仅在以下情况下响应网络变化去尝试更新缓存:
|
||
// - 距离上次处理事件至少过去了较长时间,<E997B4><EFBC8C>?
|
||
// - 网络状态发生变化且至少过去了较短时<E79FAD><E697B6>?
|
||
NSTimeInterval elapsedTime = currentTimestamp - _lastUpdateTimestamp;
|
||
if (elapsedTime >= 5 || (statusChanged && elapsedTime >= 1)) {
|
||
HttpdnsLogDebug("Processing network change: oldStatus: %ld, newStatus: %ld(%@), elapsedTime=%.2f seconds",
|
||
_lastNetworkStatus, currentStatus, currentStatusString, elapsedTime);
|
||
|
||
// 更新调度
|
||
// 网络在切换过程中可能不稳定,所以发送请求前等待2<E5BE85><32>?
|
||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
|
||
HttpdnsScheduleCenter *scheduleCenter = self.ownerService.scheduleCenter;
|
||
[scheduleCenter asyncUpdateRegionScheduleConfig];
|
||
});
|
||
|
||
// 复杂逻辑(仅注释关键点,避免冗余):
|
||
// 网络切换后只预解析“cacheKey 等于 hostName”的条目<E69DA1><E79BAE>?
|
||
// 这些条目代表标准域名缓存;SDNS 等使用自定义 cacheKey 的记录不在此批次处理<E5A484><E79086>?
|
||
NSArray *allKeys = [_hostObjectInMemoryCache allCacheKeys];
|
||
NSMutableArray<NSString *> *hostArray = [NSMutableArray array];
|
||
for (NSString *key in allKeys) {
|
||
HttpdnsHostObject *obj = [self->_hostObjectInMemoryCache getHostObjectByCacheKey:key];
|
||
if (!obj) {
|
||
continue;
|
||
}
|
||
NSString *cacheKey = [obj getCacheKey];
|
||
NSString *hostName = [obj getHostName];
|
||
if (cacheKey && hostName && [cacheKey isEqualToString:hostName]) {
|
||
[hostArray addObject:hostName];
|
||
}
|
||
}
|
||
|
||
// 预解<E9A284><E8A7A3>?
|
||
// 网络在切换过程中可能不稳定,所以在清理缓存和发送请求前等待3<E5BE85><33>?
|
||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
|
||
// 仅清理“hostName 键”的缓存,保<EFBC8C><E4BF9D>?SDNS 等自定义 cacheKey 的记<E79A84><E8AEB0>?
|
||
for (NSString *host in hostArray) {
|
||
[self->_hostObjectInMemoryCache removeHostObjectByCacheKey:host];
|
||
}
|
||
|
||
if (self.atomicPreResolveAfterNetworkChanged && hostArray.count > 0) {
|
||
HttpdnsLogDebug("Network changed, pre resolve for host-key entries: %@", hostArray);
|
||
[self preResolveHosts:hostArray queryType:HttpdnsQueryIPTypeAuto];
|
||
}
|
||
});
|
||
|
||
// 更新时间戳和状<E5928C><E78AB6>?
|
||
_lastNetworkStatus = currentStatus;
|
||
_lastUpdateTimestamp = currentTimestamp;
|
||
} else {
|
||
HttpdnsLogDebug("Ignoring network change event: oldStatus: %ld, newStatus: %ld(%@), elapsedTime=%.2f seconds",
|
||
_lastNetworkStatus, currentStatus, currentStatusString, elapsedTime);
|
||
}
|
||
}
|
||
|
||
#pragma mark -
|
||
#pragma mark - disable status Setter and Getter Method
|
||
|
||
- (dispatch_queue_t)cacheQueue {
|
||
if (!_cacheQueue) {
|
||
_cacheQueue = dispatch_queue_create("com.New.sdk.httpdns.cacheDisableStatusQueue", DISPATCH_QUEUE_SERIAL);
|
||
}
|
||
return _cacheQueue;
|
||
}
|
||
|
||
#pragma mark -
|
||
#pragma mark - Flag for Disable and Sniffer Method
|
||
|
||
- (void)loadCacheFromDbToMemory {
|
||
NSArray<HttpdnsHostRecord *> *hostRecords = [self->_httpdnsDB getAllRecords];
|
||
|
||
if ([HttpdnsUtil isEmptyArray:hostRecords]) {
|
||
return;
|
||
}
|
||
|
||
for (HttpdnsHostRecord *hostRecord in hostRecords) {
|
||
NSString *hostName = hostRecord.hostName;
|
||
NSString *cacheKey = hostRecord.cacheKey;
|
||
|
||
HttpdnsHostObject *hostObject = [HttpdnsHostObject fromDBRecord:hostRecord];
|
||
|
||
// 从持久层加载到内存的缓存,需要做个标记,App启动后从缓存使用结果时,根据标记做特殊处<E6AE8A><E5A484>?
|
||
[hostObject setIsLoadFromDB:YES];
|
||
|
||
[self->_hostObjectInMemoryCache setHostObject:hostObject forCacheKey:cacheKey];
|
||
|
||
NSArray *v4IpStrArr = [hostObject getV4IpStrings];
|
||
if ([HttpdnsUtil isNotEmptyArray:v4IpStrArr]) {
|
||
[self initiateQualityDetectionForIP:v4IpStrArr forHost:hostName cacheKey:cacheKey];
|
||
}
|
||
NSArray *v6IpStrArr = [hostObject getV6IpStrings];
|
||
if ([HttpdnsUtil isNotEmptyArray:v6IpStrArr]) {
|
||
[self initiateQualityDetectionForIP:v6IpStrArr forHost:hostName cacheKey:cacheKey];
|
||
}
|
||
}
|
||
}
|
||
|
||
- (void)cleanMemoryAndPersistentCacheOfHostArray:(NSArray<NSString *> *)hostArray {
|
||
for (NSString *host in hostArray) {
|
||
if ([HttpdnsUtil isNotEmptyString:host]) {
|
||
[_hostObjectInMemoryCache removeHostObjectByCacheKey:host];
|
||
}
|
||
}
|
||
|
||
// 清空数据库数<E5BA93><E695B0>?
|
||
dispatch_async(_persistentCacheConcurrentQueue, ^{
|
||
[self->_httpdnsDB deleteByHostNameArr:hostArray];
|
||
});
|
||
}
|
||
|
||
- (void)cleanMemoryAndPersistentCacheOfAllHosts {
|
||
[_hostObjectInMemoryCache removeAllHostObjects];
|
||
|
||
// 清空数据库数<E5BA93><E695B0>?
|
||
dispatch_async(_persistentCacheConcurrentQueue, ^{
|
||
[self->_httpdnsDB deleteAll];
|
||
});
|
||
}
|
||
|
||
- (void)persistToDB:(NSString *)cacheKey hostObject:(HttpdnsHostObject *)hostObject {
|
||
if (!_persistentCacheIpEnabled) {
|
||
return;
|
||
}
|
||
dispatch_async(_persistentCacheConcurrentQueue, ^{
|
||
HttpdnsHostRecord *hostRecord = [hostObject toDBRecord];
|
||
[self->_httpdnsDB createOrUpdate:hostRecord];
|
||
});
|
||
}
|
||
|
||
#pragma mark -
|
||
#pragma mark - 以下函数仅用于测试目<E8AF95><E79BAE>?
|
||
|
||
- (NSString *)showMemoryCache {
|
||
NSString *cacheDes;
|
||
cacheDes = [NSString stringWithFormat:@"%@", _hostObjectInMemoryCache];
|
||
return cacheDes;
|
||
}
|
||
|
||
- (void)cleanAllHostMemoryCache {
|
||
[_hostObjectInMemoryCache removeAllHostObjects];
|
||
}
|
||
|
||
- (void)syncLoadCacheFromDbToMemory {
|
||
[self loadCacheFromDbToMemory];
|
||
}
|
||
|
||
- (void)dealloc {
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||
}
|
||
|
||
@end
|