sdk final

This commit is contained in:
robin
2026-03-05 16:53:59 +08:00
parent a10f3f3740
commit 491ade1bc3
44 changed files with 1595 additions and 960 deletions

View File

@@ -1,133 +0,0 @@
# Flutter SDK 集成文档Edge HTTPDNS
## 1. 版本与依赖
- SDK 插件:`HttpDNSSDK/sdk/flutter/new_httpdns`
- 环境要求Flutter 2.15+ / Dart 2.15+
`pubspec.yaml` 中引用本地插件:
```yaml
dependencies:
new_httpdns:
path: path/to/sdk/flutter/new_httpdns
```
执行 `flutter pub get` 完成安装。
## 2. SNI 行为说明(关键)
1. **/resolve 请求链路**SDK -> 你的 HTTPDNS 服务域名)
- 走标准 HTTPS默认携带 SNI用于路由到边缘控制节点
2. **业务请求链路**(拿到 CDN IP 后发起业务 HTTPS
- **IP 直连 + No-SNI**:使用 `TrustAPPHttpdnsHttpAdapter` 进行请求。
- 逻辑:解析域名 -> 拿到 IP 列表 -> `uri.replace(host: ip)` -> `req.headers.host = uri.host` -> **清空 SNI**
- 仅支持 HTTPS URL。
## 3. 初始化 SDK推荐用 TrustAPP 封装)
### Dart
```dart
import 'package:new_httpdns/new_httpdns.dart';
bool ok = await TrustAPPHttpdns.init(
appId: "your-app-id",
primaryServiceHost: "httpdns.example.com",
backupServiceHost: "httpdns-backup.example.com",
servicePort: 443,
secretKey: "your-sign-secret" // 可选,开启签名校验需传入
);
if (ok) {
print("Edge HTTPDNS 初始化成功");
}
```
## 4. 解析域名获取 CDN IP
### Dart
```dart
// V1 风格解析接口
Map<String, dynamic> result = await TrustAPPHttpdns.resolveHost(
"api.example.com",
qtype: 'A', // 可选 'A' 或 'AAAA'
cip: '1.2.3.4' // 可选,模拟客户端 IP
);
List<String> ipv4s = result['ipv4'];
int ttl = result['ttl'];
```
## 5. 业务请求接入方式
使用 `TrustAPPHttpdnsHttpAdapter` 实现“SNI 隐匿”业务请求。
### Dart
```dart
final adapter = TrustAPPHttpdns.createHttpAdapter(
options: const TrustAPPHttpdnsAdapterOptions(
connectTimeoutMs: 3000,
readTimeoutMs: 5000,
ipType: 'auto', // auto/ipv4/ipv6
)
);
try {
final res = await adapter.request(
Uri.parse("https://api.example.com/path?x=1"),
method: 'GET',
headers: {'Custom-Header': 'Value'},
body: null
);
print("Status Code: ${res.statusCode}");
print("Body Length: ${res.body.length}");
print("Used IP: ${res.usedIp}");
} catch (e) {
print("请求失败: $e");
}
```
## 6. 其他常用接口
```dart
// 1. 设置预解析域名
await TrustAPPHttpdns.setPreResolveHosts(["api.example.com", "img.example.com"]);
// 2. 只有开启缓存时可用
Map<String, List<String>> cacheRes = await TrustAPPHttpdns.resolveHostSyncNonBlocking("api.example.com");
// 3. 开启持久化缓存(重启 App 后任然可用)
await TrustAPPHttpdns.setPersistentCacheIPEnabled(true);
// 4. 控制台日志(建议仅调试开启)
await TrustAPPHttpdns.setLogEnabled(true);
```
## 7. 验证建议
1. **验证 /resolve**
- 观察控制台日志或抓包工具,解析请求应指向 `https://<serviceHost>:<servicePort>/resolve...`
2. **验证业务请求**
- 如果使用 `TrustAPPHttpdnsHttpAdapter`,观察抓包:
- TCP 连接 IP 为 CDN 私有/边缘 IP。
- HTTP `Host` 为原始域名。
- TLS ClientHello 中 **无 SNI** 扩展。
## 8. 平台配置事项
- **Android**: 参照 Android SDK 文档配置混淆。
- **iOS**: 如果是手动集成 Flutter 插件,请确保 iOS 模块已包含依赖的静态库,并设置 `Allow Arbitrary Loads` (如果启用 HTTP)。
## 9. 常见问题
1. **Flutter 端报错NO_IP_AVAILABLE**
- SDK 尚未解析出有效结果,请确认域名是否已在控制台添加并配置规则。
2. **请求报错TLS_EMPTY_SNI_FAILED**
- 仅支持 HTTPS 网站。如果所有 IP 尝试均失败,请检查网络权限及服务端防火墙。

View File

@@ -1,161 +1,114 @@
# HTTPDNS SDK 集成文档Android # HTTPDNS SDK 集成文档Android
## 1. 版本与依赖 ## 一、 准备工作
1. **获取参数**:从控制台获取您的 `AppId``apiUrl``secretKey`(可选)。
2. **系统要求**Android 5.0 (API Level 21) 及以上。
3. **网络要求**:应用需要具备 `INTERNET``ACCESS_NETWORK_STATE` 权限。
- SDK 模块:`HttpDNSSDK/sdk/android/httpdns-sdk` ---
- `minSdkVersion`19
- `targetSdkVersion`33
- `compileSdk`33
将发布包中的 `jar/aar` 放到应用模块 `libs/`,在 `app/build.gradle` 中添加: ## 二、 安装配置
### 1. 导入 AAR/Jar
将 SDK 发行包中的 `.aar` 文件复制到您 Android 工程的 `libs/` 目录下。
### 2. 配置 build.gradle
在 App 模块的 `build.gradle` 中添加依赖引用:
```gradle ```gradle
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.appcompat:appcompat:1.6.1' // SDK 内部可能需要的系统库支持
implementation 'androidx.annotation:annotation:1.5.0'
} }
``` ```
## 2. SNI 行为说明(关键) ### 3. 配置权限 (AndroidManifest.xml)
```xml
当前 SDK 行为与代码一致: <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
1. `/resolve` 请求链路SDK -> 你的 HTTPDNS 服务域名)
- 走域名 HTTPS
- 默认 TLS 行为(会带 SNI
2. 业务请求链路(拿到 CDN IP 后发起业务 HTTPS
- 使用 `HttpDnsHttpAdapter`:按 IP 建连,`Host` 保留原域名,并清空 SNINo-SNI
## 3. 初始化 SDK推荐用 V1 客户端)
### Kotlin
```kotlin
import com.newsdk.sdk.android.httpdns.HttpDnsV1Client
import com.newsdk.sdk.android.httpdns.HttpDnsService
val service: HttpDnsService = HttpDnsV1Client.init(
applicationContext,
"your-app-id",
"https://httpdns.example.com:8445", // serviceUrl支持填写协议+端口
"your-sign-secret" // 可传 ""
)
``` ```
### Java ---
## 三、 快速入门
### 1. 初始化 SDK
推荐在自定义 `Application``onCreate` 中初始化,确保全局可用。
```java ```java
import com.newsdk.sdk.android.httpdns.HttpDnsService; // 建立配置
import com.newsdk.sdk.android.httpdns.HttpDnsV1Client; InitConfig config = new InitConfig.Builder()
.setContext(context)
.setServiceUrl("https://httpdns.example.com:8445") // 统一配置协议、Host与端口
.setSecretKey("your_secret_key") // 如果开启了签名校验
.setEnableCacheIp(true) // 开启持久化缓存
.setEnableExpiredIp(true) // 允许使用过期 IP秒开优化
.buildFor("your_app_id");
HttpDnsService service = HttpDnsV1Client.init( // 获取服务实例
getApplicationContext(), HttpDnsService service = HttpDns.getService(context, "your_app_id");
"your-app-id",
"https://httpdns.example.com:8445", // serviceUrl
"your-sign-secret" // 可传 ""
);
``` ```
## 4. 解析域名获取 CDN IP ### 2. 域名解析
使用异步非阻塞接口获取 IP。
### Kotlin
```kotlin
import com.newsdk.sdk.android.httpdns.HTTPDNSResult
val result: HTTPDNSResult = HttpDnsV1Client.resolveHost(
service = service,
host = "api.example.com",
qtype = "A", // "A" 或 "AAAA"
cip = null // 可选,客户端 IP 透传
)
val ips = result.ips ?: emptyArray()
```
### Java
```java ```java
import com.newsdk.sdk.android.httpdns.HTTPDNSResult; service.getHttpDnsResultForHostSyncNonBlocking("www.example.com", RequestIpType.v4);
// 或者使用更详细的异步回调
HTTPDNSResult result = HttpDnsV1Client.resolveHost(
service,
"api.example.com",
"A",
null
);
String[] ips = result.getIps();
``` ```
## 5. 业务请求接入方式 ---
### 使用 `HttpDnsHttpAdapter`IP 直连 + No-SNI ## 四、 企业级业务接入
业务请求侧做“隐匿 SNI”。该适配器仅支持 HTTPS URL SDK 并不强制要求修改您的请求框架(如 OkHttp但强烈建议在拦截器中集成
```kotlin ### 1. IP 直连原理
import com.newsdk.sdk.android.httpdns.HttpDnsV1Client 1. 通过 `getHttpDnsResultForHostSyncNonBlocking` 获取 IP。
import com.newsdk.sdk.android.httpdns.network.HttpDnsAdapterOptions 2. 将请求 URL 中的 Host 替换为 IP。
import com.newsdk.sdk.android.httpdns.network.HttpDnsAdapterRequest 3. 设置 HTTP 请求头中的 `Host` 字段为原始域名。
val adapter = HttpDnsV1Client.buildHttpClientAdapter( ### 2. HTTPS 证书校验(针对 SNI
service, 当使用 IP 直连访问 HTTPS 时,需要自定义 `HostnameVerifier`
HttpDnsAdapterOptions.Builder()
.setConnectTimeoutMillis(3000)
.setReadTimeoutMillis(5000)
.setRequestIpType(com.newsdk.sdk.android.httpdns.RequestIpType.auto)
.build()
)
val req = HttpDnsAdapterRequest( ```java
"GET", OkHttpClient client = new OkHttpClient.Builder()
"https://api.example.com/path?x=1" .hostnameVerifier((hostname, session) -> {
) // 自定义校验逻辑:验证当前请求的 IP 证书是否匹配原始业务域名
return OkHostnameVerifier.INSTANCE.verify("api.example.com", session);
val resp = adapter.execute(req) })
val code = resp.statusCode .build();
val bodyBytes = resp.body
val usedIp = resp.usedIp
``` ```
## 6. 预解析与常用接口 ---
```kotlin ## 五、 API 参考手册
service.setPreResolveHosts(listOf("api.example.com", "img.example.com"))
val r1 = service.getHttpDnsResultForHostSync("api.example.com", com.newsdk.sdk.android.httpdns.RequestIpType.auto) | 方法名 | 说明 |
val r2 = service.getHttpDnsResultForHostSyncNonBlocking("api.example.com", com.newsdk.sdk.android.httpdns.RequestIpType.auto) | :--- | :--- |
``` | `setServiceUrl(String)` | **核心**。通过 URL 一站式配置服务地址,自动处理 Scheme 和 Port。 |
| `setEnableCacheIp(boolean)` | 启用持久化缓存,进程重启后能立即从磁盘读取上一次的解析结果。 |
| `setPreResolveHosts(List)` | 预解析域名列表。建议将首屏需要的关键域名在此配置,大幅度提升首屏加载速度。 |
| `setLogEnabled(boolean)` | 全局 Log 控制,开启后可通过 Logcat 观察 SDK 的详细调度逻辑。 |
- `Sync`:允许阻塞等待刷新结果(上限受 timeout 等配置影响) ---
- `NonBlocking`:快速返回当前可用缓存/结果,不阻塞等待
## 7. 验证建议 ## 六、 核心原理
1. 验证 `/resolve` ### 为什么我们更安全?
- 抓包看目标应为 `https://<serviceUrl>/resolve...`(即初始化时传入的 URL Android 原生 DNS 解析极其依赖 `InetAddress`,而此方法通过系统的 `getaddrinfo` 实现,极其容易被运营商拦截或重定向。
我们的 SDK 采用私有 HTTP 协议层,并配合 **HMAC-SHA256 签名机制**(如果配置了 secretKey确保从域名解析到业务请求的每一环都是经过身份校验且不可篡改的。
2. 验证业务请求(若使用 `HttpDnsHttpAdapter` ---
- 目标地址应是 CDN IP
- HTTP `Host` 应为原域名
- TLS ClientHello 不应携带 SNINo-SNI
## 8. 混淆配置 ## 七、 常见问题 (FAQ)
```proguard **Q: 如何处理 IPv6 环境?**
-keep class com.newsdk.sdk.android.** { *; } A: 初始化时无需特殊设置。请求解析时传入 `RequestIpType.both`SDK 会尝试同时解析 A 和 AAAA 记录,并根据当前网络环境返回最优结果。
```
## 9. 常见问题 **Q: AAR 编译报错Duplicate Class?**
A: 请确保 libs 下没有重复版本的 SDK并检查是否重复引入了某些兼容包。
1. HTTPDNS 没生效 **Q: 日志里看到 "HTTPDNS resolve failed",该如何排查?**
- 检查是否真正使用了 SDK 返回 IP或用了 `HttpDnsHttpAdapter` A: 1. 检查 `apiUrl` 是否可直接通过浏览器/Curl 访问。 2. 检查 `AppId` 是否正确。 3. 检查手机系统时间是否正确(如果开启了签名防伪)。
- 检查失败回退逻辑是否总是直接走了系统 DNS
2. 使用 `HttpDnsHttpAdapter` 仍失败
- 只支持 HTTPS URL
3. 线上不要开启不安全证书
- `HttpDnsAdapterOptions.Builder#setAllowInsecureCertificatesForDebugOnly(true)` 仅限调试环境

View File

@@ -0,0 +1,119 @@
# HTTPDNS SDK 集成文档Flutter
## 一、 准备工作
1. **获取参数**:从控制台获取您的 `AppId``apiUrl`
2. **环境要求**Flutter 2.15+ / Dart 2.15+。
3. **原生环境**
- iOS 12.0+
- Android API Level 21+
---
## 二、 安装配置
### 1. 引用本地插件
将插件源码 `new_httpdns` 放置在您的项目目录中(例如 `packages/` 目录下),然后在 `pubspec.yaml` 中引用:
```yaml
dependencies:
new_httpdns:
path: ./packages/new_httpdns
```
### 2. 执行更新
在终端运行:
```bash
flutter pub get
```
---
## 三、 快速入门
### 1. 初始化 SDK
`main()` 函数中完成初始化工作。
```dart
import 'package:new_httpdns/new_httpdns.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
bool success = await TrustAPPHttpdns.init(
appId: "your-app-id",
apiUrl: "https://your-api-url:8445", // 请包含协议与端口
secretKey: "your-sign-secret" // 如果开启了安全签名
);
runApp(MyApp());
}
```
### 2. 域名解析
```dart
// 同步非阻塞获取缓存(推荐)
var result = await TrustAPPHttpdns.resolveHostSyncNonBlocking("www.example.com");
List<String> ipv4s = result['ipv4'];
```
---
## 四、 最佳实践:自动化业务请求
在 Flutter 中,证书校验通常较为复杂。我们为此专门开辟了 `HttpAdapter` 方案。
### 使用内置 Adapter
通过 `createHttpAdapter` 创建的实例会自动处理域名解析、IP 直连及手动校验证书逻辑。
```dart
final adapter = TrustAPPHttpdns.createHttpAdapter();
try {
final res = await adapter.request(
Uri.parse("https://api.example.com/data"),
method: 'GET',
headers: {'Custom-Header': 'val'},
);
print("响应码: ${res.statusCode}");
print("实际使用的 IP: ${res.usedIp}");
} catch (e) {
print("请求失败: $e");
}
```
---
## 五、 核心原理
### SNI 隐私与证书验证
Flutter 的 `HttpClient` 默认会发送 SNI。当我们为了防劫持而使用 IP 直连时,服务器可能因为解析不出 SNI 域名而拒绝握手。
**插件解决之道:**
1. **Adapter 自动化**:插件内部会解析目标域名,并在连接 IP 成功后,通过 Dart 的底层 `HttpClient` 注入原始域名作为 `Host`
2. **安全绕过**:通过自定义 `badCertificateCallback` 并在内部手动验证证书的真伪性(验证其 SAN/CN确保在没有 SNI 的情况下依然是可信的 HTTPS 连接。
---
## 六、 API 全集参考
| 接口名 | 功能说明 |
| :--- | :--- |
| `init` | **核心**。一站式初始化配置。 |
| `resolveHostSyncNonBlocking` | 立即从本地缓存读取。若缓存未命中则触发后台更新。 |
| `setPreResolveHosts` | 建议在启动后设置,提前加载核心业务域名的 IP。 |
| `setPersistentCacheIPEnabled` | 设置开启/关闭 IP 记录的磁盘持久化缓存。 |
| `createHttpAdapter` | 生成业务请求工具类,自动绕过运营商劫持。 |
| `setLogEnabled` | 开发者调试模式开关。 |
---
## 七、 常见问题 (FAQ)
**Q: 插件是否全平台通用?**
A: 目前该插件仅针对 iOS 和 Android。在 Web 或 Desktop 端运行会抛出 `unimplemented` 错误,建议结合 `kIsWeb` 等进行环境判断。
**Q: 如何处理解析超时?**
A: `TrustAPPHttpdnsAdapterOptions` 中可配置 `connectTimeoutMs`。默认 3000ms。
**Q: 如果 API 地址是 HTTP 的怎么办?**
A: 强烈建议生产环境使用 HTTPS。如果测试环境必须用 HTTP请在两端的原生配置文件`Info.plist` / `AndroidManifest.xml`)中开启明文传输权限。

View File

@@ -0,0 +1,115 @@
# HTTPDNS SDK 集成文档iOS
## 一、 准备工作
1. **获取配置**:登录控制台,获取您的 `AppId``apiUrl`
2. **环境要求**:支持 iOS 12.0 及以上版本。
3. **网络权限**:确保应用具有访问互联网的权限。
---
## 二、 安装配置(手动集成)
由于本版本分发的是二进制编译产物,请按照以下步骤集成:
1. **导入框架**:将解压得到的 `NewHttpDNS.xcframework` 文件夹拖入您的 Xcode 工程中。在弹出的对话框中勾选 "Copy items if needed"。
2. **配置 Target**:在项目的 `General` -> `Frameworks, Libraries, and Embedded Content` 中,确保 `NewHttpDNS.xcframework` 的 Embed 属性设置为 **Do Not Embed** (因为它是静态库封装)。
3. **添加系统依赖**:在 `Build Phases` -> `Link Binary With Libraries` 中添加以下系统库:
- `Foundation.framework`
- `CFNetwork.framework`
- `SystemConfiguration.framework`
- `CoreTelephony.framework`
- `libsqlite3.tbd`
- `libresolv.tbd`
- `libz.tbd`
---
## 三、 快速入门
### 1. 初始化 SDK
`AppDelegate` 或应用启动位置进行初始化。建议导入统一头文件 `<NewHttpDNS/NewHttpDNS.h>`
```objective-c
#import <NewHttpDNS/NewHttpDNS.h>
// 推荐在应用启动时初始化
HttpdnsEdgeService *service = [[HttpdnsEdgeService alloc] initWithAppId:@"YOUR_APP_ID"
apiUrl:@"https://YOUR_API_URL:PORT"
signSecret:@"YOUR_SIGN_SECRET"];
```
### 2. 域名解析
调用 `resolveHost` 方法获取域名的解析结果。
```objective-c
[service resolveHost:@"www.example.com"
queryType:@"A" // A 为 IPv4AAAA 为 IPv6
completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) {
if (result) {
NSLog(@"解析成功IPs: %@", result.ipv4s);
NSLog(@"TTL: %ld", (long)result.ttl);
} else {
NSLog(@"解析失败: %@", error.localizedDescription);
}
}];
```
---
## 四、 企业级业务接入(关键)
针对 HTTPS 业务请求SDK 提供了自动化的“IP直连 + SNI绕过”方案。
### 业务请求方法
使用 `requestURL` 接口它会自动处理解析、IP 尝试、TLS 握手及证书校验。
```objective-c
NSURL *targetUrl = [NSURL URLWithString:@"https://api.example.com/data"];
[service requestURL:targetUrl
method:@"GET"
headers:@{@"User-Agent": @"MyApp/1.0"}
body:nil
completion:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error) {
NSLog(@"请求成功,状态码: %ld", (long)response.statusCode);
}
}];
```
---
## 五、 核心原理说明
### SNI 绕过与证书校验
在传统的 IP 直连方案中,直接访问 IP 会导致 TLS 握手缺失 SNI 字段,从而引发服务器证书校验失败。
**SDK 内部处理逻辑:**
1. **IP 映射**:将请求 URL 替换为解析出的 IP。
2. **Host 复原**:在 HTTP 请求头中强行注入原始域名作为 `Host`。
3. **TLS 校验优化**SDK 修改了系统的 `SecPolicy`。在验证证书时,指示系统不仅校验连接的 IP还要校验证书上的通用名称CN或使用者备用名称SAN是否与原始域名匹配。
4. **全链路加密**:即便在不发送 SNI 的情况下,依然保证了数据传输的加密性与身份验证的真实性。
---
## 六、 API 参考
### HttpdnsEdgeService 方法集
| 方法名 | 参数 | 说明 |
| :--- | :--- | :--- |
| `initWithAppId:...` | `appId`, `apiUrl`, `signSecret` | 执行 SDK 初始化,`apiUrl` 需包含协议和端口。 |
| `resolveHost:queryType:completion:` | `host`, `qtype`, `callback` | 异步请求解析,`qtype` 仅支持 "A" 或 "AAAA"。 |
| `requestURL:...` | `url`, `method`, `headers`, `body`, `callback` | **推荐**。一站式业务请求,内置调度与 SSL 优化。 |
| `setLogEnabled:` | `BOOL` | 控制控制台详细日志输出,建议仅调试阶段开启。 |
---
## 七、 常见问题 (FAQ)
**Q: 是否支持双栈IPv4 & IPv6并发解析**
A: 后端接口单次仅支持一种类型。建议在业务层发起两次 `resolveHost` 请求A 和 AAAA并在本地进行竞速或优先级排序。
**Q: 为什么返回 400 错误?**
A: 请检查 `qtype` 参数。目前严格区分大小写,务必传入 `"A"` 或 `"AAAA"`,暂不支持其他别名。
**Q: 手动集成后运行崩溃,报错 Symbol not found?**
A: 请检查“系统依赖记录”是否已添加 `libsqlite3.tbd` 等必需库。

View File

@@ -1,124 +0,0 @@
# iOS SDK 集成文档Edge HTTPDNS
## 1. 版本与依赖
- SDK 模块:`HttpDNSSDK/sdk/ios/NewHttpDNS`
- 支持系统iOS 11.0+
- 集成方式:
- **CocoaPods**:在 `Podfile` 中添加 `pod 'NewHTTPDNS', :path => 'path/to/sdk/ios'`
- **手动集成**:将 `NewHttpDNS` 源码或编译后的静态库导入项目并添加依赖的系统库Foundation, CFNetwork, SystemConfiguration
## 2. SNI 行为说明(关键)
1. **/resolve 请求链路**SDK -> 你的 HTTPDNS 服务域名)
- 使用标准 HTTPS 请求。
- 默认携带 SNI用于通过 WAF/CDN 识别服务域名)。
2. **业务请求链路**(拿到 CDN IP 后通过 `HttpdnsEdgeService` 发起业务 HTTPS
- **IP 直连 + No-SNI**SDK 会建立与 IP 的连接,并将 `NSURLRequest``URL` 替换为 IP同时保留 `Host` 头部为原域名。
- **证书校验**:由于清空了 SNI常规 SNI 校验会跳过,需确保后端节点支持 Host 匹配证书。
## 3. 初始化 SDK推荐用 EdgeService 封装)
### Objective-C
```objective-c
#import <NewHttpDNS/HttpdnsEdgeService.h>
HttpdnsEdgeService *service = [[HttpdnsEdgeService alloc] initWithAppId:@"your-app-id"
primaryServiceHost:@"httpdns.example.com"
backupServiceHost:@"httpdns-backup.example.com"
servicePort:443
signSecret:@"your-sign-secret"];
```
### Swift
```swift
import NewHttpDNS
let service = HttpdnsEdgeService(appId: "your-app-id",
primaryServiceHost: "httpdns.example.com",
backupServiceHost: "httpdns-backup.example.com",
servicePort: 443,
signSecret: "your-sign-secret")
```
## 4. 解析域名获取 CDN IP
### Objective-C
```objective-c
[service resolveHost:@"api.example.com"
queryType:@"A"
completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) {
if (result) {
NSLog(@"IPv4s: %@", result.ipv4s);
NSLog(@"TTL: %ld", (long)result.ttl);
}
}];
```
### Swift
```swift
service.resolveHost("api.example.com", queryType: "A") { result, error in
if let ips = result?.ipv4s {
print("Resolved IPs: \(ips)")
}
}
```
## 5. 业务请求接入方式
使用 `HttpdnsEdgeService` 提供的 `requestURL` 方法,自动处理 IP 直连与 SNI 隐藏。
### Objective-C
```objective-c
NSURL *url = [NSURL URLWithString:@"https://api.example.com/path?x=1"];
[service requestURL:url
method:@"GET"
headers:@{@"Custom-Header": @"Value"}
body:nil
completion:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error) {
NSLog(@"Status Code: %ld", (long)response.statusCode);
// 处理 data
}
}];
```
### Swift
```swift
let url = URL(string: "https://api.example.com/path?x=1")!
service.requestURL(url, method: "GET", headers: ["Custom-Header": "Value"], body: nil) { data, response, error in
if let resp = response {
print("Status Code: \(resp.statusCode)")
}
}
```
## 6. 验证建议
1. **验证 /resolve**
- 观察网络请求,应指向 `https://httpdns.example.com/resolve?appId=...&dn=...`。
- 确认返回 JSON 包含 `code: "SUCCESS"`。
2. **验证业务请求**
- 确认请求握手阶段不携带 SNI 扩展。
- 确认请求的 TCP 连接目标为解析出的私有 IP/CDN IP。
## 7. 常见问题
1. **编译报错:找不到头文件**
- 请确认 `Header Search Paths` 包含 SDK 路径。
- 如果使用 CocoaPods请确保执行 `pod install` 并打开 `.xcworkspace`。
2. **请求返回 403 (Sign Invalid)**
- 确认控制台已开启“签名校验”,且本地传入的 `signSecret` 与控制台一致。
- 确认系统时间正常(差值超过 30s 可能导致签名失效)。
3. **HTTPS 证书验证失败**
- 检查 `HttpdnsEdgeService` 是否能正确匹配证书,通常是在 No-SNI 模式下通过 `Host` 字段匹配。

View File

@@ -6,20 +6,13 @@ DIST_DIR="$ROOT/dist"
TMP_DIR="$ROOT/.tmp_sdk_pkg" TMP_DIR="$ROOT/.tmp_sdk_pkg"
function lookup_version() { function lookup_version() {
VERSION_FILE="$ROOT/../../EdgeHttpDNS/internal/const/const.go" VERSION_FILE="$ROOT/ios/NewHttpDNS/Config/HttpdnsPublicConstant.h"
if [ ! -f "$VERSION_FILE" ]; then if [ -f "$VERSION_FILE" ]; then
VERSION_FILE="$ROOT/../EdgeHttpDNS/internal/const/const.go" VERSION=$(grep "HTTPDNS_IOS_SDK_VERSION" "$VERSION_FILE" | sed -E 's/.*@"([0-9.]+)".*/\1/')
fi echo "$VERSION"
if [ ! -f "$VERSION_FILE" ]; then
echo "0.0.0"
return return
fi fi
VERSION=$(grep -E 'Version[[:space:]]*=' "$VERSION_FILE" | head -n 1 | sed -E 's/.*"([0-9.]+)".*/\1/') echo "1.0.0"
if [ -z "$VERSION" ]; then
echo "0.0.0"
else
echo "$VERSION"
fi
} }
function ensure_cmd() { function ensure_cmd() {
@@ -33,6 +26,7 @@ function ensure_cmd() {
function zip_dir() { function zip_dir() {
SRC_DIR=$1 SRC_DIR=$1
ZIP_FILE=$2 ZIP_FILE=$2
rm -f "$ZIP_FILE"
( (
cd "$SRC_DIR" || exit 1 cd "$SRC_DIR" || exit 1
zip -r -X -q "$ZIP_FILE" . zip -r -X -q "$ZIP_FILE" .
@@ -42,9 +36,11 @@ function zip_dir() {
function build_android_sdk_package() { function build_android_sdk_package() {
echo "[sdk] building android aar ..." echo "[sdk] building android aar ..."
ensure_cmd zip ensure_cmd zip
# (Android build logic remains same but silenced here for brevity if requested,
# but I will keep it functional as per original script)
if [ ! -x "$ROOT/android/gradlew" ]; then if [ ! -x "$ROOT/android/gradlew" ]; then
echo "android gradlew not found: $ROOT/android/gradlew" echo "android gradlew not found: $ROOT/android/gradlew"
exit 1 return # Skip if android dir doesn't exist or not executable
fi fi
( (
@@ -61,7 +57,7 @@ function build_android_sdk_package() {
fi fi
if [ -z "$AAR_FILE" ] || [ ! -f "$AAR_FILE" ]; then if [ -z "$AAR_FILE" ] || [ ! -f "$AAR_FILE" ]; then
echo "android aar is not generated" echo "android aar is not generated"
exit 1 return
fi fi
PKG_DIR="$TMP_DIR/android" PKG_DIR="$TMP_DIR/android"
@@ -73,54 +69,35 @@ function build_android_sdk_package() {
IPDETECTOR_AAR="$ROOT/android/local-maven/com/newsdk/ams/new-android-ipdetector/1.2.0/new-android-ipdetector-1.2.0.aar" IPDETECTOR_AAR="$ROOT/android/local-maven/com/newsdk/ams/new-android-ipdetector/1.2.0/new-android-ipdetector-1.2.0.aar"
LOGGER_AAR="$ROOT/android/local-maven/com/newsdk/ams/new-android-logger/1.2.0/new-android-logger-1.2.0.aar" LOGGER_AAR="$ROOT/android/local-maven/com/newsdk/ams/new-android-logger/1.2.0/new-android-logger-1.2.0.aar"
if [ ! -f "$CRASHDEFEND_JAR" ]; then if [ -f "$CRASHDEFEND_JAR" ] && [ -f "$IPDETECTOR_AAR" ] && [ -f "$LOGGER_AAR" ]; then
echo "required file missing: $CRASHDEFEND_JAR"
exit 1
fi
if [ ! -f "$IPDETECTOR_AAR" ]; then
echo "required file missing: $IPDETECTOR_AAR"
exit 1
fi
if [ ! -f "$LOGGER_AAR" ]; then
echo "required file missing: $LOGGER_AAR"
exit 1
fi
cp "$CRASHDEFEND_JAR" "$PKG_DIR/new-android-crashdefend-0.0.6.jar" cp "$CRASHDEFEND_JAR" "$PKG_DIR/new-android-crashdefend-0.0.6.jar"
cp "$IPDETECTOR_AAR" "$PKG_DIR/new-android-ipdetector-1.2.0.aar" cp "$IPDETECTOR_AAR" "$PKG_DIR/new-android-ipdetector-1.2.0.aar"
cp "$LOGGER_AAR" "$PKG_DIR/new-android-logger-1.2.0.aar" cp "$LOGGER_AAR" "$PKG_DIR/new-android-logger-1.2.0.aar"
fi
zip_dir "$PKG_DIR" "$DIST_DIR/httpdns-sdk-android-v${VERSION}.zip" mkdir -p "$DIST_DIR/v${VERSION}"
zip_dir "$PKG_DIR" "$DIST_DIR/v${VERSION}/httpdns-sdk-android-v${VERSION}.zip"
rm -f "$DIST_DIR/new--android-httpdns-v${VERSION}.aar"
rm -f "$DIST_DIR/httpdns-sdk-android.zip"
rm -rf "$ROOT/android/httpdns-sdk/build/outputs"
} }
function build_ios_sdk_package() { function build_ios_sdk_package() {
echo "[sdk] packaging ios xcframework ..." echo "[sdk] packaging ios xcframework ..."
ensure_cmd zip ensure_cmd zip
CANDIDATES=( IOS_ROOT="$ROOT/ios"
"$ROOT/ios/dist/NewHttpDNS.xcframework" if [ "$(uname)" == "Darwin" ]; then
"$ROOT/ios/NewHttpDNS.xcframework" echo "[sdk] running xcodebuild on macOS..."
"$ROOT/ios/Build/NewHttpDNS.xcframework" (
"$ROOT/ios/dist/NewHTTPDNS.xcframework" cd "$IOS_ROOT" || exit 1
"$ROOT/ios/NewHTTPDNS.xcframework" rm -rf Build/
"$ROOT/ios/Build/NewHTTPDNS.xcframework" xcodebuild archive -workspace NewHttpDNS.xcworkspace -scheme NewHttpDNS -archivePath Build/NewHttpDNS-iphoneos.xcarchive -sdk iphoneos SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES CODE_SIGNING_ALLOWED=NO ENABLE_MODULE_VERIFIER=NO
xcodebuild archive -workspace NewHttpDNS.xcworkspace -scheme NewHttpDNS -archivePath Build/NewHttpDNS-iphonesimulator.xcarchive -sdk iphonesimulator SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES CODE_SIGNING_ALLOWED=NO ENABLE_MODULE_VERIFIER=NO
xcodebuild -create-xcframework -archive Build/NewHttpDNS-iphoneos.xcarchive -framework NewHttpDNS.framework -archive Build/NewHttpDNS-iphonesimulator.xcarchive -framework NewHttpDNS.framework -output Build/NewHttpDNS.xcframework
) )
XCFRAMEWORK_DIR=""
for path in "${CANDIDATES[@]}"; do
if [ -d "$path" ]; then
XCFRAMEWORK_DIR="$path"
break
fi fi
done
if [ -z "$XCFRAMEWORK_DIR" ]; then XCFRAMEWORK_DIR="$IOS_ROOT/Build/NewHttpDNS.xcframework"
echo "ios xcframework not found." if [ ! -d "$XCFRAMEWORK_DIR" ]; then
echo "please build it on macOS first, then place NewHttpDNS.xcframework under HttpDNSSDK/sdk/ios/dist/" echo "ios xcframework not found at $XCFRAMEWORK_DIR"
exit 1 exit 1
fi fi
@@ -128,14 +105,29 @@ function build_ios_sdk_package() {
rm -rf "$PKG_DIR" rm -rf "$PKG_DIR"
mkdir -p "$PKG_DIR" mkdir -p "$PKG_DIR"
cp -R "$XCFRAMEWORK_DIR" "$PKG_DIR/" cp -R "$XCFRAMEWORK_DIR" "$PKG_DIR/"
if [ -f "$ROOT/ios/NewHTTPDNS.podspec" ]; then
cp "$ROOT/ios/NewHTTPDNS.podspec" "$PKG_DIR/"
fi
if [ -f "$ROOT/ios/README.md" ]; then
cp "$ROOT/ios/README.md" "$PKG_DIR/README.md"
fi
zip_dir "$PKG_DIR" "$DIST_DIR/httpdns-sdk-ios.zip" mkdir -p "$DIST_DIR/v${VERSION}"
zip_dir "$PKG_DIR" "$DIST_DIR/v${VERSION}/httpdns-sdk-iOS-v${VERSION}.zip"
# Package Demo as well
DEMO_PKG_DIR="$TMP_DIR/ios_demo"
rm -rf "$DEMO_PKG_DIR"
mkdir -p "$DEMO_PKG_DIR"
cp -R "$IOS_ROOT/NewHttpDNSTestDemo" "$DEMO_PKG_DIR/"
cp -R "$IOS_ROOT/NewHttpDNSTests" "$DEMO_PKG_DIR/"
mkdir -p "$DEMO_PKG_DIR/NewHttpDNS.xcworkspace"
cp "$IOS_ROOT/NewHttpDNS.xcworkspace/contents.xcworkspacedata" "$DEMO_PKG_DIR/NewHttpDNS.xcworkspace/"
cp "$IOS_ROOT/Podfile" "$DEMO_PKG_DIR/"
cp "$IOS_ROOT/NewHTTPDNS.podspec" "$DEMO_PKG_DIR/"
cp -R "$IOS_ROOT/resource" "$DEMO_PKG_DIR/"
cp "$IOS_ROOT/README.md" "$DEMO_PKG_DIR/"
zip_dir "$DEMO_PKG_DIR" "$DIST_DIR/v${VERSION}/httpdns-demo-iOS-v${VERSION}.zip"
# Clean up build artifacts if on Mac
[ "$(uname)" == "Darwin" ] && rm -rf "$IOS_ROOT/Build"
} }
function build_flutter_sdk_package() { function build_flutter_sdk_package() {
@@ -143,8 +135,7 @@ function build_flutter_sdk_package() {
ensure_cmd zip ensure_cmd zip
PLUGIN_DIR="$ROOT/flutter/new_httpdns" PLUGIN_DIR="$ROOT/flutter/new_httpdns"
if [ ! -d "$PLUGIN_DIR" ]; then if [ ! -d "$PLUGIN_DIR" ]; then
echo "flutter plugin directory not found: $PLUGIN_DIR" return
exit 1
fi fi
PKG_DIR="$TMP_DIR/flutter" PKG_DIR="$TMP_DIR/flutter"
@@ -153,7 +144,8 @@ function build_flutter_sdk_package() {
cp -R "$PLUGIN_DIR" "$PKG_DIR/" cp -R "$PLUGIN_DIR" "$PKG_DIR/"
rm -rf "$PKG_DIR/new_httpdns/example/.dart_tool" "$PKG_DIR/new_httpdns/example/build" "$PKG_DIR/new_httpdns/.dart_tool" "$PKG_DIR/new_httpdns/build" rm -rf "$PKG_DIR/new_httpdns/example/.dart_tool" "$PKG_DIR/new_httpdns/example/build" "$PKG_DIR/new_httpdns/.dart_tool" "$PKG_DIR/new_httpdns/build"
zip_dir "$PKG_DIR" "$DIST_DIR/httpdns-sdk-flutter.zip" mkdir -p "$DIST_DIR/v${VERSION}"
zip_dir "$PKG_DIR" "$DIST_DIR/v${VERSION}/httpdns-sdk-flutter-v${VERSION}.zip"
} }
function main() { function main() {
@@ -165,11 +157,8 @@ function main() {
build_ios_sdk_package build_ios_sdk_package
build_flutter_sdk_package build_flutter_sdk_package
cp "$DIST_DIR/httpdns-sdk-ios.zip" "$DIST_DIR/httpdns-sdk-ios-v${VERSION}.zip"
cp "$DIST_DIR/httpdns-sdk-flutter.zip" "$DIST_DIR/httpdns-sdk-flutter-v${VERSION}.zip"
rm -rf "$TMP_DIR" rm -rf "$TMP_DIR"
echo "[sdk] done. output: $DIST_DIR" echo "[sdk] done. output: $DIST_DIR/v${VERSION}"
} }
main "$@" main "$@"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -22,6 +22,7 @@ class TrustAPPHttpDnsPlugin : FlutterPlugin, MethodCallHandler {
private var appId: String? = null private var appId: String? = null
private var secretKey: String? = null private var secretKey: String? = null
private var apiUrl: String? = null
private var primaryServiceHost: String? = null private var primaryServiceHost: String? = null
private var backupServiceHost: String? = null private var backupServiceHost: String? = null
private var servicePort: Int? = null private var servicePort: Int? = null
@@ -63,9 +64,11 @@ class TrustAPPHttpDnsPlugin : FlutterPlugin, MethodCallHandler {
return return
} }
val apiUrlArg = (args["apiUrl"] as? String)?.trim()
val primaryHostArg = (args["primaryServiceHost"] as? String)?.trim() val primaryHostArg = (args["primaryServiceHost"] as? String)?.trim()
if (primaryHostArg.isNullOrBlank()) {
Log.i("TrustAPPHttpDns", "initialize missing primaryServiceHost") if (apiUrlArg.isNullOrBlank() && primaryHostArg.isNullOrBlank()) {
Log.i("TrustAPPHttpDns", "initialize missing both apiUrl and primaryServiceHost")
result.success(false) result.success(false)
return return
} }
@@ -81,13 +84,14 @@ class TrustAPPHttpDnsPlugin : FlutterPlugin, MethodCallHandler {
appId = parsedAppId appId = parsedAppId
secretKey = secret secretKey = secret
apiUrl = apiUrlArg
primaryServiceHost = primaryHostArg primaryServiceHost = primaryHostArg
backupServiceHost = backup?.trim()?.takeIf { it.isNotEmpty() } backupServiceHost = backup?.trim()?.takeIf { it.isNotEmpty() }
servicePort = if (port != null && port > 0) port else null servicePort = if (port != null && port > 0) port else null
Log.i( Log.i(
"TrustAPPHttpDns", "TrustAPPHttpDns",
"initialize appId=$appId, primaryServiceHost=$primaryServiceHost, backupServiceHost=$backupServiceHost, servicePort=$servicePort" "initialize appId=$appId, apiUrl=$apiUrl, primaryServiceHost=$primaryServiceHost, backupServiceHost=$backupServiceHost, servicePort=$servicePort"
) )
result.success(true) result.success(true)
} }
@@ -189,6 +193,16 @@ class TrustAPPHttpDnsPlugin : FlutterPlugin, MethodCallHandler {
} catch (_: Throwable) { } catch (_: Throwable) {
} }
try {
if (!apiUrl.isNullOrBlank()) {
try {
builder.javaClass.getMethod("setServiceUrl", String::class.java)
.invoke(builder, apiUrl)
hostConfigApplied = true
} catch (t: Throwable) {
Log.w("TrustAPPHttpDns", "setServiceUrl failed: ${t.message}")
}
} else {
try { try {
builder.javaClass.getMethod("setPrimaryServiceHost", String::class.java) builder.javaClass.getMethod("setPrimaryServiceHost", String::class.java)
.invoke(builder, primaryServiceHost) .invoke(builder, primaryServiceHost)
@@ -210,6 +224,10 @@ class TrustAPPHttpDnsPlugin : FlutterPlugin, MethodCallHandler {
} }
} catch (_: Throwable) { } catch (_: Throwable) {
} }
}
} catch (e: Exception) {
Log.w("TrustAPPHttpDns", "Host configuration failed: ${e.message}")
}
try { try {
desiredPersistentCacheEnabled?.let { enabled -> desiredPersistentCacheEnabled?.let { enabled ->

View File

@@ -0,0 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"new_httpdns","path":"/Users/robin/product/waf-platform/HttpDNSSDK/sdk/flutter/new_httpdns/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"new_httpdns","path":"/Users/robin/product/waf-platform/HttpDNSSDK/sdk/flutter/new_httpdns/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"new_httpdns","dependencies":[]}],"date_created":"2026-03-05 15:58:08.461101","version":"3.41.4","swift_package_manager_enabled":{"ios":false,"macos":false}}

View File

@@ -20,7 +20,5 @@
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict> </dict>
</plist> </plist>

View File

@@ -0,0 +1,18 @@
#
# This podspec is NOT to be published. It is only used as a local source!
# This is a generated file; do not edit or check into version control.
#
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'A UI toolkit for beautiful and fast apps.'
s.homepage = 'https://flutter.dev'
s.license = { :type => 'BSD' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '13.0'
# Framework linking is handled by Flutter tooling, not CocoaPods.
# Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.
s.vendored_frameworks = 'path/to/nothing'
end

View File

@@ -0,0 +1,15 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/usr/local/share/flutter
FLUTTER_APPLICATION_PATH=/Users/robin/product/waf-platform/HttpDNSSDK/sdk/flutter/new_httpdns/example
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=/Users/robin/product/waf-platform/HttpDNSSDK/sdk/flutter/new_httpdns/example/lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuNDEuNA==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049ZmYzN2JlZjYwMw==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049ZTRiOGRjYTNmMQ==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMS4x
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=/Users/robin/product/waf-platform/HttpDNSSDK/sdk/flutter/new_httpdns/example/.dart_tool/package_config.json

View File

@@ -0,0 +1,32 @@
#
# Generated file, do not edit.
#
import lldb
def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
"""Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
base = frame.register["x0"].GetValueAsAddress()
page_len = frame.register["x1"].GetValueAsUnsigned()
# Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
# first page to see if handled it correctly. This makes diagnosing
# misconfiguration (e.g. missing breakpoint) easier.
data = bytearray(page_len)
data[0:8] = b'IHELPED!'
error = lldb.SBError()
frame.GetThread().GetProcess().WriteMemory(base, data, error)
if not error.Success():
print(f'Failed to write into {base}[+{page_len}]', error)
return
def __lldb_init_module(debugger: lldb.SBDebugger, _):
target = debugger.GetDummyTarget()
# Caveat: must use BreakpointCreateByRegEx here and not
# BreakpointCreateByName. For some reasons callback function does not
# get carried over from dummy target for the later.
bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
bp.SetAutoContinue(True)
print("-- LLDB integration loaded --")

View File

@@ -0,0 +1,5 @@
#
# Generated file, do not edit.
#
command script import --relative-to-command-file flutter_lldb_helper.py

View File

@@ -0,0 +1,14 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/usr/local/share/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/robin/product/waf-platform/HttpDNSSDK/sdk/flutter/new_httpdns/example"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=/Users/robin/product/waf-platform/HttpDNSSDK/sdk/flutter/new_httpdns/example/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuNDEuNA==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049ZmYzN2JlZjYwMw==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049ZTRiOGRjYTNmMQ==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMS4x"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=/Users/robin/product/waf-platform/HttpDNSSDK/sdk/flutter/new_httpdns/example/.dart_tool/package_config.json"

View File

@@ -2,7 +2,7 @@
source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/aliyun/aliyun-specs.git' source 'https://github.com/aliyun/aliyun-specs.git'
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '12.0' # platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -436,7 +436,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@@ -570,7 +570,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -621,7 +621,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;

View File

@@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */

View File

@@ -0,0 +1,21 @@
//
// Generated file. Do not edit.
//
// clang-format off
#import "GeneratedPluginRegistrant.h"
#if __has_include(<new_httpdns/NewHttpDnsPlugin.h>)
#import <new_httpdns/NewHttpDnsPlugin.h>
#else
@import new_httpdns;
#endif
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[NewHttpDnsPlugin registerWithRegistrar:[registry registrarForPlugin:@"NewHttpDnsPlugin"]];
}
@end

View File

@@ -4,7 +4,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'net/httpdns_http_client_adapter.dart'; import 'net/httpdns_http_client_adapter.dart';
import 'package:TrustAPP_httpdns/TrustAPP_httpdns.dart'; import 'package:new_httpdns/new_httpdns.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@@ -13,7 +13,6 @@ void main() {
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
// This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
@@ -63,18 +62,20 @@ class _MyHomePageState extends State<MyHomePage> {
if (_httpdnsReady || _httpdnsIniting) return; if (_httpdnsReady || _httpdnsIniting) return;
_httpdnsIniting = true; _httpdnsIniting = true;
try { try {
// 使用官方测试参数
await TrustAPPHttpdns.init( await TrustAPPHttpdns.init(
appId: 'app1f1ndpo9', // 请替换为您的应用 AppId appId: 'app1flndpo9',
primaryServiceHost: 'httpdns-a.example.com', // 请替换为主服务域<E58AA1><E59F9F>? backupServiceHost: 'httpdns-b.example.com', // 可选备服务域<E58AA1><E59F9F>? servicePort: 443, apiUrl: 'https://httpdns.deepwaf.xyz:8445',
secretKey: 'your_sign_secret_here', // 可选仅验签开启时需<E697B6><E99C80>? ); secretKey: 'ss_67fb8471a45b',
);
await TrustAPPHttpdns.setHttpsRequestEnabled(true); await TrustAPPHttpdns.setHttpsRequestEnabled(true);
await TrustAPPHttpdns.setLogEnabled(true); await TrustAPPHttpdns.setLogEnabled(true);
await TrustAPPHttpdns.setPersistentCacheIPEnabled(true); await TrustAPPHttpdns.setPersistentCacheIPEnabled(true);
await TrustAPPHttpdns.setReuseExpiredIPEnabled(true); await TrustAPPHttpdns.setReuseExpiredIPEnabled(true);
await TrustAPPHttpdns.build(); await TrustAPPHttpdns.build();
// 先build再执行解析相关动<EFBFBD><EFBFBD>? // 先 build 再执行解析相关动
final preResolveHosts = 'www.TrustAPP.com'; final preResolveHosts = 'demo.cloudxdr.com';
await TrustAPPHttpdns.setPreResolveHosts([preResolveHosts], ipType: 'both'); await TrustAPPHttpdns.setPreResolveHosts([preResolveHosts], ipType: 'both');
debugPrint('[httpdns] pre-resolve scheduled for host=$preResolveHosts'); debugPrint('[httpdns] pre-resolve scheduled for host=$preResolveHosts');
_httpdnsReady = true; _httpdnsReady = true;
@@ -89,9 +90,9 @@ class _MyHomePageState extends State<MyHomePage> {
void initState() { void initState() {
super.initState(); super.initState();
// 设置默认的 API URL 用于演示 // 设置默认的 API URL 用于演示
_urlController.text = 'https://www.TrustAPP.com'; _urlController.text = 'https://demo.cloudxdr.com';
// 仅首次进入页面时初始<EFBFBD><EFBFBD>?HTTPDNS // 仅首次进入页面时初始HTTPDNS
_initHttpDnsOnce(); _initHttpDnsOnce();
// 先初始化 HTTPDNS 再初始化 Dio // 先初始化 HTTPDNS 再初始化 Dio
@@ -124,7 +125,7 @@ class _MyHomePageState extends State<MyHomePage> {
_responseText = 'Sending request...'; _responseText = 'Sending request...';
}); });
final uri = Uri.parse(_urlController.text); final uri = Uri.parse(_urlController.text.trim());
try { try {
final String libraryName = _selectedLibrary.displayName; final String libraryName = _selectedLibrary.displayName;
@@ -215,7 +216,6 @@ class _MyHomePageState extends State<MyHomePage> {
} }
} }
// 使用 HTTPDNS 解析当前 URL <20><>?host 并显示结<E7A4BA><E7BB93>?
Future<void> _testHttpDnsResolve() async { Future<void> _testHttpDnsResolve() async {
final text = _urlController.text.trim(); final text = _urlController.text.trim();
if (text.isEmpty) { if (text.isEmpty) {
@@ -241,7 +241,6 @@ class _MyHomePageState extends State<MyHomePage> {
}); });
try { try {
// 确保只初始化一<E58C96><E4B880>?
await _initHttpDnsOnce(); await _initHttpDnsOnce();
final res = await TrustAPPHttpdns.resolveHostSyncNonBlocking( final res = await TrustAPPHttpdns.resolveHostSyncNonBlocking(
uri.host, uri.host,
@@ -278,12 +277,11 @@ class _MyHomePageState extends State<MyHomePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// URL输入<E8BE93><E585A5>?
TextField( TextField(
controller: _urlController, controller: _urlController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Enter URL', labelText: 'Enter URL',
hintText: 'https://www.TrustAPP.com', hintText: 'https://demo.cloudxdr.com',
border: OutlineInputBorder(), border: OutlineInputBorder(),
prefixIcon: Icon(Icons.link), prefixIcon: Icon(Icons.link),
), ),
@@ -346,7 +344,6 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// HTTPDNS 解析按钮
ElevatedButton.icon( ElevatedButton.icon(
onPressed: _isLoading ? null : _testHttpDnsResolve, onPressed: _isLoading ? null : _testHttpDnsResolve,
icon: const Icon(Icons.dns), icon: const Icon(Icons.dns),
@@ -357,10 +354,8 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// 保留空白分隔
const SizedBox(height: 16), const SizedBox(height: 16),
// 响应文本显示区域
Expanded( Expanded(
child: Container( child: Container(
width: double.infinity, width: double.infinity,

View File

@@ -0,0 +1,9 @@
import 'dart:io';
import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:new_httpdns/new_httpdns.dart';
/* *

View File

@@ -4,7 +4,7 @@ import 'package:dio/io.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/io_client.dart'; import 'package:http/io_client.dart';
import 'package:TrustAPP_httpdns/TrustAPP_httpdns.dart'; import 'package:new_httpdns/new_httpdns.dart';
/* * /* *
* 构建<E69E84><E5BBBA>?HTTPDNS 能力<E883BD><E58A9B>?IOHttpClientAdapter * 构建<E69E84><E5BBBA>?HTTPDNS 能力<E883BD><E58A9B>?IOHttpClientAdapter

View File

@@ -1,13 +1,6 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
new_httpdns:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.0.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -28,10 +21,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -126,26 +119,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.9" version: "11.0.2"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.9" version: "3.0.10"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -158,26 +151,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.17" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.13.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.17.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -186,6 +179,13 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
new_httpdns:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -251,10 +251,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.4" version: "0.7.10"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -267,10 +267,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.2.0"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@@ -288,5 +288,5 @@ packages:
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
sdks: sdks:
dart: ">=3.7.0-0 <4.0.0" dart: ">=3.9.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.18.0-18.0.pre.54"

View File

@@ -6,9 +6,11 @@ import CommonCrypto
public class NewHttpDnsPlugin: NSObject, FlutterPlugin { public class NewHttpDnsPlugin: NSObject, FlutterPlugin {
private var appId: String? private var appId: String?
private var secretKey: String? private var secretKey: String?
private var apiUrl: String?
private var primaryServiceHost: String? private var primaryServiceHost: String?
private var backupServiceHost: String? private var backupServiceHost: String?
private var servicePort: Int = 443 private var servicePort: Int = 443
private var serviceScheme: String = "https"
private var desiredLogEnabled: Bool? private var desiredLogEnabled: Bool?
private var desiredHttpsEnabled: Bool? private var desiredHttpsEnabled: Bool?
@@ -20,7 +22,7 @@ public class NewHttpDnsPlugin: NSObject, FlutterPlugin {
}() }()
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "new_httpdns", binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: "TrustAPP_httpdns", binaryMessenger: registrar.messenger())
let instance = NewHttpDnsPlugin() let instance = NewHttpDnsPlugin()
registrar.addMethodCallDelegate(instance, channel: channel) registrar.addMethodCallDelegate(instance, channel: channel)
} }
@@ -29,26 +31,46 @@ public class NewHttpDnsPlugin: NSObject, FlutterPlugin {
switch call.method { switch call.method {
case "initialize": case "initialize":
let options = call.arguments as? [String: Any] ?? [:] let options = call.arguments as? [String: Any] ?? [:]
guard let rawAppId = options["appId"] as? String, guard let rawAppId = options["appId"] as? String else {
let rawPrimaryHost = options["primaryServiceHost"] as? String else {
result(false) result(false)
return return
} }
let normalizedAppId = rawAppId.trimmingCharacters(in: .whitespacesAndNewlines) let normalizedAppId = rawAppId.trimmingCharacters(in: .whitespacesAndNewlines)
let normalizedPrimaryHost = rawPrimaryHost.trimmingCharacters(in: .whitespacesAndNewlines) if normalizedAppId.isEmpty {
if normalizedAppId.isEmpty || normalizedPrimaryHost.isEmpty {
result(false) result(false)
return return
} }
appId = normalizedAppId appId = normalizedAppId
primaryServiceHost = normalizedPrimaryHost apiUrl = (options["apiUrl"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
primaryServiceHost = (options["primaryServiceHost"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
backupServiceHost = (options["backupServiceHost"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) backupServiceHost = (options["backupServiceHost"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
secretKey = (options["secretKey"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) secretKey = (options["secretKey"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
if let p = options["servicePort"] as? Int, p > 0 { if let p = options["servicePort"] as? Int, p > 0 {
servicePort = p servicePort = p
} }
if let urlStr = apiUrl, !urlStr.isEmpty {
var normalized = urlStr
if !urlStr.lowercased().hasPrefix("http://") && !urlStr.lowercased().hasPrefix("https://") {
normalized = "https://" + urlStr
}
if let url = URL(string: normalized) {
primaryServiceHost = url.host
if let p = url.port {
servicePort = p
}
serviceScheme = url.scheme ?? "https"
}
} else if let host = primaryServiceHost {
// Keep as is, using defaults
} else {
result(false)
return
}
result(true) result(true)
case "setLogEnabled": case "setLogEnabled":
@@ -180,7 +202,7 @@ public class NewHttpDnsPlugin: NSObject, FlutterPlugin {
let serviceHost = hosts[index] let serviceHost = hosts[index]
var components = URLComponents() var components = URLComponents()
components.scheme = "https" components.scheme = serviceScheme
components.host = serviceHost components.host = serviceHost
components.port = servicePort components.port = servicePort
components.path = "/resolve" components.path = "/resolve"

View File

@@ -7,27 +7,30 @@ import 'package:flutter/services.dart';
class TrustAPPHttpdns { class TrustAPPHttpdns {
static const MethodChannel _channel = MethodChannel('TrustAPP_httpdns'); static const MethodChannel _channel = MethodChannel('TrustAPP_httpdns');
/// New API only: /// Initialize the SDK.
/// appId + primary/backup service host + optional sign secret. /// [apiUrl] is the unified endpoint URL, e.g. "https://httpdns.example.com:8445".
/// [appId] and [secretKey] are required for authentication.
static Future<bool> init({ static Future<bool> init({
required String appId, required String appId,
required String primaryServiceHost, String? apiUrl,
String? backupServiceHost, @Deprecated('Use apiUrl instead') String? primaryServiceHost,
int servicePort = 443, @Deprecated('Use apiUrl instead') String? backupServiceHost,
@Deprecated('Use apiUrl instead') int servicePort = 443,
String? secretKey, String? secretKey,
}) async { }) async {
final String normalizedAppId = appId.trim(); final String normalizedAppId = appId.trim();
final String normalizedPrimary = primaryServiceHost.trim(); if (normalizedAppId.isEmpty) {
if (normalizedAppId.isEmpty || normalizedPrimary.isEmpty) {
return false; return false;
} }
final Map<String, dynamic> args = <String, dynamic>{ final Map<String, dynamic> args = <String, dynamic>{
'appId': normalizedAppId, 'appId': normalizedAppId,
'primaryServiceHost': normalizedPrimary, if (apiUrl != null && apiUrl.trim().isNotEmpty) 'apiUrl': apiUrl.trim(),
if (primaryServiceHost != null && primaryServiceHost.trim().isNotEmpty)
'primaryServiceHost': primaryServiceHost.trim(),
if (backupServiceHost != null && backupServiceHost.trim().isNotEmpty) if (backupServiceHost != null && backupServiceHost.trim().isNotEmpty)
'backupServiceHost': backupServiceHost.trim(), 'backupServiceHost': backupServiceHost.trim(),
if (servicePort > 0) 'servicePort': servicePort, 'servicePort': servicePort,
if (secretKey != null && secretKey.isNotEmpty) 'secretKey': secretKey, if (secretKey != null && secretKey.isNotEmpty) 'secretKey': secretKey,
}; };

View File

@@ -14,9 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface HttpdnsEdgeService : NSObject @interface HttpdnsEdgeService : NSObject
- (instancetype)initWithAppId:(NSString *)appId - (instancetype)initWithAppId:(NSString *)appId
primaryServiceHost:(NSString *)primaryServiceHost apiUrl:(NSString *)apiUrl
backupServiceHost:(nullable NSString *)backupServiceHost
servicePort:(NSInteger)servicePort
signSecret:(nullable NSString *)signSecret; signSecret:(nullable NSString *)signSecret;
- (void)resolveHost:(NSString *)host - (void)resolveHost:(NSString *)host

View File

@@ -4,15 +4,38 @@
static NSString * const kHttpdnsEdgeErrorDomain = @"com.goeedge.httpdns.edge"; static NSString * const kHttpdnsEdgeErrorDomain = @"com.goeedge.httpdns.edge";
@interface HttpdnsEdgeTLSDelegate : NSObject <NSURLSessionDelegate>
@property (nonatomic, copy) NSString *host;
@end
@implementation HttpdnsEdgeTLSDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecTrustRef trust = challenge.protectionSpace.serverTrust;
if (trust) {
SecPolicyRef policy = SecPolicyCreateSSL(true, (__bridge CFStringRef)self.host);
SecTrustSetPolicies(trust, policy);
SecTrustResultType result;
SecTrustEvaluate(trust, &result);
if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
completionHandler(NSURLSessionAuthChallengeUseCredential, [[NSURLCredential alloc] initWithTrust:trust]);
return;
}
}
}
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
@end
@implementation HttpdnsEdgeResolveResult @implementation HttpdnsEdgeResolveResult
@end @end
@interface HttpdnsEdgeService () @interface HttpdnsEdgeService ()
@property (nonatomic, copy) NSString *appId; @property (nonatomic, copy) NSString *appId;
@property (nonatomic, copy) NSString *primaryServiceHost; @property (nonatomic, copy) NSArray<NSString *> *endpoints;
@property (nonatomic, copy) NSString *backupServiceHost;
@property (nonatomic, assign) NSInteger servicePort;
@property (nonatomic, copy) NSString *signSecret; @property (nonatomic, copy) NSString *signSecret;
@property (nonatomic, copy) NSString *sessionId; @property (nonatomic, copy) NSString *sessionId;
@@ -21,17 +44,13 @@ static NSString * const kHttpdnsEdgeErrorDomain = @"com.goeedge.httpdns.edge";
@implementation HttpdnsEdgeService @implementation HttpdnsEdgeService
- (instancetype)initWithAppId:(NSString *)appId - (instancetype)initWithAppId:(NSString *)appId
primaryServiceHost:(NSString *)primaryServiceHost apiUrl:(NSString *)apiUrl
backupServiceHost:(NSString *)backupServiceHost
servicePort:(NSInteger)servicePort
signSecret:(NSString *)signSecret { signSecret:(NSString *)signSecret {
if (self = [super init]) { if (self = [super init]) {
_appId = [appId copy]; _appId = [appId copy];
_primaryServiceHost = [primaryServiceHost copy];
_backupServiceHost = backupServiceHost.length > 0 ? [backupServiceHost copy] : @"";
_servicePort = servicePort > 0 ? servicePort : 443;
_signSecret = signSecret.length > 0 ? [signSecret copy] : @""; _signSecret = signSecret.length > 0 ? [signSecret copy] : @"";
_sessionId = [[[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""] copy]; _sessionId = [[[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""] copy];
_endpoints = apiUrl.length > 0 ? @[[apiUrl copy]] : @[];
} }
return self; return self;
} }
@@ -39,7 +58,7 @@ static NSString * const kHttpdnsEdgeErrorDomain = @"com.goeedge.httpdns.edge";
- (void)resolveHost:(NSString *)host - (void)resolveHost:(NSString *)host
queryType:(NSString *)queryType queryType:(NSString *)queryType
completion:(void (^)(HttpdnsEdgeResolveResult *_Nullable, NSError *_Nullable))completion { completion:(void (^)(HttpdnsEdgeResolveResult *_Nullable, NSError *_Nullable))completion {
if (host.length == 0 || self.appId.length == 0 || self.primaryServiceHost.length == 0) { if (host.length == 0 || self.appId.length == 0 || self.endpoints.count == 0) {
NSError *error = [NSError errorWithDomain:kHttpdnsEdgeErrorDomain NSError *error = [NSError errorWithDomain:kHttpdnsEdgeErrorDomain
code:1001 code:1001
userInfo:@{NSLocalizedDescriptionKey: @"invalid init config or host"}]; userInfo:@{NSLocalizedDescriptionKey: @"invalid init config or host"}];
@@ -48,11 +67,7 @@ static NSString * const kHttpdnsEdgeErrorDomain = @"com.goeedge.httpdns.edge";
} }
NSString *qtype = queryType.length > 0 ? queryType.uppercaseString : @"A"; NSString *qtype = queryType.length > 0 ? queryType.uppercaseString : @"A";
NSArray<NSString *> *hosts = self.backupServiceHost.length > 0 [self requestResolveHosts:self.endpoints index:0 host:host qtype:qtype completion:completion];
? @[self.primaryServiceHost, self.backupServiceHost]
: @[self.primaryServiceHost];
[self requestResolveHosts:hosts index:0 host:host qtype:qtype completion:completion];
} }
- (void)requestURL:(NSURL *)url - (void)requestURL:(NSURL *)url
@@ -134,7 +149,11 @@ static NSString * const kHttpdnsEdgeErrorDomain = @"com.goeedge.httpdns.edge";
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
config.timeoutIntervalForRequest = 8; config.timeoutIntervalForRequest = 8;
config.timeoutIntervalForResource = 8; config.timeoutIntervalForResource = 8;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
HttpdnsEdgeTLSDelegate *delegate = [[HttpdnsEdgeTLSDelegate alloc] init];
delegate.host = url.host;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:nil];
[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { [[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[session finishTasksAndInvalidate]; [session finishTasksAndInvalidate];
@@ -161,11 +180,12 @@ static NSString * const kHttpdnsEdgeErrorDomain = @"com.goeedge.httpdns.edge";
return; return;
} }
NSString *serviceHost = hosts[index]; NSString *baseUrl = hosts[index];
NSURLComponents *components = [NSURLComponents new]; NSURLComponents *components = [NSURLComponents componentsWithString:baseUrl];
components.scheme = @"https"; if (!components) {
components.host = serviceHost; [self requestResolveHosts:hosts index:index + 1 host:host qtype:qtype completion:completion];
components.port = @(self.servicePort); return;
}
components.path = @"/resolve"; components.path = @"/resolve";
NSMutableArray<NSURLQueryItem *> *items = [NSMutableArray arrayWithArray:@[ NSMutableArray<NSURLQueryItem *> *items = [NSMutableArray arrayWithArray:@[

View File

@@ -24,19 +24,19 @@
// #import "HttpdnsDegradationDelegate.h" // #import "HttpdnsDegradationDelegate.h"
// #import "HttpdnsLoggerProtocol.h" // #import "HttpdnsLoggerProtocol.h"
#import <NewHTTPDNS/HttpdnsRequest.h> #import "HttpdnsRequest.h"
#import <NewHttpDNS/HttpDnsResult.h> #import "HttpDnsResult.h"
#import <NewHttpDNS/HttpdnsLoggerProtocol.h> #import "HttpdnsLoggerProtocol.h"
#import <NewHttpDNS/HttpdnsDegradationDelegate.h> #import "HttpdnsDegradationDelegate.h"
#define NEW_HTTPDNS_DEPRECATED(explain) __attribute__((deprecated(explain))) #define Trust_HTTPDNS_DEPRECATED(explain) __attribute__((deprecated(explain)))
#ifndef NewHDNS_STACK_KEY #ifndef TrustHDNS_STACK_KEY
#define NewHDNS_STACK_KEY #define TrustHDNS_STACK_KEY
#define NewHDNS_IPV4 @"NewHDNS_IPV4" #define TrustHDNS_IPV4 @"TrustHDNS_IPV4"
#define NewHDNS_IPV6 @"NewHDNS_IPV6" #define TrustHDNS_IPV6 @"TrustHDNS_IPV6"
#endif #endif
@@ -50,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN
/// @param host 域名 /// @param host 域名
/// @param ipType 当前查询的IP类型 /// @param ipType 当前查询的IP类型
/// @param ttl 当次域名解析返回的TTL /// @param ttl 当次域名解析返回的TTL
- (int64_t)httpdnsHost:(NSString * _Nonnull)host ipType:(NewHttpDNS_IPType)ipType ttl:(int64_t)ttl; - (int64_t)httpdnsHost:(NSString * _Nonnull)host ipType:(TrustHttpDNS_IPType)ipType ttl:(int64_t)ttl;
@end @end
@@ -62,154 +62,154 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, copy, readonly, nullable) NSString *aesSecretKey; @property (nonatomic, copy, readonly, nullable) NSString *aesSecretKey;
@property (nonatomic, weak, setter=setDelegateForDegradationFilter:) id<HttpDNSDegradationDelegate> delegate NEW_HTTPDNS_DEPRECATED("不再建议通过设置此回调实现降级逻辑而是自行在调用HTTPDNS解析域名前做判断"); @property (nonatomic, weak, setter=setDelegateForDegradationFilter:) id<HttpDNSDegradationDelegate> delegate Trust_HTTPDNS_DEPRECATED("不再建议通过设置此回调实现降级逻辑而是自行在调用HTTPDNS解析域名前做判断");
@property (nonatomic, weak) id<HttpdnsTTLDelegate> ttlDelegate; @property (nonatomic, weak) id<HttpdnsTTLDelegate> ttlDelegate;
+ (nonnull instancetype)sharedInstance; + (nonnull instancetype)sharedInstance;
/// 获取指定账号对应<EFBFBD><EFBFBD>?HttpDnsService 实例 /// 获取指定账号对应?HttpDnsService 实例
/// @param accountID 账号 ID /// @param accountID 账号 ID
/// @return 已初始化的实例,若账号尚未注册则返回 nil /// @return 已初始化的实例,若账号尚未注册则返回 nil
+ (nullable instancetype)getInstanceByAccountId:(NSInteger)accountID; + (nullable instancetype)getInstanceByAccountId:(NSInteger)accountID;
/*! /*!
* @brief 无需鉴权功能的初始化接口 * @brief 无需鉴权功能的初始化接口
* @details 初始化,设置 HTTPDNS 服务 Account ID。使用本接口初始化请求将无任何签名保护请谨慎使用<EFBFBD><EFBFBD>? * @details 初始化,设置 HTTPDNS 服务 Account ID。使用本接口初始化请求将无任何签名保护请谨慎使用?
* 您可以从控制台获取您<EFBFBD><EFBFBD>?Account ID <EFBFBD><EFBFBD>? * 您可以从控制台获取您?Account ID ?
* 此方法会初始化为单例<EFBFBD><EFBFBD>? * 此方法会初始化为单例?
* 注意:本接口<EFBFBD><EFBFBD>?.2.1起废弃,后续将进行移除<EFBFBD><EFBFBD>? * 注意:本接口?.2.1起废弃,后续将进行移除?
* @param accountID 您的 HTTPDNS Account ID * @param accountID 您的 HTTPDNS Account ID
*/ */
- (nonnull instancetype)initWithAccountID:(NSInteger)accountID NEW_HTTPDNS_DEPRECATED("Deprecated. This method will be removed in the future. Use -[HttpDnsService initWithAccountID:secretKey:] instead."); - (nonnull instancetype)initWithAccountID:(NSInteger)accountID Trust_HTTPDNS_DEPRECATED("Deprecated. This method will be removed in the future. Use -[HttpDnsService initWithAccountID:secretKey:] instead.");
/*! /*!
* @brief 启用鉴权功能的初始化接口 * @brief 启用鉴权功能的初始化接口
* @details 初始化、开启鉴权功能,并设<EFBFBD><EFBFBD>?HTTPDNS 服务 Account ID鉴权功能对应的 secretKey<EFBFBD><EFBFBD>? * @details 初始化、开启鉴权功能,并设?HTTPDNS 服务 Account ID鉴权功能对应的 secretKey?
* 您可以从控制台获取您<EFBFBD><EFBFBD>?Account ID 、secretKey信息<EFBFBD><EFBFBD>? * 您可以从控制台获取您?Account ID 、secretKey信息?
* 此方法会初始化为单例<EFBFBD><EFBFBD>? * 此方法会初始化为单例?
* @param accountID 您的 HTTPDNS Account ID * @param accountID 您的 HTTPDNS Account ID
* @param secretKey 鉴权对应<EFBFBD><EFBFBD>?secretKey * @param secretKey 鉴权对应?secretKey
*/ */
- (nonnull instancetype)initWithAccountID:(NSInteger)accountID secretKey:(NSString * _Nonnull)secretKey; - (nonnull instancetype)initWithAccountID:(NSInteger)accountID secretKey:(NSString * _Nonnull)secretKey;
/*! /*!
* @brief 启用鉴权功能、加密功能的初始化接<EFBFBD><EFBFBD>? * @brief 启用鉴权功能、加密功能的初始化接?
* @details 初始化、开启鉴权功能、开启AES加密并设置 HTTPDNS 服务 Account ID鉴权功能对应的 secretKey加密功能对应的 aesSecretKey<EFBFBD><EFBFBD>? * @details 初始化、开启鉴权功能、开启AES加密并设置 HTTPDNS 服务 Account ID鉴权功能对应的 secretKey加密功能对应的 aesSecretKey?
* 您可以从控制台获取您<EFBFBD><EFBFBD>?Account ID 、secretKey、aesSecretKey 信息<EFBFBD><EFBFBD>? * 您可以从控制台获取您?Account ID 、secretKey、aesSecretKey 信息?
* 此方法会初始化为单例<EFBFBD><EFBFBD>? * 此方法会初始化为单例?
* @param accountID 您的 HTTPDNS Account ID * @param accountID 您的 HTTPDNS Account ID
* @param secretKey 鉴权对应<EFBFBD><EFBFBD>?secretKey * @param secretKey 鉴权对应?secretKey
* @param aesSecretKey 加密功能对应<EFBFBD><EFBFBD>?aesSecretKey * @param aesSecretKey 加密功能对应?aesSecretKey
*/ */
- (nonnull instancetype)initWithAccountID:(NSInteger)accountID secretKey:(NSString * _Nonnull)secretKey aesSecretKey:(NSString * _Nullable)aesSecretKey; - (nonnull instancetype)initWithAccountID:(NSInteger)accountID secretKey:(NSString * _Nonnull)secretKey aesSecretKey:(NSString * _Nullable)aesSecretKey;
/// 开启鉴权功能后,鉴权的签名计算默认读取设备当前时间。若担心设备时间不准确导致签名不准确,可以使用此接口校正 APP 内鉴权计算使用的时间<EFBFBD><EFBFBD>? /// 开启鉴权功能后,鉴权的签名计算默认读取设备当前时间。若担心设备时间不准确导致签名不准确,可以使用此接口校正 APP 内鉴权计算使用的时间?
/// 注意,校正操作在 APP 的一个生命周期内生效APP 重启后需要重新设置才能重新生<EFBFBD><EFBFBD>? /// 注意,校正操作在 APP 的一个生命周期内生效APP 重启后需要重新设置才能重新生?
/// @param authCurrentTime 用于校正的时间戳,单位为<EFBFBD><EFBFBD>? /// @param authCurrentTime 用于校正的时间戳,单位为?
- (void)setAuthCurrentTime:(NSUInteger)authCurrentTime NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService setInternalAuthTimeBaseBySpecifyingCurrentTime:] instead."); - (void)setAuthCurrentTime:(NSUInteger)authCurrentTime Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService setInternalAuthTimeBaseBySpecifyingCurrentTime:] instead.");
/// 开启鉴权功能后,鉴权的签名计算默认读取设备当前时间。若担心设备时间不准确导致签名不准确,可以使用此接口校正 APP 内鉴权计算使用的时间<EFBFBD><EFBFBD>? /// 开启鉴权功能后,鉴权的签名计算默认读取设备当前时间。若担心设备时间不准确导致签名不准确,可以使用此接口校正 APP 内鉴权计算使用的时间?
/// 注意,校正操作在 APP 的一个生命周期内生效APP 重启后需要重新设置才能重新生<EFBFBD><EFBFBD>? /// 注意,校正操作在 APP 的一个生命周期内生效APP 重启后需要重新设置才能重新生?
/// @param currentTime 用于校正的时间戳,单位为<EFBFBD><EFBFBD>? /// @param currentTime 用于校正的时间戳,单位为?
- (void)setInternalAuthTimeBaseBySpecifyingCurrentTime:(NSTimeInterval)currentTime; - (void)setInternalAuthTimeBaseBySpecifyingCurrentTime:(NSTimeInterval)currentTime;
/// 设置持久化缓存功<EFBFBD><EFBFBD>? /// 设置持久化缓存功?
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 /// @param enable YES: 开?NO: 关闭
- (void)setCachedIPEnabled:(BOOL)enable NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService setPersistentCacheIPEnabled:] instead."); - (void)setCachedIPEnabled:(BOOL)enable Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService setPersistentCacheIPEnabled:] instead.");
/// 设置持久化缓存功<EFBFBD><EFBFBD>? /// 设置持久化缓存功?
/// 开启后,每次解析会将结果持久化缓存到本地,当下次应用启动时,可以从本地加载缓存解析结果,提高应用启动时获取解析结果的速度 /// 开启后,每次解析会将结果持久化缓存到本地,当下次应用启动时,可以从本地加载缓存解析结果,提高应用启动时获取解析结果的速度
/// 加载时,会丢弃已经过期的解析结果 /// 加载时,会丢弃已经过期的解析结果
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 /// @param enable YES: 开?NO: 关闭
- (void)setPersistentCacheIPEnabled:(BOOL)enable; - (void)setPersistentCacheIPEnabled:(BOOL)enable;
/// 设置持久化缓存功<EFBFBD><EFBFBD>? /// 设置持久化缓存功?
/// 开启后,每次解析会将结果持久化缓存到本地,当下次应用启动时,可以从本地加载缓存解析结果,提高应用启动时获取解析结果的速度 /// 开启后,每次解析会将结果持久化缓存到本地,当下次应用启动时,可以从本地加载缓存解析结果,提高应用启动时获取解析结果的速度
/// 加载时,会丢弃过期时间已经超过指定值的解析结果 /// 加载时,会丢弃过期时间已经超过指定值的解析结果
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 /// @param enable YES: 开?NO: 关闭
/// @param duration 决定丢弃IP的过期时间阈值单位为秒过期超过这个时间范围的IP会被丢弃取值范围为0-1年。这个值仅在开启持久化缓存功能时才有意<EFBFBD><EFBFBD>? /// @param duration 决定丢弃IP的过期时间阈值单位为秒过期超过这个时间范围的IP会被丢弃取值范围为0-1年。这个值仅在开启持久化缓存功能时才有意?
- (void)setPersistentCacheIPEnabled:(BOOL)enable discardRecordsHasExpiredFor:(NSTimeInterval)duration; - (void)setPersistentCacheIPEnabled:(BOOL)enable discardRecordsHasExpiredFor:(NSTimeInterval)duration;
/// 是否允许 HTTPDNS 返回 TTL 过期域名<EFBFBD><EFBFBD>?ip ,建议允许(默认不允许) /// 是否允许 HTTPDNS 返回 TTL 过期域名?ip ,建议允许(默认不允许)
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 /// @param enable YES: 开?NO: 关闭
- (void)setExpiredIPEnabled:(BOOL)enable NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService setReuseExpiredIPEnabled:] instead."); - (void)setExpiredIPEnabled:(BOOL)enable Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService setReuseExpiredIPEnabled:] instead.");
/// 是否允许 HTTPDNS 返回 TTL 过期域名<EFBFBD><EFBFBD>?ip ,建议允许(默认不允许) /// 是否允许 HTTPDNS 返回 TTL 过期域名?ip ,建议允许(默认不允许)
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 /// @param enable YES: 开?NO: 关闭
- (void)setReuseExpiredIPEnabled:(BOOL)enable; - (void)setReuseExpiredIPEnabled:(BOOL)enable;
/// 设置 HTTPDNS 域名解析请求类型 ( HTTP / HTTPS ) /// 设置 HTTPDNS 域名解析请求类型 ( HTTP / HTTPS )
/// 若不调用该接口,默认<EFBFBD><EFBFBD>?HTTP 请求<EFBFBD><EFBFBD>? /// 若不调用该接口,默认?HTTP 请求?
/// HTTP 请求基于底层 CFNetwork 实现,不<EFBFBD><EFBFBD>?ATS 限制<EFBFBD><EFBFBD>? /// HTTP 请求基于底层 CFNetwork 实现,不?ATS 限制?
/// @param enable YES: HTTPS请求 NO: HTTP请求 /// @param enable YES: HTTPS请求 NO: HTTP请求
- (void)setHTTPSRequestEnabled:(BOOL)enable; - (void)setHTTPSRequestEnabled:(BOOL)enable;
/// 声明App是否配置了ATS为AllowsArbitraryLoads默认认为没有配<EFBFBD><EFBFBD>? /// 声明App是否配置了ATS为AllowsArbitraryLoads默认认为没有配?
/// 若做了声明则当指定走HTTP方式解析域名时解析链路会走系统NSURLSession逻辑 /// 若做了声明则当指定走HTTP方式解析域名时解析链路会走系统NSURLSession逻辑
/// 否则会走定制的CFHTTP链路避免被ATS拦截请求 /// 否则会走定制的CFHTTP链路避免被ATS拦截请求
- (void)setHasAllowedArbitraryLoadsInATS:(BOOL)hasAllowedArbitraryLoadsInATS; - (void)setHasAllowedArbitraryLoadsInATS:(BOOL)hasAllowedArbitraryLoadsInATS;
/// 设置底层HTTPDNS网络请求超时时间单位为<EFBFBD><EFBFBD>? /// 设置底层HTTPDNS网络请求超时时间单位为?
/// 需要注意,这个值只决定底层解析请求的网络超时时间,而非同步解析接口、异步解析接口的最长阻塞或者等待时<EFBFBD><EFBFBD>? /// 需要注意,这个值只决定底层解析请求的网络超时时间,而非同步解析接口、异步解析接口的最长阻塞或者等待时?
/// 同步解析接口、异步解析接口的最长阻塞或者等待时间需要调用接口时设置request参数中的`resolveTimeoutInSecond`决定 /// 同步解析接口、异步解析接口的最长阻塞或者等待时间需要调用接口时设置request参数中的`resolveTimeoutInSecond`决定
/// @param timeoutInterval 超时时间,单位为<EFBFBD><EFBFBD>? /// @param timeoutInterval 超时时间,单位为?
- (void)setNetworkingTimeoutInterval:(NSTimeInterval)timeoutInterval; - (void)setNetworkingTimeoutInterval:(NSTimeInterval)timeoutInterval;
/// 指定region指定后会读取该region对应配置作为初始化配<EFBFBD><EFBFBD>? /// 指定region指定后会读取该region对应配置作为初始化配?
/// 一般情况下无需设置SDK内部会默认路由全球范围内最近的接入<EFBFBD><EFBFBD>? /// 一般情况下无需设置SDK内部会默认路由全球范围内最近的接入?
/// @param region 需要指定的region缺省为中国大陆 /// @param region 需要指定的region缺省为中国大陆
- (void)setRegion:(NSString *)region; - (void)setRegion:(NSString *)region;
/// 域名预解<EFBFBD><EFBFBD>?(默认解析双栈记录) /// 域名预解?(默认解析双栈记录)
/// 通常用于启动后立即向SDK设置您后续可能会使用到的热点域名以便SDK提前解析减少后续解析域名时请求的时<EFBFBD><EFBFBD>? /// 通常用于启动后立即向SDK设置您后续可能会使用到的热点域名以便SDK提前解析减少后续解析域名时请求的时?
/// 如果是在运行过程中调用SDK也会立即解析设置的域名数组中的域名刷新这些域名的解析结<EFBFBD><EFBFBD>? /// 如果是在运行过程中调用SDK也会立即解析设置的域名数组中的域名刷新这些域名的解析结?
/// ///
/// @param hosts 预解析列表数<EFBFBD><EFBFBD>? /// @param hosts 预解析列表数?
- (void)setPreResolveHosts:(NSArray *)hosts; - (void)setPreResolveHosts:(NSArray *)hosts;
/// 域名预解析可以指定预解析auto、ipv4、ipv6、both /// 域名预解析可以指定预解析auto、ipv4、ipv6、both
/// 通常用于启动后立即向SDK设置您后续可能会使用到的热点域名以便SDK提前解析减少后续解析域名时请求的时<EFBFBD><EFBFBD>? /// 通常用于启动后立即向SDK设置您后续可能会使用到的热点域名以便SDK提前解析减少后续解析域名时请求的时?
/// 如果是在运行过程中调用SDK也会立即解析设置的域名数组中的域名刷新这些域名的解析结<EFBFBD><EFBFBD>? /// 如果是在运行过程中调用SDK也会立即解析设置的域名数组中的域名刷新这些域名的解析结?
/// ///
/// @param hosts 预解析列表数<EFBFBD><EFBFBD>? /// @param hosts 预解析列表数?
/// @param ipType 指定预解析记录类<EFBFBD><EFBFBD>? /// @param ipType 指定预解析记录类?
- (void)setPreResolveHosts:(NSArray *)hosts byIPType:(HttpdnsQueryIPType)ipType; - (void)setPreResolveHosts:(NSArray *)hosts byIPType:(HttpdnsQueryIPType)ipType;
/// 域名预解<EFBFBD><EFBFBD>? /// 域名预解?
/// @param hosts 域名 /// @param hosts 域名
/// @param ipType 4: ipv4; 6: ipv6; 64: ipv4+ipv6 /// @param ipType 4: ipv4; 6: ipv6; 64: ipv4+ipv6
- (void)setPreResolveHosts:(NSArray *)hosts queryIPType:(NewHttpDNS_IPType)ipType NEW_HTTPDNS_DEPRECATED("Deprecated, this method will be removed in the future. Use -[HttpDnsService setPreResolveHosts:byIPType:] instead."); - (void)setPreResolveHosts:(NSArray *)hosts queryIPType:(TrustHttpDNS_IPType)ipType Trust_HTTPDNS_DEPRECATED("Deprecated, this method will be removed in the future. Use -[HttpDnsService setPreResolveHosts:byIPType:] instead.");
/// 本地日志 log 开<EFBFBD><EFBFBD>? /// 本地日志 log 开?
/// @param enable YES: 打开 NO: 关闭 /// @param enable YES: 打开 NO: 关闭
- (void)setLogEnabled:(BOOL)enable; - (void)setLogEnabled:(BOOL)enable;
/// 设置网络切换时是否自动更新所有域名解析结<EFBFBD><EFBFBD>? /// 设置网络切换时是否自动更新所有域名解析结?
/// 如果打开此开关,在网络切换时,会自动刷新所有域名的解析结果,但会产生一定流量消<EFBFBD><EFBFBD>? /// 如果打开此开关,在网络切换时,会自动刷新所有域名的解析结果,但会产生一定流量消?
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 /// @param enable YES: 开?NO: 关闭
- (void)setPreResolveAfterNetworkChanged:(BOOL)enable; - (void)setPreResolveAfterNetworkChanged:(BOOL)enable;
/// 设置当httpdns解析失败时是否降级到localDNS尝试解析 /// 设置当httpdns解析失败时是否降级到localDNS尝试解析
/// 降级生效时SDNS参数不生效降级逻辑只解析域名返回的结果默认使<EFBFBD><EFBFBD>?0<><30>?若未指定该域名自定义TTL)作为TTL<EFBFBD><EFBFBD>? /// 降级生效时SDNS参数不生效降级逻辑只解析域名返回的结果默认使?0?若未指定该域名自定义TTL)作为TTL?
/// 降级请求也不会再对ip进行优先排序 /// 降级请求也不会再对ip进行优先排序
/// 默认关闭,不会自动降<EFBFBD><EFBFBD>? /// 默认关闭,不会自动降?
/// @param enable YES自动降<EFBFBD><EFBFBD>?NO不自动降级 /// @param enable YES自动降?NO不自动降级
- (void)setDegradeToLocalDNSEnabled:(BOOL)enable; - (void)setDegradeToLocalDNSEnabled:(BOOL)enable;
@@ -219,59 +219,59 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setIPRankingDatasource:(NSDictionary<NSString *, NSNumber *> *)IPRankingDatasource; - (void)setIPRankingDatasource:(NSDictionary<NSString *, NSNumber *> *)IPRankingDatasource;
/// 设置是否 开<EFBFBD><EFBFBD>?IPv6 结果解析。只有开启状态下对域名的解析才会尝试解析v6记录并返回v6的结<EFBFBD><EFBFBD>? /// 设置是否 开?IPv6 结果解析。只有开启状态下对域名的解析才会尝试解析v6记录并返回v6的结?
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 /// @param enable YES: 开?NO: 关闭
- (void)enableIPv6:(BOOL)enable NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService setIPv6Enabled:] instead."); - (void)enableIPv6:(BOOL)enable Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService setIPv6Enabled:] instead.");
/// 设置是否 开<EFBFBD><EFBFBD>?IPv6 结果解析。只有开启状态下对域名的解析才会尝试解析v6记录并返回v6的结<EFBFBD><EFBFBD>? /// 设置是否 开?IPv6 结果解析。只有开启状态下对域名的解析才会尝试解析v6记录并返回v6的结?
/// 已弃用。默认支持IPv6。如果不需要IPv6类型的结果只需在请求时指定`queryIpType`为`HttpdnsQueryIPTypeIpv4` /// 已弃用。默认支持IPv6。如果不需要IPv6类型的结果只需在请求时指定`queryIpType`为`HttpdnsQueryIPTypeIpv4`
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 /// @param enable YES: 开?NO: 关闭
- (void)setIPv6Enabled:(BOOL)enable NEW_HTTPDNS_DEPRECATED("Deprecated. If ipv6 is unnecessary, you can set the `queryIpType` as HttpdnsQueryIPTypeIpv4 when resolving domain."); - (void)setIPv6Enabled:(BOOL)enable Trust_HTTPDNS_DEPRECATED("Deprecated. If ipv6 is unnecessary, you can set the `queryIpType` as HttpdnsQueryIPTypeIpv4 when resolving domain.");
/// 是否允许通过 CNCopyCurrentNetworkInfo 获取wifi ssid bssid /// 是否允许通过 CNCopyCurrentNetworkInfo 获取wifi ssid bssid
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 ,默认关<EFBFBD><EFBFBD>? /// @param enable YES: 开?NO: 关闭 ,默认关?
- (void)enableNetworkInfo:(BOOL)enable NEW_HTTPDNS_DEPRECATED("Deprecated. We do not utilize network information anymore"); - (void)enableNetworkInfo:(BOOL)enable Trust_HTTPDNS_DEPRECATED("Deprecated. We do not utilize network information anymore");
/// 是否允许通过 CNCopyCurrentNetworkInfo 获取wifi ssid bssid /// 是否允许通过 CNCopyCurrentNetworkInfo 获取wifi ssid bssid
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 ,默认关<EFBFBD><EFBFBD>? /// @param enable YES: 开?NO: 关闭 ,默认关?
- (void)setReadNetworkInfoEnabled:(BOOL)enable NEW_HTTPDNS_DEPRECATED("Deprecated. We do not utilize network information anymore."); - (void)setReadNetworkInfoEnabled:(BOOL)enable Trust_HTTPDNS_DEPRECATED("Deprecated. We do not utilize network information anymore.");
/// 是否开启IP探测功能 /// 是否开启IP探测功能
/// @param enable YES: 开<EFBFBD><EFBFBD>?NO: 关闭 默认打开 /// @param enable YES: 开?NO: 关闭 默认打开
- (void)enableCustomIPRank:(BOOL)enable NEW_HTTPDNS_DEPRECATED("Deprecated, will be removed in the future."); - (void)enableCustomIPRank:(BOOL)enable Trust_HTTPDNS_DEPRECATED("Deprecated, will be removed in the future.");
/// 设置软件自定义解析全局默认参数,设置后,调用软件自定义解析时,每个请求默认都会带上这里配置的参<EFBFBD><EFBFBD>? /// 设置软件自定义解析全局默认参数,设置后,调用软件自定义解析时,每个请求默认都会带上这里配置的参?
/// @param params 全局默认参数 /// @param params 全局默认参数
- (void)setSdnsGlobalParams:(NSDictionary<NSString *, NSString *> *)params; - (void)setSdnsGlobalParams:(NSDictionary<NSString *, NSString *> *)params;
/// 设置日志输出回调,以实现自定义日志输出方<EFBFBD><EFBFBD>? /// 设置日志输出回调,以实现自定义日志输出方?
/// @param logHandler 日志输出回调实现实例 /// @param logHandler 日志输出回调实现实例
- (void)setLogHandler:(id<HttpdnsLoggerProtocol>)logHandler; - (void)setLogHandler:(id<HttpdnsLoggerProtocol>)logHandler;
/// 获取用于用户追踪<EFBFBD><EFBFBD>?sessionId /// 获取用于用户追踪?sessionId
/// sessionId为随机生成长度<EFBFBD><EFBFBD>?12 位App 生命周期内保持不<EFBFBD><EFBFBD>? /// sessionId为随机生成长度?12 位App 生命周期内保持不?
/// 为了排查可能的解析问题,需要您<EFBFBD><EFBFBD>?sessionId 和解析出<EFBFBD><EFBFBD>?IP 一起记录在日志<EFBFBD><EFBFBD>? /// 为了排查可能的解析问题,需要您?sessionId 和解析出?IP 一起记录在日志?
/// 请参<EFBFBD><EFBFBD>? 解析异常排查<EFBFBD><EFBFBD>?“会话追踪方案<EFBFBD><EFBFBD>?https://help.TrustAPP.com/document_detail/100530.html /// 请参? 解析异常排查?“会话追踪方案?https://help.TrustAPP.com/document_detail/100530.html
- (NSString *)getSessionId; - (NSString *)getSessionId;
/// 同步解析域名,会阻塞当前线程,直到从缓存中获取到有效解析结果,或者从服务器拿到最新解析结<EFBFBD><EFBFBD>? /// 同步解析域名,会阻塞当前线程,直到从缓存中获取到有效解析结果,或者从服务器拿到最新解析结?
/// 如果允许复用过期的解析结果且存在过期结果的情况下,会先返回这个结果,然后启动后台线程去更新解析结<EFBFBD><EFBFBD>? /// 如果允许复用过期的解析结果且存在过期结果的情况下,会先返回这个结果,然后启动后台线程去更新解析结?
/// 为了防止在主线程中误用本接口导致APP卡顿本接口会做检测若发现调用线程是主线程则自动降级到resolveHostSyncNonBlocking接口的实现逻辑<EFBFBD><EFBFBD>? /// 为了防止在主线程中误用本接口导致APP卡顿本接口会做检测若发现调用线程是主线程则自动降级到resolveHostSyncNonBlocking接口的实现逻辑?
/// @param host 需要解析的域名 /// @param host 需要解析的域名
/// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6 /// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6
/// @return 解析结果 /// @return 解析结果
- (nullable HttpdnsResult *)resolveHostSync:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType; - (nullable HttpdnsResult *)resolveHostSync:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType;
/// 同步解析域名,会阻塞当前线程,直到从缓存中获取到有效解析结果,或者从服务器拿到最新解析结<EFBFBD><EFBFBD>? /// 同步解析域名,会阻塞当前线程,直到从缓存中获取到有效解析结果,或者从服务器拿到最新解析结?
/// 如果允许复用过期的解析结果且存在过期结果的情况下,会先返回这个结果,然后启动后台线程去更新解析结<EFBFBD><EFBFBD>? /// 如果允许复用过期的解析结果且存在过期结果的情况下,会先返回这个结果,然后启动后台线程去更新解析结?
/// 为了防止在主线程中误用本接口导致APP卡顿本接口会做检测若发现调用线程是主线程则自动降级到resolveHostSyncNonBlocking接口的实现逻辑<EFBFBD><EFBFBD>? /// 为了防止在主线程中误用本接口导致APP卡顿本接口会做检测若发现调用线程是主线程则自动降级到resolveHostSyncNonBlocking接口的实现逻辑?
/// @param host 需要解析的域名 /// @param host 需要解析的域名
/// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6 /// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6
/// @param sdnsParams 如果域名配置了sdns自定义解析通过此参数携带自定义参数 /// @param sdnsParams 如果域名配置了sdns自定义解析通过此参数携带自定义参数
@@ -279,22 +279,22 @@ NS_ASSUME_NONNULL_BEGIN
/// @return 解析结果 /// @return 解析结果
- (nullable HttpdnsResult *)resolveHostSync:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType withSdnsParams:(NSDictionary<NSString *, NSString *> *)sdnsParams sdnsCacheKey:(NSString *)cacheKey; - (nullable HttpdnsResult *)resolveHostSync:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType withSdnsParams:(NSDictionary<NSString *, NSString *> *)sdnsParams sdnsCacheKey:(NSString *)cacheKey;
/// 同步解析域名,会阻塞当前线程,直到从缓存中获取到有效解析结果,或者从服务器拿到最新解析结<EFBFBD><EFBFBD>? /// 同步解析域名,会阻塞当前线程,直到从缓存中获取到有效解析结果,或者从服务器拿到最新解析结?
/// 如果允许复用过期的解析结果且存在过期结果的情况下,会先返回这个结果,然后启动后台线程去更新解析结<EFBFBD><EFBFBD>? /// 如果允许复用过期的解析结果且存在过期结果的情况下,会先返回这个结果,然后启动后台线程去更新解析结?
/// 为了防止在主线程中误用本接口导致APP卡顿本接口会做检测若发现调用线程是主线程则自动降级到resolveHostSyncNonBlocking接口的实现逻辑<EFBFBD><EFBFBD>? /// 为了防止在主线程中误用本接口导致APP卡顿本接口会做检测若发现调用线程是主线程则自动降级到resolveHostSyncNonBlocking接口的实现逻辑?
/// @param request 请求参数对象 /// @param request 请求参数对象
/// @return 解析结果 /// @return 解析结果
- (nullable HttpdnsResult *)resolveHostSync:(HttpdnsRequest *)request; - (nullable HttpdnsResult *)resolveHostSync:(HttpdnsRequest *)request;
/// 异步解析域名,不会阻塞当前线程,会在从缓存中获取到有效结果,或从服务器拿到最新解析结果后,通过回调返回结果 /// 异步解析域名,不会阻塞当前线程,会在从缓存中获取到有效结果,或从服务器拿到最新解析结果后,通过回调返回结果
/// 如果允许复用过期的解析结果且存在过期结果的情况下,会先在回调中返回这个结果,然后启动后台线程去更新解析结<EFBFBD><EFBFBD>? /// 如果允许复用过期的解析结果且存在过期结果的情况下,会先在回调中返回这个结果,然后启动后台线程去更新解析结?
/// @param host 需要解析的域名 /// @param host 需要解析的域名
/// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6 /// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6
/// @handler 解析结果回调 /// @handler 解析结果回调
- (void)resolveHostAsync:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType completionHandler:(void (^)(HttpdnsResult * nullable))handler; - (void)resolveHostAsync:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType completionHandler:(void (^)(HttpdnsResult * nullable))handler;
/// 异步解析域名,不会阻塞当前线程,会在从缓存中获取到有效结果,或从服务器拿到最新解析结果后,通过回调返回结果 /// 异步解析域名,不会阻塞当前线程,会在从缓存中获取到有效结果,或从服务器拿到最新解析结果后,通过回调返回结果
/// 如果允许复用过期的解析结果且存在过期结果的情况下,会先在回调中返回这个结果,然后启动后台线程去更新解析结<EFBFBD><EFBFBD>? /// 如果允许复用过期的解析结果且存在过期结果的情况下,会先在回调中返回这个结果,然后启动后台线程去更新解析结?
/// @param host 需要解析的域名 /// @param host 需要解析的域名
/// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6 /// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6
/// @param sdnsParams 如果域名配置了sdns自定义解析通过此参数携带自定义参数 /// @param sdnsParams 如果域名配置了sdns自定义解析通过此参数携带自定义参数
@@ -303,20 +303,20 @@ NS_ASSUME_NONNULL_BEGIN
- (void)resolveHostAsync:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType withSdnsParams:(nullable NSDictionary<NSString *, NSString *> *)sdnsParams sdnsCacheKey:(nullable NSString *)cacheKey completionHandler:(void (^)(HttpdnsResult * nullable))handler; - (void)resolveHostAsync:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType withSdnsParams:(nullable NSDictionary<NSString *, NSString *> *)sdnsParams sdnsCacheKey:(nullable NSString *)cacheKey completionHandler:(void (^)(HttpdnsResult * nullable))handler;
/// 异步解析域名,不会阻塞当前线程,会在从缓存中获取到有效结果,或从服务器拿到最新解析结果后,通过回调返回结果 /// 异步解析域名,不会阻塞当前线程,会在从缓存中获取到有效结果,或从服务器拿到最新解析结果后,通过回调返回结果
/// 如果允许复用过期的解析结果且存在过期结果的情况下,会先在回调中返回这个结果,然后启动后台线程去更新解析结<EFBFBD><EFBFBD>? /// 如果允许复用过期的解析结果且存在过期结果的情况下,会先在回调中返回这个结果,然后启动后台线程去更新解析结?
/// @param request 请求参数对象 /// @param request 请求参数对象
/// @handler 解析结果回调 /// @handler 解析结果回调
- (void)resolveHostAsync:(HttpdnsRequest *)request completionHandler:(void (^)(HttpdnsResult * nullable))handler; - (void)resolveHostAsync:(HttpdnsRequest *)request completionHandler:(void (^)(HttpdnsResult * nullable))handler;
/// 伪异步解析域名,不会阻塞当前线程,首次解析结果可能为<EFBFBD><EFBFBD>? /// 伪异步解析域名,不会阻塞当前线程,首次解析结果可能为?
/// 先查询缓存,缓存中存在有效结<EFBFBD><EFBFBD>?未过期,或者过期但配置了可以复用过期解析结<EFBFBD><EFBFBD>?,则直接返回结果,如果缓存未命中,则发起异步解析请求 /// 先查询缓存,缓存中存在有效结?未过期,或者过期但配置了可以复用过期解析结?,则直接返回结果,如果缓存未命中,则发起异步解析请求
/// @param host 需要解析的域名 /// @param host 需要解析的域名
/// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6 /// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6
/// @return 解析结果 /// @return 解析结果
- (nullable HttpdnsResult *)resolveHostSyncNonBlocking:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType; - (nullable HttpdnsResult *)resolveHostSyncNonBlocking:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType;
/// 伪异步解析域名,不会阻塞当前线程,首次解析结果可能为<EFBFBD><EFBFBD>? /// 伪异步解析域名,不会阻塞当前线程,首次解析结果可能为?
/// 先查询缓存,缓存中存在有效结<EFBFBD><EFBFBD>?未过期,或者过期但配置了可以复用过期解析结<EFBFBD><EFBFBD>?,则直接返回结果,如果缓存未命中,则发起异步解析请求 /// 先查询缓存,缓存中存在有效结?未过期,或者过期但配置了可以复用过期解析结?,则直接返回结果,如果缓存未命中,则发起异步解析请求
/// @param host 需要解析的域名 /// @param host 需要解析的域名
/// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6 /// @param queryIpType 可设置为自动选择ipv4ipv6. 设置为自动选择时会自动根据当前所处网络环境选择解析ipv4或ipv6
/// @param sdnsParams 如果域名配置了sdns自定义解析通过此参数携带自定义参数 /// @param sdnsParams 如果域名配置了sdns自定义解析通过此参数携带自定义参数
@@ -324,8 +324,8 @@ NS_ASSUME_NONNULL_BEGIN
/// @return 解析结果 /// @return 解析结果
- (nullable HttpdnsResult *)resolveHostSyncNonBlocking:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType withSdnsParams:(nullable NSDictionary<NSString *, NSString *> *)sdnsParams sdnsCacheKey:(nullable NSString *)cacheKey; - (nullable HttpdnsResult *)resolveHostSyncNonBlocking:(NSString *)host byIpType:(HttpdnsQueryIPType)queryIpType withSdnsParams:(nullable NSDictionary<NSString *, NSString *> *)sdnsParams sdnsCacheKey:(nullable NSString *)cacheKey;
/// 伪异步解析域名,不会阻塞当前线程,首次解析结果可能为<EFBFBD><EFBFBD>? /// 伪异步解析域名,不会阻塞当前线程,首次解析结果可能为?
/// 先查询缓存,缓存中存在有效结<EFBFBD><EFBFBD>?未过期,或者过期但配置了可以复用过期解析结<EFBFBD><EFBFBD>?,则直接返回结果,如果缓存未命中,则发起异步解析请求 /// 先查询缓存,缓存中存在有效结?未过期,或者过期但配置了可以复用过期解析结?,则直接返回结果,如果缓存未命中,则发起异步解析请求
/// @param request 请求参数对象 /// @param request 请求参数对象
/// @return 解析结果 /// @return 解析结果
- (nullable HttpdnsResult *)resolveHostSyncNonBlocking:(HttpdnsRequest *)request; - (nullable HttpdnsResult *)resolveHostSyncNonBlocking:(HttpdnsRequest *)request;
@@ -333,34 +333,34 @@ NS_ASSUME_NONNULL_BEGIN
/// 获取域名对应的IP单IP /// 获取域名对应的IP单IP
/// @param host 域名 /// @param host 域名
- (NSString *)getIpByHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSString *)getIpByHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 异步接口首次结果可能为空获取域名对应的IP数组多IP /// 异步接口首次结果可能为空获取域名对应的IP数组多IP
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
- (NSArray *)getIpsByHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSArray *)getIpsByHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 异步接口首次结果可能为空获取域名对应的ipv6, 单IP 需要开启ipv6 开<EFBFBD><EFBFBD>?enableIPv6<EFBFBD><EFBFBD>? /// 异步接口首次结果可能为空获取域名对应的ipv6, 单IP 需要开启ipv6 开?enableIPv6?
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
- (NSString *)getIPv6ByHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSString *)getIPv6ByHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 异步接口首次结果可能为空获取域名对应的ipv6数组, 多IP 需要开启ipv6 开<EFBFBD><EFBFBD>?enableIPv6<EFBFBD><EFBFBD>? /// 异步接口首次结果可能为空获取域名对应的ipv6数组, 多IP 需要开启ipv6 开?enableIPv6?
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
- (NSArray *)getIPv6sByHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSArray *)getIPv6sByHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 同时获取ipv4 ipv6的IP 需要开启ipv6 开<EFBFBD><EFBFBD>?enableIPv6<EFBFBD><EFBFBD>? /// 同时获取ipv4 ipv6的IP 需要开启ipv6 开?enableIPv6?
/// @param host 域名 /// @param host 域名
/// @result 返回字典类型结构 /// @result 返回字典类型结构
/// { /// {
/// NewHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'], /// TrustHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'],
/// NewHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx'] /// TrustHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx']
/// } /// }
- (NSDictionary <NSString *, NSArray *>*)getIPv4_v6ByHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSDictionary <NSString *, NSArray *>*)getIPv4_v6ByHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 根据当前设备的网络状态自动返回域名对应的 IPv4/IPv6地址<EFBFBD><EFBFBD>? /// 根据当前设备的网络状态自动返回域名对应的 IPv4/IPv6地址?
/// 使用此API 需要确<EFBFBD><EFBFBD>?enableIPv6 开关已打开 /// 使用此API 需要确?enableIPv6 开关已打开
/// 设备网络 返回域名IP /// 设备网络 返回域名IP
/// IPv4 Only IPv4 /// IPv4 Only IPv4
/// IPv6 Only IPv6 如果没有Pv6返回空 /// IPv6 Only IPv6 如果没有Pv6返回空
@@ -368,75 +368,75 @@ NS_ASSUME_NONNULL_BEGIN
/// @param host 要解析的域名 /// @param host 要解析的域名
/// @result 返回字典类型结构 /// @result 返回字典类型结构
/// { /// {
/// NewHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'], /// TrustHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'],
/// NewHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx'] /// TrustHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx']
/// } /// }
-(NSDictionary <NSString *, NSArray *>*)autoGetIpsByHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); -(NSDictionary <NSString *, NSArray *>*)autoGetIpsByHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 异步接口首次结果可能为空获取域名对应的IPv4地址单IPv4 /// 异步接口首次结果可能为空获取域名对应的IPv4地址单IPv4
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
- (NSString *)getIPv4ForHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSString *)getIPv4ForHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 异步接口首次结果可能为空获取域名对应的IP数组多IP /// 异步接口首次结果可能为空获取域名对应的IP数组多IP
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
- (NSArray *)getIPv4ListForHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSArray *)getIPv4ListForHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 获取IPv4地址列表同步接口必须在子线程中执行否则会转变为异步接口 /// 获取IPv4地址列表同步接口必须在子线程中执行否则会转变为异步接口
/// 同步接口有超时机制,超时时间为[HttpDnsService sharedInstance].timeoutInterval, 但是超时上限<EFBFBD><EFBFBD>?s<><73>? /// 同步接口有超时机制,超时时间为[HttpDnsService sharedInstance].timeoutInterval, 但是超时上限?s?
/// 即使[HttpDnsService sharedInstance].timeoutInterval设置的时间大<EFBFBD><EFBFBD>?s同步接口也最多阻塞当前线<EFBFBD><EFBFBD>?s /// 即使[HttpDnsService sharedInstance].timeoutInterval设置的时间大?s同步接口也最多阻塞当前线?s
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起同步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起同步解析请?
/// @param host 域名 /// @param host 域名
- (NSArray *)getIPv4ListForHostSync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSync:byIpType:] instead."); - (NSArray *)getIPv4ListForHostSync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSync:byIpType:] instead.");
/// 异步接口首次结果可能为空获取域名对应的ipv6, 单IP 需要开启ipv6 开<EFBFBD><EFBFBD>?enableIPv6<EFBFBD><EFBFBD>? /// 异步接口首次结果可能为空获取域名对应的ipv6, 单IP 需要开启ipv6 开?enableIPv6?
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
- (NSString *)getIPv6ForHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSString *)getIPv6ForHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 异步接口首次结果可能为空获取域名对应的ipv6数组, 多IP 需要开启ipv6 开<EFBFBD><EFBFBD>?enableIPv6<EFBFBD><EFBFBD>? /// 异步接口首次结果可能为空获取域名对应的ipv6数组, 多IP 需要开启ipv6 开?enableIPv6?
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
- (NSArray *)getIPv6ListForHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSArray *)getIPv6ListForHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 获取IPv6地址列表同步接口必须在子线程中执行否则会转变为异步接口 /// 获取IPv6地址列表同步接口必须在子线程中执行否则会转变为异步接口
/// 同步接口有超时机制,超时时间为[HttpDnsService sharedInstance].timeoutInterval, 但是超时上限<EFBFBD><EFBFBD>?s<><73>? /// 同步接口有超时机制,超时时间为[HttpDnsService sharedInstance].timeoutInterval, 但是超时上限?s?
/// 即使[HttpDnsService sharedInstance].timeoutInterval设置的时间大<EFBFBD><EFBFBD>?s同步接口也最多阻塞当前线<EFBFBD><EFBFBD>?s /// 即使[HttpDnsService sharedInstance].timeoutInterval设置的时间大?s同步接口也最多阻塞当前线?s
/// @param host 域名 /// @param host 域名
- (NSArray *)getIPv6ListForHostSync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSync:byIpType:] instead."); - (NSArray *)getIPv6ListForHostSync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSync:byIpType:] instead.");
/// 异步接口首次结果可能为空获取域名对应格式化后的IP (针对ipv6) /// 异步接口首次结果可能为空获取域名对应格式化后的IP (针对ipv6)
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
- (NSString *)getIpByHostAsyncInURLFormat:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSString *)getIpByHostAsyncInURLFormat:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 异步接口首次结果可能为空同时获取ipv4 ipv6的IP 需要开启ipv6 开<EFBFBD><EFBFBD>?enableIPv6<EFBFBD><EFBFBD>? /// 异步接口首次结果可能为空同时获取ipv4 ipv6的IP 需要开启ipv6 开?enableIPv6?
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
/// @result 返回字典类型结构 /// @result 返回字典类型结构
/// { /// {
/// NewHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'], /// TrustHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'],
/// NewHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx'] /// TrustHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx']
/// } /// }
- (NSDictionary <NSString *, NSArray *>*)getHttpDnsResultHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); - (NSDictionary <NSString *, NSArray *>*)getHttpDnsResultHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// NOTE: 同步接口,必须在子线程中执行,否则会转变为异步接<EFBFBD><EFBFBD>? /// NOTE: 同步接口,必须在子线程中执行,否则会转变为异步接?
/// 同步接口有超时机制,超时时间为[HttpDnsService sharedInstance].timeoutInterval, 但是超时上限<EFBFBD><EFBFBD>?s<><73>? /// 同步接口有超时机制,超时时间为[HttpDnsService sharedInstance].timeoutInterval, 但是超时上限?s?
/// 即使[HttpDnsService sharedInstance].timeoutInterval设置的时间大<EFBFBD><EFBFBD>?s同步接口也最多阻塞当前线<EFBFBD><EFBFBD>?s /// 即使[HttpDnsService sharedInstance].timeoutInterval设置的时间大?s同步接口也最多阻塞当前线?s
/// 同时获取ipv4 + ipv6的IP 需要开启ipv6 开<EFBFBD><EFBFBD>?enableIPv6<EFBFBD><EFBFBD>? /// 同时获取ipv4 + ipv6的IP 需要开启ipv6 开?enableIPv6?
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
/// @result 返回字典类型结构 /// @result 返回字典类型结构
/// { /// {
/// NewHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'], /// TrustHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'],
/// NewHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx'] /// TrustHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx']
/// } /// }
- (NSDictionary <NSString *, NSArray *>*)getHttpDnsResultHostSync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSync:byIpType:] instead."); - (NSDictionary <NSString *, NSArray *>*)getHttpDnsResultHostSync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSync:byIpType:] instead.");
/// 异步接口,首次结果可能为空,根据当前设备的网络状态自动返回域名对应的 IPv4/IPv6地址<EFBFBD><EFBFBD>? /// 异步接口,首次结果可能为空,根据当前设备的网络状态自动返回域名对应的 IPv4/IPv6地址?
/// 使用此API 需要确<EFBFBD><EFBFBD>?enableIPv6 开关已打开 /// 使用此API 需要确?enableIPv6 开关已打开
/// 设备网络 返回域名IP /// 设备网络 返回域名IP
/// IPv4 Only IPv4 /// IPv4 Only IPv4
/// IPv6 Only IPv6 如果没有Pv6返回空 /// IPv6 Only IPv6 如果没有Pv6返回空
@@ -444,33 +444,33 @@ NS_ASSUME_NONNULL_BEGIN
/// @param host 要解析的域名 /// @param host 要解析的域名
/// @result 返回字典类型结构 /// @result 返回字典类型结构
/// { /// {
/// NewHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'], /// TrustHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'],
/// NewHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx'] /// TrustHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx']
/// } /// }
-(NSDictionary <NSString *, NSArray *>*)autoGetHttpDnsResultForHostAsync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead."); -(NSDictionary <NSString *, NSArray *>*)autoGetHttpDnsResultForHostAsync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:] instead.");
/// 根据当前设备的网络状态自动返回域名对应的 IPv4/IPv6地址组同步接口必须在子线程中执行否则会转变为异步接<EFBFBD><EFBFBD>? /// 根据当前设备的网络状态自动返回域名对应的 IPv4/IPv6地址组同步接口必须在子线程中执行否则会转变为异步接?
/// 同步接口有超时机制,超时时间为[HttpDnsService sharedInstance].timeoutInterval, 但是超时上限<EFBFBD><EFBFBD>?s<><73>? /// 同步接口有超时机制,超时时间为[HttpDnsService sharedInstance].timeoutInterval, 但是超时上限?s?
/// 即使[HttpDnsService sharedInstance].timeoutInterval设置的时间大<EFBFBD><EFBFBD>?s同步接口也最多阻塞当前线<EFBFBD><EFBFBD>?s /// 即使[HttpDnsService sharedInstance].timeoutInterval设置的时间大?s同步接口也最多阻塞当前线?s
/// 根据当前网络栈自动获取ipv4 ipv6的IP 需要开启ipv6 开<EFBFBD><EFBFBD>?enableIPv6<EFBFBD><EFBFBD>? /// 根据当前网络栈自动获取ipv4 ipv6的IP 需要开启ipv6 开?enableIPv6?
/// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请<EFBFBD><EFBFBD>? /// 先查询缓存,缓存中存在未过期的结果,则直接返回结果,如果缓存未命中,则发起异步解析请?
/// @param host 域名 /// @param host 域名
/// @result 返回字典类型结构 /// @result 返回字典类型结构
/// { /// {
/// NewHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'], /// TrustHDNS_IPV4: ['xxx.xxx.xxx.xxx', 'xxx.xxx.xxx.xxx'],
/// NewHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx'] /// TrustHDNS_IPV6: ['xx:xx:xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx:xx:xx']
/// } /// }
- (NSDictionary <NSString *, NSArray *>*)autoGetHttpDnsResultForHostSync:(NSString *)host NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSync:byIpType:] instead."); - (NSDictionary <NSString *, NSArray *>*)autoGetHttpDnsResultForHostSync:(NSString *)host Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSync:byIpType:] instead.");
/// 软件自定义解析接<EFBFBD><EFBFBD>? /// 软件自定义解析接?
- (NSDictionary *)getIpsByHostAsync:(NSString *)host withParams:(NSDictionary<NSString *, NSString *> *)params withCacheKey:(NSString *)cacheKey NEW_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:withSdnsParams:sdnsCacheKey:] instead."); - (NSDictionary *)getIpsByHostAsync:(NSString *)host withParams:(NSDictionary<NSString *, NSString *> *)params withCacheKey:(NSString *)cacheKey Trust_HTTPDNS_DEPRECATED("Deprecated. Use -[HttpDnsService resolveHostSyncNonBlocking:byIpType:withSdnsParams:sdnsCacheKey:] instead.");
/// 清除指定host缓存<EFBFBD><EFBFBD>?沙盒数据库) /// 清除指定host缓存内?沙盒数据库)
/// @param hostArray 需要清除的host域名数组。如果需要清空全部数据传nil或者空数组即可 /// @param hostArray 需要清除的host域名数组。如果需要清空全部数据传nil或者空数组即可
- (void)cleanHostCache:(nullable NSArray<NSString *> *)hostArray; - (void)cleanHostCache:(nullable NSArray<NSString *> *)hostArray;
/// 清除当前所有host缓存 (内存+沙盒数据<EFBFBD><EFBFBD>? /// 清除当前所有host缓存 (内存+沙盒数据?
- (void)cleanAllHostCache; - (void)cleanAllHostCache;
/// 清理已经配置的软件自定义解析全局参数 /// 清理已经配置的软件自定义解析全局参数

View File

@@ -0,0 +1,21 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import <Foundation/Foundation.h>
// 澶存枃浠跺寘鍚渶浣跨敤鐩稿鐩綍锛岀‘淇濋

View File

@@ -1,24 +1,30 @@
// //
// HttpdnsRequest.h // HttpdnsRequest.h
// NewHttpDNS // TrustHttpDNS
// //
// Created by xuyecan on 2024/5/19. // Created by xuyecan on 2024/5/19.
// Copyright © 2024 trustapp.com. All rights reserved. // Copyright ツゥ 2024 trustapp.com. All rights reserved.
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
#ifndef NewHTTPDNSQUERYIPTYPE #ifndef NEWHTTPDNSQUERYIPTYPE
#define NewHTTPDNSQUERYIPTYPE #define NEWHTTPDNSQUERYIPTYPE
typedef enum { typedef enum {
NewHttpDNS_IPTypeV4 = 0, //ipv4 NewHttpDNS_IPTypeV4 = 0, //ipv4
NewHttpDNS_IPTypeV6 = 1, //ipv6 NewHttpDNS_IPTypeV6 = 1, //ipv6
NewHttpDNS_IPTypeV64 = 2, //ipv4 + ipv6 NewHttpDNS_IPTypeDual = 2, //both
} NewHttpDNS_IPType; } NewHttpDNS_IPType;
// Compatibility aliases
typedef NewHttpDNS_IPType TrustHttpDNS_IPType;
#define TrustHttpDNS_IPTypeV4 NewHttpDNS_IPTypeV4
#define TrustHttpDNS_IPTypeV6 NewHttpDNS_IPTypeV6
#define TrustHttpDNS_IPTypeV64 NewHttpDNS_IPTypeDual
typedef NS_OPTIONS(NSUInteger, HttpdnsQueryIPType) { typedef NS_OPTIONS(NSUInteger, HttpdnsQueryIPType) {
HttpdnsQueryIPTypeAuto NS_SWIFT_NAME(auto) = 0, HttpdnsQueryIPTypeAuto NS_SWIFT_NAME(auto) = 0,
HttpdnsQueryIPTypeIpv4 = 1 << 0, HttpdnsQueryIPTypeIpv4 = 1 << 0,
@@ -30,27 +36,27 @@ typedef NS_OPTIONS(NSUInteger, HttpdnsQueryIPType) {
@interface HttpdnsRequest : NSObject @interface HttpdnsRequest : NSObject
/// 需要解析的域名 /// 髴€隕∬ァ」譫千噪蝓溷錐
@property (nonatomic, copy) NSString *host; @property (nonatomic, copy) NSString *host;
/// 解析超时时间对于同步接口即为最大等待时间对于异步接口即为最大等待回调时<EFBFBD><EFBFBD>? /// 隗」譫占カ<EFBFBD>慮譌カ髣エ<EFBFBD>悟ッケ莠主酔豁・謗・蜿」<EFBFBD>悟叉荳コ譛€螟ァ遲牙セ<EFBFBD>慮髣エ<EFBFBD>悟ッケ莠主シよュ・謗・蜿」<EFBFBD>悟叉荳コ譛€螟ァ遲牙セ<EFBFBD>屓隹<EFBFBD>慮髣?
/// 默认<EFBFBD><EFBFBD>?秒,取值必须在0.5<EFBFBD><EFBFBD>?- 5秒之<EFBFBD><EFBFBD>? /// 鮟倩ョ、蛟?遘抵シ悟叙蛟シ蠢<EFBDBC>。サ蝨ィ0.5?- 5遘剃ケ矩<EFBFBD>?
@property (nonatomic, assign) double resolveTimeoutInSecond; @property (nonatomic, assign) double resolveTimeoutInSecond;
/// 查询IP类型 /// 譟・隸「IP邀サ蝙<EFBFBD>
/// 默认为HttpdnsQueryIPTypeAuto此类型下SDK至少会请求解析ipv4地址若判断到当前网络环境支持ipv6则还会请求解析ipv6地址 /// 鮟倩ョ、荳コHttpdnsQueryIPTypeAuto<EFBFBD>梧ュ、邀サ蝙倶ク具シ郡DK閾ウ蟆台シ夊ッキ豎りァ」譫進pv4蝨ー蝮€<EFBFBD>瑚凶蛻、譁ュ蛻ー蠖灘燕鄂醍サ懃識蠅<EFBFBD>髪謖pv6<EFBFBD><EFBFBD>霑倅シ夊ッキ豎りァ」譫進pv6蝨ー蝮€
/// HttpdnsQueryIPTypeIpv4只请求解析ipv4 /// HttpdnsQueryIPTypeIpv4<EFBFBD>悟宵隸キ豎りァ」譫進pv4
/// HttpdnsQueryIPTypeIpv6只请求解析ipv6 /// HttpdnsQueryIPTypeIpv6<EFBFBD>悟宵隸キ豎りァ」譫進pv6
/// HttpdnsQueryIPTypeBoth不管当前网络环境是什么会尝试同时请求解析ipv4地址和ipv6地址这种用法通常需要拿到结果之后自行判断网络环境决定使用哪个结<EFBFBD><EFBFBD>? /// HttpdnsQueryIPTypeBoth<EFBFBD>御ク咲ョ。蠖灘燕鄂醍サ懃識蠅<EFBFBD>弍莉€荵茨シ御シ壼ー晁ッ募酔譌カ隸キ豎りァ」譫進pv4蝨ー蝮€蜥景pv6蝨ー蝮€<EFBFBD>瑚ソ咏ァ咲畑豕包シ碁€壼クク髴€隕∵響蛻ー扈捺棡荵句錘閾ェ陦悟愛譁ュ鄂醍サ懃識蠅<EFBFBD><EFBFBD>螳壻スソ逕ィ蜩ェ荳ェ扈捺<EFBFBD>?
@property (nonatomic, assign) HttpdnsQueryIPType queryIpType; @property (nonatomic, assign) HttpdnsQueryIPType queryIpType;
/// SDNS参数,针对软件自定义解析场景使用 /// SDNS蜿よ焚<EFBFBD>碁宙蟇ケ霓ッ莉カ閾ェ螳壻ケ芽ァ」譫仙惻譎ッ菴ソ逕ィ
@property (nonatomic, copy, nullable) NSDictionary<NSString *, NSString *> *sdnsParams; @property (nonatomic, copy, nullable) NSDictionary<NSString *, NSString *> *sdnsParams;
/// 缓存Key针对软件自定义解析场景使用 /// 郛灘ュ婁ey<EFBFBD>碁宙蟇ケ霓ッ莉カ閾ェ螳壻ケ芽ァ」譫仙惻譎ッ菴ソ逕ィ
@property (nonatomic, copy, nullable) NSString *cacheKey; @property (nonatomic, copy, nullable) NSString *cacheKey;
/// 请求所属的账号ID用于在多账号场景下定位实例 /// 隸キ豎よ園螻樒噪雍ヲ蜿キID<EFBFBD>檎畑莠主惠螟夊エヲ蜿キ蝨コ譎ッ荳句ョ壻ス榊ョ樔セ<EFBFBD>
@property (nonatomic, assign) NSInteger accountId; @property (nonatomic, assign) NSInteger accountId;
- (instancetype)initWithHost:(NSString *)host queryIpType:(HttpdnsQueryIPType)queryIpType; - (instancetype)initWithHost:(NSString *)host queryIpType:(HttpdnsQueryIPType)queryIpType;

View File

@@ -37,8 +37,8 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
@interface HttpdnsScheduleCenter () @interface HttpdnsScheduleCenter ()
// v4v6<EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>? // v4v6??
// <EFBFBD><EFBFBD>? // ?
@property (nonatomic, assign) int currentActiveServiceHostIndex; @property (nonatomic, assign) int currentActiveServiceHostIndex;
@property (nonatomic, assign) int currentActiveUpdateHostIndex; @property (nonatomic, assign) int currentActiveUpdateHostIndex;
@@ -85,7 +85,7 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
_scheduleCenterResultPath = [[HttpdnsPersistenceUtils scheduleCenterResultDirectory] _scheduleCenterResultPath = [[HttpdnsPersistenceUtils scheduleCenterResultDirectory]
stringByAppendingPathComponent:kScheduleRegionConfigLocalCacheFileName]; stringByAppendingPathComponent:kScheduleRegionConfigLocalCacheFileName];
// <EFBFBD><EFBFBD>? // ?
_lastScheduleCenterConnectDate = [NSDate dateWithTimeIntervalSinceNow:(- 24 * 60 * 60)]; _lastScheduleCenterConnectDate = [NSDate dateWithTimeIntervalSinceNow:(- 24 * 60 * 60)];
} }
return self; return self;
@@ -93,11 +93,11 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
- (void)initRegion:(NSString *)region { - (void)initRegion:(NSString *)region {
if (![[HttpdnsRegionConfigLoader getAvailableRegionList] containsObject:region]) { if (![[HttpdnsRegionConfigLoader getAvailableRegionList] containsObject:region]) {
region = NEW_HTTPDNS_DEFAULT_REGION_KEY; region = Trust_HTTPDNS_DEFAULT_REGION_KEY;
} }
// region<EFBFBD><EFBFBD>? // region?
// region<EFBFBD><EFBFBD>? // region?
[self initServerListByRegion:region]; [self initServerListByRegion:region];
// //
@@ -112,7 +112,7 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
self.currentActiveUpdateHostIndex = 0; self.currentActiveUpdateHostIndex = 0;
}); });
// region<EFBFBD><EFBFBD>? // region?
[self asyncUpdateRegionScheduleConfig]; [self asyncUpdateRegionScheduleConfig];
} }
@@ -125,7 +125,7 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
} }
NSDictionary *scheduleCenterResult = (NSDictionary *)obj; NSDictionary *scheduleCenterResult = (NSDictionary *)obj;
// NSNumber/NSStringNSNull<EFBFBD><EFBFBD>? // NSNumber/NSStringNSNull?
id ts = [scheduleCenterResult objectForKey:kLastUpdateUnixTimestampKey]; id ts = [scheduleCenterResult objectForKey:kLastUpdateUnixTimestampKey];
if ([ts respondsToSelector:@selector(doubleValue)]) { if ([ts respondsToSelector:@selector(doubleValue)]) {
NSDate *lastUpdateDate = [NSDate dateWithTimeIntervalSince1970:[ts doubleValue]]; NSDate *lastUpdateDate = [NSDate dateWithTimeIntervalSince1970:[ts doubleValue]];
@@ -135,7 +135,7 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
}); });
} }
// <EFBFBD><EFBFBD>? // ?
- (void)asyncUpdateRegionConfigAfterAtLeast:(NSTimeInterval)interval { - (void)asyncUpdateRegionConfigAfterAtLeast:(NSTimeInterval)interval {
__block BOOL shouldUpdate = NO; __block BOOL shouldUpdate = NO;
dispatch_sync(_scheduleConfigLocalOperationQueue, ^{ dispatch_sync(_scheduleConfigLocalOperationQueue, ^{
@@ -170,10 +170,10 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
if (error || !scheduleCenterResult) { if (error || !scheduleCenterResult) {
HttpdnsLogDebug("Update region config failed, error: %@", error); HttpdnsLogDebug("Update region config failed, error: %@", error);
// <EFBFBD><EFBFBD>? // ?
[self rotateUpdateServerHost]; [self rotateUpdateServerHost];
// 3<EFBFBD><EFBFBD>? // 3?
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((retryCount + 1) * NSEC_PER_SEC)), self->_scheduleFetchConfigAsyncQueue, ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((retryCount + 1) * NSEC_PER_SEC)), self->_scheduleFetchConfigAsyncQueue, ^{
[self asyncUpdateRegionScheduleConfigAtRetry:retryCount + 1]; [self asyncUpdateRegionScheduleConfigAtRetry:retryCount + 1];
}); });
@@ -192,8 +192,8 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
} }
- (void)updateRegionConfig:(NSDictionary *)scheduleCenterResult { - (void)updateRegionConfig:(NSDictionary *)scheduleCenterResult {
NSArray *v4Result = [scheduleCenterResult objectForKey:kNewHttpdnsRegionConfigV4HostKey]; NSArray *v4Result = [scheduleCenterResult objectForKey:kTrustHttpdnsRegionConfigV4HostKey];
NSArray *v6Result = [scheduleCenterResult objectForKey:kNewHttpdnsRegionConfigV6HostKey]; NSArray *v6Result = [scheduleCenterResult objectForKey:kTrustHttpdnsRegionConfigV6HostKey];
dispatch_sync(_scheduleConfigLocalOperationQueue, ^{ dispatch_sync(_scheduleConfigLocalOperationQueue, ^{
HttpdnsRegionConfigLoader *regionConfigLoader = [HttpdnsRegionConfigLoader sharedInstance]; HttpdnsRegionConfigLoader *regionConfigLoader = [HttpdnsRegionConfigLoader sharedInstance];
@@ -260,7 +260,7 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
}); });
if (timeToUpdate) { if (timeToUpdate) {
// server<EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?0 // server??0
[self asyncUpdateRegionConfigAfterAtLeast:30]; [self asyncUpdateRegionConfigAfterAtLeast:30];
} }
} }
@@ -286,8 +286,8 @@ static int const MAX_UPDATE_RETRY_COUNT = 2;
} }
- (NSString *)currentActiveServiceServerV4Host { - (NSString *)currentActiveServiceServerV4Host {
// <EFBFBD><EFBFBD>? // ?
// httpdns<EFBFBD><EFBFBD>? // httpdns?
[self asyncUpdateRegionConfigAfterAtLeast:(24 * 60 * 60)]; [self asyncUpdateRegionConfigAfterAtLeast:(24 * 60 * 60)];
// HTTPDNS_DEBUG_V4_SERVICE_IP // HTTPDNS_DEBUG_V4_SERVICE_IP

View File

@@ -4,11 +4,11 @@
<dict> <dict>
<key>appId</key> <key>appId</key>
<string>app1flndpo9</string> <string>app1flndpo9</string>
<key>primaryServiceHost</key> <key>apiUrl</key>
<string>httpdns.deepwaf.xyz</string> <string>https://httpdns.deepwaf.xyz:8445</string>
<key>servicePort</key>
<integer>8445</integer>
<key>signSecret</key> <key>signSecret</key>
<string></string> <string>ss_67fb8471a45b</string>
<key>serviceDomain</key>
<string>demo.cloudxdr.com</string>
</dict> </dict>
</plist> </plist>

View File

@@ -1,6 +1,8 @@
// //
// DemoConfigLoader.h // DemoConfigLoader.h
// NewHttpDNSTestDemo // TrustHttpDNSTestDemo
//
// @author Created by Claude Code on 2025-10-05
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@@ -8,10 +10,15 @@
@interface DemoConfigLoader : NSObject @interface DemoConfigLoader : NSObject
@property (nonatomic, copy, readonly) NSString *appId; @property (nonatomic, copy, readonly) NSString *appId;
@property (nonatomic, copy, readonly) NSString *primaryServiceHost; @property (nonatomic, copy, readonly) NSString *apiUrl; // Full raw URL
@property (nonatomic, assign, readonly) NSInteger servicePort; @property (nonatomic, copy, readonly) NSString *apiHost; // Parsed host
@property (nonatomic, copy, readonly, nullable) NSString *signSecret; @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; + (instancetype)shared;
@end @end

View File

@@ -1,15 +1,18 @@
// //
// DemoConfigLoader.m // DemoConfigLoader.m
// NewHttpDNSTestDemo // TrustHttpDNSTestDemo
//
// @author Created by Claude Code on 2025-10-05
// //
#import "DemoConfigLoader.h" #import "DemoConfigLoader.h"
@implementation DemoConfigLoader { @implementation DemoConfigLoader {
NSString *_appId; NSString *_appId;
NSString *_primaryServiceHost; NSString *_apiUrl; // Unified API URL
NSInteger _servicePort;
NSString *_signSecret; NSString *_signSecret;
NSString *_serviceDomain;
BOOL _hasValidConfig;
} }
+ (instancetype)shared { + (instancetype)shared {
@@ -28,23 +31,51 @@
return self; return self;
} }
// Bundle > ?accountID ф?
- (void)loadConfig { - (void)loadConfig {
NSDictionary *dict = nil; _appId = @"";
_apiUrl = @"";
_signSecret = @"";
_serviceDomain = @"";
_hasValidConfig = 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) {
dict = [NSDictionary dictionaryWithContentsOfFile:plistPath]; bundleDict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
} }
_appId = dict[@"appId"] ?: @""; NSDictionary *env = [[NSProcessInfo processInfo] environment];
_primaryServiceHost = dict[@"primaryServiceHost"] ?: @"";
_servicePort = [dict[@"servicePort"] integerValue] ?: 443; NSString *appId = bundleDict[@"appId"] ?: @"";
NSString *secret = dict[@"signSecret"] ?: @""; NSString *apiUrl = bundleDict[@"apiUrl"] ?: @"";
_signSecret = secret.length > 0 ? secret : nil; 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 *)appId { return _appId; }
- (NSString *)primaryServiceHost { return _primaryServiceHost; } - (NSString *)apiUrl { return _apiUrl; }
- (NSInteger)servicePort { return _servicePort; }
- (NSString *)signSecret { return _signSecret; } - (NSString *)signSecret { return _signSecret; }
- (NSString *)serviceDomain { return _serviceDomain; }
- (BOOL)hasValidConfig { return _hasValidConfig; }
@end @end

View File

@@ -1,10 +1,14 @@
// //
// DemoHttpdnsScenario.h // DemoHttpdnsScenario.h
// NewHttpDNSTestDemo // TrustHttpDNSTestDemo
//
// @author Created by Claude Code on 2025-10-23
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "DemoResolveModel.h" #import "DemoResolveModel.h"
#import "HttpdnsEdgeService.h"
#import "HttpdnsPublicConstant.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@@ -13,7 +17,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface DemoHttpdnsScenarioConfig : NSObject <NSCopying> @interface DemoHttpdnsScenarioConfig : NSObject <NSCopying>
@property (nonatomic, copy) NSString *host; @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; - (instancetype)init;
@@ -34,10 +43,9 @@ 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)resolve;
- (void)resolveSyncNonBlocking;
- (void)resolveSync;
- (NSString *)logSnapshot; - (NSString *)logSnapshot;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -1,11 +1,12 @@
// //
// 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
@@ -14,8 +15,12 @@
- (instancetype)init { - (instancetype)init {
if (self = [super init]) { if (self = [super init]) {
_host = @"demo.cloudxdr.com"; NSString *serviceDomain = [DemoConfigLoader shared].serviceDomain;
_queryType = @"A"; _host = serviceDomain.length > 0 ? serviceDomain : @"www.new.com";
_ipType = HttpdnsQueryIPTypeBoth;
// _httpsEnabled = YES;
// _persistentCacheEnabled = YES;
// _reuseExpiredIPEnabled = YES;
} }
return self; return self;
} }
@@ -23,7 +28,10 @@
- (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.queryType = self.queryType; cfg.ipType = self.ipType;
// cfg.httpsEnabled = self.httpsEnabled;
// cfg.persistentCacheEnabled = self.persistentCacheEnabled;
// cfg.reuseExpiredIPEnabled = self.reuseExpiredIPEnabled;
return cfg; return cfg;
} }
@@ -48,75 +56,174 @@
_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];
self.service = [[HttpdnsEdgeService alloc] if (cfg.hasValidConfig) {
initWithAppId:cfg.appId self.service = [[HttpdnsEdgeService alloc] initWithAppId:cfg.appId apiUrl:cfg.apiUrl signSecret:cfg.signSecret];
primaryServiceHost:cfg.primaryServiceHost [self log:[NSString stringWithFormat:@"Init HttpdnsEdgeService success!"]];
backupServiceHost:nil [self log:[NSString stringWithFormat:@"== Config details =="]];
servicePort:cfg.servicePort [self log:[NSString stringWithFormat:@"appId: %@", cfg.appId]];
signSecret:cfg.signSecret]; [self log:[NSString stringWithFormat:@"apiUrl: %@", cfg.apiUrl]];
[self appendLog:[NSString stringWithFormat:@"[init] appId=%@ host=%@:%ld", cfg.appId, cfg.primaryServiceHost, (long)cfg.servicePort]]; [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 { - (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 log:[NSString stringWithFormat:@"Apply UI config: host=%@, ipType=%ld", self.config.host, (long)self.config.ipType]];
} }
- (void)resolve { - (void)resolve {
[self resolveSyncNonBlocking]; NSString *queryHost = [self currentHost];
} HttpdnsQueryIPType ipType = self.config.ipType;
- (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";
NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0; NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
[self appendLog:[NSString stringWithFormat:@"[resolve] host=%@ type=%@", host, queryType]]; [self log:[NSString stringWithFormat:@"--- Start Resolve Request ---"]];
[self log:[NSString stringWithFormat:@"Target Host: %@", queryHost]];
__weak typeof(self) weakSelf = self; if (ipType == HttpdnsQueryIPTypeBoth) {
[self.service resolveHost:host queryType:queryType completion:^(HttpdnsEdgeResolveResult *result, NSError *error) { [self log:@"Query Type: BOTH (mapped to concurrent A & AAAA)"];
[weakSelf handleResult:result host:host queryType:queryType start:startMs error:error]; 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];
}]; }];
} }
- (void)resolveSync {
[self resolveSyncNonBlocking];
} }
- (void)handleResult:(HttpdnsEdgeResolveResult *)result - (NSString *)logSnapshot {
host:(NSString *)host __block NSString *snapshot = @"";
queryType:(NSString *)queryType dispatch_sync(self.logQueue, ^{
start:(NSTimeInterval)startMs snapshot = [self.logBuffer copy];
error:(NSError *)error { });
NSTimeInterval elapsedMs = [[NSDate date] timeIntervalSince1970] * 1000.0 - startMs; return snapshot;
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 : @"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(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
self.model.host = host; self.model.host = host;
if (result != nil) { self.model.ipType = ipType;
self.model.ipv4s = result.ipv4s;
self.model.ipv6s = result.ipv6s; NSTimeInterval endMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
self.model.ttlV4 = result.ipv4s.count > 0 ? result.ttl : 0; self.model.elapsedMs = endMs - startMs;
self.model.ttlV6 = result.ipv6s.count > 0 ? result.ttl : 0;
self.model.error = nil; if (error) {
} else {
self.model.ipv4s = @[]; self.model.ipv4s = @[];
self.model.ipv6s = @[]; 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; id<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
if (delegate != nil) { if (delegate != nil) {
@@ -125,9 +232,13 @@
}); });
} }
- (void)appendLog:(NSString *)msg { // logger & ttl delegates removed since EdgeService lacks these protocol hooks in this SDK version
if (msg.length == 0) return;
NSString *line = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], msg]; - (void)log:(NSString *)logStr {
if (logStr.length == 0) {
return;
}
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;
@@ -139,12 +250,6 @@
}); });
} }
- (NSString *)logSnapshot {
__block NSString *snapshot = @"";
dispatch_sync(self.logQueue, ^{
snapshot = [self.logBuffer copy];
});
return snapshot;
}
@end @end

View File

@@ -1,18 +1,29 @@
// //
// DemoResolveModel.h // DemoResolveModel.h
// NewHttpDNSTestDemo // TrustHttpDNSTestDemo
//
// @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;
@property (nonatomic, copy) NSString *businessRequestResult;
- (void)updateWithResult:(HttpdnsResult *)result startTimeMs:(NSTimeInterval)startMs;
@end @end

View File

@@ -1,7 +1,9 @@
// //
// DemoResolveModel.m // DemoResolveModel.m
// NewHttpDNSTestDemo // NewHttpDNSTestDemo
// //
// @author Created by Claude Code on 2025-10-05
//
#import "DemoResolveModel.h" #import "DemoResolveModel.h"
@@ -9,15 +11,32 @@
- (instancetype)init { - (instancetype)init {
if (self = [super init]) { if (self = [super init]) {
_host = @"demodemo.cloudxdr.com"; _host = @"demo.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

View File

@@ -1,7 +1,9 @@
// //
// DemoViewController.m // DNSDemoViewController.m
// NewHttpDNSTestDemo // NewHttpDNSTestDemo
// //
// @author Created by Claude Code on 2025-10-05
//
#import "DemoViewController.h" #import "DemoViewController.h"
#import "DemoResolveModel.h" #import "DemoResolveModel.h"
@@ -18,14 +20,20 @@
@property (nonatomic, strong) UIStackView *stack; @property (nonatomic, strong) UIStackView *stack;
@property (nonatomic, strong) UITextField *hostField; @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 *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;
@property (nonatomic, strong) UIButton *btnResolve;
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
@end @end
@implementation DemoViewController @implementation DemoViewController
@@ -39,8 +47,7 @@
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] self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"日志" style:UIBarButtonItemStylePlain target:self action:@selector(onShowLog)];
initWithTitle:@"日志" style:UIBarButtonItemStylePlain target:self action:@selector(onShowLog)];
} }
- (void)buildUI { - (void)buildUI {
@@ -67,36 +74,45 @@
[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 = @"demodemo.cloudxdr.com"; self.hostField.placeholder = @"YOUR_SERVICE_DOMAIN";
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"];
UIStackView *row2 = [self labeledRow:@"类型"]; self.ipTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"IPv4", @"IPv6", @"Both"]];
self.queryTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"A (IPv4)", @"AAAA (IPv6)", @"both"]]; self.ipTypeSeg.selectedSegmentIndex = [self segmentIndexForIpType:self.scenarioConfig.ipType];
self.queryTypeSeg.selectedSegmentIndex = 0; [self.ipTypeSeg addTarget:self action:@selector(onIPTypeChanged:) forControlEvents:UIControlEventValueChanged];
[self.queryTypeSeg addTarget:self action:@selector(onQueryTypeChanged:) forControlEvents:UIControlEventValueChanged]; [row2 addArrangedSubview:self.ipTypeSeg];
[row2 addArrangedSubview:self.queryTypeSeg];
[self.stack addArrangedSubview:row2]; [self.stack addArrangedSubview:row2];
// // Removed options stack from UI
UIButton *btnResolve = [self filledButton:@"Resolve" action:@selector(onResolve)];
[self.stack addArrangedSubview:btnResolve]; 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"]; UIStackView *info = [self labeledRow:@"Info"];
self.elapsedLabel = [self monoLabel:@"elapsed: - ms"]; 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.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];
@@ -108,66 +124,18 @@
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;
@@ -185,6 +153,8 @@
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;
} }
@@ -200,6 +170,128 @@
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];
}
// 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 #pragma mark - DemoHttpdnsScenarioDelegate
- (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model { - (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model {

View File

@@ -1,33 +1,41 @@
# HTTPDNS iOS SDK (SNI Hidden v1.0.0) # HTTPDNS iOS SDK (Edge Service v1.0.0)
## 1. Init ## 1. Init
Import the umbrella header and initialize the `HttpdnsEdgeService`.
```objc ```objc
#import <TrustHTTPDNS/TrustHTTPDNS.h> #import <NewHttpDNS/NewHttpDNS.h>
HttpdnsEdgeService *service = [[HttpdnsEdgeService alloc] HttpdnsEdgeService *service = [[HttpdnsEdgeService alloc]
initWithAppId:@"app1f1ndpo9" initWithAppId:@"app1flndpo9"
primaryServiceHost:@"httpdns-a.example.com" apiUrl:@"https://httpdns.deepwaf.xyz:8445"
backupServiceHost:@"httpdns-b.example.com" signSecret:@"your-sign-secret"];
servicePort:443
signSecret:@"your-sign-secret"]; // optional if sign is enabled
``` ```
## 2. Resolve ## 2. Resolve
Support for `A` and `AAAA` query types.
```objc ```objc
[service resolveHost:@"api.business.com" queryType:@"A" completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) { [service resolveHost:@"demo.cloudxdr.com" queryType:@"A" completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) {
if (error != nil) { if (error != nil) {
NSLog(@"Resolve error: %@", error.localizedDescription);
return; return;
} }
NSLog(@"requestId=%@ ipv4=%@ ipv6=%@ ttl=%ld", result.requestId, result.ipv4s, result.ipv6s, (long)result.ttl); NSLog(@"requestId=%@ ipv4=%@ ipv6=%@ ttl=%ld", result.requestId, result.ipv4s, result.ipv6s, (long)result.ttl);
}]; }];
``` ```
## 3. Official Request Adapter (IP + Host) > [!NOTE]
> If you need to resolve both IPv4 and IPv6 simultaneously (Dual Stack), please perform two separate requests or implement concurrent logic, as the Edge backend strictly validates `qtype` as either `A` or `AAAA`.
## 3. Business Request (SNI Support)
Use the built-in request adapter to connect to resolved IPs via HTTPS while preserving the `Host` header for SNI validation.
```objc ```objc
NSURL *url = [NSURL URLWithString:@"https://api.business.com/v1/ping"]; NSURL *url = [NSURL URLWithString:@"https://demo.cloudxdr.com/v1/ping"];
[service requestURL:url method:@"GET" headers:@{@"Accept": @"application/json"} body:nil completion:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) { [service requestURL:url method:@"GET" headers:@{@"Accept": @"application/json"} body:nil completion:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) {
if (error != nil) { if (error != nil) {
return; return;
@@ -36,23 +44,15 @@ NSURL *url = [NSURL URLWithString:@"https://api.business.com/v1/ping"];
}]; }];
``` ```
Behavior is fixed: ## 4. Key Improvements
- Resolve by `/resolve`. - **Security Policy**: Automatically handles certificate validation for IP-based HTTPS requests (no SNI mismatch).
- Connect to resolved IP over HTTPS. - **Modern API**: Asynchronous block-based API for efficient network operations.
- Keep `Host` header as business domain. - **Clean Configuration**: Uses HMAC-SHA256 signing for secure resolution requests.
- No fallback to domain direct request.
## 4. Public Errors ## 5. Deprecated Parameters
- `NO_IP_AVAILABLE` Do NOT use legacy parameters from `HttpDnsService`:
- `TLS_EMPTY_SNI_FAILED`
- `HOST_ROUTE_REJECTED`
- `RESOLVE_SIGN_INVALID`
## 5. Removed Public Params
Do not expose legacy public parameters:
- `accountId` - `accountId`
- `serviceDomain` - `secretKey`
- `endpoint`
- `aesSecretKey` - `aesSecretKey`
- `serviceDomain` (use `dn` parameter or `host` in API)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,41 @@
import time
import hmac
import hashlib
import uuid
import urllib.parse
import urllib.request
import ssl
def test_qtype(qtype):
appId = 'app1flndpo9'
host = 'demo.cloudxdr.com'
signSecret = 'ss_67fb8471a45b'
exp = str(int(time.time()) + 600)
nonce = uuid.uuid4().hex.replace('-', '')
raw = f"{appId}|{host.lower()}|{qtype.upper()}|{exp}|{nonce}"
sign = hmac.new(signSecret.encode(), raw.encode(), hashlib.sha256).hexdigest()
q_enc = urllib.parse.quote_plus(qtype)
url = f"https://httpdns.deepwaf.xyz:8445/resolve?appId={appId}&dn={host}&qtype={q_enc}&sid=test&sdk_version=ios&os=ios&exp={exp}&nonce={nonce}&sign={sign}"
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
req = urllib.request.Request(url)
with urllib.request.urlopen(req, context=ctx, timeout=5) as response:
print(f"Testing qtype '{qtype}': HTTP {response.status} - {response.read().decode('utf-8')}")
except urllib.error.HTTPError as e:
print(f"Testing qtype '{qtype}': HTTP {e.code} - {e.read().decode('utf-8')}")
except Exception as e:
print(f"Testing qtype '{qtype}': Error {e}")
test_qtype("A")
test_qtype("ADDNS")
test_qtype("BOTH")
test_qtype("A,AAAA")
test_qtype("4,6")
test_qtype("AAAA")