//
|
// OSSHttpdns.m
|
// AliyunOSSiOS
|
//
|
// Created by zhouzhuo on 5/1/16.
|
// Copyright © 2016 zhouzhuo. All rights reserved.
|
//
|
|
#import "OSSLog.h"
|
#import "OSSHttpdns.h"
|
#import "OSSIPv6Adapter.h"
|
|
NSString * const OSS_HTTPDNS_SERVER_IP = @"203.107.1.1";
|
NSString * const OSS_HTTPDNS_SERVER_PORT = @"80";
|
|
NSString * const ACCOUNT_ID = @"181345";
|
NSTimeInterval const MAX_ENDURABLE_EXPIRED_TIME_IN_SECOND = 60; // The DNS entry's expiration time in seconds. After it expires, the entry is invalid.
|
NSTimeInterval const PRERESOLVE_IN_ADVANCE_IN_SECOND = 10; // Once the remaining valid time of an DNS entry is less than this number, issue a DNS request to prefetch the data.
|
|
@interface IpObject : NSObject
|
|
@property (nonatomic, copy) NSString * ip;
|
@property (nonatomic, assign) NSTimeInterval expiredTime;
|
|
@end
|
|
@implementation IpObject
|
@end
|
|
|
@implementation OSSHttpdns {
|
NSMutableDictionary * gHostIpMap;
|
NSMutableSet * penddingSet;
|
}
|
|
+ (instancetype)sharedInstance {
|
static OSSHttpdns * sharedInstance = nil;
|
static dispatch_once_t onceToken;
|
dispatch_once(&onceToken, ^{
|
sharedInstance = [OSSHttpdns new];
|
});
|
return sharedInstance;
|
}
|
|
- (instancetype)init {
|
if (self = [super init]) {
|
gHostIpMap = [NSMutableDictionary new];
|
penddingSet = [NSMutableSet new];
|
}
|
return self;
|
}
|
|
/**
|
* OSS SDK specific
|
*
|
* @param host it needs strictly follow the domain's format, such as oss-cn-hangzhou.aliyuncs.com
|
*
|
* @return an ip in the ip list of the resolved host.
|
*/
|
- (NSString *)asynGetIpByHost:(NSString *)host {
|
IpObject * ipObject = [gHostIpMap objectForKey:host];
|
if (!ipObject) {
|
|
// if the host is not resolved, asynchronously resolve it and return nil
|
[self resolveHost:host];
|
return nil;
|
} else if ([[NSDate date] timeIntervalSince1970] - ipObject.expiredTime > MAX_ENDURABLE_EXPIRED_TIME_IN_SECOND) {
|
|
// If the entry is expired, asynchronously resolve it and return nil.
|
[self resolveHost:host];
|
return nil;
|
} else if (ipObject.expiredTime -[[NSDate date] timeIntervalSince1970] < PRERESOLVE_IN_ADVANCE_IN_SECOND) {
|
|
// If the entry is about to expire, asynchronously resolve it and return the current value.
|
[self resolveHost:host];
|
return ipObject.ip;
|
} else {
|
|
// returns the current result.
|
return ipObject.ip;
|
}
|
}
|
|
/**
|
* resolve the host asynchronously
|
|
* If the host is being resolved, the call will be skipped.
|
*
|
* @param host the host to resolve
|
*/
|
- (void)resolveHost:(NSString *)host {
|
|
@synchronized (self) {
|
if ([penddingSet containsObject:host]) {
|
return;
|
} else {
|
[penddingSet addObject:host];
|
}
|
}
|
|
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@/%@/d?host=%@", [[OSSIPv6Adapter getInstance] handleIpv4Address:OSS_HTTPDNS_SERVER_IP], ACCOUNT_ID, host]];
|
NSURLSession * session = [NSURLSession sharedSession];
|
|
NSURLSessionDataTask * dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
|
IpObject * ipObject = nil;
|
NSUInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
|
if (statusCode != 200) {
|
OSSLogError(@"Httpdns resolve host: %@ failed, responseCode: %lu", host, (unsigned long)statusCode);
|
} else {
|
NSError *error = nil;
|
NSDictionary *json = nil;
|
if (data != nil){
|
json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
|
}
|
|
if (error != nil || json == nil){
|
return;
|
}
|
|
NSTimeInterval expiredTime = [[NSDate new] timeIntervalSince1970] + [[json objectForKey:@"ttl"] longLongValue];
|
|
NSArray *ips = [json objectForKey:@"ips"];
|
if (ips == nil || [ips count] == 0) {
|
OSSLogError(@"Httpdns resolve host: %@ failed, ip list empty.", host);
|
} else {
|
NSString * ip = ips[0];
|
ipObject = [IpObject new];
|
ipObject.expiredTime = expiredTime;
|
ipObject.ip = ip;
|
OSSLogDebug(@"Httpdns resolve host: %@ success, ip: %@, expiredTime: %lf", host, ipObject.ip, ipObject.expiredTime);
|
}
|
}
|
|
@synchronized (self) {
|
if (ipObject) {
|
gHostIpMap[host] = ipObject;
|
}
|
|
[penddingSet removeObject:host];
|
}
|
}];
|
|
[dataTask resume];
|
}
|
|
@end
|