sdk final
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
<dict>
|
||||
<key>appId</key>
|
||||
<string>app1flndpo9</string>
|
||||
<key>primaryServiceHost</key>
|
||||
<string>httpdns.deepwaf.xyz</string>
|
||||
<key>servicePort</key>
|
||||
<integer>8445</integer>
|
||||
<key>apiUrl</key>
|
||||
<string>https://httpdns.deepwaf.xyz:8445</string>
|
||||
<key>signSecret</key>
|
||||
<string></string>
|
||||
<string>ss_67fb8471a45b</string>
|
||||
<key>serviceDomain</key>
|
||||
<string>demo.cloudxdr.com</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//
|
||||
// DemoConfigLoader.h
|
||||
// NewHttpDNSTestDemo
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@@ -8,10 +10,15 @@
|
||||
@interface DemoConfigLoader : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *appId;
|
||||
@property (nonatomic, copy, readonly) NSString *primaryServiceHost;
|
||||
@property (nonatomic, assign, readonly) NSInteger servicePort;
|
||||
@property (nonatomic, copy, readonly, nullable) NSString *signSecret;
|
||||
@property (nonatomic, copy, readonly) NSString *apiUrl; // Full raw URL
|
||||
@property (nonatomic, copy, readonly) NSString *apiHost; // Parsed host
|
||||
@property (nonatomic, assign, readonly) NSInteger apiPort; // Parsed port
|
||||
@property (nonatomic, copy, readonly) NSString *signSecret;
|
||||
@property (nonatomic, copy, readonly) NSString *serviceDomain;
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL hasValidConfig;
|
||||
|
||||
+ (instancetype)shared;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
//
|
||||
// DemoConfigLoader.m
|
||||
// NewHttpDNSTestDemo
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import "DemoConfigLoader.h"
|
||||
|
||||
@implementation DemoConfigLoader {
|
||||
NSString *_appId;
|
||||
NSString *_primaryServiceHost;
|
||||
NSInteger _servicePort;
|
||||
NSString *_apiUrl; // Unified API URL
|
||||
NSString *_signSecret;
|
||||
NSString *_serviceDomain;
|
||||
BOOL _hasValidConfig;
|
||||
}
|
||||
|
||||
+ (instancetype)shared {
|
||||
@@ -28,23 +31,51 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
// 澶嶆潅閫昏緫锛氶厤缃姞杞介『搴忎负 Bundle > 鐜鍙橀噺锛涘苟瀵?accountID 杩涜鏈夋晥鎬ф牎楠?
|
||||
- (void)loadConfig {
|
||||
NSDictionary *dict = nil;
|
||||
_appId = @"";
|
||||
_apiUrl = @"";
|
||||
_signSecret = @"";
|
||||
_serviceDomain = @"";
|
||||
_hasValidConfig = NO;
|
||||
|
||||
NSDictionary *bundleDict = nil;
|
||||
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"DemoConfig" ofType:@"plist"];
|
||||
if (plistPath.length > 0) {
|
||||
dict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
||||
bundleDict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
||||
}
|
||||
|
||||
_appId = dict[@"appId"] ?: @"";
|
||||
_primaryServiceHost = dict[@"primaryServiceHost"] ?: @"";
|
||||
_servicePort = [dict[@"servicePort"] integerValue] ?: 443;
|
||||
NSString *secret = dict[@"signSecret"] ?: @"";
|
||||
_signSecret = secret.length > 0 ? secret : nil;
|
||||
NSDictionary *env = [[NSProcessInfo processInfo] environment];
|
||||
|
||||
NSString *appId = bundleDict[@"appId"] ?: @"";
|
||||
NSString *apiUrl = bundleDict[@"apiUrl"] ?: @"";
|
||||
NSString *signSecret = bundleDict[@"signSecret"] ?: @"";
|
||||
NSString *serviceDomain = bundleDict[@"serviceDomain"] ?: @"";
|
||||
|
||||
NSString *envAppId = env[@"HTTPDNS_APP_ID"];
|
||||
NSString *envApiUrl = env[@"HTTPDNS_API_URL"];
|
||||
NSString *envSignSecret = env[@"HTTPDNS_SIGN_SECRET"];
|
||||
NSString *envServiceDomain = env[@"HTTPDNS_SERVICE_DOMAIN"];
|
||||
|
||||
if (envAppId.length > 0) appId = envAppId;
|
||||
if (envApiUrl.length > 0) apiUrl = envApiUrl;
|
||||
if (envSignSecret.length > 0) signSecret = envSignSecret;
|
||||
if (envServiceDomain.length > 0) serviceDomain = envServiceDomain;
|
||||
|
||||
if (appId.length > 0 && apiUrl.length > 0 && serviceDomain.length > 0) {
|
||||
_appId = appId;
|
||||
_apiUrl = apiUrl;
|
||||
_signSecret = signSecret;
|
||||
_serviceDomain = serviceDomain;
|
||||
_hasValidConfig = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)appId { return _appId; }
|
||||
- (NSString *)primaryServiceHost { return _primaryServiceHost; }
|
||||
- (NSInteger)servicePort { return _servicePort; }
|
||||
- (NSString *)apiUrl { return _apiUrl; }
|
||||
- (NSString *)signSecret { return _signSecret; }
|
||||
- (NSString *)serviceDomain { return _serviceDomain; }
|
||||
- (BOOL)hasValidConfig { return _hasValidConfig; }
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
//
|
||||
// DemoHttpdnsScenario.h
|
||||
// NewHttpDNSTestDemo
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-23
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "DemoResolveModel.h"
|
||||
#import "HttpdnsEdgeService.h"
|
||||
#import "HttpdnsPublicConstant.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -13,7 +17,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface DemoHttpdnsScenarioConfig : NSObject <NSCopying>
|
||||
|
||||
@property (nonatomic, copy) NSString *host;
|
||||
@property (nonatomic, copy) NSString *queryType; // @"A", @"AAAA", @"both"
|
||||
@property (nonatomic, assign) HttpdnsQueryIPType ipType;
|
||||
|
||||
// HttpdnsEdgeService unsupported features removed:
|
||||
// @property (nonatomic, assign) BOOL httpsEnabled;
|
||||
// @property (nonatomic, assign) BOOL persistentCacheEnabled;
|
||||
// @property (nonatomic, assign) BOOL reuseExpiredIPEnabled;
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
@@ -34,10 +43,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (instancetype)initWithDelegate:(id<DemoHttpdnsScenarioDelegate>)delegate;
|
||||
- (void)applyConfig:(DemoHttpdnsScenarioConfig *)config;
|
||||
- (void)resolve;
|
||||
- (void)resolveSyncNonBlocking;
|
||||
- (void)resolveSync;
|
||||
- (NSString *)logSnapshot;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//
|
||||
//
|
||||
// DemoHttpdnsScenario.m
|
||||
// NewHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-23
|
||||
//
|
||||
|
||||
#import "DemoHttpdnsScenario.h"
|
||||
#import "DemoConfigLoader.h"
|
||||
#import <NewHTTPDNS/HttpdnsEdgeService.h>
|
||||
|
||||
@interface DemoHttpdnsScenarioConfig ()
|
||||
@end
|
||||
@@ -14,8 +15,12 @@
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_host = @"demo.cloudxdr.com";
|
||||
_queryType = @"A";
|
||||
NSString *serviceDomain = [DemoConfigLoader shared].serviceDomain;
|
||||
_host = serviceDomain.length > 0 ? serviceDomain : @"www.new.com";
|
||||
_ipType = HttpdnsQueryIPTypeBoth;
|
||||
// _httpsEnabled = YES;
|
||||
// _persistentCacheEnabled = YES;
|
||||
// _reuseExpiredIPEnabled = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -23,7 +28,10 @@
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
DemoHttpdnsScenarioConfig *cfg = [[[self class] allocWithZone:zone] init];
|
||||
cfg.host = self.host;
|
||||
cfg.queryType = self.queryType;
|
||||
cfg.ipType = self.ipType;
|
||||
// cfg.httpsEnabled = self.httpsEnabled;
|
||||
// cfg.persistentCacheEnabled = self.persistentCacheEnabled;
|
||||
// cfg.reuseExpiredIPEnabled = self.reuseExpiredIPEnabled;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@@ -48,76 +56,175 @@
|
||||
_logBuffer = [NSMutableString string];
|
||||
_logQueue = dispatch_queue_create("com.new.httpdns.demo.log", DISPATCH_QUEUE_SERIAL);
|
||||
[self buildService];
|
||||
[self applyConfig:_config];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)buildService {
|
||||
DemoConfigLoader *cfg = [DemoConfigLoader shared];
|
||||
self.service = [[HttpdnsEdgeService alloc]
|
||||
initWithAppId:cfg.appId
|
||||
primaryServiceHost:cfg.primaryServiceHost
|
||||
backupServiceHost:nil
|
||||
servicePort:cfg.servicePort
|
||||
signSecret:cfg.signSecret];
|
||||
[self appendLog:[NSString stringWithFormat:@"[init] appId=%@ host=%@:%ld", cfg.appId, cfg.primaryServiceHost, (long)cfg.servicePort]];
|
||||
if (cfg.hasValidConfig) {
|
||||
self.service = [[HttpdnsEdgeService alloc] initWithAppId:cfg.appId apiUrl:cfg.apiUrl signSecret:cfg.signSecret];
|
||||
[self log:[NSString stringWithFormat:@"Init HttpdnsEdgeService success!"]];
|
||||
[self log:[NSString stringWithFormat:@"== Config details =="]];
|
||||
[self log:[NSString stringWithFormat:@"appId: %@", cfg.appId]];
|
||||
[self log:[NSString stringWithFormat:@"apiUrl: %@", cfg.apiUrl]];
|
||||
[self log:[NSString stringWithFormat:@"serviceDomain: %@", cfg.serviceDomain]];
|
||||
[self log:[NSString stringWithFormat:@"signSecret length: %tu", cfg.signSecret.length]];
|
||||
} else {
|
||||
[self log:@"Init HttpdnsEdgeService failed! Missing required fields."];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyConfig:(DemoHttpdnsScenarioConfig *)config {
|
||||
self.config = [config copy];
|
||||
self.model.host = self.config.host;
|
||||
self.model.ipType = self.config.ipType;
|
||||
[self log:[NSString stringWithFormat:@"Apply UI config: host=%@, ipType=%ld", self.config.host, (long)self.config.ipType]];
|
||||
}
|
||||
|
||||
- (void)resolve {
|
||||
[self resolveSyncNonBlocking];
|
||||
}
|
||||
|
||||
- (void)resolveSyncNonBlocking {
|
||||
NSString *host = self.config.host.length > 0 ? self.config.host : @"demo.cloudxdr.com";
|
||||
NSString *queryType = self.config.queryType.length > 0 ? self.config.queryType : @"A";
|
||||
NSString *queryHost = [self currentHost];
|
||||
HttpdnsQueryIPType ipType = self.config.ipType;
|
||||
NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
|
||||
|
||||
[self appendLog:[NSString stringWithFormat:@"[resolve] host=%@ type=%@", host, queryType]];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.service resolveHost:host queryType:queryType completion:^(HttpdnsEdgeResolveResult *result, NSError *error) {
|
||||
[weakSelf handleResult:result host:host queryType:queryType start:startMs error:error];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)resolveSync {
|
||||
[self resolveSyncNonBlocking];
|
||||
}
|
||||
|
||||
- (void)handleResult:(HttpdnsEdgeResolveResult *)result
|
||||
host:(NSString *)host
|
||||
queryType:(NSString *)queryType
|
||||
start:(NSTimeInterval)startMs
|
||||
error:(NSError *)error {
|
||||
NSTimeInterval elapsedMs = [[NSDate date] timeIntervalSince1970] * 1000.0 - startMs;
|
||||
|
||||
if (error != nil) {
|
||||
[self appendLog:[NSString stringWithFormat:@"[error] %@", error.localizedDescription]];
|
||||
|
||||
[self log:[NSString stringWithFormat:@"--- Start Resolve Request ---"]];
|
||||
[self log:[NSString stringWithFormat:@"Target Host: %@", queryHost]];
|
||||
|
||||
if (ipType == HttpdnsQueryIPTypeBoth) {
|
||||
[self log:@"Query Type: BOTH (mapped to concurrent A & AAAA)"];
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
__block HttpdnsEdgeResolveResult *resA = nil;
|
||||
__block NSError *errA = nil;
|
||||
__block HttpdnsEdgeResolveResult *resAAAA = nil;
|
||||
__block NSError *errAAAA = nil;
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[self.service resolveHost:queryHost queryType:@"A" completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) {
|
||||
resA = result;
|
||||
errA = error;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
dispatch_group_enter(group);
|
||||
[self.service resolveHost:queryHost queryType:@"AAAA" completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) {
|
||||
resAAAA = result;
|
||||
errAAAA = error;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
[self log:@"--- Edge Service Callback Triggered (BOTH) ---"];
|
||||
HttpdnsEdgeResolveResult *merged = [[HttpdnsEdgeResolveResult alloc] init];
|
||||
merged.ipv4s = resA ? resA.ipv4s : @[];
|
||||
merged.ipv6s = resAAAA ? resAAAA.ipv6s : @[];
|
||||
merged.ttl = 0;
|
||||
if (resA && resAAAA) {
|
||||
merged.ttl = MIN(resA.ttl, resAAAA.ttl);
|
||||
} else if (resA) {
|
||||
merged.ttl = resA.ttl;
|
||||
} else if (resAAAA) {
|
||||
merged.ttl = resAAAA.ttl;
|
||||
}
|
||||
if (resA.requestId) merged.requestId = resA.requestId;
|
||||
else if (resAAAA.requestId) merged.requestId = resAAAA.requestId;
|
||||
|
||||
NSError *finalErr = nil;
|
||||
if (errA && errAAAA) {
|
||||
finalErr = errA;
|
||||
}
|
||||
|
||||
[self handleEdgeResult:merged error:finalErr host:queryHost ipType:ipType start:startMs];
|
||||
});
|
||||
|
||||
} else {
|
||||
[self appendLog:[NSString stringWithFormat:@"[result] requestId=%@ ipv4=%@ ipv6=%@ ttl=%ld elapsed=%.0fms",
|
||||
result.requestId, result.ipv4s, result.ipv6s, (long)result.ttl, elapsedMs]];
|
||||
NSString *qtype = (ipType == HttpdnsQueryIPTypeIpv6) ? @"AAAA" : @"A";
|
||||
[self log:[NSString stringWithFormat:@"Query Type: %@", qtype]];
|
||||
[self.service resolveHost:queryHost queryType:qtype completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) {
|
||||
[self log:[NSString stringWithFormat:@"--- Edge Service Callback Triggered ---"]];
|
||||
[self handleEdgeResult:result error:error host:queryHost ipType:ipType start:startMs];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)logSnapshot {
|
||||
__block NSString *snapshot = @"";
|
||||
dispatch_sync(self.logQueue, ^{
|
||||
snapshot = [self.logBuffer copy];
|
||||
});
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
- (NSString *)currentHost {
|
||||
return self.config.host.length > 0 ? self.config.host : @"demo.cloudxdr.com";
|
||||
}
|
||||
|
||||
- (void)handleEdgeResult:(HttpdnsEdgeResolveResult *)result error:(NSError *)error host:(NSString *)host ipType:(HttpdnsQueryIPType)ipType start:(NSTimeInterval)startMs {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.model.host = host;
|
||||
if (result != nil) {
|
||||
self.model.ipv4s = result.ipv4s;
|
||||
self.model.ipv6s = result.ipv6s;
|
||||
self.model.ttlV4 = result.ipv4s.count > 0 ? result.ttl : 0;
|
||||
self.model.ttlV6 = result.ipv6s.count > 0 ? result.ttl : 0;
|
||||
self.model.error = nil;
|
||||
} else {
|
||||
self.model.ipType = ipType;
|
||||
|
||||
NSTimeInterval endMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
|
||||
self.model.elapsedMs = endMs - startMs;
|
||||
|
||||
if (error) {
|
||||
self.model.ipv4s = @[];
|
||||
self.model.ipv6s = @[];
|
||||
self.model.error = error;
|
||||
self.model.ttlV4 = 0;
|
||||
self.model.ttlV6 = 0;
|
||||
[self log:[NSString stringWithFormat:@"Edge Resolve Failed!"]];
|
||||
[self log:[NSString stringWithFormat:@"Error domain: %@", error.domain]];
|
||||
[self log:[NSString stringWithFormat:@"Error code: %ld", (long)error.code]];
|
||||
[self log:[NSString stringWithFormat:@"Error description: %@", error.localizedDescription]];
|
||||
if (error.userInfo) {
|
||||
[self log:[NSString stringWithFormat:@"Error user info: %@", error.userInfo]];
|
||||
}
|
||||
} else {
|
||||
self.model.ipv4s = result.ipv4s ?: @[];
|
||||
self.model.ipv6s = result.ipv6s ?: @[];
|
||||
self.model.ttlV4 = result.ttl;
|
||||
self.model.ttlV6 = result.ttl;
|
||||
self.model.businessRequestResult = @"Waiting for request...";
|
||||
[self log:[NSString stringWithFormat:@"Edge Resolve Success!"]];
|
||||
[self log:[NSString stringWithFormat:@"Host: %@", host]];
|
||||
[self log:[NSString stringWithFormat:@"IPv4s: %@", result.ipv4s]];
|
||||
[self log:[NSString stringWithFormat:@"IPv6s: %@", result.ipv6s]];
|
||||
[self log:[NSString stringWithFormat:@"TTL: %ld", (long)result.ttl]];
|
||||
[self log:[NSString stringWithFormat:@"Request ID: %@", result.requestId]];
|
||||
|
||||
// Fire off a business request to demonstrate real usage if we have an IP
|
||||
if (result.ipv4s.count > 0 || result.ipv6s.count > 0) {
|
||||
[self log:@"\n--- Start Business Request Demo ---"];
|
||||
// Construct a URL for the business domain
|
||||
NSURL *businessURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", host]];
|
||||
[self log:[NSString stringWithFormat:@"Requesting URL: %@", businessURL.absoluteString]];
|
||||
|
||||
[self.service requestURL:businessURL method:@"GET" headers:nil body:nil completion:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable reqError) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (reqError) {
|
||||
NSString *err = [NSString stringWithFormat:@"Failed: %@", reqError.localizedDescription];
|
||||
[self log:[NSString stringWithFormat:@"Business Request %@", err]];
|
||||
self.model.businessRequestResult = err;
|
||||
} else {
|
||||
[self log:[NSString stringWithFormat:@"Business Request Success! Status Code: %ld", (long)response.statusCode]];
|
||||
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
if (responseString.length > 200) {
|
||||
responseString = [[responseString substringToIndex:200] stringByAppendingString:@"... (truncated)"];
|
||||
}
|
||||
[self log:[NSString stringWithFormat:@"Response Body:\n%@", responseString]];
|
||||
self.model.businessRequestResult = [NSString stringWithFormat:@"HTTP %ld\n%@", (long)response.statusCode, responseString];
|
||||
}
|
||||
|
||||
id<DemoHttpdnsScenarioDelegate> bDelegate = self.delegate;
|
||||
if (bDelegate != nil) {
|
||||
[bDelegate scenario:self didUpdateModel:self.model];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
}
|
||||
self.model.elapsedMs = elapsedMs;
|
||||
|
||||
|
||||
id<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
|
||||
if (delegate != nil) {
|
||||
[delegate scenario:self didUpdateModel:self.model];
|
||||
@@ -125,9 +232,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (void)appendLog:(NSString *)msg {
|
||||
if (msg.length == 0) return;
|
||||
NSString *line = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], msg];
|
||||
// logger & ttl delegates removed since EdgeService lacks these protocol hooks in this SDK version
|
||||
|
||||
- (void)log:(NSString *)logStr {
|
||||
if (logStr.length == 0) {
|
||||
return;
|
||||
}
|
||||
NSString *line = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], logStr];
|
||||
dispatch_async(self.logQueue, ^{
|
||||
[self.logBuffer appendString:line];
|
||||
id<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
|
||||
@@ -139,12 +250,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (NSString *)logSnapshot {
|
||||
__block NSString *snapshot = @"";
|
||||
dispatch_sync(self.logQueue, ^{
|
||||
snapshot = [self.logBuffer copy];
|
||||
});
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
//
|
||||
// DemoResolveModel.h
|
||||
// NewHttpDNSTestDemo
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HttpdnsRequest.h"
|
||||
#import "HttpdnsResult.h"
|
||||
|
||||
@interface DemoResolveModel : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString *host;
|
||||
@property (nonatomic, assign) HttpdnsQueryIPType ipType;
|
||||
|
||||
@property (nonatomic, copy) NSArray<NSString *> *ipv4s;
|
||||
@property (nonatomic, copy) NSArray<NSString *> *ipv6s;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval elapsedMs;
|
||||
@property (nonatomic, assign) NSTimeInterval ttlV4;
|
||||
@property (nonatomic, assign) NSTimeInterval ttlV6;
|
||||
@property (nonatomic, strong, nullable) NSError *error;
|
||||
|
||||
@property (nonatomic, copy) NSString *businessRequestResult;
|
||||
|
||||
- (void)updateWithResult:(HttpdnsResult *)result startTimeMs:(NSTimeInterval)startMs;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//
|
||||
//
|
||||
// DemoResolveModel.m
|
||||
// NewHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import "DemoResolveModel.h"
|
||||
|
||||
@@ -9,15 +11,32 @@
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_host = @"demodemo.cloudxdr.com";
|
||||
_host = @"demo.cloudxdr.com";
|
||||
_ipType = HttpdnsQueryIPTypeBoth;
|
||||
_ipv4s = @[];
|
||||
_ipv6s = @[];
|
||||
_elapsedMs = 0;
|
||||
_ttlV4 = 0;
|
||||
_ttlV6 = 0;
|
||||
_error = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateWithResult:(HttpdnsResult *)result startTimeMs:(NSTimeInterval)startMs {
|
||||
NSTimeInterval now = [[NSDate date] timeIntervalSince1970] * 1000.0;
|
||||
_elapsedMs = MAX(0, now - startMs);
|
||||
if (result != nil) {
|
||||
_ipv4s = result.ips ?: @[];
|
||||
_ipv6s = result.ipv6s ?: @[];
|
||||
_ttlV4 = result.ttl;
|
||||
_ttlV6 = result.v6ttl;
|
||||
} else {
|
||||
_ipv4s = @[];
|
||||
_ipv6s = @[];
|
||||
_ttlV4 = 0;
|
||||
_ttlV6 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//
|
||||
// DemoViewController.m
|
||||
//
|
||||
// DNSDemoViewController.m
|
||||
// NewHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import "DemoViewController.h"
|
||||
#import "DemoResolveModel.h"
|
||||
@@ -18,14 +20,20 @@
|
||||
@property (nonatomic, strong) UIStackView *stack;
|
||||
|
||||
@property (nonatomic, strong) UITextField *hostField;
|
||||
@property (nonatomic, strong) UISegmentedControl *queryTypeSeg;
|
||||
@property (nonatomic, strong) UISegmentedControl *ipTypeSeg;
|
||||
|
||||
// Removed toggles: HTTPS, Persist, Reuse
|
||||
|
||||
@property (nonatomic, strong) UILabel *elapsedLabel;
|
||||
@property (nonatomic, strong) UILabel *ttlLabel;
|
||||
|
||||
@property (nonatomic, strong) UITextView *resultTextView;
|
||||
|
||||
@property (nonatomic, weak) DemoLogViewController *presentedLogVC;
|
||||
|
||||
@property (nonatomic, strong) UIButton *btnResolve;
|
||||
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DemoViewController
|
||||
@@ -39,8 +47,7 @@
|
||||
self.scenarioConfig = [[DemoHttpdnsScenarioConfig alloc] init];
|
||||
[self buildUI];
|
||||
[self reloadUIFromModel:self.model];
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
|
||||
initWithTitle:@"日志" style:UIBarButtonItemStylePlain target:self action:@selector(onShowLog)];
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"日志" style:UIBarButtonItemStylePlain target:self action:@selector(onShowLog)];
|
||||
}
|
||||
|
||||
- (void)buildUI {
|
||||
@@ -67,36 +74,45 @@
|
||||
[self.stack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-32]
|
||||
]];
|
||||
|
||||
// Host 输入
|
||||
UIStackView *row1 = [self labeledRow:@"Host"];
|
||||
self.hostField = [[UITextField alloc] init];
|
||||
self.hostField.placeholder = @"demodemo.cloudxdr.com";
|
||||
self.hostField.text = self.scenarioConfig.host;
|
||||
self.hostField.placeholder = @"YOUR_SERVICE_DOMAIN";
|
||||
self.hostField.text = self.scenarioConfig.host ?: @"";
|
||||
self.hostField.borderStyle = UITextBorderStyleRoundedRect;
|
||||
[row1 addArrangedSubview:self.hostField];
|
||||
[self.stack addArrangedSubview:row1];
|
||||
|
||||
// 查询类型
|
||||
UIStackView *row2 = [self labeledRow:@"类型"];
|
||||
self.queryTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"A (IPv4)", @"AAAA (IPv6)", @"both"]];
|
||||
self.queryTypeSeg.selectedSegmentIndex = 0;
|
||||
[self.queryTypeSeg addTarget:self action:@selector(onQueryTypeChanged:) forControlEvents:UIControlEventValueChanged];
|
||||
[row2 addArrangedSubview:self.queryTypeSeg];
|
||||
UIStackView *row2 = [self labeledRow:@"IP Type"];
|
||||
self.ipTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"IPv4", @"IPv6", @"Both"]];
|
||||
self.ipTypeSeg.selectedSegmentIndex = [self segmentIndexForIpType:self.scenarioConfig.ipType];
|
||||
[self.ipTypeSeg addTarget:self action:@selector(onIPTypeChanged:) forControlEvents:UIControlEventValueChanged];
|
||||
[row2 addArrangedSubview:self.ipTypeSeg];
|
||||
[self.stack addArrangedSubview:row2];
|
||||
|
||||
// 解析按钮
|
||||
UIButton *btnResolve = [self filledButton:@"Resolve" action:@selector(onResolve)];
|
||||
[self.stack addArrangedSubview:btnResolve];
|
||||
// Removed options stack from UI
|
||||
|
||||
UIStackView *actions = [[UIStackView alloc] init];
|
||||
actions.axis = UILayoutConstraintAxisHorizontal;
|
||||
actions.spacing = 12;
|
||||
actions.distribution = UIStackViewDistributionFillEqually;
|
||||
[self.stack addArrangedSubview:actions];
|
||||
|
||||
self.btnResolve = [self filledButton:@"Resolve IP & Test Request" action:@selector(onResolve)];
|
||||
[actions addArrangedSubview:self.btnResolve];
|
||||
|
||||
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
|
||||
self.activityIndicator.hidesWhenStopped = YES;
|
||||
[actions addArrangedSubview:self.activityIndicator];
|
||||
|
||||
// 耗时 / TTL
|
||||
UIStackView *info = [self labeledRow:@"Info"];
|
||||
self.elapsedLabel = [self monoLabel:@"elapsed: - ms"];
|
||||
self.ttlLabel = [self monoLabel:@"ttl: -"];
|
||||
self.ttlLabel = [self monoLabel:@"ttl v4/v6: -/- s"];
|
||||
[info addArrangedSubview:self.elapsedLabel];
|
||||
[info addArrangedSubview:self.ttlLabel];
|
||||
[self.stack addArrangedSubview:info];
|
||||
if (@available(iOS 11.0, *)) { [self.stack setCustomSpacing:24 afterView:info]; }
|
||||
|
||||
|
||||
// 结果
|
||||
UILabel *resultTitle = [[UILabel alloc] init];
|
||||
resultTitle.text = @"结果";
|
||||
resultTitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
|
||||
@@ -108,66 +124,18 @@
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self.resultTextView.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||
self.resultTextView.textColor = [UIColor labelColor];
|
||||
} else {
|
||||
self.resultTextView.font = [UIFont systemFontOfSize:12];
|
||||
self.resultTextView.textColor = [UIColor blackColor];
|
||||
}
|
||||
self.resultTextView.backgroundColor = [UIColor clearColor];
|
||||
self.resultTextView.textContainerInset = UIEdgeInsetsMake(8, 12, 8, 12);
|
||||
[self.stack addArrangedSubview:self.resultTextView];
|
||||
[self.resultTextView.heightAnchor constraintEqualToConstant:320].active = YES;
|
||||
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onQueryTypeChanged:(UISegmentedControl *)seg {
|
||||
NSArray *types = @[@"A", @"AAAA", @"both"];
|
||||
self.scenarioConfig.queryType = types[seg.selectedSegmentIndex];
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
}
|
||||
|
||||
- (void)onResolve {
|
||||
[self.view endEditing:YES];
|
||||
NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"demodemo.cloudxdr.com";
|
||||
self.scenarioConfig.host = host;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
[self.scenario resolve];
|
||||
}
|
||||
|
||||
- (void)onShowLog {
|
||||
DemoLogViewController *logVC = [DemoLogViewController new];
|
||||
[logVC setInitialText:[self.scenario logSnapshot]];
|
||||
self.presentedLogVC = logVC;
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:logVC];
|
||||
nav.modalPresentationStyle = UIModalPresentationAutomatic;
|
||||
[self presentViewController:nav animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)reloadUIFromModel:(DemoResolveModel *)model {
|
||||
self.model = model;
|
||||
if (![self.hostField isFirstResponder]) {
|
||||
self.hostField.text = model.host;
|
||||
}
|
||||
self.elapsedLabel.text = [NSString stringWithFormat:@"elapsed: %.0f ms", model.elapsedMs];
|
||||
self.ttlLabel.text = [NSString stringWithFormat:@"ttl v4/v6: %.0f/%.0f s", model.ttlV4, model.ttlV6];
|
||||
self.resultTextView.text = [self buildResultText:model];
|
||||
}
|
||||
|
||||
- (NSString *)buildResultText:(DemoResolveModel *)model {
|
||||
if (model.error != nil) {
|
||||
return [NSString stringWithFormat:@"Error:\n%@", model.error.localizedDescription];
|
||||
}
|
||||
NSDictionary *dict = @{
|
||||
@"host": model.host ?: @"",
|
||||
@"elapsed": [NSString stringWithFormat:@"%.0f ms", model.elapsedMs],
|
||||
@"ttl": @{ @"v4": @(model.ttlV4), @"v6": @(model.ttlV6) },
|
||||
@"ipv4": model.ipv4s ?: @[],
|
||||
@"ipv6": model.ipv6s ?: @[]
|
||||
};
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
|
||||
if (data == nil) return @"{}";
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (UIStackView *)labeledRow:(NSString *)title {
|
||||
UIStackView *row = [[UIStackView alloc] init];
|
||||
row.axis = UILayoutConstraintAxisHorizontal;
|
||||
@@ -185,6 +153,8 @@
|
||||
l.text = text;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
l.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||
} else {
|
||||
l.font = [UIFont systemFontOfSize:12];
|
||||
}
|
||||
return l;
|
||||
}
|
||||
@@ -200,6 +170,128 @@
|
||||
return b;
|
||||
}
|
||||
|
||||
- (UIButton *)borderButton:(NSString *)title action:(SEL)sel {
|
||||
UIButton *b = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[b setTitle:title forState:UIControlStateNormal];
|
||||
b.layer.borderWidth = 1;
|
||||
b.layer.borderColor = [UIColor systemBlueColor].CGColor;
|
||||
b.layer.cornerRadius = 8;
|
||||
[b.heightAnchor constraintEqualToConstant:44].active = YES;
|
||||
[b addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
|
||||
return b;
|
||||
}
|
||||
|
||||
- (UIView *)switchItem:(NSString *)title action:(SEL)sel out:(UISwitch * __strong *)outSwitch {
|
||||
UIStackView *box = [[UIStackView alloc] init];
|
||||
box.axis = UILayoutConstraintAxisVertical;
|
||||
box.alignment = UIStackViewAlignmentCenter;
|
||||
UILabel *l = [[UILabel alloc] init];
|
||||
l.text = title;
|
||||
UISwitch *s = [[UISwitch alloc] init];
|
||||
[s addTarget:self action:sel forControlEvents:UIControlEventValueChanged];
|
||||
[box addArrangedSubview:s];
|
||||
[box addArrangedSubview:l];
|
||||
if (outSwitch != NULL) {
|
||||
*outSwitch = s;
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
- (NSInteger)segmentIndexForIpType:(HttpdnsQueryIPType)ipType {
|
||||
switch (ipType) {
|
||||
case HttpdnsQueryIPTypeIpv4: { return 0; }
|
||||
case HttpdnsQueryIPTypeIpv6: { return 1; }
|
||||
default: { return 2; }
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onIPTypeChanged:(UISegmentedControl *)seg {
|
||||
HttpdnsQueryIPType type = HttpdnsQueryIPTypeBoth;
|
||||
switch (seg.selectedSegmentIndex) {
|
||||
case 0: type = HttpdnsQueryIPTypeIpv4; break;
|
||||
case 1: type = HttpdnsQueryIPTypeIpv6; break;
|
||||
default: type = HttpdnsQueryIPTypeBoth; break;
|
||||
}
|
||||
self.model.ipType = type;
|
||||
self.scenarioConfig.ipType = type;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
}
|
||||
|
||||
// Toggles delegates removed
|
||||
|
||||
- (void)onResolve {
|
||||
[self.view endEditing:YES];
|
||||
|
||||
// Show loading state
|
||||
self.btnResolve.enabled = NO;
|
||||
[self.btnResolve setTitle:@"Resolving..." forState:UIControlStateNormal];
|
||||
[self.activityIndicator startAnimating];
|
||||
|
||||
NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"";
|
||||
self.model.host = host;
|
||||
self.scenarioConfig.host = host;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
[self.scenario resolve];
|
||||
}
|
||||
|
||||
- (void)onShowLog {
|
||||
DemoLogViewController *logVC = [DemoLogViewController new];
|
||||
[logVC setInitialText:[self.scenario logSnapshot]];
|
||||
self.presentedLogVC = logVC;
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:logVC];
|
||||
nav.modalPresentationStyle = UIModalPresentationAutomatic;
|
||||
[self presentViewController:nav animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)reloadUIFromModel:(DemoResolveModel *)model {
|
||||
self.model = model;
|
||||
|
||||
// Hide loading state
|
||||
self.btnResolve.enabled = YES;
|
||||
[self.btnResolve setTitle:@"Resolve IP & Test Request" forState:UIControlStateNormal];
|
||||
[self.activityIndicator stopAnimating];
|
||||
|
||||
if (![self.hostField isFirstResponder]) {
|
||||
self.hostField.text = model.host;
|
||||
}
|
||||
NSInteger segIndex = [self segmentIndexForIpType:model.ipType];
|
||||
if (self.ipTypeSeg.selectedSegmentIndex != segIndex) {
|
||||
self.ipTypeSeg.selectedSegmentIndex = segIndex;
|
||||
}
|
||||
self.elapsedLabel.text = [NSString stringWithFormat:@"elapsed: %.0f ms", model.elapsedMs];
|
||||
self.ttlLabel.text = [NSString stringWithFormat:@"ttl v4/v6: %.0f/%.0f s", model.ttlV4, model.ttlV6];
|
||||
self.resultTextView.text = [self buildJSONText:model];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)buildJSONText:(DemoResolveModel *)model {
|
||||
NSString *ipTypeStr = @"both";
|
||||
switch (model.ipType) {
|
||||
case HttpdnsQueryIPTypeIpv4: { ipTypeStr = @"ipv4"; break; }
|
||||
case HttpdnsQueryIPTypeIpv6: { ipTypeStr = @"ipv6"; break; }
|
||||
default: { ipTypeStr = @"both"; break; }
|
||||
}
|
||||
NSMutableDictionary *dict = [@{
|
||||
@"host": model.host ?: @"",
|
||||
@"ipType": ipTypeStr,
|
||||
@"elapsedMs": @(model.elapsedMs),
|
||||
@"ttl": @{ @"v4": @(model.ttlV4), @"v6": @(model.ttlV6) },
|
||||
@"ipv4": model.ipv4s ?: @[],
|
||||
@"ipv6": model.ipv6s ?: @[]
|
||||
} mutableCopy];
|
||||
if (model.businessRequestResult.length > 0) {
|
||||
dict[@"businessRequestResult"] = model.businessRequestResult;
|
||||
}
|
||||
NSError *err = nil;
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&err];
|
||||
if (data == nil || err != nil) {
|
||||
return [NSString stringWithFormat:@"{\n \"error\": \"%@\"\n}", err.localizedDescription ?: @"json serialize failed"];
|
||||
}
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
#pragma mark - DemoHttpdnsScenarioDelegate
|
||||
|
||||
- (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model {
|
||||
|
||||
Reference in New Issue
Block a user