ios demo
This commit is contained in:
@@ -2,11 +2,13 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>accountID</key>
|
<key>appId</key>
|
||||||
<integer>139450</integer>
|
<string>app1flndpo9</string>
|
||||||
<key>secretKey</key>
|
<key>primaryServiceHost</key>
|
||||||
<string>807a19762f8eaefa8563489baf198535</string>
|
<string>httpdns.deepwaf.xyz</string>
|
||||||
<key>aesSecretKey</key>
|
<key>servicePort</key>
|
||||||
<string>82c0af0d0cb2d69c4f87bb25c2e23929</string>
|
<integer>8445</integer>
|
||||||
|
<key>signSecret</key>
|
||||||
|
<string></string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
//
|
//
|
||||||
// DemoConfigLoader.h
|
// DemoConfigLoader.h
|
||||||
// TrustHttpDNSTestDemo
|
// NewHttpDNSTestDemo
|
||||||
//
|
|
||||||
// @author Created by Claude Code on 2025-10-05
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
@interface DemoConfigLoader : NSObject
|
@interface DemoConfigLoader : NSObject
|
||||||
|
|
||||||
@property (nonatomic, assign, readonly) NSInteger accountID;
|
@property (nonatomic, copy, readonly) NSString *appId;
|
||||||
@property (nonatomic, copy, readonly) NSString *secretKey;
|
@property (nonatomic, copy, readonly) NSString *primaryServiceHost;
|
||||||
@property (nonatomic, copy, readonly) NSString *aesSecretKey;
|
@property (nonatomic, assign, readonly) NSInteger servicePort;
|
||||||
|
@property (nonatomic, copy, readonly, nullable) NSString *signSecret;
|
||||||
@property (nonatomic, assign, readonly) BOOL hasValidAccount;
|
|
||||||
|
|
||||||
+ (instancetype)shared;
|
+ (instancetype)shared;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
//
|
//
|
||||||
// DemoConfigLoader.m
|
// DemoConfigLoader.m
|
||||||
// TrustHttpDNSTestDemo
|
// NewHttpDNSTestDemo
|
||||||
//
|
|
||||||
// @author Created by Claude Code on 2025-10-05
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "DemoConfigLoader.h"
|
#import "DemoConfigLoader.h"
|
||||||
|
|
||||||
@implementation DemoConfigLoader {
|
@implementation DemoConfigLoader {
|
||||||
NSInteger _accountID;
|
NSString *_appId;
|
||||||
NSString *_secretKey;
|
NSString *_primaryServiceHost;
|
||||||
NSString *_aesSecretKey;
|
NSInteger _servicePort;
|
||||||
BOOL _hasValidAccount;
|
NSString *_signSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)shared {
|
+ (instancetype)shared {
|
||||||
@@ -30,56 +28,23 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复杂逻辑:配置加载顺序为 Bundle > 环境变量;并<EFBFBD><EFBFBD>?accountID 进行有效性校<EFBFBD><EFBFBD>?
|
|
||||||
- (void)loadConfig {
|
- (void)loadConfig {
|
||||||
_accountID = 0;
|
NSDictionary *dict = nil;
|
||||||
_secretKey = @"";
|
|
||||||
_aesSecretKey = @"";
|
|
||||||
_hasValidAccount = NO;
|
|
||||||
|
|
||||||
NSDictionary *bundleDict = nil;
|
|
||||||
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"DemoConfig" ofType:@"plist"];
|
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"DemoConfig" ofType:@"plist"];
|
||||||
if (plistPath.length > 0) {
|
if (plistPath.length > 0) {
|
||||||
bundleDict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
dict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary *env = [[NSProcessInfo processInfo] environment];
|
_appId = dict[@"appId"] ?: @"";
|
||||||
|
_primaryServiceHost = dict[@"primaryServiceHost"] ?: @"";
|
||||||
NSNumber *acc = bundleDict[@"accountID"]; // NSNumber preferred in plist
|
_servicePort = [dict[@"servicePort"] integerValue] ?: 443;
|
||||||
NSString *secret = bundleDict[@"secretKey"] ?: @"";
|
NSString *secret = dict[@"signSecret"] ?: @"";
|
||||||
NSString *aes = bundleDict[@"aesSecretKey"] ?: @"";
|
_signSecret = secret.length > 0 ? secret : nil;
|
||||||
|
|
||||||
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) {
|
- (NSString *)appId { return _appId; }
|
||||||
_accountID = [acc integerValue];
|
- (NSString *)primaryServiceHost { return _primaryServiceHost; }
|
||||||
_secretKey = secret;
|
- (NSInteger)servicePort { return _servicePort; }
|
||||||
_aesSecretKey = aes ?: @"";
|
- (NSString *)signSecret { return _signSecret; }
|
||||||
_hasValidAccount = YES;
|
|
||||||
} else {
|
|
||||||
_accountID = 0;
|
|
||||||
_secretKey = @"";
|
|
||||||
_aesSecretKey = @"";
|
|
||||||
_hasValidAccount = NO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)accountID { return _accountID; }
|
|
||||||
- (NSString *)secretKey { return _secretKey; }
|
|
||||||
- (NSString *)aesSecretKey { return _aesSecretKey; }
|
|
||||||
- (BOOL)hasValidAccount { return _hasValidAccount; }
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
//
|
//
|
||||||
// DemoHttpdnsScenario.h
|
// DemoHttpdnsScenario.h
|
||||||
// TrustHttpDNSTestDemo
|
// NewHttpDNSTestDemo
|
||||||
//
|
|
||||||
// @author Created by Claude Code on 2025-10-23
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "DemoResolveModel.h"
|
#import "DemoResolveModel.h"
|
||||||
#import "HttpdnsService.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@@ -16,10 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@interface DemoHttpdnsScenarioConfig : NSObject <NSCopying>
|
@interface DemoHttpdnsScenarioConfig : NSObject <NSCopying>
|
||||||
|
|
||||||
@property (nonatomic, copy) NSString *host;
|
@property (nonatomic, copy) NSString *host;
|
||||||
@property (nonatomic, assign) HttpdnsQueryIPType ipType;
|
@property (nonatomic, copy) NSString *queryType; // @"A", @"AAAA", @"both"
|
||||||
@property (nonatomic, assign) BOOL httpsEnabled;
|
|
||||||
@property (nonatomic, assign) BOOL persistentCacheEnabled;
|
|
||||||
@property (nonatomic, assign) BOOL reuseExpiredIPEnabled;
|
|
||||||
|
|
||||||
- (instancetype)init;
|
- (instancetype)init;
|
||||||
|
|
||||||
@@ -39,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
|
|
||||||
- (instancetype)initWithDelegate:(id<DemoHttpdnsScenarioDelegate>)delegate;
|
- (instancetype)initWithDelegate:(id<DemoHttpdnsScenarioDelegate>)delegate;
|
||||||
- (void)applyConfig:(DemoHttpdnsScenarioConfig *)config;
|
- (void)applyConfig:(DemoHttpdnsScenarioConfig *)config;
|
||||||
|
- (void)resolve;
|
||||||
- (void)resolveSyncNonBlocking;
|
- (void)resolveSyncNonBlocking;
|
||||||
- (void)resolveSync;
|
- (void)resolveSync;
|
||||||
- (NSString *)logSnapshot;
|
- (NSString *)logSnapshot;
|
||||||
@@ -46,4 +41,3 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
//
|
//
|
||||||
// DemoHttpdnsScenario.m
|
// DemoHttpdnsScenario.m
|
||||||
// NewHttpDNSTestDemo
|
// NewHttpDNSTestDemo
|
||||||
//
|
//
|
||||||
// @author Created by Claude Code on 2025-10-23
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "DemoHttpdnsScenario.h"
|
#import "DemoHttpdnsScenario.h"
|
||||||
#import "DemoConfigLoader.h"
|
#import "DemoConfigLoader.h"
|
||||||
|
#import <NewHTTPDNS/HttpdnsEdgeService.h>
|
||||||
|
|
||||||
@interface DemoHttpdnsScenarioConfig ()
|
@interface DemoHttpdnsScenarioConfig ()
|
||||||
@end
|
@end
|
||||||
@@ -15,11 +14,8 @@
|
|||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_host = @"www.new.com";
|
_host = @"demo.cloudxdr.com";
|
||||||
_ipType = HttpdnsQueryIPTypeBoth;
|
_queryType = @"A";
|
||||||
_httpsEnabled = YES;
|
|
||||||
_persistentCacheEnabled = YES;
|
|
||||||
_reuseExpiredIPEnabled = YES;
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -27,18 +23,15 @@
|
|||||||
- (id)copyWithZone:(NSZone *)zone {
|
- (id)copyWithZone:(NSZone *)zone {
|
||||||
DemoHttpdnsScenarioConfig *cfg = [[[self class] allocWithZone:zone] init];
|
DemoHttpdnsScenarioConfig *cfg = [[[self class] allocWithZone:zone] init];
|
||||||
cfg.host = self.host;
|
cfg.host = self.host;
|
||||||
cfg.ipType = self.ipType;
|
cfg.queryType = self.queryType;
|
||||||
cfg.httpsEnabled = self.httpsEnabled;
|
|
||||||
cfg.persistentCacheEnabled = self.persistentCacheEnabled;
|
|
||||||
cfg.reuseExpiredIPEnabled = self.reuseExpiredIPEnabled;
|
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface DemoHttpdnsScenario () <HttpdnsLoggerProtocol, HttpdnsTTLDelegate>
|
@interface DemoHttpdnsScenario ()
|
||||||
|
|
||||||
@property (nonatomic, strong) HttpDnsService *service;
|
@property (nonatomic, strong) HttpdnsEdgeService *service;
|
||||||
@property (nonatomic, strong) DemoHttpdnsScenarioConfig *config;
|
@property (nonatomic, strong) DemoHttpdnsScenarioConfig *config;
|
||||||
@property (nonatomic, strong) NSMutableString *logBuffer;
|
@property (nonatomic, strong) NSMutableString *logBuffer;
|
||||||
@property (nonatomic, strong) dispatch_queue_t logQueue;
|
@property (nonatomic, strong) dispatch_queue_t logQueue;
|
||||||
@@ -55,73 +48,76 @@
|
|||||||
_logBuffer = [NSMutableString string];
|
_logBuffer = [NSMutableString string];
|
||||||
_logQueue = dispatch_queue_create("com.new.httpdns.demo.log", DISPATCH_QUEUE_SERIAL);
|
_logQueue = dispatch_queue_create("com.new.httpdns.demo.log", DISPATCH_QUEUE_SERIAL);
|
||||||
[self buildService];
|
[self buildService];
|
||||||
[self applyConfig:_config];
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)buildService {
|
- (void)buildService {
|
||||||
DemoConfigLoader *cfg = [DemoConfigLoader shared];
|
DemoConfigLoader *cfg = [DemoConfigLoader shared];
|
||||||
if (cfg.hasValidAccount) {
|
self.service = [[HttpdnsEdgeService alloc]
|
||||||
if (cfg.aesSecretKey.length > 0) {
|
initWithAppId:cfg.appId
|
||||||
self.service = [[HttpDnsService alloc] initWithAccountID:cfg.accountID secretKey:cfg.secretKey aesSecretKey:cfg.aesSecretKey];
|
primaryServiceHost:cfg.primaryServiceHost
|
||||||
} else {
|
backupServiceHost:nil
|
||||||
self.service = [[HttpDnsService alloc] initWithAccountID:cfg.accountID secretKey:cfg.secretKey];
|
servicePort:cfg.servicePort
|
||||||
}
|
signSecret:cfg.signSecret];
|
||||||
} else {
|
[self appendLog:[NSString stringWithFormat:@"[init] appId=%@ host=%@:%ld", cfg.appId, cfg.primaryServiceHost, (long)cfg.servicePort]];
|
||||||
self.service = [HttpDnsService sharedInstance];
|
|
||||||
}
|
|
||||||
[self.service setLogEnabled:YES];
|
|
||||||
[self.service setNetworkingTimeoutInterval:8];
|
|
||||||
[self.service setDegradeToLocalDNSEnabled:YES];
|
|
||||||
self.service.ttlDelegate = self;
|
|
||||||
[self.service setLogHandler:self];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applyConfig:(DemoHttpdnsScenarioConfig *)config {
|
- (void)applyConfig:(DemoHttpdnsScenarioConfig *)config {
|
||||||
self.config = [config copy];
|
self.config = [config copy];
|
||||||
self.model.host = self.config.host;
|
self.model.host = self.config.host;
|
||||||
self.model.ipType = self.config.ipType;
|
}
|
||||||
[self.service setHTTPSRequestEnabled:self.config.httpsEnabled];
|
|
||||||
[self.service setPersistentCacheIPEnabled:self.config.persistentCacheEnabled];
|
- (void)resolve {
|
||||||
[self.service setReuseExpiredIPEnabled:self.config.reuseExpiredIPEnabled];
|
[self resolveSyncNonBlocking];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resolveSyncNonBlocking {
|
- (void)resolveSyncNonBlocking {
|
||||||
NSString *queryHost = [self currentHost];
|
NSString *host = self.config.host.length > 0 ? self.config.host : @"demo.cloudxdr.com";
|
||||||
HttpdnsQueryIPType ipType = self.config.ipType;
|
NSString *queryType = self.config.queryType.length > 0 ? self.config.queryType : @"A";
|
||||||
NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
|
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 {
|
- (void)resolveSync {
|
||||||
NSString *queryHost = [self currentHost];
|
[self resolveSyncNonBlocking];
|
||||||
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];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)logSnapshot {
|
- (void)handleResult:(HttpdnsEdgeResolveResult *)result
|
||||||
__block NSString *snapshot = @"";
|
host:(NSString *)host
|
||||||
dispatch_sync(self.logQueue, ^{
|
queryType:(NSString *)queryType
|
||||||
snapshot = [self.logBuffer copy];
|
start:(NSTimeInterval)startMs
|
||||||
});
|
error:(NSError *)error {
|
||||||
return snapshot;
|
NSTimeInterval elapsedMs = [[NSDate date] timeIntervalSince1970] * 1000.0 - startMs;
|
||||||
|
|
||||||
|
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]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)currentHost {
|
|
||||||
return self.config.host.length > 0 ? self.config.host : @"www.new.com";
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)handleResult:(HttpdnsResult *)result host:(NSString *)host ipType:(HttpdnsQueryIPType)ipType start:(NSTimeInterval)startMs {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
self.model.host = host;
|
self.model.host = host;
|
||||||
self.model.ipType = ipType;
|
if (result != nil) {
|
||||||
[self.model updateWithResult:result startTimeMs:startMs];
|
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<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
|
id<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
|
||||||
if (delegate != nil) {
|
if (delegate != nil) {
|
||||||
[delegate scenario:self didUpdateModel:self.model];
|
[delegate scenario:self didUpdateModel:self.model];
|
||||||
@@ -129,12 +125,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)log:(NSString *)logStr {
|
- (void)appendLog:(NSString *)msg {
|
||||||
if (logStr.length == 0) {
|
if (msg.length == 0) return;
|
||||||
return;
|
NSString *line = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], msg];
|
||||||
}
|
|
||||||
NSString *line = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], logStr];
|
|
||||||
// 使用串行队列保证日志追加与快照的一致性
|
|
||||||
dispatch_async(self.logQueue, ^{
|
dispatch_async(self.logQueue, ^{
|
||||||
[self.logBuffer appendString:line];
|
[self.logBuffer appendString:line];
|
||||||
id<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
|
id<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
|
||||||
@@ -146,8 +139,12 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int64_t)httpdnsHost:(NSString *)host ipType:(NewHttpDNS_IPType)ipType ttl:(int64_t)ttl {
|
- (NSString *)logSnapshot {
|
||||||
return ttl;
|
__block NSString *snapshot = @"";
|
||||||
|
dispatch_sync(self.logQueue, ^{
|
||||||
|
snapshot = [self.logBuffer copy];
|
||||||
|
});
|
||||||
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
//
|
//
|
||||||
// DemoResolveModel.h
|
// DemoResolveModel.h
|
||||||
// TrustHttpDNSTestDemo
|
// NewHttpDNSTestDemo
|
||||||
//
|
|
||||||
// @author Created by Claude Code on 2025-10-05
|
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import "HttpdnsRequest.h"
|
|
||||||
#import "HttpdnsResult.h"
|
|
||||||
|
|
||||||
@interface DemoResolveModel : NSObject
|
@interface DemoResolveModel : NSObject
|
||||||
|
|
||||||
@property (nonatomic, copy) NSString *host;
|
@property (nonatomic, copy) NSString *host;
|
||||||
@property (nonatomic, assign) HttpdnsQueryIPType ipType;
|
|
||||||
|
|
||||||
@property (nonatomic, copy) NSArray<NSString *> *ipv4s;
|
@property (nonatomic, copy) NSArray<NSString *> *ipv4s;
|
||||||
@property (nonatomic, copy) NSArray<NSString *> *ipv6s;
|
@property (nonatomic, copy) NSArray<NSString *> *ipv6s;
|
||||||
|
|
||||||
@property (nonatomic, assign) NSTimeInterval elapsedMs;
|
@property (nonatomic, assign) NSTimeInterval elapsedMs;
|
||||||
@property (nonatomic, assign) NSTimeInterval ttlV4;
|
@property (nonatomic, assign) NSTimeInterval ttlV4;
|
||||||
@property (nonatomic, assign) NSTimeInterval ttlV6;
|
@property (nonatomic, assign) NSTimeInterval ttlV6;
|
||||||
|
@property (nonatomic, strong, nullable) NSError *error;
|
||||||
- (void)updateWithResult:(HttpdnsResult *)result startTimeMs:(NSTimeInterval)startMs;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
//
|
//
|
||||||
// DemoResolveModel.m
|
// DemoResolveModel.m
|
||||||
// NewHttpDNSTestDemo
|
// NewHttpDNSTestDemo
|
||||||
//
|
//
|
||||||
// @author Created by Claude Code on 2025-10-05
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "DemoResolveModel.h"
|
#import "DemoResolveModel.h"
|
||||||
|
|
||||||
@@ -11,32 +9,15 @@
|
|||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_host = @"www.new.com";
|
_host = @"demodemo.cloudxdr.com";
|
||||||
_ipType = HttpdnsQueryIPTypeBoth;
|
|
||||||
_ipv4s = @[];
|
_ipv4s = @[];
|
||||||
_ipv6s = @[];
|
_ipv6s = @[];
|
||||||
_elapsedMs = 0;
|
_elapsedMs = 0;
|
||||||
_ttlV4 = 0;
|
_ttlV4 = 0;
|
||||||
_ttlV6 = 0;
|
_ttlV6 = 0;
|
||||||
|
_error = nil;
|
||||||
}
|
}
|
||||||
return self;
|
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
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
//
|
|
||||||
// DNSDemoViewController.m
|
|
||||||
// NewHttpDNSTestDemo
|
|
||||||
//
|
//
|
||||||
// @author Created by Claude Code on 2025-10-05
|
// DemoViewController.m
|
||||||
|
// NewHttpDNSTestDemo
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "DemoViewController.h"
|
#import "DemoViewController.h"
|
||||||
@@ -20,15 +18,10 @@
|
|||||||
@property (nonatomic, strong) UIStackView *stack;
|
@property (nonatomic, strong) UIStackView *stack;
|
||||||
|
|
||||||
@property (nonatomic, strong) UITextField *hostField;
|
@property (nonatomic, strong) UITextField *hostField;
|
||||||
@property (nonatomic, strong) UISegmentedControl *ipTypeSeg;
|
@property (nonatomic, strong) UISegmentedControl *queryTypeSeg;
|
||||||
|
|
||||||
@property (nonatomic, strong) UISwitch *swHTTPS;
|
|
||||||
@property (nonatomic, strong) UISwitch *swPersist;
|
|
||||||
@property (nonatomic, strong) UISwitch *swReuse;
|
|
||||||
|
|
||||||
@property (nonatomic, strong) UILabel *elapsedLabel;
|
@property (nonatomic, strong) UILabel *elapsedLabel;
|
||||||
@property (nonatomic, strong) UILabel *ttlLabel;
|
@property (nonatomic, strong) UILabel *ttlLabel;
|
||||||
|
|
||||||
@property (nonatomic, strong) UITextView *resultTextView;
|
@property (nonatomic, strong) UITextView *resultTextView;
|
||||||
|
|
||||||
@property (nonatomic, weak) DemoLogViewController *presentedLogVC;
|
@property (nonatomic, weak) DemoLogViewController *presentedLogVC;
|
||||||
@@ -46,7 +39,8 @@
|
|||||||
self.scenarioConfig = [[DemoHttpdnsScenarioConfig alloc] init];
|
self.scenarioConfig = [[DemoHttpdnsScenarioConfig alloc] init];
|
||||||
[self buildUI];
|
[self buildUI];
|
||||||
[self reloadUIFromModel:self.model];
|
[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 {
|
- (void)buildUI {
|
||||||
@@ -73,57 +67,36 @@
|
|||||||
[self.stack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-32]
|
[self.stack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-32]
|
||||||
]];
|
]];
|
||||||
|
|
||||||
|
// Host 输入
|
||||||
UIStackView *row1 = [self labeledRow:@"Host"];
|
UIStackView *row1 = [self labeledRow:@"Host"];
|
||||||
self.hostField = [[UITextField alloc] init];
|
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.text = self.scenarioConfig.host;
|
||||||
self.hostField.borderStyle = UITextBorderStyleRoundedRect;
|
self.hostField.borderStyle = UITextBorderStyleRoundedRect;
|
||||||
[row1 addArrangedSubview:self.hostField];
|
[row1 addArrangedSubview:self.hostField];
|
||||||
[self.stack addArrangedSubview:row1];
|
[self.stack addArrangedSubview:row1];
|
||||||
|
|
||||||
UIStackView *row2 = [self labeledRow:@"IP Type"];
|
// 查询类型
|
||||||
self.ipTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"IPv4", @"IPv6", @"Both"]];
|
UIStackView *row2 = [self labeledRow:@"类型"];
|
||||||
self.ipTypeSeg.selectedSegmentIndex = [self segmentIndexForIpType:self.scenarioConfig.ipType];
|
self.queryTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"A (IPv4)", @"AAAA (IPv6)", @"both"]];
|
||||||
[self.ipTypeSeg addTarget:self action:@selector(onIPTypeChanged:) forControlEvents:UIControlEventValueChanged];
|
self.queryTypeSeg.selectedSegmentIndex = 0;
|
||||||
[row2 addArrangedSubview:self.ipTypeSeg];
|
[self.queryTypeSeg addTarget:self action:@selector(onQueryTypeChanged:) forControlEvents:UIControlEventValueChanged];
|
||||||
|
[row2 addArrangedSubview:self.queryTypeSeg];
|
||||||
[self.stack addArrangedSubview:row2];
|
[self.stack addArrangedSubview:row2];
|
||||||
|
|
||||||
UIStackView *opts = [[UIStackView alloc] init];
|
// 解析按钮
|
||||||
opts.axis = UILayoutConstraintAxisHorizontal;
|
UIButton *btnResolve = [self filledButton:@"Resolve" action:@selector(onResolve)];
|
||||||
opts.alignment = UIStackViewAlignmentCenter;
|
[self.stack addArrangedSubview:btnResolve];
|
||||||
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];
|
|
||||||
|
|
||||||
|
// 耗时 / TTL
|
||||||
UIStackView *info = [self labeledRow:@"Info"];
|
UIStackView *info = [self labeledRow:@"Info"];
|
||||||
self.elapsedLabel = [self monoLabel:@"elapsed: - ms"];
|
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.elapsedLabel];
|
||||||
[info addArrangedSubview:self.ttlLabel];
|
[info addArrangedSubview:self.ttlLabel];
|
||||||
[self.stack addArrangedSubview:info];
|
[self.stack addArrangedSubview:info];
|
||||||
if (@available(iOS 11.0, *)) { [self.stack setCustomSpacing:24 afterView:info]; }
|
|
||||||
|
|
||||||
|
|
||||||
|
// 结果
|
||||||
UILabel *resultTitle = [[UILabel alloc] init];
|
UILabel *resultTitle = [[UILabel alloc] init];
|
||||||
resultTitle.text = @"结果";
|
resultTitle.text = @"结果";
|
||||||
resultTitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
|
resultTitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
|
||||||
@@ -135,18 +108,66 @@
|
|||||||
if (@available(iOS 13.0, *)) {
|
if (@available(iOS 13.0, *)) {
|
||||||
self.resultTextView.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
self.resultTextView.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||||
self.resultTextView.textColor = [UIColor labelColor];
|
self.resultTextView.textColor = [UIColor labelColor];
|
||||||
} else {
|
|
||||||
self.resultTextView.font = [UIFont systemFontOfSize:12];
|
|
||||||
self.resultTextView.textColor = [UIColor blackColor];
|
|
||||||
}
|
}
|
||||||
self.resultTextView.backgroundColor = [UIColor clearColor];
|
self.resultTextView.backgroundColor = [UIColor clearColor];
|
||||||
self.resultTextView.textContainerInset = UIEdgeInsetsMake(8, 12, 8, 12);
|
self.resultTextView.textContainerInset = UIEdgeInsetsMake(8, 12, 8, 12);
|
||||||
[self.stack addArrangedSubview:self.resultTextView];
|
[self.stack addArrangedSubview:self.resultTextView];
|
||||||
[self.resultTextView.heightAnchor constraintEqualToConstant:320].active = YES;
|
[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 *)labeledRow:(NSString *)title {
|
||||||
UIStackView *row = [[UIStackView alloc] init];
|
UIStackView *row = [[UIStackView alloc] init];
|
||||||
row.axis = UILayoutConstraintAxisHorizontal;
|
row.axis = UILayoutConstraintAxisHorizontal;
|
||||||
@@ -164,8 +185,6 @@
|
|||||||
l.text = text;
|
l.text = text;
|
||||||
if (@available(iOS 13.0, *)) {
|
if (@available(iOS 13.0, *)) {
|
||||||
l.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
l.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||||
} else {
|
|
||||||
l.font = [UIFont systemFontOfSize:12];
|
|
||||||
}
|
}
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
@@ -181,131 +200,6 @@
|
|||||||
return b;
|
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
|
#pragma mark - DemoHttpdnsScenarioDelegate
|
||||||
|
|
||||||
- (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model {
|
- (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model {
|
||||||
|
|||||||
Reference in New Issue
Block a user