From a10f3f3740a488f0c957b4217235381e6282023f Mon Sep 17 00:00:00 2001 From: robin Date: Thu, 5 Mar 2026 02:52:45 +0800 Subject: [PATCH] ios demo --- .../ios/NewHttpDNSTestDemo/DemoConfig.plist | 14 +- .../ios/NewHttpDNSTestDemo/DemoConfigLoader.h | 14 +- .../ios/NewHttpDNSTestDemo/DemoConfigLoader.m | 67 ++--- .../NewHttpDNSTestDemo/DemoHttpdnsScenario.h | 12 +- .../NewHttpDNSTestDemo/DemoHttpdnsScenario.m | 127 +++++---- .../ios/NewHttpDNSTestDemo/DemoResolveModel.h | 13 +- .../ios/NewHttpDNSTestDemo/DemoResolveModel.m | 25 +- .../NewHttpDNSTestDemo/DemoViewController.m | 250 +++++------------- 8 files changed, 171 insertions(+), 351 deletions(-) diff --git a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfig.plist b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfig.plist index 6ac5211..ffa5dbb 100644 --- a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfig.plist +++ b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfig.plist @@ -2,11 +2,13 @@ - accountID - 139450 - secretKey - 807a19762f8eaefa8563489baf198535 - aesSecretKey - 82c0af0d0cb2d69c4f87bb25c2e23929 + appId + app1flndpo9 + primaryServiceHost + httpdns.deepwaf.xyz + servicePort + 8445 + signSecret + diff --git a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.h b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.h index 14bbe35..f9083f1 100644 --- a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.h +++ b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.h @@ -1,21 +1,17 @@ // // DemoConfigLoader.h -// TrustHttpDNSTestDemo -// -// @author Created by Claude Code on 2025-10-05 +// NewHttpDNSTestDemo // #import @interface DemoConfigLoader : NSObject -@property (nonatomic, assign, readonly) NSInteger accountID; -@property (nonatomic, copy, readonly) NSString *secretKey; -@property (nonatomic, copy, readonly) NSString *aesSecretKey; - -@property (nonatomic, assign, readonly) BOOL hasValidAccount; +@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; + (instancetype)shared; @end - diff --git a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.m b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.m index b9abafc..13c5223 100644 --- a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.m +++ b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.m @@ -1,17 +1,15 @@ // // DemoConfigLoader.m -// TrustHttpDNSTestDemo -// -// @author Created by Claude Code on 2025-10-05 +// NewHttpDNSTestDemo // #import "DemoConfigLoader.h" @implementation DemoConfigLoader { - NSInteger _accountID; - NSString *_secretKey; - NSString *_aesSecretKey; - BOOL _hasValidAccount; + NSString *_appId; + NSString *_primaryServiceHost; + NSInteger _servicePort; + NSString *_signSecret; } + (instancetype)shared { @@ -30,56 +28,23 @@ return self; } -// 复杂逻辑:配置加载顺序为 Bundle > 环境变量;并?accountID 进行有效性校? - (void)loadConfig { - _accountID = 0; - _secretKey = @""; - _aesSecretKey = @""; - _hasValidAccount = NO; - - NSDictionary *bundleDict = nil; + NSDictionary *dict = nil; NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"DemoConfig" ofType:@"plist"]; if (plistPath.length > 0) { - bundleDict = [NSDictionary dictionaryWithContentsOfFile:plistPath]; + dict = [NSDictionary dictionaryWithContentsOfFile:plistPath]; } - NSDictionary *env = [[NSProcessInfo processInfo] environment]; - - NSNumber *acc = bundleDict[@"accountID"]; // NSNumber preferred in plist - NSString *secret = bundleDict[@"secretKey"] ?: @""; - NSString *aes = bundleDict[@"aesSecretKey"] ?: @""; - - NSString *envAcc = env[@"HTTPDNS_ACCOUNT_ID"]; - NSString *envSecret = env[@"HTTPDNS_SECRET_KEY"]; - NSString *envAes = env[@"HTTPDNS_AES_SECRET_KEY"]; - - if (envAcc.length > 0) { - acc = @([envAcc integerValue]); - } - if (envSecret.length > 0) { - secret = envSecret; - } - if (envAes.length > 0) { - aes = envAes; - } - - if (acc != nil && [acc integerValue] > 0 && secret.length > 0) { - _accountID = [acc integerValue]; - _secretKey = secret; - _aesSecretKey = aes ?: @""; - _hasValidAccount = YES; - } else { - _accountID = 0; - _secretKey = @""; - _aesSecretKey = @""; - _hasValidAccount = NO; - } + _appId = dict[@"appId"] ?: @""; + _primaryServiceHost = dict[@"primaryServiceHost"] ?: @""; + _servicePort = [dict[@"servicePort"] integerValue] ?: 443; + NSString *secret = dict[@"signSecret"] ?: @""; + _signSecret = secret.length > 0 ? secret : nil; } -- (NSInteger)accountID { return _accountID; } -- (NSString *)secretKey { return _secretKey; } -- (NSString *)aesSecretKey { return _aesSecretKey; } -- (BOOL)hasValidAccount { return _hasValidAccount; } +- (NSString *)appId { return _appId; } +- (NSString *)primaryServiceHost { return _primaryServiceHost; } +- (NSInteger)servicePort { return _servicePort; } +- (NSString *)signSecret { return _signSecret; } @end - diff --git a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.h b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.h index 8e0840e..f247ceb 100644 --- a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.h +++ b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.h @@ -1,13 +1,10 @@ // // DemoHttpdnsScenario.h -// TrustHttpDNSTestDemo -// -// @author Created by Claude Code on 2025-10-23 +// NewHttpDNSTestDemo // #import #import "DemoResolveModel.h" -#import "HttpdnsService.h" NS_ASSUME_NONNULL_BEGIN @@ -16,10 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface DemoHttpdnsScenarioConfig : NSObject @property (nonatomic, copy) NSString *host; -@property (nonatomic, assign) HttpdnsQueryIPType ipType; -@property (nonatomic, assign) BOOL httpsEnabled; -@property (nonatomic, assign) BOOL persistentCacheEnabled; -@property (nonatomic, assign) BOOL reuseExpiredIPEnabled; +@property (nonatomic, copy) NSString *queryType; // @"A", @"AAAA", @"both" - (instancetype)init; @@ -39,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithDelegate:(id)delegate; - (void)applyConfig:(DemoHttpdnsScenarioConfig *)config; +- (void)resolve; - (void)resolveSyncNonBlocking; - (void)resolveSync; - (NSString *)logSnapshot; @@ -46,4 +41,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - diff --git a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.m b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.m index 496e6ca..b708d71 100644 --- a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.m +++ b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.m @@ -1,12 +1,11 @@ -// +// // DemoHttpdnsScenario.m // NewHttpDNSTestDemo // -// @author Created by Claude Code on 2025-10-23 -// #import "DemoHttpdnsScenario.h" #import "DemoConfigLoader.h" +#import @interface DemoHttpdnsScenarioConfig () @end @@ -15,11 +14,8 @@ - (instancetype)init { if (self = [super init]) { - _host = @"www.new.com"; - _ipType = HttpdnsQueryIPTypeBoth; - _httpsEnabled = YES; - _persistentCacheEnabled = YES; - _reuseExpiredIPEnabled = YES; + _host = @"demo.cloudxdr.com"; + _queryType = @"A"; } return self; } @@ -27,18 +23,15 @@ - (id)copyWithZone:(NSZone *)zone { DemoHttpdnsScenarioConfig *cfg = [[[self class] allocWithZone:zone] init]; cfg.host = self.host; - cfg.ipType = self.ipType; - cfg.httpsEnabled = self.httpsEnabled; - cfg.persistentCacheEnabled = self.persistentCacheEnabled; - cfg.reuseExpiredIPEnabled = self.reuseExpiredIPEnabled; + cfg.queryType = self.queryType; return cfg; } @end -@interface DemoHttpdnsScenario () +@interface DemoHttpdnsScenario () -@property (nonatomic, strong) HttpDnsService *service; +@property (nonatomic, strong) HttpdnsEdgeService *service; @property (nonatomic, strong) DemoHttpdnsScenarioConfig *config; @property (nonatomic, strong) NSMutableString *logBuffer; @property (nonatomic, strong) dispatch_queue_t logQueue; @@ -55,73 +48,76 @@ _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]; - if (cfg.hasValidAccount) { - if (cfg.aesSecretKey.length > 0) { - self.service = [[HttpDnsService alloc] initWithAccountID:cfg.accountID secretKey:cfg.secretKey aesSecretKey:cfg.aesSecretKey]; - } else { - self.service = [[HttpDnsService alloc] initWithAccountID:cfg.accountID secretKey:cfg.secretKey]; - } - } else { - self.service = [HttpDnsService sharedInstance]; - } - [self.service setLogEnabled:YES]; - [self.service setNetworkingTimeoutInterval:8]; - [self.service setDegradeToLocalDNSEnabled:YES]; - self.service.ttlDelegate = self; - [self.service setLogHandler:self]; + 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]]; } - (void)applyConfig:(DemoHttpdnsScenarioConfig *)config { self.config = [config copy]; self.model.host = self.config.host; - self.model.ipType = self.config.ipType; - [self.service setHTTPSRequestEnabled:self.config.httpsEnabled]; - [self.service setPersistentCacheIPEnabled:self.config.persistentCacheEnabled]; - [self.service setReuseExpiredIPEnabled:self.config.reuseExpiredIPEnabled]; +} + +- (void)resolve { + [self resolveSyncNonBlocking]; } - (void)resolveSyncNonBlocking { - NSString *queryHost = [self currentHost]; - HttpdnsQueryIPType ipType = self.config.ipType; + NSString *host = self.config.host.length > 0 ? self.config.host : @"demo.cloudxdr.com"; + NSString *queryType = self.config.queryType.length > 0 ? self.config.queryType : @"A"; NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0; - HttpdnsResult *result = [self.service resolveHostSyncNonBlocking:queryHost byIpType:ipType]; - [self handleResult:result host:queryHost ipType:ipType start:startMs]; + + [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 { - NSString *queryHost = [self currentHost]; - HttpdnsQueryIPType ipType = self.config.ipType; - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ - NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0; - HttpdnsResult *result = [self.service resolveHostSync:queryHost byIpType:ipType]; - [self handleResult:result host:queryHost ipType:ipType start:startMs]; - }); + [self resolveSyncNonBlocking]; } -- (NSString *)logSnapshot { - __block NSString *snapshot = @""; - dispatch_sync(self.logQueue, ^{ - snapshot = [self.logBuffer copy]; - }); - return snapshot; -} +- (void)handleResult:(HttpdnsEdgeResolveResult *)result + host:(NSString *)host + queryType:(NSString *)queryType + start:(NSTimeInterval)startMs + error:(NSError *)error { + NSTimeInterval elapsedMs = [[NSDate date] timeIntervalSince1970] * 1000.0 - startMs; -- (NSString *)currentHost { - return self.config.host.length > 0 ? self.config.host : @"www.new.com"; -} + if (error != nil) { + [self appendLog:[NSString stringWithFormat:@"[error] %@", error.localizedDescription]]; + } else { + [self appendLog:[NSString stringWithFormat:@"[result] requestId=%@ ipv4=%@ ipv6=%@ ttl=%ld elapsed=%.0fms", + result.requestId, result.ipv4s, result.ipv6s, (long)result.ttl, elapsedMs]]; + } -- (void)handleResult:(HttpdnsResult *)result host:(NSString *)host ipType:(HttpdnsQueryIPType)ipType start:(NSTimeInterval)startMs { dispatch_async(dispatch_get_main_queue(), ^{ self.model.host = host; - self.model.ipType = ipType; - [self.model updateWithResult:result startTimeMs:startMs]; + 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.ipv4s = @[]; + self.model.ipv6s = @[]; + self.model.error = error; + } + self.model.elapsedMs = elapsedMs; + id delegate = self.delegate; if (delegate != nil) { [delegate scenario:self didUpdateModel:self.model]; @@ -129,12 +125,9 @@ }); } -- (void)log:(NSString *)logStr { - if (logStr.length == 0) { - return; - } - NSString *line = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], logStr]; - // 使用串行队列保证日志追加与快照的一致性 +- (void)appendLog:(NSString *)msg { + if (msg.length == 0) return; + NSString *line = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], msg]; dispatch_async(self.logQueue, ^{ [self.logBuffer appendString:line]; id delegate = self.delegate; @@ -146,8 +139,12 @@ }); } -- (int64_t)httpdnsHost:(NSString *)host ipType:(NewHttpDNS_IPType)ipType ttl:(int64_t)ttl { - return ttl; +- (NSString *)logSnapshot { + __block NSString *snapshot = @""; + dispatch_sync(self.logQueue, ^{ + snapshot = [self.logBuffer copy]; + }); + return snapshot; } @end diff --git a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.h b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.h index 751f721..e20da78 100644 --- a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.h +++ b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.h @@ -1,27 +1,18 @@ // // DemoResolveModel.h -// TrustHttpDNSTestDemo -// -// @author Created by Claude Code on 2025-10-05 +// NewHttpDNSTestDemo // #import -#import "HttpdnsRequest.h" -#import "HttpdnsResult.h" @interface DemoResolveModel : NSObject @property (nonatomic, copy) NSString *host; -@property (nonatomic, assign) HttpdnsQueryIPType ipType; - @property (nonatomic, copy) NSArray *ipv4s; @property (nonatomic, copy) NSArray *ipv6s; - @property (nonatomic, assign) NSTimeInterval elapsedMs; @property (nonatomic, assign) NSTimeInterval ttlV4; @property (nonatomic, assign) NSTimeInterval ttlV6; - -- (void)updateWithResult:(HttpdnsResult *)result startTimeMs:(NSTimeInterval)startMs; +@property (nonatomic, strong, nullable) NSError *error; @end - diff --git a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.m b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.m index 403258e..7500640 100644 --- a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.m +++ b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.m @@ -1,9 +1,7 @@ -// +// // DemoResolveModel.m // NewHttpDNSTestDemo // -// @author Created by Claude Code on 2025-10-05 -// #import "DemoResolveModel.h" @@ -11,32 +9,15 @@ - (instancetype)init { if (self = [super init]) { - _host = @"www.new.com"; - _ipType = HttpdnsQueryIPTypeBoth; + _host = @"demodemo.cloudxdr.com"; _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 - diff --git a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoViewController.m b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoViewController.m index 1b43cde..2b6a9c4 100644 --- a/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoViewController.m +++ b/HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoViewController.m @@ -1,8 +1,6 @@ -// -// DNSDemoViewController.m -// NewHttpDNSTestDemo // -// @author Created by Claude Code on 2025-10-05 +// DemoViewController.m +// NewHttpDNSTestDemo // #import "DemoViewController.h" @@ -20,15 +18,10 @@ @property (nonatomic, strong) UIStackView *stack; @property (nonatomic, strong) UITextField *hostField; -@property (nonatomic, strong) UISegmentedControl *ipTypeSeg; - -@property (nonatomic, strong) UISwitch *swHTTPS; -@property (nonatomic, strong) UISwitch *swPersist; -@property (nonatomic, strong) UISwitch *swReuse; +@property (nonatomic, strong) UISegmentedControl *queryTypeSeg; @property (nonatomic, strong) UILabel *elapsedLabel; @property (nonatomic, strong) UILabel *ttlLabel; - @property (nonatomic, strong) UITextView *resultTextView; @property (nonatomic, weak) DemoLogViewController *presentedLogVC; @@ -46,7 +39,8 @@ 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 { @@ -73,57 +67,36 @@ [self.stack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-32] ]]; + // Host 输入 UIStackView *row1 = [self labeledRow:@"Host"]; self.hostField = [[UITextField alloc] init]; - self.hostField.placeholder = @"www.new.com"; + self.hostField.placeholder = @"demodemo.cloudxdr.com"; self.hostField.text = self.scenarioConfig.host; self.hostField.borderStyle = UITextBorderStyleRoundedRect; [row1 addArrangedSubview:self.hostField]; [self.stack addArrangedSubview:row1]; - 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]; + // 查询类型 + 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]; [self.stack addArrangedSubview:row2]; - UIStackView *opts = [[UIStackView alloc] init]; - opts.axis = UILayoutConstraintAxisHorizontal; - opts.alignment = UIStackViewAlignmentCenter; - opts.distribution = UIStackViewDistributionFillEqually; - opts.spacing = 8; - [self.stack addArrangedSubview:opts]; - - [opts addArrangedSubview:[self switchItem:@"HTTPS" action:@selector(onToggleHTTPS:) out:&_swHTTPS]]; - [opts addArrangedSubview:[self switchItem:@"持久化" action:@selector(onTogglePersist:) out:&_swPersist]]; - [opts addArrangedSubview:[self switchItem:@"复用过期" action:@selector(onToggleReuse:) out:&_swReuse]]; - - self.swHTTPS.on = self.scenarioConfig.httpsEnabled; - self.swPersist.on = self.scenarioConfig.persistentCacheEnabled; - self.swReuse.on = self.scenarioConfig.reuseExpiredIPEnabled; - [self applyOptionSwitches]; - - UIStackView *actions = [[UIStackView alloc] init]; - actions.axis = UILayoutConstraintAxisHorizontal; - actions.spacing = 12; - actions.distribution = UIStackViewDistributionFillEqually; - [self.stack addArrangedSubview:actions]; - - UIButton *btnAsync = [self filledButton:@"Resolve (SyncNonBlocing)" action:@selector(onResolveAsync)]; - UIButton *btnSync = [self borderButton:@"Resolve (Sync)" action:@selector(onResolveSync)]; - [actions addArrangedSubview:btnAsync]; - [actions addArrangedSubview:btnSync]; + // 解析按钮 + UIButton *btnResolve = [self filledButton:@"Resolve" action:@selector(onResolve)]; + [self.stack addArrangedSubview:btnResolve]; + // 耗时 / TTL UIStackView *info = [self labeledRow:@"Info"]; self.elapsedLabel = [self monoLabel:@"elapsed: - ms"]; - self.ttlLabel = [self monoLabel:@"ttl v4/v6: -/- s"]; + self.ttlLabel = [self monoLabel:@"ttl: -"]; [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]; @@ -135,18 +108,66 @@ 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; @@ -164,8 +185,6 @@ l.text = text; if (@available(iOS 13.0, *)) { l.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular]; - } else { - l.font = [UIFont systemFontOfSize:12]; } return l; } @@ -181,131 +200,6 @@ 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]; -} - -- (void)applyOptionSwitches { - self.scenarioConfig.httpsEnabled = self.swHTTPS.isOn; - self.scenarioConfig.persistentCacheEnabled = self.swPersist.isOn; - self.scenarioConfig.reuseExpiredIPEnabled = self.swReuse.isOn; - [self.scenario applyConfig:self.scenarioConfig]; -} - -- (void)onToggleHTTPS:(UISwitch *)s { [self applyOptionSwitches]; } -- (void)onTogglePersist:(UISwitch *)s { [self applyOptionSwitches]; } -- (void)onToggleReuse:(UISwitch *)s { [self applyOptionSwitches]; } - -- (void)onResolveAsync { - [self.view endEditing:YES]; - NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"www.new.com"; - self.model.host = host; - self.scenarioConfig.host = host; - [self.scenario applyConfig:self.scenarioConfig]; - [self.scenario resolveSyncNonBlocking]; -} - -- (void)onResolveSync { - [self.view endEditing:YES]; - NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"www.new.com"; - self.model.host = host; - self.scenarioConfig.host = host; - [self.scenario applyConfig:self.scenarioConfig]; - [self.scenario resolveSync]; -} - -- (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; - } - 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; } - } - NSDictionary *dict = @{ - @"host": model.host ?: @"", - @"ipType": ipTypeStr, - @"elapsedMs": @(model.elapsedMs), - @"ttl": @{ @"v4": @(model.ttlV4), @"v6": @(model.ttlV6) }, - @"ipv4": model.ipv4s ?: @[], - @"ipv6": model.ipv6s ?: @[] - }; - 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 {