杨锴
2025-04-16 09a372bc45fde16fd42257ab6f78b8deeecf720b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//
//  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