// // DemoHttpdnsScenario.m // NewHttpDNSTestDemo // // @author Created by Claude Code on 2025-10-23 // #import "DemoHttpdnsScenario.h" #import "DemoConfigLoader.h" @interface DemoHttpdnsScenarioConfig () @end @implementation DemoHttpdnsScenarioConfig - (instancetype)init { if (self = [super init]) { NSString *serviceDomain = [DemoConfigLoader shared].serviceDomain; _host = serviceDomain.length > 0 ? serviceDomain : @"www.new.com"; _ipType = HttpdnsQueryIPTypeBoth; // _httpsEnabled = YES; // _persistentCacheEnabled = YES; // _reuseExpiredIPEnabled = YES; } return self; } - (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; return cfg; } @end @interface DemoHttpdnsScenario () @property (nonatomic, strong) HttpdnsEdgeService *service; @property (nonatomic, strong) DemoHttpdnsScenarioConfig *config; @property (nonatomic, strong) NSMutableString *logBuffer; @property (nonatomic, strong) dispatch_queue_t logQueue; @end @implementation DemoHttpdnsScenario - (instancetype)initWithDelegate:(id)delegate { if (self = [super init]) { _delegate = delegate; _model = [[DemoResolveModel alloc] init]; _config = [[DemoHttpdnsScenarioConfig alloc] init]; _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.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 { NSString *queryHost = [self currentHost]; HttpdnsQueryIPType ipType = self.config.ipType; NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0; [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 { 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; 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.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 bDelegate = self.delegate; if (bDelegate != nil) { [bDelegate scenario:self didUpdateModel:self.model]; } }); }]; } } id delegate = self.delegate; if (delegate != nil) { [delegate scenario:self didUpdateModel:self.model]; } }); } // 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 delegate = self.delegate; if (delegate != nil) { dispatch_async(dispatch_get_main_queue(), ^{ [delegate scenario:self didAppendLogLine:line]; }); } }); } @end