feat: sync httpdns sdk/platform updates without large binaries
This commit is contained in:
277
HttpDNSSDK/sdk/ios/NewHttpDNS/HttpdnsEdgeService.m
Normal file
277
HttpDNSSDK/sdk/ios/NewHttpDNS/HttpdnsEdgeService.m
Normal file
@@ -0,0 +1,277 @@
|
||||
#import "HttpdnsEdgeService.h"
|
||||
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
|
||||
static NSString * const kHttpdnsEdgeErrorDomain = @"com.goeedge.httpdns.edge";
|
||||
|
||||
@implementation HttpdnsEdgeResolveResult
|
||||
@end
|
||||
|
||||
@interface HttpdnsEdgeService ()
|
||||
|
||||
@property (nonatomic, copy) NSString *appId;
|
||||
@property (nonatomic, copy) NSString *primaryServiceHost;
|
||||
@property (nonatomic, copy) NSString *backupServiceHost;
|
||||
@property (nonatomic, assign) NSInteger servicePort;
|
||||
@property (nonatomic, copy) NSString *signSecret;
|
||||
@property (nonatomic, copy) NSString *sessionId;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HttpdnsEdgeService
|
||||
|
||||
- (instancetype)initWithAppId:(NSString *)appId
|
||||
primaryServiceHost:(NSString *)primaryServiceHost
|
||||
backupServiceHost:(NSString *)backupServiceHost
|
||||
servicePort:(NSInteger)servicePort
|
||||
signSecret:(NSString *)signSecret {
|
||||
if (self = [super init]) {
|
||||
_appId = [appId copy];
|
||||
_primaryServiceHost = [primaryServiceHost copy];
|
||||
_backupServiceHost = backupServiceHost.length > 0 ? [backupServiceHost copy] : @"";
|
||||
_servicePort = servicePort > 0 ? servicePort : 443;
|
||||
_signSecret = signSecret.length > 0 ? [signSecret copy] : @"";
|
||||
_sessionId = [[[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""] copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)resolveHost:(NSString *)host
|
||||
queryType:(NSString *)queryType
|
||||
completion:(void (^)(HttpdnsEdgeResolveResult *_Nullable, NSError *_Nullable))completion {
|
||||
if (host.length == 0 || self.appId.length == 0 || self.primaryServiceHost.length == 0) {
|
||||
NSError *error = [NSError errorWithDomain:kHttpdnsEdgeErrorDomain
|
||||
code:1001
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"invalid init config or host"}];
|
||||
completion(nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *qtype = queryType.length > 0 ? queryType.uppercaseString : @"A";
|
||||
NSArray<NSString *> *hosts = self.backupServiceHost.length > 0
|
||||
? @[self.primaryServiceHost, self.backupServiceHost]
|
||||
: @[self.primaryServiceHost];
|
||||
|
||||
[self requestResolveHosts:hosts index:0 host:host qtype:qtype completion:completion];
|
||||
}
|
||||
|
||||
- (void)requestURL:(NSURL *)url
|
||||
method:(NSString *)method
|
||||
headers:(NSDictionary<NSString *,NSString *> *)headers
|
||||
body:(NSData *)body
|
||||
completion:(void (^)(NSData *_Nullable, NSHTTPURLResponse *_Nullable, NSError *_Nullable))completion {
|
||||
NSString *originHost = url.host ?: @"";
|
||||
if (originHost.length == 0) {
|
||||
NSError *error = [NSError errorWithDomain:kHttpdnsEdgeErrorDomain
|
||||
code:2001
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"invalid request host"}];
|
||||
completion(nil, nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
[self resolveHost:originHost queryType:@"A" completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) {
|
||||
if (error != nil || result.ipv4s.count == 0) {
|
||||
completion(nil, nil, error ?: [NSError errorWithDomain:kHttpdnsEdgeErrorDomain
|
||||
code:2002
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"NO_IP_AVAILABLE"}]);
|
||||
return;
|
||||
}
|
||||
|
||||
[self requestByIPList:result.ipv4s
|
||||
index:0
|
||||
url:url
|
||||
method:method
|
||||
headers:headers
|
||||
body:body
|
||||
completion:completion];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)requestByIPList:(NSArray<NSString *> *)ips
|
||||
index:(NSInteger)index
|
||||
url:(NSURL *)url
|
||||
method:(NSString *)method
|
||||
headers:(NSDictionary<NSString *, NSString *> *)headers
|
||||
body:(NSData *)body
|
||||
completion:(void (^)(NSData *_Nullable, NSHTTPURLResponse *_Nullable, NSError *_Nullable))completion {
|
||||
if (index >= ips.count) {
|
||||
NSError *error = [NSError errorWithDomain:kHttpdnsEdgeErrorDomain
|
||||
code:2003
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"TLS_EMPTY_SNI_FAILED"}];
|
||||
completion(nil, nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *ip = ips[index];
|
||||
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
||||
components.host = ip;
|
||||
if (components.port == nil) {
|
||||
components.port = @(443);
|
||||
}
|
||||
|
||||
NSURL *targetURL = components.URL;
|
||||
if (targetURL == nil) {
|
||||
[self requestByIPList:ips index:index + 1 url:url method:method headers:headers body:body completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:targetURL
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
||||
timeoutInterval:8];
|
||||
request.HTTPMethod = method.length > 0 ? method : @"GET";
|
||||
if (body.length > 0) {
|
||||
request.HTTPBody = body;
|
||||
}
|
||||
|
||||
[request setValue:url.host forHTTPHeaderField:@"Host"];
|
||||
[headers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
if ([key.lowercaseString isEqualToString:@"host"]) {
|
||||
return;
|
||||
}
|
||||
[request setValue:obj forHTTPHeaderField:key];
|
||||
}];
|
||||
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
config.timeoutIntervalForRequest = 8;
|
||||
config.timeoutIntervalForResource = 8;
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
|
||||
[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
[session finishTasksAndInvalidate];
|
||||
|
||||
if (error != nil) {
|
||||
[self requestByIPList:ips index:index + 1 url:url method:method headers:headers body:body completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
completion(data, (NSHTTPURLResponse *)response, nil);
|
||||
}] resume];
|
||||
}
|
||||
|
||||
- (void)requestResolveHosts:(NSArray<NSString *> *)hosts
|
||||
index:(NSInteger)index
|
||||
host:(NSString *)host
|
||||
qtype:(NSString *)qtype
|
||||
completion:(void (^)(HttpdnsEdgeResolveResult *_Nullable result, NSError *_Nullable error))completion {
|
||||
if (index >= hosts.count) {
|
||||
NSError *error = [NSError errorWithDomain:kHttpdnsEdgeErrorDomain
|
||||
code:1002
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"resolve failed on all service hosts"}];
|
||||
completion(nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *serviceHost = hosts[index];
|
||||
NSURLComponents *components = [NSURLComponents new];
|
||||
components.scheme = @"https";
|
||||
components.host = serviceHost;
|
||||
components.port = @(self.servicePort);
|
||||
components.path = @"/resolve";
|
||||
|
||||
NSMutableArray<NSURLQueryItem *> *items = [NSMutableArray arrayWithArray:@[
|
||||
[NSURLQueryItem queryItemWithName:@"appId" value:self.appId],
|
||||
[NSURLQueryItem queryItemWithName:@"dn" value:host],
|
||||
[NSURLQueryItem queryItemWithName:@"qtype" value:qtype],
|
||||
[NSURLQueryItem queryItemWithName:@"sid" value:self.sessionId],
|
||||
[NSURLQueryItem queryItemWithName:@"sdk_version" value:@"ios-native-1.0.0"],
|
||||
[NSURLQueryItem queryItemWithName:@"os" value:@"ios"],
|
||||
]];
|
||||
|
||||
if (self.signSecret.length > 0) {
|
||||
NSString *exp = [NSString stringWithFormat:@"%ld", (long)([[NSDate date] timeIntervalSince1970] + 600)];
|
||||
NSString *nonce = [[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""];
|
||||
NSString *raw = [NSString stringWithFormat:@"%@|%@|%@|%@|%@",
|
||||
self.appId,
|
||||
host.lowercaseString,
|
||||
qtype.uppercaseString,
|
||||
exp,
|
||||
nonce];
|
||||
NSString *sign = [self hmacSha256Hex:raw secret:self.signSecret];
|
||||
[items addObject:[NSURLQueryItem queryItemWithName:@"exp" value:exp]];
|
||||
[items addObject:[NSURLQueryItem queryItemWithName:@"nonce" value:nonce]];
|
||||
[items addObject:[NSURLQueryItem queryItemWithName:@"sign" value:sign]];
|
||||
}
|
||||
|
||||
components.queryItems = items;
|
||||
NSURL *url = components.URL;
|
||||
if (url == nil) {
|
||||
[self requestResolveHosts:hosts index:index + 1 host:host qtype:qtype completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
|
||||
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
|
||||
timeoutInterval:6];
|
||||
request.HTTPMethod = @"GET";
|
||||
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
config.timeoutIntervalForRequest = 6;
|
||||
config.timeoutIntervalForResource = 6;
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
|
||||
[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
[session finishTasksAndInvalidate];
|
||||
|
||||
if (error != nil || data.length == 0) {
|
||||
[self requestResolveHosts:hosts index:index + 1 host:host qtype:qtype completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
if (![json isKindOfClass:NSDictionary.class]) {
|
||||
[self requestResolveHosts:hosts index:index + 1 host:host qtype:qtype completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *code = [NSString stringWithFormat:@"%@", json[@"code"] ?: @""];
|
||||
if (![code.uppercaseString isEqualToString:@"SUCCESS"]) {
|
||||
[self requestResolveHosts:hosts index:index + 1 host:host qtype:qtype completion:completion];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *dataJSON = [json[@"data"] isKindOfClass:NSDictionary.class] ? json[@"data"] : @{};
|
||||
NSArray *records = [dataJSON[@"records"] isKindOfClass:NSArray.class] ? dataJSON[@"records"] : @[];
|
||||
|
||||
NSMutableArray<NSString *> *ipv4 = [NSMutableArray array];
|
||||
NSMutableArray<NSString *> *ipv6 = [NSMutableArray array];
|
||||
for (NSDictionary *row in records) {
|
||||
NSString *type = [NSString stringWithFormat:@"%@", row[@"type"] ?: @""];
|
||||
NSString *ip = [NSString stringWithFormat:@"%@", row[@"ip"] ?: @""];
|
||||
if (ip.length == 0) {
|
||||
continue;
|
||||
}
|
||||
if ([type.uppercaseString isEqualToString:@"AAAA"]) {
|
||||
[ipv6 addObject:ip];
|
||||
} else {
|
||||
[ipv4 addObject:ip];
|
||||
}
|
||||
}
|
||||
|
||||
HttpdnsEdgeResolveResult *result = [HttpdnsEdgeResolveResult new];
|
||||
result.requestId = [NSString stringWithFormat:@"%@", json[@"requestId"] ?: @""];
|
||||
result.ipv4s = ipv4;
|
||||
result.ipv6s = ipv6;
|
||||
result.ttl = [dataJSON[@"ttl"] integerValue];
|
||||
completion(result, nil);
|
||||
}] resume];
|
||||
}
|
||||
|
||||
- (NSString *)hmacSha256Hex:(NSString *)data secret:(NSString *)secret {
|
||||
NSData *keyData = [secret dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *messageData = [data dataUsingEncoding:NSUTF8StringEncoding];
|
||||
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
|
||||
CCHmac(kCCHmacAlgSHA256,
|
||||
keyData.bytes,
|
||||
keyData.length,
|
||||
messageData.bytes,
|
||||
messageData.length,
|
||||
cHMAC);
|
||||
|
||||
NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
|
||||
[result appendFormat:@"%02x", cHMAC[i]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user