AliyunOSSSDK/OSSHttpdns.m (94 lines of code) (raw):
//
// 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