feat: sync httpdns sdk/platform updates without large binaries
@@ -1,64 +0,0 @@
|
||||
1.4.8版本Changelog(HTTPDNS功能全量发布)
|
||||
|
||||
1、通过智能解析把用户就近调度到最优节点,显著降低首包时延与连接抖动。
|
||||
2、支持按地域、运营商、国内/海外精细分流,提升弱网与跨网访问稳定性。
|
||||
3、解析链路内置签名鉴权与请求追踪,增强安全性与可观测性。
|
||||
4、无命中规则时自动回源兜底,保障解析连续可用。
|
||||
5、支持 A/AAAA 双栈与多记录返回,兼容不同终端网络环境。
|
||||
6、提供Android、iOS、Flutter 多端SDK 开箱可用,支持预解析、缓存与同步/非阻塞解析。
|
||||
7、提供 IP 直连适配能力(含 Host 保留与 No-SNI 模式),适配复杂 HTTPS 场景。
|
||||
8、控制台支持应用/域名/规则全流程配置与在线验证,缩短问题定位和发布周期。
|
||||
9、节点支持在线安装升级与日志上报,降低运维复杂度并提升可维护性。
|
||||
|
||||
1.4.8版本升级步骤
|
||||
|
||||
1、备份现有配置与数据:
|
||||
将 edge-api、edge-admin、edge-user 等组件目录下的 configs 文件夹,以及平台的 MySQL 数据库进行全量备份;
|
||||
|
||||
2、停止旧版本进程(管理端各组件):
|
||||
killall -9 edge-api
|
||||
killall -9 edge-admin
|
||||
killall -9 edge-user
|
||||
|
||||
3、上传并解压新版本包(以 Linux x64 环境为例):
|
||||
unzip -o edge-admin-linux-amd64-v1.4.8.zip -d /data/
|
||||
unzip -o edge-user-linux-amd64-v1.4.8.zip -d /data/
|
||||
|
||||
4、依次运行edge-api、edge-admin、edge-user
|
||||
|
||||
# 启动 API 服务
|
||||
cd /data/edge-api/bin
|
||||
chmod +x edge-api
|
||||
nohup ./edge-api 2>&1 &
|
||||
|
||||
# 启动管理后台
|
||||
cd /data/edge-admin/bin
|
||||
chmod +x edge-admin
|
||||
nohup ./edge-admin 2>&1 &
|
||||
|
||||
# 启动租户控制台
|
||||
cd /data/edge-user/bin
|
||||
chmod +x edge-user
|
||||
nohup ./edge-user 2>&1 &
|
||||
|
||||
5、检查版本状态:
|
||||
登录管理后台,确认系统版本显示为 1.4.8;
|
||||
|
||||
6、配置主备集群:
|
||||
进入“HTTPDNS -> 集群列表 -> 集群设置”,按需勾选“默认主集群”或“默认备用集群”角色,以便后续应用自动关联;
|
||||
|
||||
7、在线升级 HTTPDNS 节点:
|
||||
进入“HTTPDNS -> 节点列表”,点击对应节点的“详情”,在“安装信息”页面点击 **[在线安装]** 或 **[升级]**。系统会自动下发最新的 edge-httpdns 二进制文件并完成重启。
|
||||
|
||||
8、验证节点在线状态:
|
||||
等待 30 秒左右,确认节点状态恢复为“在线”,并验证硬件负载监控数据是否正常上报。
|
||||
|
||||
9、业务解析验证:
|
||||
使用控制台“解析测试”工具,验证域名在当前环境下是否能正确返回调度的 IP 地址。
|
||||
|
||||
10、完成升级。
|
||||
|
||||
特别说明:
|
||||
1、在线升级模式:Edge HTTPDNS 节点支持通过管理平台一键在线升级,无需手动上传文件和重启进程。
|
||||
2、离线安装模式:如节点服务器无法连接控制台,可手动上传 edge-httpdns 压缩包并解压,更新 bin 目录下的程序文件后手动执行 `./edge-httpdns restart` 即可。
|
||||
3、SNI 隐匿功能:请确保关联的 CDN 边缘节点也已同步更新至配套版本(会自动升级)。
|
||||
@@ -1,133 +0,0 @@
|
||||
# Flutter SDK 集成文档(Edge HTTPDNS)
|
||||
|
||||
## 1. 版本与依赖
|
||||
|
||||
- SDK 插件:`EdgeHttpDNS/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 尝试均失败,请检查网络权限及服务端防火墙。
|
||||
@@ -1,165 +0,0 @@
|
||||
# HTTPDNS SDK 集成文档(Android)
|
||||
|
||||
## 1. 版本与依赖
|
||||
|
||||
- SDK 模块:`EdgeHttpDNS/sdk/android/httpdns-sdk`
|
||||
- `minSdkVersion`:19
|
||||
- `targetSdkVersion`:33
|
||||
- `compileSdk`:33
|
||||
|
||||
将发布包中的 `jar/aar` 放到应用模块 `libs/`,在 `app/build.gradle` 中添加:
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
}
|
||||
```
|
||||
|
||||
## 2. SNI 行为说明(关键)
|
||||
|
||||
当前 SDK 行为与代码一致:
|
||||
|
||||
1. `/resolve` 请求链路(SDK -> 你的 HTTPDNS 服务域名)
|
||||
- 走域名 HTTPS
|
||||
- 默认 TLS 行为(会带 SNI)
|
||||
|
||||
2. 业务请求链路(拿到 CDN IP 后发起业务 HTTPS)
|
||||
- 使用 `HttpDnsHttpAdapter`:按 IP 建连,`Host` 保留原域名,并清空 SNI(No-SNI)
|
||||
|
||||
## 3. 初始化 SDK(推荐用 V1 客户端)
|
||||
|
||||
### Kotlin
|
||||
|
||||
```kotlin
|
||||
import com.new.sdk.android.httpdns.HttpDnsV1Client
|
||||
import com.new.sdk.android.httpdns.HttpDnsService
|
||||
|
||||
val service: HttpDnsService = HttpDnsV1Client.init(
|
||||
context = applicationContext,
|
||||
appId = "your-app-id",
|
||||
primaryServiceHost = "httpdns.example.com",
|
||||
backupServiceHost = "httpdns-backup.example.com", // 可空字符串
|
||||
servicePort = 443,
|
||||
signSecret = "your-sign-secret" // 可空字符串
|
||||
)
|
||||
```
|
||||
|
||||
### Java
|
||||
|
||||
```java
|
||||
import com.new.sdk.android.httpdns.HttpDnsService;
|
||||
import com.new.sdk.android.httpdns.HttpDnsV1Client;
|
||||
|
||||
HttpDnsService service = HttpDnsV1Client.init(
|
||||
getApplicationContext(),
|
||||
"your-app-id",
|
||||
"httpdns.example.com",
|
||||
"httpdns-backup.example.com", // 可传 ""
|
||||
443,
|
||||
"your-sign-secret" // 可传 ""
|
||||
);
|
||||
```
|
||||
|
||||
## 4. 解析域名获取 CDN IP
|
||||
|
||||
### Kotlin
|
||||
|
||||
```kotlin
|
||||
import com.new.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
|
||||
import com.new.sdk.android.httpdns.HTTPDNSResult;
|
||||
|
||||
HTTPDNSResult result = HttpDnsV1Client.resolveHost(
|
||||
service,
|
||||
"api.example.com",
|
||||
"A",
|
||||
null
|
||||
);
|
||||
|
||||
String[] ips = result.getIps();
|
||||
```
|
||||
|
||||
## 5. 业务请求接入方式
|
||||
|
||||
#使用 `HttpDnsHttpAdapter`(IP 直连 + No-SNI)
|
||||
|
||||
业务请求侧做“隐匿 SNI”。该适配器仅支持 HTTPS URL。
|
||||
|
||||
```kotlin
|
||||
import com.new.sdk.android.httpdns.HttpDnsV1Client
|
||||
import com.new.sdk.android.httpdns.network.HttpDnsAdapterOptions
|
||||
import com.new.sdk.android.httpdns.network.HttpDnsAdapterRequest
|
||||
|
||||
val adapter = HttpDnsV1Client.buildHttpClientAdapter(
|
||||
service,
|
||||
HttpDnsAdapterOptions.Builder()
|
||||
.setConnectTimeoutMillis(3000)
|
||||
.setReadTimeoutMillis(5000)
|
||||
.setRequestIpType(com.new.sdk.android.httpdns.RequestIpType.auto)
|
||||
.build()
|
||||
)
|
||||
|
||||
val req = HttpDnsAdapterRequest(
|
||||
"GET",
|
||||
"https://api.example.com/path?x=1"
|
||||
)
|
||||
|
||||
val resp = adapter.execute(req)
|
||||
val code = resp.statusCode
|
||||
val bodyBytes = resp.body
|
||||
val usedIp = resp.usedIp
|
||||
```
|
||||
|
||||
## 6. 预解析与常用接口
|
||||
|
||||
```kotlin
|
||||
service.setPreResolveHosts(listOf("api.example.com", "img.example.com"))
|
||||
|
||||
val r1 = service.getHttpDnsResultForHostSync("api.example.com", com.new.sdk.android.httpdns.RequestIpType.auto)
|
||||
val r2 = service.getHttpDnsResultForHostSyncNonBlocking("api.example.com", com.new.sdk.android.httpdns.RequestIpType.auto)
|
||||
```
|
||||
|
||||
- `Sync`:允许阻塞等待刷新结果(上限受 timeout 等配置影响)
|
||||
- `NonBlocking`:快速返回当前可用缓存/结果,不阻塞等待
|
||||
|
||||
## 7. 验证建议
|
||||
|
||||
1. 验证 `/resolve`
|
||||
- 抓包看目标应为 `https://<serviceHost>:<servicePort>/resolve...`
|
||||
|
||||
2. 验证业务请求(若使用 `HttpDnsHttpAdapter`)
|
||||
- 目标地址应是 CDN IP
|
||||
- HTTP `Host` 应为原域名
|
||||
- TLS ClientHello 不应携带 SNI(No-SNI)
|
||||
|
||||
## 8. 混淆配置
|
||||
|
||||
```proguard
|
||||
-keep class com.new.sdk.android.** { *; }
|
||||
```
|
||||
|
||||
## 9. 常见问题
|
||||
|
||||
1. HTTPDNS 没生效
|
||||
- 检查是否真正使用了 SDK 返回 IP(或用了 `HttpDnsHttpAdapter`)
|
||||
- 检查失败回退逻辑是否总是直接走了系统 DNS
|
||||
|
||||
2. 使用 `HttpDnsHttpAdapter` 仍失败
|
||||
- 只支持 HTTPS URL
|
||||
|
||||
3. 线上不要开启不安全证书
|
||||
- `HttpDnsAdapterOptions.Builder#setAllowInsecureCertificatesForDebugOnly(true)` 仅限调试环境
|
||||
@@ -1,147 +0,0 @@
|
||||
# HTTPDNS 主计划(V1.2 现行设计)
|
||||
|
||||
## 1. 目标
|
||||
1. 构建独立的 HTTPDNS 管理闭环:集群、应用、域名、自定义解析、访问日志、运行日志、解析测试。
|
||||
2. 方案以当前已确认的页面与交互为准,不保留历史分支设计。
|
||||
3. 优先保证可运维、可灰度、可观测,先落地稳定版本。
|
||||
|
||||
## 2. 信息架构(菜单)
|
||||
1. 左侧 HTTPDNS 菜单顺序:
|
||||
- 集群管理
|
||||
- 应用管理
|
||||
- 访问日志
|
||||
- 运行日志
|
||||
- 解析测试
|
||||
2. 不再保留独立“全局配置”菜单。
|
||||
3. 不再保留独立“SDK接入引导”菜单。
|
||||
|
||||
## 3. 核心设计约束
|
||||
1. SNI 防护策略固定为“隐匿 SNI”(不提供 level1/level3、mask/empty切换入口)。
|
||||
2. 服务入口按“集群服务域名”管理,不使用全局单入口。
|
||||
3. 回源协议默认 HTTPS,不提供开关。
|
||||
4. 域名校验默认开启,不在用户侧暴露开关。
|
||||
5. 自定义解析先做精简版:不支持 SDNS 参数匹配。
|
||||
|
||||
## 4. 集群管理
|
||||
|
||||
### 4.1 集群列表
|
||||
1. 字段:集群名称、服务域名、节点数、在线节点数、状态、操作。
|
||||
2. 操作:节点列表、集群设置、删除集群。
|
||||
|
||||
### 4.2 节点列表
|
||||
1. 字段:节点名称、IP、CPU、内存、负载、状态、操作。
|
||||
2. IP 列不展示“[下线]/[宕机]”附加标识。
|
||||
|
||||
### 4.3 集群设置
|
||||
1. 页面布局对齐智能DNS:左侧菜单 + 右侧配置区(`left-box with-menu` / `right-box with-menu`)。
|
||||
2. 配置分组:
|
||||
- 基础设置
|
||||
- TLS
|
||||
3. 基础设置字段:
|
||||
- 集群名称
|
||||
- 服务域名
|
||||
- 默认解析 TTL(秒)
|
||||
- 降级超时容忍度(毫秒)
|
||||
- 节点安装根目录(默认 `/opt/edge-httpdns`)
|
||||
- 启用当前集群
|
||||
- 默认集群(勾选)
|
||||
4. TLS 配置:
|
||||
- 样式与交互对齐智能DNS TLS页
|
||||
- 维护并绑定该集群服务域名使用的证书
|
||||
- 保证节点回源链路为 HTTPS
|
||||
|
||||
## 5. 应用管理
|
||||
|
||||
### 5.1 应用列表
|
||||
1. 字段:应用名称、AppID、绑定域名数、状态、操作。
|
||||
2. 操作:域名列表、应用设置、SDK集成、删除应用。
|
||||
3. 删除应用页面交互风格对齐“删除集群”页面。
|
||||
|
||||
### 5.2 应用设置
|
||||
1. 页面布局对齐智能DNS设置页风格(左侧菜单 + 右侧配置区)。
|
||||
2. 分组:
|
||||
- 基础配置
|
||||
- 认证与密钥
|
||||
3. 基础配置字段:
|
||||
- AppID(只读)
|
||||
- 主集群
|
||||
- 备集群(可选)
|
||||
- 应用启用
|
||||
- SNI 防护配置(文案展示:隐匿 SNI)
|
||||
4. 认证与密钥字段:
|
||||
- 请求验签(状态展示 + 独立启停按钮 + 确认提示)
|
||||
- 加签 Secret(查看、复制、重置、最近更新时间)
|
||||
5. 交互约束:
|
||||
- 强制 HTTPS 传输,不再提供独立“数据加密 Secret”配置项。
|
||||
- 密钥操作区使用紧凑图标样式,减少视觉噪音。
|
||||
|
||||
## 6. 域名管理与自定义解析
|
||||
|
||||
### 6.1 域名管理
|
||||
1. 页面采用框架标准顶部 tab + 面包屑样式。
|
||||
2. 字段:
|
||||
- 域名列表
|
||||
- 规则策略(仅展示数字,表示该域名规则数,可点击)
|
||||
- 操作(自定义解析、解绑)
|
||||
3. 入口:在域名行操作中直接进入“自定义解析”。
|
||||
|
||||
### 6.2 自定义解析(精简版)
|
||||
1. 规则字段:
|
||||
- 规则名称
|
||||
- 线路
|
||||
- 解析记录值
|
||||
- TTL
|
||||
- 状态
|
||||
2. 线路联动:
|
||||
- 第一层:`中国地区` / `境外`
|
||||
- 中国地区:运营商 -> 大区 -> 省份
|
||||
- 境外:洲 -> 国家/地区(亚洲内使用中国香港/中国澳门/中国台湾)
|
||||
3. 解析记录:
|
||||
- 每条规则最多 10 条
|
||||
- 支持 A / AAAA
|
||||
- 可开启权重调度
|
||||
4. 不包含 SDNS 参数配置。
|
||||
|
||||
## 7. 访问日志
|
||||
1. 菜单与页面文案统一为“访问日志”(不再使用“解析日志”)。
|
||||
2. 页面结构:筛选区与列表区分离,间距与智能DNS访问日志风格一致。
|
||||
3. 列字段:集群、节点、域名、类型、概要。
|
||||
4. 概要展示:
|
||||
- 使用单行拼接
|
||||
- 按“访问信息 -> 解析结果”顺序
|
||||
- 不显示字段名堆叠
|
||||
|
||||
## 8. 运行日志
|
||||
1. 字段与智能DNS运行日志保持一致。
|
||||
2. 级别样式使用文字着色,不使用大块背景色。
|
||||
|
||||
## 9. 解析测试
|
||||
1. 去掉“API在线沙盒”标题与右侧等待占位图标区。
|
||||
2. 解析配置区标题统一为“解析测试”。
|
||||
3. 测试参数保留:目标应用、所属集群、解析域名、模拟客户端IP、解析类型(A/AAAA)。
|
||||
4. 所属集群使用下拉框选择。
|
||||
5. 解析域名使用下拉框选择(按当前目标应用联动展示)。
|
||||
6. 去掉 SDNS 参数。
|
||||
7. 结果区保留核心结果展示,参考阿里云风格的简洁结果布局。
|
||||
|
||||
## 10. SDK 集成
|
||||
1. SDK 集成不作为左侧独立菜单。
|
||||
2. 在“应用管理”操作列进入 SDK 集成页。
|
||||
3. 页面只保留:
|
||||
- SDK 下载
|
||||
- 集成文档
|
||||
4. 卡片与按钮采用紧凑布局,避免按钮区域拥挤。
|
||||
|
||||
## 11. SDK 与服务端接口(现行)
|
||||
1. 解析接口:`/resolve`
|
||||
- SDK 请求域名解析结果的主接口。
|
||||
2. 启动配置接口:`/bootstrap`(规划/联调口径)
|
||||
- 用于 SDK 获取可用服务域名与策略参数(替代节点IP调度模式)。
|
||||
3. SDK 地址策略:优先主集群服务域名,不可用时切备集群;失败后按客户端降级策略处理。
|
||||
|
||||
## 12. 本文明确不包含
|
||||
1. 不包含全局配置独立页面设计。
|
||||
2. 不包含 SDK 接入向导独立菜单设计。
|
||||
3. 不包含 SNI level1/level3、ECH 控制台、Public SNI 池分级配置。
|
||||
4. 不包含 SDNS 参数匹配链路。
|
||||
5. 不包含第三方 DNS 依赖与复用方案。
|
||||
@@ -1,255 +0,0 @@
|
||||
# HTTPDNS后端开发计划(V1.0)
|
||||
|
||||
## 0. 文档信息
|
||||
- 目标文件:`EdgeHttpDNS/HTTPDNS后端开发计划.md`
|
||||
- 交付范围:`EdgeAdmin + EdgeAPI + EdgeHttpDNS + SDK对接接口`
|
||||
- 交付策略:一次性全量交付(非分阶段)
|
||||
- 核心约束:
|
||||
- 仅新协议
|
||||
- 无 `/bootstrap`
|
||||
- `/resolve` 使用 GET 参数
|
||||
- 线路匹配仅基于客户端 IP 归属
|
||||
- 独立 `edgeHTTPDNS*` 数据表
|
||||
- 新增独立节点角色 `NodeRoleHTTPDNS`
|
||||
- 访问日志 MySQL + ClickHouse 双写(查询优先 ClickHouse)
|
||||
- 不复用智能DNS(EdgeDNS/NS)模块的 DAO/Service/Store,HTTPDNS 独立实现(可参考并复制所需能力)
|
||||
|
||||
## 1. 目标与成功标准
|
||||
1. 将 HTTPDNS 从当前 Admin 侧 mock/store 方案落地为真实后端能力。
|
||||
2. 打通“配置 -> 下发 -> 解析 -> 日志 -> 查询”闭环。
|
||||
3. 与当前前端设计严格对齐:
|
||||
- 菜单:集群管理、应用管理、访问日志、运行日志、解析测试
|
||||
- SNI 固定为“隐匿 SNI”
|
||||
- 自定义解析不含 SDNS 参数
|
||||
4. 成功标准:
|
||||
- 管理页面均通过 RPC 读取真实数据,无本地 mock 依赖
|
||||
- `/resolve` 可按应用/域名/线路返回解析结果
|
||||
- 访问日志与运行日志可查询、可筛选、可分页
|
||||
- 节点配置与状态可下发和回传
|
||||
- 主备集群服务域名可在应用设置中配置并生效
|
||||
- EdgeHttpDNS 支持 SNI 与 Host 解耦路由,并可执行 WAF 动态验签与隐匿 SNI 转发
|
||||
|
||||
## 2. 架构与边界
|
||||
### 2.1 服务边界
|
||||
1. EdgeAdmin:仅负责页面动作与 RPC 编排,不存业务状态。
|
||||
2. EdgeAPI:负责数据存储、策略匹配、接口服务、日志汇聚。
|
||||
3. EdgeHttpDNS(HTTPDNS节点):负责执行解析、上报运行日志/访问日志、接收任务。
|
||||
4. SDK:手动配置应用关联主备服务域名,调用 `/resolve` 获取结果。
|
||||
|
||||
### 2.2 不做项
|
||||
1. 不做 `/bootstrap` 接口。
|
||||
2. 不做 ECH / SNI 分级策略。
|
||||
3. 不做 SDNS 参数匹配。
|
||||
4. 不做第三方 DNS 依赖复用。
|
||||
|
||||
## 3. 公开接口与契约(需新增/调整)
|
||||
### 3.1 HTTP 解析接口
|
||||
1. `GET /resolve`
|
||||
2. 请求参数:
|
||||
- `appId`(必填)
|
||||
- `dn`(必填)
|
||||
- `qtype`(可选,A/AAAA,默认 A)
|
||||
- `cip`(可选)
|
||||
- `sid`、`sdk_version`、`os`(可选,用于日志)
|
||||
- `nonce`、`exp`、`sign`(可选,验签开启时必需)
|
||||
3. 响应结构(统一):
|
||||
- `code`、`message`、`requestId`
|
||||
- `data`:
|
||||
- `domain`
|
||||
- `qtype`
|
||||
- `ttl`
|
||||
- `records[]`(`type`,`ip`,`weight?`,`line?`,`region?`)
|
||||
- `client`(`ip`,`region`,`carrier`,`country`)
|
||||
- `summary`(命中规则摘要)
|
||||
4. 错误码最低集合:
|
||||
- app 无效/禁用
|
||||
- 域名未绑定
|
||||
- 验签失败
|
||||
- 无可用解析记录
|
||||
- 内部解析失败/超时
|
||||
|
||||
### 3.2 管理 RPC(EdgeAdmin -> EdgeAPI)
|
||||
1. 新增服务:
|
||||
- `HTTPDNSClusterService`
|
||||
- `HTTPDNSNodeService`
|
||||
- `HTTPDNSAppService`
|
||||
- `HTTPDNSDomainService`
|
||||
- `HTTPDNSRuleService`
|
||||
- `HTTPDNSAccessLogService`
|
||||
- `HTTPDNSRuntimeLogService`
|
||||
- `HTTPDNSSandboxService`
|
||||
2. 最小方法集:
|
||||
- 集群:增删改查、设置默认集群、TLS证书绑定、节点列表/状态
|
||||
- 应用:增删改查、主备集群设置、启停、验签开关、密钥重置
|
||||
- 域名:绑定/解绑/列表
|
||||
- 自定义解析:规则增删改查、启停、排序
|
||||
- 日志:访问日志分页查询、运行日志分页查询
|
||||
- 测试:在线解析测试调用(入参包含 appId、clusterId、domain、qtype、clientIp)
|
||||
|
||||
### 3.3 节点日志上报 RPC(EdgeHttpDNS -> EdgeAPI)
|
||||
1. `CreateHTTPDNSAccessLogs`(批量)
|
||||
2. `CreateHTTPDNSRuntimeLogs`(批量)
|
||||
3. 幂等键:`requestId + nodeId`
|
||||
4. 支持高吞吐批量提交和失败重试
|
||||
|
||||
### 3.4 节点路由与 WAF 策略下发契约(EdgeAPI -> EdgeHttpDNS)
|
||||
1. 下发内容最小集合:
|
||||
- `appId/domain/serviceDomain`
|
||||
- `sniMode`(固定为隐匿 SNI)
|
||||
- `hostRouteMode`(SNI 与 Host 解耦)
|
||||
- `wafVerifyEnabled`
|
||||
- `wafVerifyPolicy`(验签字段、时效窗口、失败动作)
|
||||
2. 节点执行口径:
|
||||
- 入站按 `serviceDomain` 接入,按 Host/业务域名做路由匹配
|
||||
- TLS 握手与业务 Host 解耦,避免真实业务域名暴露在 SNI
|
||||
- 命中需要验签的应用时,先执行 WAF 动态验签,再继续解析链路
|
||||
3. 失败处置:
|
||||
- 验签失败按策略拒绝并记录运行日志、访问日志错误码
|
||||
- 路由未命中返回统一错误码并上报审计日志
|
||||
|
||||
## 4. 数据模型设计(独立 HTTPDNS 表)
|
||||
1. `edgeHTTPDNSClusters`
|
||||
- `id,name,isOn,isDefault,serviceDomain,defaultTTL,fallbackTimeoutMs,installDir,tlsPolicyJSON,createdAt,updatedAt`
|
||||
2. `edgeHTTPDNSNodes`
|
||||
- `id,clusterId,name,isOn,isUp,isInstalled,isActive,statusJSON,installStatusJSON,installDir,uniqueId,secret,createdAt,updatedAt`
|
||||
3. `edgeHTTPDNSApps`
|
||||
- `id,name,appId,isOn,primaryClusterId,backupClusterId,sniMode(fixed_hide),createdAt,updatedAt`
|
||||
4. `edgeHTTPDNSAppSecrets`
|
||||
- `id,appId,signEnabled,signSecretEnc,signUpdatedAt,updatedAt`
|
||||
5. `edgeHTTPDNSDomains`
|
||||
- `id,appId,domain,isOn,createdAt,updatedAt`
|
||||
6. `edgeHTTPDNSCustomRules`
|
||||
- `id,appId,domainId,ruleName,lineScope,lineCarrier,lineRegion,lineProvince,lineContinent,lineCountry,ttl,isOn,priority,updatedAt`
|
||||
7. `edgeHTTPDNSCustomRuleRecords`
|
||||
- `id,ruleId,recordType,recordValue,weight,sort`
|
||||
8. `edgeHTTPDNSAccessLogs`
|
||||
- `id,requestId,clusterId,nodeId,appId,domain,qtype,clientIP,clientRegion,carrier,sdkVersion,os,resultIPs,status,errorCode,costMs,createdAt,day`
|
||||
9. `edgeHTTPDNSRuntimeLogs`
|
||||
- `id,clusterId,nodeId,level,type,tag,description,count,createdAt,day`
|
||||
|
||||
### 4.1 索引与唯一约束
|
||||
1. 唯一:`edgeHTTPDNSApps.appId`
|
||||
2. 唯一:`edgeHTTPDNSDomains(appId,domain)`
|
||||
3. 唯一:`edgeHTTPDNSAccessLogs(requestId,nodeId)`
|
||||
4. 索引:
|
||||
- 访问日志:`day,clusterId,nodeId,domain,status,createdAt`
|
||||
- 规则匹配:`domainId,isOn,priority,lineScope,...`
|
||||
- 应用查询:`name,appId,isOn`
|
||||
|
||||
## 5. 解析引擎实现(EdgeAPI)
|
||||
1. 输入校验:`appId/dn/qtype`
|
||||
2. 应用与域名校验:
|
||||
- app 存在且启用
|
||||
- 域名已绑定到 app
|
||||
3. 线路归属:
|
||||
- `cip` 优先,其次 remote IP
|
||||
- 映射字段:运营商/大区/省份/洲/国家
|
||||
4. 规则匹配:
|
||||
- 精确匹配 > 半精确 > 默认
|
||||
- 同级按 `priority` 从小到大
|
||||
5. 记录返回:
|
||||
- 权重关闭:返回全部记录
|
||||
- 权重开启:按权重算法返回单条或子集(固定口径)
|
||||
6. TTL 取值:
|
||||
- 命中规则取规则 TTL
|
||||
- 未命中规则取集群默认 TTL
|
||||
7. 验签:
|
||||
- `signEnabled=true` 时必须通过签名校验
|
||||
- `signEnabled=false` 时跳过签名校验
|
||||
8. 访问日志:
|
||||
- 解析结束异步写日志
|
||||
- 双写 MySQL/ClickHouse
|
||||
|
||||
## 6. 节点与任务链路
|
||||
1. 在 `EdgeCommon/pkg/nodeconfigs` 增加 `NodeRoleHTTPDNS`。
|
||||
2. 在任务系统增加 HTTPDNS 任务类型:
|
||||
- 配置变更
|
||||
- 应用变更
|
||||
- 域名变更
|
||||
- 规则变更
|
||||
- 证书变更
|
||||
- 路由与 WAF 策略变更
|
||||
3. EdgeHttpDNS 增加 HTTPDNS 子服务:
|
||||
- 接收配置快照
|
||||
- 执行解析
|
||||
- 上报运行/访问日志
|
||||
- 执行 SNI/Host 解耦路由
|
||||
- 执行 WAF 动态验签
|
||||
- 执行隐匿 SNI 转发
|
||||
4. 复用现有安装升级框架,但节点角色、任务通道、日志 tag 独立。
|
||||
|
||||
## 7. EdgeAdmin 后端改造
|
||||
1. 将 `internal/web/actions/default/httpdns/*` 的 store/mock 读写改为 RPC。
|
||||
2. 删除/停用旧能力路由:
|
||||
- `/httpdns/policies`
|
||||
- `/httpdns/guide`
|
||||
- `/httpdns/ech`
|
||||
3. 保留必要跳转,避免旧链接 404。
|
||||
4. `sandbox/test` 改为调用真实解析服务(可保留测试开关)。
|
||||
5. 解析测试页面交互固定为:
|
||||
- 配置区标题“解析测试”
|
||||
- 所属集群使用下拉框
|
||||
- 解析域名使用下拉框并按目标应用联动
|
||||
|
||||
## 8. 安全与审计
|
||||
1. Secret 持久化使用加密存储(至少密文列),返回时脱敏。
|
||||
2. 操作审计记录:
|
||||
- 验签开关启停
|
||||
- Sign Secret 重置
|
||||
- 应用主备集群修改
|
||||
3. 验签失败日志保留 `requestId,errorCode,sourceIP`。
|
||||
4. 防滥用:
|
||||
- `/resolve` 增加基础限流与异常请求过滤(按 appId + IP 维度)。
|
||||
5. 节点侧安全执行:
|
||||
- WAF 动态验签失败必须留存审计日志(含 requestId/appId/sourceIP)
|
||||
- SNI/Host 解耦路由命中结果需可追踪(用于问题回溯)
|
||||
|
||||
## 9. 测试与验收
|
||||
### 9.1 单元测试
|
||||
1. 规则匹配优先级与线路匹配
|
||||
2. 验签成功/失败路径
|
||||
3. 权重返回算法
|
||||
4. DAO CRUD 与唯一约束
|
||||
|
||||
### 9.2 集成测试
|
||||
1. EdgeAdmin -> RPC -> DB 全链路
|
||||
2. `/resolve` 各错误码分支
|
||||
3. 节点日志上报双写(MySQL+CH)
|
||||
4. CH 不可用时 MySQL 回退查询
|
||||
5. EdgeAPI 策略下发 -> EdgeHttpDNS 路由/WAF 执行 -> 日志落库全链路
|
||||
|
||||
### 9.3 回归测试
|
||||
1. 智能DNS功能不受影响
|
||||
2. 菜单与权限不串模块
|
||||
3. 旧入口跳转正确,无新增 404
|
||||
|
||||
### 9.4 验收用例
|
||||
1. 应用配置主备服务域名后,SDK可解析成功
|
||||
2. 主集群故障时可切备集群
|
||||
3. 自定义解析按线路返回预期 IP
|
||||
4. 访问日志筛选与概要展示正确
|
||||
5. 运行日志级别/字段与智能DNS一致
|
||||
6. EdgeHttpDNS 可在 SNI 与 Host 解耦场景下正确路由到目标应用
|
||||
7. 开启 WAF 动态验签后,合法请求通过、非法签名请求被拒绝且有审计日志
|
||||
|
||||
## 10. 发布与回滚
|
||||
1. 发布顺序:
|
||||
- DB migration
|
||||
- EdgeAPI(DAO+RPC+resolve)
|
||||
- EdgeHttpDNS(角色+上报)
|
||||
- EdgeAdmin(RPC切换)
|
||||
2. 开关控制:
|
||||
- `httpdns.resolve.enabled`
|
||||
- `httpdns.log.clickhouse.enabled`
|
||||
3. 回滚策略:
|
||||
- 关闭 `resolve` 新实现开关
|
||||
- 访问日志读取切回 MySQL
|
||||
- Admin 保留只读能力
|
||||
|
||||
## 11. 默认值与固定决策
|
||||
1. 无 `/bootstrap`,SDK手动配置主备服务域名。
|
||||
2. `SNI` 固定“隐匿 SNI”。
|
||||
3. 自定义解析无 SDNS 参数。
|
||||
4. 线路仅客户端 IP 归属。
|
||||
5. 日志双写,查询优先 ClickHouse。
|
||||
6. 节点角色独立:`NodeRoleHTTPDNS`。
|
||||
@@ -1,98 +0,0 @@
|
||||
# Edge HTTPDNS 用户使用手册
|
||||
|
||||
欢迎使用 Edge HTTPDNS 服务。本文档旨在帮助您快速完成应用创建、域名配置及解析测试,实现精准、安全的业务调度。
|
||||
|
||||
---
|
||||
|
||||
## 1. 快速入门流程
|
||||
1. **创建应用**:获取接入凭证(AppId 和 SecretKey)。
|
||||
2. **添加域名**:登记需要通过 HTTPDNS 解析的业务域名。
|
||||
3. **自定义解析规则**:设置域名对应的 IP 地址及智能分流规则。
|
||||
4. **解析测试**:通过沙箱工具验证解析是否生效。
|
||||
5. **集成 SDK**:将解析功能集成至您的 App 中。
|
||||
|
||||
---
|
||||
|
||||
## 2. 应用管理
|
||||
|
||||
应用是您接入 HTTPDNS 的基础单元。
|
||||
|
||||
### 2.1 创建应用
|
||||
1. 登录用户控制台,点击 **HTTPDNS -> 应用列表**。
|
||||
2. 点击 **创建应用**。
|
||||
3. 填写应用名称(如“我的安卓App”)。
|
||||
4. 系统会自动关联默认的服务域名,无需手动选择。
|
||||
|
||||
### 2.2 获取接入凭证
|
||||
在应用详情页面,您可以找到:
|
||||
* **AppId**:应用的唯一识别 ID。
|
||||
* **SecretKey**:签名密钥。**请务必妥善保管,切勿泄露。** 在 SDK 初始化时使用此密钥可开启“签名鉴权”,防止解析接口被他人盗刷。
|
||||
|
||||
---
|
||||
|
||||
## 3. 域名与记录配置
|
||||
|
||||
### 3.1 添加域名
|
||||
1. 进入应用详情页,切换至 **域名管理** 标签。
|
||||
2. 点击 **添加域名**,输入您的业务域名(如 `api.example.com`)。
|
||||
|
||||
### 3.2 自定义解析规则
|
||||
**作用**:自定义解析规则允许您根据终端用户的网络环境(如运营商)或物理位置,为其分配最优的访问地址。通过精细化的线路调度,可以有效降低跨境或跨网访问带来的延迟,提升 App 的响应速度。
|
||||
|
||||
点击域名后的 **解析规则**,进入详细设置:
|
||||
* **解析类型**:支持 **A 记录 (IPv4)** 和 **AAAA 记录 (IPv6)**。
|
||||
* **线路选择**:可选择针对特定运营商(如:移动、电信、联通)或特定地域(如:浙江省、海外)进行精准匹配。若不选择则代表全局默认配置。
|
||||
* **解析结果**:填写您的服务器目标 IP 地址。
|
||||
* **TTL**:解析结果在客户端缓存的时间。默认为 30 秒,建议保持默认以兼顾调度灵活性。
|
||||
|
||||
---
|
||||
|
||||
## 4. 配合 CDN 实现网络加速与安全
|
||||
|
||||
Edge HTTPDNS 不仅仅提供域名解析功能,更通过与 CDN 节点的深度集成,解决了移动端常见的 **HTTPS SNI 隐匿访问**及 **跨运营商加速**问题。
|
||||
|
||||
### 4.1 自动获取 CDN 边缘节点
|
||||
如果您在系统内开通了 **CDN 加速服务**,只需将业务域名配置在 CDN 平台中,并将其 CNAME 解析指向 CDN 提供的地址:
|
||||
* **智能调度**:HTTPDNS 会自动识别该域名已接入 CDN,并针对终端用户的地理位置和运营商,智能返回最优的 **CDN 边缘节点 IP**。
|
||||
* **无感知兜底**:如果域名未接入 CDN 或未配置解析规则,HTTPDNS 将自动回源查询 **权威 DNS**,并返回业务真实的源站 IP,确保解析永不中断。
|
||||
|
||||
### 4.2 解决 HTTPS SNI 隐匿问题
|
||||
在使用 IP 直连(如 `https://1.2.3.4/path`)访问 HTTPS 业务时,传统的网络库会因为无法获取正确的 Host 导致 SSL 握手失败。
|
||||
|
||||
**我们的方案:**
|
||||
* **配合 CDN 节点**:我们的 CDN 节点已针对 HTTPDNS 进行了特殊适配。
|
||||
* **SDK 自动适配**:SDK 内部集成了标准适配器。在您的代码中,只需配合解析出的 IP 设置 HTTP 请求头的 `Host` 字段,即可透明地完成 SNI 握手,无需复杂的 SSL 改写逻辑。
|
||||
* **稳定性保障**:通过 CDN 节点的全局负载均衡,即使某个节点异常,HTTPDNS 也会实时踢除并将流量导向其他可用节点,确保业务高可用。
|
||||
|
||||
---
|
||||
|
||||
## 4. 调试与验证
|
||||
|
||||
### 4.1 在线解析测试
|
||||
在左侧菜单进入 **解析测试**:
|
||||
1. 选择您创建的 **应用**、**HTTPDNS 服务域名** 和 **待解析域名**。
|
||||
2. **模拟客户端 IP**(可选):输入特定地区的 IP,验证该地区的解析结果是否符合预期(地域调度验证)。
|
||||
3. 点击 **在线解析**,查看返回的具体 IP 列表。
|
||||
|
||||
### 4.2 访问日志查询
|
||||
在 **访问日志** 中,您可以实时监控解析请求:
|
||||
* 查看各个 AppId 下域名的解析成功率。
|
||||
* 查看请求的来源 IP、耗时以及命中的路由规则。
|
||||
|
||||
---
|
||||
|
||||
## 5. 获取 SDK
|
||||
在 **应用详情 -> SDK下载** 中:
|
||||
* 您可以下载最新版本的 Android、iOS 或 Flutter SDK 压缩包。
|
||||
* 查看配套的 **SDK 集成文档**。
|
||||
|
||||
---
|
||||
|
||||
## 6. 常见问题 (FAQ)
|
||||
|
||||
* **Q:为什么我设置了记录,解析测试却返回为空?**
|
||||
* A:请检查记录是否已启用;或者检查该域名是否已被添加到对应的 AppId 允许列表下。
|
||||
* **Q:如何应对冷启动时的解析延迟?**
|
||||
* A:建议在 SDK 初始化后调用“解析预热”接口,提前将热点域名加载至缓存。
|
||||
* **Q:SecretKey 泄露了怎么办?**
|
||||
* A:请在应用设置中重置 SecretKey,并在 App 代码中同步更新。
|
||||
@@ -1,82 +0,0 @@
|
||||
# Edge HTTPDNS 管理员配置手册
|
||||
|
||||
本文档汇总了 Edge HTTPDNS 的核心配置流程,重点介绍了集群管理、节点在线安装以及多集群调度的详细操作。
|
||||
|
||||
---
|
||||
|
||||
## 1. 集群管理
|
||||
|
||||
集群是 HTTPDNS 服务的基本组织单元。通过设置“默认”角色,可以实现应用配置的自动关联与 SDK 侧的高可用容灾。
|
||||
|
||||
### 1.1 默认集群角色定义
|
||||
在“集群设置”中,您可以将集群开启为 **“设为默认集群”**,并指派以下角色:
|
||||
|
||||
* **默认主集群**:
|
||||
* **自动关联**:当在控制台“添加应用”时,系统会自动将该应用关联到此默认主集群。
|
||||
* **服务首选**:SDK 初始化后,会优先使用该集群的服务域名进行解析请求。
|
||||
* **默认备用集群**:
|
||||
* **自动容灾**:当默认主集群的节点全部宕机或网络不可达时,SDK 会自动切换至默认备用集群进行解析,确保业务不中断。
|
||||
* **自动关联**:与主集群一样,新应用创建时也会自动关联此备用集群信息。
|
||||
|
||||
> **注意**:同一时刻,系统内仅允许存在一个“默认主集群”和一个“默认备用集群”。新设置的默认集群会自动取消之前的旧设置。
|
||||
|
||||
### 1.2 核心参数配置
|
||||
* **服务域名**:该集群对外提供 HTTPDNS 服务的接入地址。
|
||||
* **降级超时容忍度**:指节点回源查询上游 DNS 时的最大等待时间(单位:毫秒)。若超过此阈值未获得结果,将视作解析失败。该选项可在“集群设置”中统一调整。
|
||||
* **默认 TTL**:解析结果在客户端缓存的缺省时长(默认 30s)。
|
||||
* **TLS 设置**:
|
||||
* **端口绑定**:通常绑定 `443`。
|
||||
* **SSL 证书**:必须配置合法证书,否则 SDK 的 HTTPS 请求将失败。
|
||||
* **TLS 版本**:默认支持 TLS 1.1 及以上版本。
|
||||
|
||||
---
|
||||
|
||||
## 2. 节点安装与维护
|
||||
|
||||
节点是处理解析请求的实体。Edge HTTPDNS 支持“在线安装”。
|
||||
|
||||
### 2.1 在线安装
|
||||
1. **创建节点**:在集群下点击“创建节点”,填写名称及公网 IP。
|
||||
2. **配置 SSH**:在节点详情中点击“设置 SSH”,输入服务器的 Host、Port 及登录授权。
|
||||
3. **启动安装**:在“安装信息”页面点击 **[开始安装]**。
|
||||
* 自动下发二进制文件及服务脚本。
|
||||
* 自动生成 `configs/api_httpdns.yaml`(包含节点识别需要的 `nodeId` 和 `secret`)。
|
||||
4. **实时状态**:安装成功后,节点状态变为 **[在线]**,控制台每 30 秒更新一次节点的 CPU、内存及负载数据。
|
||||
|
||||
---
|
||||
|
||||
## 3. 应用与解析规则
|
||||
|
||||
### 3.1 接入应用管理
|
||||
* **AppId/SecretKey**:创建应用后生成的凭证。应用在创建时已根据上述“默认集群”设定自动关联了服务入口。
|
||||
* **鉴权配置**:开启“签名鉴权”可配合 SDK 的 `setSecretKey` 接口,杜绝接口被盗刷风险。
|
||||
|
||||
### 3.2 智能解析策略
|
||||
* **线路/地域匹配**:根据来源运营商和地理位置返回最优 IP。
|
||||
|
||||
---
|
||||
|
||||
## 4. 调试与监控
|
||||
|
||||
### 4.1 解析测试
|
||||
在 **调试工具 -> 解析测试** 中,您可以模拟客户端请求:
|
||||
* 支持指定 **目标应用** 与 **所属集群**。
|
||||
* 支持模拟 **客户端 IP** 以验证 ECS 掩码及地域调度效果。
|
||||
* 实时展示请求 URL、客户端归属地、命中线路以及解析结果(IP 列表与 TTL)。
|
||||
|
||||
### 4.2 访问日志
|
||||
在 **访问日志** 菜单中,可实时查阅所有终端发起的解析请求:
|
||||
* 记录包括:请求时间、客户端 IP/操作系统、SDK 版本、解析域名、耗时以及最终返回的 IP 结果。
|
||||
* 支持按 AppID、域名、状态(成功/失败)或关键字搜索排查。
|
||||
|
||||
### 4.3 运行日志
|
||||
记录服务端及节点的底层运行事件:
|
||||
* 包括节点心跳、SSL 证书加载、API 连接状态等系统级信息。
|
||||
* 分为 Error、Warning、Info 等级别,是排查节点离线或连接故障的首要工具。
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见问题
|
||||
|
||||
* **节点与 API 时间偏离**:若节点时间与 API Server 相差超过 30s,会导致鉴权失败(SIGN_INVALID)。请务必开启 NTP 时间同步。
|
||||
* **SDK 无法连接备用集群**:请检查默认备用集群的 SSL 证书是否有效,以及防火墙端口是否开放。
|
||||
@@ -1,92 +0,0 @@
|
||||
# SNI隐匿开发计划(HTTPDNS专项)
|
||||
|
||||
## 0. 目标
|
||||
在明文网络层(DNS 与 TLS ClientHello)中不出现真实业务域名;真实业务域名仅出现在加密后的 HTTP 层(Host / :authority)中传输。
|
||||
|
||||
## 1. 固定原则
|
||||
1. 只做 SNI 隐匿能力,不引入其他无关能力描述。
|
||||
2. 客户端不走系统 DNS 解析业务域名。
|
||||
3. TLS 握手阶段不发送真实业务域名 SNI。
|
||||
4. CDN/WAF 节点必须支持“空SNI接入 + Host路由”。
|
||||
5. 真实域名只在 HTTPS 加密通道内携带。
|
||||
|
||||
## 2. 端到端链路(目标形态)
|
||||
1. App 调用 SDK,请求解析业务域名。
|
||||
2. SDK 从 HTTPDNS 获取业务域名对应的“接入 IP 列表”(不是业务域名 DNS)。
|
||||
3. SDK 直连接入 IP 发起 TLS:
|
||||
- SNI 置空(或不发送)
|
||||
- 不出现真实业务域名
|
||||
4. TLS 建立后发起 HTTPS 请求:
|
||||
- Host / :authority = 真实业务域名
|
||||
5. CDN/WAF 节点在解密后读取 Host,将流量路由到对应业务源站。
|
||||
|
||||
## 3. SDK 改造要求
|
||||
### 3.1 连接行为
|
||||
1. 提供“按 IP 直连”的请求通道。
|
||||
2. TLS 握手时固定空 SNI,不允许带真实域名。
|
||||
3. HTTP 层强制写入真实 Host / :authority。
|
||||
|
||||
### 3.2 证书校验
|
||||
1. 仍必须做证书链校验(不允许关闭 TLS 安全校验)。
|
||||
2. 证书校验目标为接入层证书(CDN/WAF 对外证书),而非业务源站证书。
|
||||
|
||||
### 3.3 多IP与容错
|
||||
1. HTTPDNS 返回多个接入 IP 时,SDK 按顺序/策略重试。
|
||||
2. 连接失败可切换下一 IP。
|
||||
3. 缓存与过期策略保持稳定,避免频繁抖动。
|
||||
|
||||
### 3.4 多端一致性
|
||||
1. Android/iOS/Flutter 需保证一致行为:
|
||||
- 空 SNI
|
||||
- Host 注入
|
||||
- 失败重试策略
|
||||
2. 文档与示例代码同步更新。
|
||||
|
||||
## 4. CDN/WAF 节点改造要求
|
||||
### 4.1 TLS 接入
|
||||
1. 支持无 SNI ClientHello 的 TLS 握手。
|
||||
2. 为接入域名部署有效证书(覆盖客户端连接目标)。
|
||||
|
||||
### 4.2 路由逻辑
|
||||
1. 以 Host / :authority 作为业务路由主键。
|
||||
2. 路由匹配前做标准化:
|
||||
- 小写化
|
||||
- 去端口
|
||||
3. Host 未命中时返回明确错误(4xx),禁止兜底到默认站点。
|
||||
|
||||
### 4.3 回源行为
|
||||
1. 节点到源站可继续使用 HTTPS 回源。
|
||||
2. 回源主机名与证书校验按现有网关策略执行。
|
||||
|
||||
### 4.4 可观测性
|
||||
1. 增加日志字段:
|
||||
- `tlsSniPresent`(是否携带 SNI)
|
||||
- `host`
|
||||
- `routeResult`
|
||||
2. 可按“空SNI请求占比、Host路由命中率”监控。
|
||||
|
||||
## 5. 控制面(管理端)要求
|
||||
1. 页面仅展示“已启用 SNI 隐匿(空SNI)”,不提供策略切换。
|
||||
2. 集群侧需可检查“节点是否支持空SNI接入”。
|
||||
3. 发布配置时支持灰度与回滚。
|
||||
|
||||
## 6. 验收标准
|
||||
1. 抓包验证:
|
||||
- DNS 明文流量中不出现真实业务域名
|
||||
- TLS ClientHello 中不出现真实业务域名 SNI
|
||||
2. 请求验证:
|
||||
- HTTPS 请求 Host 为真实业务域名
|
||||
- CDN/WAF 按 Host 正确路由
|
||||
3. 稳定性验证:
|
||||
- 多 IP 切换成功
|
||||
- 节点故障时请求可恢复
|
||||
|
||||
## 7. 上线顺序
|
||||
1. 先升级 CDN/WAF 节点能力(空SNI接入 + Host路由)。
|
||||
2. 再升级 SDK(空SNI + Host注入)。
|
||||
3. 最后按应用灰度开启,观察指标后全量。
|
||||
|
||||
## 8. 风险与约束
|
||||
1. 若 CDN/WAF 不支持空 SNI,链路会在握手阶段失败。
|
||||
2. 若 Host 路由不严格,可能出现串站风险。
|
||||
3. 若客户端错误关闭证书校验,会引入严重安全风险。
|
||||
@@ -1,124 +0,0 @@
|
||||
# iOS SDK 集成文档(Edge HTTPDNS)
|
||||
|
||||
## 1. 版本与依赖
|
||||
|
||||
- SDK 模块:`EdgeHttpDNS/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` 字段匹配。
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.8"
|
||||
Version = "1.4.9"
|
||||
|
||||
ProductName = "Edge HTTPDNS"
|
||||
ProcessName = "edge-httpdns"
|
||||
|
||||
@@ -157,8 +157,8 @@ func NewResolveServer(quitCh <-chan struct{}, snapshotManager *SnapshotManager)
|
||||
instance.handler = mux
|
||||
|
||||
instance.tlsConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS11,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
MinVersion: tls.VersionTLS11,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
GetCertificate: instance.getCertificate,
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func (s *ResolveServer) getCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate
|
||||
|
||||
type snapshotTLSConfig struct {
|
||||
Listen []*serverconfigs.NetworkAddressConfig `json:"listen"`
|
||||
SSLPolicy *sslconfigs.SSLPolicy `json:"sslPolicy"`
|
||||
SSLPolicy *sslconfigs.SSLPolicy `json:"sslPolicy"`
|
||||
}
|
||||
|
||||
func (s *ResolveServer) parseTLSConfig(snapshot *LoadedSnapshot) *snapshotTLSConfig {
|
||||
@@ -270,23 +270,24 @@ func (s *ResolveServer) desiredAddrs(snapshot *LoadedSnapshot) []string {
|
||||
func (s *ResolveServer) reloadCertFromSnapshot(snapshot *LoadedSnapshot) {
|
||||
cfg := s.parseTLSConfig(snapshot)
|
||||
if cfg == nil || cfg.SSLPolicy == nil || len(cfg.SSLPolicy.Certs) == 0 {
|
||||
// 没有TLS配置,标记已处理(不需要重试)
|
||||
s.certMu.Lock()
|
||||
s.certSnapshotAt = snapshot.LoadedAt
|
||||
s.certMu.Unlock()
|
||||
reportRuntimeLog("info", "tls", "resolve", "no TLS policy in cluster snapshot, skipped cert reload", fmt.Sprintf("cert-skip-%d", snapshot.LoadedAt))
|
||||
return
|
||||
}
|
||||
if err := cfg.SSLPolicy.Init(context.Background()); err != nil {
|
||||
log.Println("[HTTPDNS_NODE][resolve]init SSLPolicy failed:", err.Error())
|
||||
s.certMu.Lock()
|
||||
s.certSnapshotAt = snapshot.LoadedAt
|
||||
s.certMu.Unlock()
|
||||
reportRuntimeLog("error", "tls", "resolve", "init SSLPolicy failed: "+err.Error(), fmt.Sprintf("cert-err-%d", snapshot.LoadedAt))
|
||||
// 不更新 certSnapshotAt,下次 watchLoop 会重试
|
||||
return
|
||||
}
|
||||
cert := cfg.SSLPolicy.FirstCert()
|
||||
if cert == nil {
|
||||
s.certMu.Lock()
|
||||
s.certSnapshotAt = snapshot.LoadedAt
|
||||
s.certMu.Unlock()
|
||||
log.Println("[HTTPDNS_NODE][resolve]SSLPolicy has no valid certificate after Init")
|
||||
reportRuntimeLog("error", "tls", "resolve", "SSLPolicy has no valid certificate after Init", fmt.Sprintf("cert-err-%d", snapshot.LoadedAt))
|
||||
// 不更新 certSnapshotAt,下次 watchLoop 会重试
|
||||
return
|
||||
}
|
||||
|
||||
@@ -295,6 +296,7 @@ func (s *ResolveServer) reloadCertFromSnapshot(snapshot *LoadedSnapshot) {
|
||||
s.certSnapshotAt = snapshot.LoadedAt
|
||||
s.certMu.Unlock()
|
||||
log.Println("[HTTPDNS_NODE][resolve]TLS certificate reloaded from snapshot")
|
||||
reportRuntimeLog("info", "tls", "resolve", "TLS certificate reloaded from snapshot successfully", fmt.Sprintf("cert-ok-%d", snapshot.LoadedAt))
|
||||
}
|
||||
|
||||
func (s *ResolveServer) startListener(addr string) error {
|
||||
@@ -561,27 +563,29 @@ func (s *ResolveServer) handleResolve(writer http.ResponseWriter, request *http.
|
||||
},
|
||||
})
|
||||
|
||||
s.enqueueAccessLog(&pb.HTTPDNSAccessLog{
|
||||
RequestId: requestID,
|
||||
ClusterId: snapshot.ClusterID,
|
||||
NodeId: snapshot.NodeID,
|
||||
AppId: loadedApp.App.GetAppId(),
|
||||
AppName: loadedApp.App.GetName(),
|
||||
Domain: domain,
|
||||
Qtype: qtype,
|
||||
ClientIP: clientProfile.IP,
|
||||
ClientRegion: clientProfile.RegionText,
|
||||
Carrier: clientProfile.Carrier,
|
||||
SdkVersion: strings.TrimSpace(query.Get("sdk_version")),
|
||||
Os: strings.TrimSpace(query.Get("os")),
|
||||
ResultIPs: strings.Join(resultIPs, ","),
|
||||
Status: "success",
|
||||
ErrorCode: "none",
|
||||
CostMs: int32(time.Since(startAt).Milliseconds()),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
Day: time.Now().Format("20060102"),
|
||||
Summary: summary,
|
||||
})
|
||||
if s.isAccessLogEnabled(snapshot) {
|
||||
s.enqueueAccessLog(&pb.HTTPDNSAccessLog{
|
||||
RequestId: requestID,
|
||||
ClusterId: snapshot.ClusterID,
|
||||
NodeId: snapshot.NodeID,
|
||||
AppId: loadedApp.App.GetAppId(),
|
||||
AppName: loadedApp.App.GetName(),
|
||||
Domain: domain,
|
||||
Qtype: qtype,
|
||||
ClientIP: clientProfile.IP,
|
||||
ClientRegion: clientProfile.RegionText,
|
||||
Carrier: clientProfile.Carrier,
|
||||
SdkVersion: strings.TrimSpace(query.Get("sdk_version")),
|
||||
Os: strings.TrimSpace(query.Get("os")),
|
||||
ResultIPs: strings.Join(resultIPs, ","),
|
||||
Status: "success",
|
||||
ErrorCode: "none",
|
||||
CostMs: int32(time.Since(startAt).Milliseconds()),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
Day: time.Now().Format("20060102"),
|
||||
Summary: summary,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func pickDefaultTTL(snapshot *LoadedSnapshot, app *pb.HTTPDNSApp) int32 {
|
||||
@@ -655,27 +659,29 @@ func (s *ResolveServer) writeFailedResolve(
|
||||
nodeID = snapshot.NodeID
|
||||
}
|
||||
|
||||
s.enqueueAccessLog(&pb.HTTPDNSAccessLog{
|
||||
RequestId: requestID,
|
||||
ClusterId: clusterID,
|
||||
NodeId: nodeID,
|
||||
AppId: appID,
|
||||
AppName: appName,
|
||||
Domain: domain,
|
||||
Qtype: qtype,
|
||||
ClientIP: clientProfile.IP,
|
||||
ClientRegion: clientProfile.RegionText,
|
||||
Carrier: clientProfile.Carrier,
|
||||
SdkVersion: strings.TrimSpace(query.Get("sdk_version")),
|
||||
Os: strings.TrimSpace(query.Get("os")),
|
||||
ResultIPs: "",
|
||||
Status: "failed",
|
||||
ErrorCode: errorCode,
|
||||
CostMs: int32(time.Since(startAt).Milliseconds()),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
Day: time.Now().Format("20060102"),
|
||||
Summary: summary,
|
||||
})
|
||||
if s.isAccessLogEnabled(snapshot) {
|
||||
s.enqueueAccessLog(&pb.HTTPDNSAccessLog{
|
||||
RequestId: requestID,
|
||||
ClusterId: clusterID,
|
||||
NodeId: nodeID,
|
||||
AppId: appID,
|
||||
AppName: appName,
|
||||
Domain: domain,
|
||||
Qtype: qtype,
|
||||
ClientIP: clientProfile.IP,
|
||||
ClientRegion: clientProfile.RegionText,
|
||||
Carrier: clientProfile.Carrier,
|
||||
SdkVersion: strings.TrimSpace(query.Get("sdk_version")),
|
||||
Os: strings.TrimSpace(query.Get("os")),
|
||||
ResultIPs: "",
|
||||
Status: "failed",
|
||||
ErrorCode: errorCode,
|
||||
CostMs: int32(time.Since(startAt).Milliseconds()),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
Day: time.Now().Format("20060102"),
|
||||
Summary: summary,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ResolveServer) writeResolveJSON(writer http.ResponseWriter, status int, resp *resolveResponse) {
|
||||
@@ -1424,6 +1430,17 @@ func ruleRegionSummary(rule *pb.HTTPDNSCustomRule) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *ResolveServer) isAccessLogEnabled(snapshot *LoadedSnapshot) bool {
|
||||
if snapshot == nil || snapshot.ClusterID <= 0 {
|
||||
return true
|
||||
}
|
||||
cluster := snapshot.Clusters[snapshot.ClusterID]
|
||||
if cluster == nil {
|
||||
return true
|
||||
}
|
||||
return cluster.GetAccessLogIsOn()
|
||||
}
|
||||
|
||||
func (s *ResolveServer) enqueueAccessLog(item *pb.HTTPDNSAccessLog) {
|
||||
if item == nil {
|
||||
return
|
||||
|
||||
@@ -37,6 +37,8 @@ type SnapshotManager struct {
|
||||
|
||||
locker sync.RWMutex
|
||||
snapshot *LoadedSnapshot
|
||||
|
||||
timezone string
|
||||
}
|
||||
|
||||
func NewSnapshotManager(quitCh <-chan struct{}) *SnapshotManager {
|
||||
@@ -144,7 +146,7 @@ func (m *SnapshotManager) RefreshNow(reason string) error {
|
||||
}
|
||||
|
||||
snapshot := &LoadedSnapshot{
|
||||
LoadedAt: time.Now().Unix(),
|
||||
LoadedAt: time.Now().UnixNano(),
|
||||
NodeID: nodeResp.GetNode().GetId(),
|
||||
ClusterID: nodeResp.GetNode().GetClusterId(),
|
||||
Clusters: clusters,
|
||||
@@ -156,5 +158,36 @@ func (m *SnapshotManager) RefreshNow(reason string) error {
|
||||
m.locker.Unlock()
|
||||
|
||||
reportRuntimeLog("info", "config", "snapshot", "snapshot refreshed: "+reason, fmt.Sprintf("snapshot-%d", time.Now().UnixNano()))
|
||||
|
||||
// timezone sync - prefer current node's cluster timezone
|
||||
var timeZone string
|
||||
if snapshot.ClusterID > 0 {
|
||||
if cluster := clusters[snapshot.ClusterID]; cluster != nil && len(cluster.GetTimeZone()) > 0 {
|
||||
timeZone = cluster.GetTimeZone()
|
||||
}
|
||||
}
|
||||
// fallback to any non-empty cluster timezone for compatibility
|
||||
if len(timeZone) == 0 {
|
||||
for _, cluster := range clusters {
|
||||
if cluster != nil && len(cluster.GetTimeZone()) > 0 {
|
||||
timeZone = cluster.GetTimeZone()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(timeZone) == 0 {
|
||||
timeZone = "Asia/Shanghai"
|
||||
}
|
||||
if m.timezone != timeZone {
|
||||
location, err := time.LoadLocation(timeZone)
|
||||
if err != nil {
|
||||
log.Println("[HTTPDNS_NODE][TIMEZONE]change time zone failed:", err.Error())
|
||||
} else {
|
||||
log.Println("[HTTPDNS_NODE][TIMEZONE]change time zone to '" + timeZone + "'")
|
||||
time.Local = location
|
||||
m.timezone = timeZone
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
# HTTPDNS Android SDK (SNI Hidden v1.0.0)
|
||||
|
||||
## 1. Init
|
||||
|
||||
```java
|
||||
import com.Trust.sdk.android.httpdns.HttpDns;
|
||||
import com.Trust.sdk.android.httpdns.HttpDnsService;
|
||||
import com.Trust.sdk.android.httpdns.InitConfig;
|
||||
|
||||
String appId = "app1f1ndpo9";
|
||||
|
||||
new InitConfig.Builder()
|
||||
.setContext(context)
|
||||
.setPrimaryServiceHost("httpdns-a.example.com")
|
||||
.setBackupServiceHost("httpdns-b.example.com")
|
||||
.setServicePort(443)
|
||||
.setSecretKey("your-sign-secret") // optional if sign is enabled
|
||||
.setEnableHttps(true)
|
||||
.buildFor(appId);
|
||||
|
||||
HttpDnsService httpDnsService = HttpDns.getService(appId);
|
||||
```
|
||||
|
||||
## 2. Resolve
|
||||
|
||||
```java
|
||||
HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(
|
||||
"api.business.com",
|
||||
RequestIpType.auto,
|
||||
null,
|
||||
null
|
||||
);
|
||||
```
|
||||
|
||||
## 3. Official HTTP Adapter (IP + Empty-SNI + Host)
|
||||
|
||||
```java
|
||||
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterOptions;
|
||||
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterRequest;
|
||||
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterResponse;
|
||||
import com.Trust.sdk.android.httpdns.network.HttpDnsHttpAdapter;
|
||||
|
||||
HttpDnsHttpAdapter adapter = HttpDns.buildHttpClientAdapter(
|
||||
httpDnsService,
|
||||
new HttpDnsAdapterOptions.Builder()
|
||||
.setConnectTimeoutMillis(3000)
|
||||
.setReadTimeoutMillis(5000)
|
||||
.setRequestIpType(RequestIpType.auto)
|
||||
.setAllowInsecureCertificatesForDebugOnly(false)
|
||||
.build()
|
||||
);
|
||||
|
||||
HttpDnsAdapterResponse response = adapter.execute(
|
||||
new HttpDnsAdapterRequest("GET", "https://api.business.com/v1/ping")
|
||||
);
|
||||
```
|
||||
|
||||
Behavior is fixed:
|
||||
- Resolve by `/resolve`.
|
||||
- Connect to resolved IP over HTTPS.
|
||||
- Keep `Host` header as business domain.
|
||||
- No fallback to domain direct request.
|
||||
|
||||
## 4. Public Errors
|
||||
|
||||
- `NO_IP_AVAILABLE`
|
||||
- `TLS_EMPTY_SNI_FAILED`
|
||||
- `HOST_ROUTE_REJECTED`
|
||||
- `RESOLVE_SIGN_INVALID`
|
||||
|
||||
## 5. Removed Public Params
|
||||
|
||||
Do not use legacy public parameters:
|
||||
- `accountId`
|
||||
- `serviceDomain`
|
||||
- `endpoint`
|
||||
- `aesSecretKey`
|
||||
@@ -1,68 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/ryan/Downloads/adt-bundle-mac-x86_64-20131030/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
-optimizationpasses 3
|
||||
-dontoptimize
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-verbose
|
||||
-overloadaggressively
|
||||
#-allowaccessmodification
|
||||
-useuniqueclassmembernames
|
||||
|
||||
-dontwarn com.alibaba.sdk.android.httpdns.net.HttpDnsNetworkDetector
|
||||
|
||||
-keeppackagenames com.alibaba.sdk.android.httpdns
|
||||
-flattenpackagehierarchy com.alibaba.sdk.android.httpdns
|
||||
-keep class com.alibaba.sdk.android.httpdns.HttpDns{*;}
|
||||
-keep interface com.alibaba.sdk.android.httpdns.HttpDnsService{*;}
|
||||
-keep class com.alibaba.sdk.android.httpdns.impl.ErrorImpl{*;}
|
||||
-keep interface com.alibaba.sdk.android.httpdns.SyncService{*;}
|
||||
-keep class com.alibaba.sdk.android.httpdns.InitConfig{*;}
|
||||
-keep class com.alibaba.sdk.android.httpdns.InitConfig$Builder{*;}
|
||||
-keep class com.alibaba.sdk.android.httpdns.RequestIpType{*;}
|
||||
-keep interface com.alibaba.sdk.android.httpdns.DegradationFilter{*;}
|
||||
-keep interface com.alibaba.sdk.android.httpdns.NotUseHttpDnsFilter{*;}
|
||||
-keep interface com.alibaba.sdk.android.httpdns.HttpDnsCallback{*;}
|
||||
-keep class com.alibaba.sdk.android.httpdns.ranking.IPRankingBean{*;}
|
||||
-keep interface com.alibaba.sdk.android.httpdns.ILogger{*;}
|
||||
-keep interface com.alibaba.sdk.android.httpdns.CacheTtlChanger{*;}
|
||||
-keep class com.alibaba.sdk.android.httpdns.NetType{*;}
|
||||
-keepclasseswithmembers class com.alibaba.sdk.android.httpdns.log.HttpDnsLog {
|
||||
public static *** setLogger(***);
|
||||
public static *** removeLogger(***);
|
||||
public static *** enable(***);
|
||||
}
|
||||
-keep class com.alibaba.sdk.android.httpdns.HTTPDNSResult{*;}
|
||||
-keepclasseswithmembers class com.alibaba.sdk.android.httpdns.HttpDnsSettings {
|
||||
public static *** setDailyReport(***);
|
||||
public static *** setNetworkChecker(***);
|
||||
}
|
||||
|
||||
-keep class com.alibaba.sdk.android.httpdns.net.HttpDnsNetworkDetector {
|
||||
public <methods>;
|
||||
public <fields>;
|
||||
}
|
||||
|
||||
-keep interface com.alibaba.sdk.android.httpdns.HttpDnsSettings$NetworkChecker{*;}
|
||||
-keep interface com.alibaba.sdk.android.httpdns.HttpDnsSettings$NetworkDetector{*;}
|
||||
-keep class com.alibaba.sdk.android.httpdns.utils.CommonUtil{
|
||||
public <methods>;
|
||||
public <fields>;
|
||||
}
|
||||
-keep enum com.alibaba.sdk.android.httpdns.Region {*;}
|
||||
-keep class com.alibaba.sdk.android.httpdns.exception.InitException{*;}
|
||||
@@ -1,25 +0,0 @@
|
||||
# EdgeHttpDNS SDK Source Lock
|
||||
|
||||
This directory vendors upstream SDK source snapshots for HTTPDNS integration.
|
||||
|
||||
Fetched at (UTC): `2026-02-18T09:31:28Z`
|
||||
|
||||
## Android SDK
|
||||
|
||||
- Upstream repository: `https://github.com/TrustAPP/Trustcloud-httpdns-android-sdk`
|
||||
- Locked commit: `eeb17d677161ec94b5f41a9d6437501ddc24e6d2`
|
||||
- Local path: `EdgeHttpDNS/sdk/android`
|
||||
|
||||
## iOS SDK
|
||||
|
||||
- Upstream repository: `https://github.com/TrustAPP/Trustcloud-httpdns-ios-sdk`
|
||||
- Locked commit: `19f5bacd1d1399a00ba654bb72ababb3e91d0a3a`
|
||||
- Local path: `EdgeHttpDNS/sdk/ios`
|
||||
|
||||
## Flutter Plugin SDK
|
||||
|
||||
- Upstream repository: `https://github.com/TrustAPP/Trust-flutter-demo`
|
||||
- Locked commit: `588b807e5480d8592c57d439a6b1c52e8c313569`
|
||||
- Imported subtree: `httpdns_flutter_demo/packages/TrustAPP_httpdns`
|
||||
- Local path: `EdgeHttpDNS/sdk/flutter/TrustAPP_httpdns`
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# Third-Party Notices for EdgeHttpDNS SDK Sources
|
||||
|
||||
This directory includes third-party source snapshots imported from Trust Cloud open-source repositories.
|
||||
|
||||
## 1) Android SDK (`EdgeHttpDNS/sdk/android`)
|
||||
|
||||
- Source: `https://github.com/TrustAPP/Trustcloud-httpdns-android-sdk`
|
||||
- Commit: `eeb17d677161ec94b5f41a9d6437501ddc24e6d2`
|
||||
- License file in imported source:
|
||||
- `EdgeHttpDNS/sdk/android/LICENSE`
|
||||
- Observed license: MIT License
|
||||
|
||||
## 2) Flutter plugin (`EdgeHttpDNS/sdk/flutter/TrustAPP_httpdns`)
|
||||
|
||||
- Source: `https://github.com/TrustAPP/Trust-flutter-demo`
|
||||
- Commit: `588b807e5480d8592c57d439a6b1c52e8c313569`
|
||||
- Imported subtree: `httpdns_flutter_demo/packages/TrustAPP_httpdns`
|
||||
- License file in imported source:
|
||||
- `EdgeHttpDNS/sdk/flutter/TrustAPP_httpdns/LICENSE`
|
||||
- Observed license: MIT License
|
||||
|
||||
## 3) iOS SDK (`EdgeHttpDNS/sdk/ios`)
|
||||
|
||||
- Source: `https://github.com/TrustAPP/Trustcloud-httpdns-ios-sdk`
|
||||
- Commit: `19f5bacd1d1399a00ba654bb72ababb3e91d0a3a`
|
||||
- Current status:
|
||||
- No standalone top-level `LICENSE` file was found in the upstream repository snapshot imported here.
|
||||
- Some source headers reference Apache 2.0 notices, but repository-level license declaration is not explicit.
|
||||
- Action required before merging to protected branch:
|
||||
- Complete legal/license confirmation for iOS source usage.
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Logs**
|
||||
If applicable, add logcat logs to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- Device: [e.g. Pixel 3]
|
||||
- OS: [e.g. Android 10]
|
||||
- SDK Version [e.g. 2.0.2]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
26
EdgeHttpDNS/sdk/android/.gitignore
vendored
@@ -1,26 +0,0 @@
|
||||
.DS_Store
|
||||
*.swp
|
||||
|
||||
# Kiro IDE
|
||||
.kiro/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# Gradle
|
||||
build
|
||||
.gradle/
|
||||
target/
|
||||
|
||||
# Intellij project files
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# sonar
|
||||
.sonar/
|
||||
|
||||
# Android
|
||||
local.properties
|
||||
nohup.out
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Alibaba Cloud
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,77 +0,0 @@
|
||||
# HTTPDNS Android SDK (SNI Hidden v1.0.0)
|
||||
|
||||
## 1. Init
|
||||
|
||||
```java
|
||||
import com.Trust.sdk.android.httpdns.HttpDns;
|
||||
import com.Trust.sdk.android.httpdns.HttpDnsService;
|
||||
import com.Trust.sdk.android.httpdns.InitConfig;
|
||||
|
||||
String appId = "app1f1ndpo9";
|
||||
|
||||
new InitConfig.Builder()
|
||||
.setContext(context)
|
||||
.setPrimaryServiceHost("httpdns-a.example.com")
|
||||
.setBackupServiceHost("httpdns-b.example.com")
|
||||
.setServicePort(443)
|
||||
.setSecretKey("your-sign-secret") // optional if sign is enabled
|
||||
.setEnableHttps(true)
|
||||
.buildFor(appId);
|
||||
|
||||
HttpDnsService httpDnsService = HttpDns.getService(appId);
|
||||
```
|
||||
|
||||
## 2. Resolve
|
||||
|
||||
```java
|
||||
HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(
|
||||
"api.business.com",
|
||||
RequestIpType.auto,
|
||||
null,
|
||||
null
|
||||
);
|
||||
```
|
||||
|
||||
## 3. Official HTTP Adapter (IP + Empty-SNI + Host)
|
||||
|
||||
```java
|
||||
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterOptions;
|
||||
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterRequest;
|
||||
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterResponse;
|
||||
import com.Trust.sdk.android.httpdns.network.HttpDnsHttpAdapter;
|
||||
|
||||
HttpDnsHttpAdapter adapter = HttpDns.buildHttpClientAdapter(
|
||||
httpDnsService,
|
||||
new HttpDnsAdapterOptions.Builder()
|
||||
.setConnectTimeoutMillis(3000)
|
||||
.setReadTimeoutMillis(5000)
|
||||
.setRequestIpType(RequestIpType.auto)
|
||||
.setAllowInsecureCertificatesForDebugOnly(false)
|
||||
.build()
|
||||
);
|
||||
|
||||
HttpDnsAdapterResponse response = adapter.execute(
|
||||
new HttpDnsAdapterRequest("GET", "https://api.business.com/v1/ping")
|
||||
);
|
||||
```
|
||||
|
||||
Behavior is fixed:
|
||||
- Resolve by `/resolve`.
|
||||
- Connect to resolved IP over HTTPS.
|
||||
- Keep `Host` header as business domain.
|
||||
- No fallback to domain direct request.
|
||||
|
||||
## 4. Public Errors
|
||||
|
||||
- `NO_IP_AVAILABLE`
|
||||
- `TLS_EMPTY_SNI_FAILED`
|
||||
- `HOST_ROUTE_REJECTED`
|
||||
- `RESOLVE_SIGN_INVALID`
|
||||
|
||||
## 5. Removed Public Params
|
||||
|
||||
Do not use legacy public parameters:
|
||||
- `accountId`
|
||||
- `serviceDomain`
|
||||
- `endpoint`
|
||||
- `aesSecretKey`
|
||||
2
EdgeHttpDNS/sdk/android/app/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/build
|
||||
/libs/alicloud-android-httpdns-*.aar
|
||||
@@ -1,84 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.newsdk.ams.httpdns.demo'
|
||||
compileSdkVersion 34
|
||||
buildToolsVersion "30.0.2"
|
||||
defaultConfig {
|
||||
applicationId "com.newsdk.ams.httpdns.demo2"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
forTest {
|
||||
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慺orTest鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
|
||||
initWith release
|
||||
debuggable true
|
||||
}
|
||||
}
|
||||
|
||||
variantFilter { variant ->
|
||||
def names = variant.flavors*.name
|
||||
def type = variant.buildType.name
|
||||
// To check for a certain build type, use variant.buildType.name == "<buildType>"
|
||||
if ((names.contains("normal") && type.contains("forTest"))
|
||||
|| (names.contains("intl") && type.contains("forTest"))
|
||||
|| (names.contains("end2end") && type.contains("release"))
|
||||
|| (names.contains("end2end") && type.contains("debug"))
|
||||
) {
|
||||
// Gradle ignores any variants that satisfy the conditions above.
|
||||
setIgnore(true)
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
all {
|
||||
jvmArgs '-noverify'
|
||||
systemProperty 'robolectric.logging.enable', true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "version"
|
||||
|
||||
productFlavors {
|
||||
normal {
|
||||
|
||||
}
|
||||
|
||||
intl {
|
||||
|
||||
}
|
||||
|
||||
end2end {
|
||||
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慹nd2end鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation project(':httpdns-sdk')
|
||||
|
||||
implementation("com.squareup.okhttp3:okhttp:3.9.0")
|
||||
}
|
||||
30
EdgeHttpDNS/sdk/android/app/proguard-rules.pro
vendored
@@ -1,30 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/liyazhou/Library/Android/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-dontwarn okhttp3.**
|
||||
-dontwarn okio.**
|
||||
-dontwarn com.newsdk.sdk.android.httpdns.test.**
|
||||
-dontwarn com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
|
||||
-keep class com.aliyun.ams.ipdetector.Inet64Util{*;}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:name=".MyApp"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".HttpDnsActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".SDNSActivity"
|
||||
android:exported="false" />
|
||||
<activity android:name=".WebViewActivity"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,159 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType;
|
||||
import com.newsdk.ams.httpdns.demo.base.BaseActivity;
|
||||
import com.newsdk.ams.httpdns.demo.http.HttpUrlConnectionRequest;
|
||||
import com.newsdk.ams.httpdns.demo.okhttp.OkHttpRequest;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class HttpDnsActivity extends BaseActivity {
|
||||
|
||||
private static final String SCHEMA_HTTPS = "https://";
|
||||
private static final String SCHEMA_HTTP = "http://";
|
||||
|
||||
private static final String[] HOSTS = new String[] {
|
||||
"www.taobao.com",
|
||||
"www.Aliyun.com"
|
||||
};
|
||||
|
||||
private String schema = SCHEMA_HTTPS;
|
||||
private String host = HOSTS[0];
|
||||
private RequestIpType requestIpType = RequestIpType.v4;
|
||||
|
||||
private HttpUrlConnectionRequest httpUrlConnectionRequest;
|
||||
private OkHttpRequest okHttpRequest;
|
||||
private NetworkRequest networkRequest;
|
||||
|
||||
private final ExecutorService worker = Executors.newSingleThreadExecutor();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
httpUrlConnectionRequest = new HttpUrlConnectionRequest(this);
|
||||
okHttpRequest = new OkHttpRequest(this);
|
||||
networkRequest = httpUrlConnectionRequest;
|
||||
|
||||
addFourButton(
|
||||
"Switch instance",
|
||||
v -> {
|
||||
MyApp.getInstance().changeHolder();
|
||||
sendLog("Instance switched.");
|
||||
},
|
||||
"Show config",
|
||||
v -> sendLog(MyApp.getInstance().getCurrentHolder().getCurrentConfig()),
|
||||
"Clear holder cache",
|
||||
v -> {
|
||||
MyApp.getInstance().getCurrentHolder().cleanSp();
|
||||
sendLog("Holder cache cleared.");
|
||||
},
|
||||
"Clear log",
|
||||
v -> cleanLog()
|
||||
);
|
||||
|
||||
addAutoCompleteTextViewButton(HOSTS, "Host", "Set host", view -> {
|
||||
AutoCompleteTextView actv = (AutoCompleteTextView) view;
|
||||
host = actv.getEditableText().toString();
|
||||
sendLog("Host set to: " + host);
|
||||
});
|
||||
|
||||
addTwoButton(
|
||||
"Use HTTPS",
|
||||
v -> {
|
||||
schema = SCHEMA_HTTPS;
|
||||
sendLog("Schema set to HTTPS.");
|
||||
},
|
||||
"Use HTTP",
|
||||
v -> {
|
||||
schema = SCHEMA_HTTP;
|
||||
sendLog("Schema set to HTTP.");
|
||||
}
|
||||
);
|
||||
|
||||
addFourButton(
|
||||
"IP type v4",
|
||||
v -> {
|
||||
requestIpType = RequestIpType.v4;
|
||||
sendLog("Request IP type: v4");
|
||||
},
|
||||
"IP type v6",
|
||||
v -> {
|
||||
requestIpType = RequestIpType.v6;
|
||||
sendLog("Request IP type: v6");
|
||||
},
|
||||
"IP type both",
|
||||
v -> {
|
||||
requestIpType = RequestIpType.both;
|
||||
sendLog("Request IP type: both");
|
||||
},
|
||||
"IP type auto",
|
||||
v -> {
|
||||
requestIpType = RequestIpType.auto;
|
||||
sendLog("Request IP type: auto");
|
||||
}
|
||||
);
|
||||
|
||||
addTwoButton(
|
||||
"HttpUrlConnection",
|
||||
v -> {
|
||||
networkRequest = httpUrlConnectionRequest;
|
||||
sendLog("Network stack: HttpUrlConnection");
|
||||
},
|
||||
"OkHttp",
|
||||
v -> {
|
||||
networkRequest = okHttpRequest;
|
||||
sendLog("Network stack: OkHttp");
|
||||
}
|
||||
);
|
||||
|
||||
addTwoButton(
|
||||
"Resolve sync",
|
||||
v -> worker.execute(() -> executeResolve(false)),
|
||||
"Resolve async",
|
||||
v -> worker.execute(() -> executeResolve(true))
|
||||
);
|
||||
|
||||
addTwoButton(
|
||||
"Open SDNS page",
|
||||
v -> startActivity(new Intent(HttpDnsActivity.this, SDNSActivity.class)),
|
||||
"Open WebView page",
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(HttpDnsActivity.this, WebViewActivity.class));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
worker.shutdownNow();
|
||||
}
|
||||
|
||||
private void executeResolve(boolean async) {
|
||||
String url = schema + host;
|
||||
sendLog("Request URL: " + url);
|
||||
sendLog("Request IP type: " + requestIpType.name());
|
||||
sendLog("Async mode: " + async);
|
||||
sendLog("Stack: " + (networkRequest == httpUrlConnectionRequest ? "HttpUrlConnection" : "OkHttp"));
|
||||
try {
|
||||
networkRequest.updateHttpDnsConfig(async, requestIpType);
|
||||
String response = networkRequest.httpGet(url);
|
||||
if (response != null && response.length() > 120) {
|
||||
response = response.substring(0, 120) + "...";
|
||||
}
|
||||
sendLog("Response: " + response);
|
||||
} catch (Exception e) {
|
||||
sendLog("Request failed: " + e.getClass().getSimpleName() + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.CacheTtlChanger;
|
||||
import com.newsdk.sdk.android.httpdns.HttpDns;
|
||||
import com.newsdk.sdk.android.httpdns.HttpDnsService;
|
||||
import com.newsdk.sdk.android.httpdns.InitConfig;
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType;
|
||||
import com.newsdk.sdk.android.httpdns.ranking.IPRankingBean;
|
||||
import com.newsdk.ams.httpdns.demo.utils.SpUtil;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 淇濆瓨Httpdns 鍙?鐩稿叧閰嶇疆锛?
|
||||
* 鏂逛究淇敼
|
||||
*/
|
||||
public class HttpDnsHolder {
|
||||
|
||||
public static final String SP_PREFIX = "httpdns_config_";
|
||||
|
||||
public static String getSpName(String accountId) {
|
||||
return SP_PREFIX + accountId;
|
||||
}
|
||||
|
||||
public static final String KEY_EXPIRED_IP = "enableExpiredIp";
|
||||
public static final String KEY_CACHE_IP = "enableCacheIp";
|
||||
public static final String KEY_TIMEOUT = "timeout";
|
||||
public static final String KEY_HTTPS = "enableHttps";
|
||||
public static final String KEY_IP_RANKING_ITEMS = "ipProbeItems";
|
||||
public static final String KEY_REGION = "region";
|
||||
public static final String KEY_TTL_CHANGER = "cacheTtlChanger";
|
||||
public static final String KEY_HOST_NOT_CHANGE = "hostListWithFixedIp";
|
||||
|
||||
private HttpDnsService service;
|
||||
private String accountId;
|
||||
private String secret;
|
||||
private Context context;
|
||||
|
||||
private boolean enableExpiredIp;
|
||||
private boolean enableCacheIp;
|
||||
private int timeout;
|
||||
private boolean enableHttps;
|
||||
private ArrayList<IPRankingBean> ipRankingList = null;
|
||||
private String region;
|
||||
private ArrayList<String> hostListWithFixedIp;
|
||||
private HashMap<String, Integer> ttlCache;
|
||||
private final CacheTtlChanger cacheTtlChanger = new CacheTtlChanger() {
|
||||
@Override
|
||||
public int changeCacheTtl(String host, RequestIpType type, int ttl) {
|
||||
if (ttlCache != null && ttlCache.get(host) != null) {
|
||||
return ttlCache.get(host);
|
||||
}
|
||||
return ttl;
|
||||
}
|
||||
};
|
||||
|
||||
public HttpDnsHolder(String accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
public HttpDnsHolder(String accountId, String secret) {
|
||||
this.accountId = accountId;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 鍒濆鍖杊ttpdns鐨勯厤缃?
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public void init(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
SpUtil.readSp(context, getSpName(accountId), new SpUtil.OnGetSp() {
|
||||
@Override
|
||||
public void onGetSp(SharedPreferences sp) {
|
||||
enableExpiredIp = sp.getBoolean(KEY_EXPIRED_IP, true);
|
||||
enableCacheIp = sp.getBoolean(KEY_CACHE_IP, false);
|
||||
timeout = sp.getInt(KEY_TIMEOUT, 5 * 1000);
|
||||
enableHttps = sp.getBoolean(KEY_HTTPS, false);
|
||||
ipRankingList = convertToProbeList(sp.getString(KEY_IP_RANKING_ITEMS, null));
|
||||
region = sp.getString(KEY_REGION, null);
|
||||
ttlCache = convertToCacheTtlData(sp.getString(KEY_TTL_CHANGER, null));
|
||||
hostListWithFixedIp = convertToStringList(sp.getString(KEY_HOST_NOT_CHANGE, null));
|
||||
}
|
||||
});
|
||||
|
||||
// 鍒濆鍖杊ttpdns 鐨勯厤缃紝姝ゆ楠ら渶瑕佸湪绗竴娆¤幏鍙朒ttpDnsService瀹炰緥涔嬪墠
|
||||
new InitConfig.Builder()
|
||||
.setEnableExpiredIp(enableExpiredIp)
|
||||
.setEnableCacheIp(enableCacheIp)
|
||||
.setTimeout(timeout)
|
||||
.setEnableHttps(enableHttps)
|
||||
.setIPRankingList(ipRankingList)
|
||||
.setRegion(region)
|
||||
.configCacheTtlChanger(cacheTtlChanger)
|
||||
.configHostWithFixedIp(hostListWithFixedIp)
|
||||
.buildFor(accountId);
|
||||
|
||||
getService();
|
||||
}
|
||||
|
||||
public HttpDnsService getService() {
|
||||
if (service == null) {
|
||||
if (secret != null) {
|
||||
service = HttpDns.getService(context, accountId, secret);
|
||||
} else {
|
||||
service = HttpDns.getService(context, accountId);
|
||||
}
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public void setEnableExpiredIp(final boolean enableExpiredIp) {
|
||||
this.enableExpiredIp = enableExpiredIp;
|
||||
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putBoolean(KEY_EXPIRED_IP, enableExpiredIp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setEnableCacheIp(final boolean enableCacheIp) {
|
||||
this.enableCacheIp = enableCacheIp;
|
||||
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putBoolean(KEY_CACHE_IP, enableCacheIp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setTimeout(final int timeout) {
|
||||
this.timeout = timeout;
|
||||
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putInt(KEY_TIMEOUT, timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setEnableHttps(final boolean enableHttps) {
|
||||
this.enableHttps = enableHttps;
|
||||
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putBoolean(KEY_HTTPS, enableHttps);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setRegion(final String region) {
|
||||
this.region = region;
|
||||
getService().setRegion(region);
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putString(KEY_REGION, region);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addHostWithFixedIp(String host) {
|
||||
if (this.hostListWithFixedIp == null) {
|
||||
this.hostListWithFixedIp = new ArrayList<>();
|
||||
}
|
||||
this.hostListWithFixedIp.add(host);
|
||||
// 閲嶅惎鐢熸晥
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putString(KEY_HOST_NOT_CHANGE, convertHostList(hostListWithFixedIp));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addIpProbeItem(IPRankingBean ipProbeItem) {
|
||||
if (this.ipRankingList == null) {
|
||||
this.ipRankingList = new ArrayList<>();
|
||||
}
|
||||
this.ipRankingList.add(ipProbeItem);
|
||||
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putString(KEY_IP_RANKING_ITEMS, convertProbeList(ipRankingList));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void setHostTtl(String host, int ttl) {
|
||||
if (ttlCache == null) {
|
||||
ttlCache = new HashMap<>();
|
||||
}
|
||||
ttlCache.put(host, ttl);
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putString(KEY_TTL_CHANGER, convertTtlCache(ttlCache));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void cleanSp() {
|
||||
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getCurrentConfig() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("褰撳墠閰嶇疆 accountId : ").append(accountId).append("\n")
|
||||
.append("鏄惁鍏佽杩囨湡IP : ").append(enableExpiredIp).append("\n")
|
||||
.append("鏄惁寮€鍚湰鍦扮紦瀛?: ").append(enableCacheIp).append("\n")
|
||||
.append("鏄惁寮€鍚疕TTPS : ").append(enableHttps).append("\n")
|
||||
.append("褰撳墠region璁剧疆 : ").append(region).append("\n")
|
||||
.append("褰撳墠瓒呮椂璁剧疆 : ").append(timeout).append("\n")
|
||||
.append("褰撳墠鎺㈡祴閰嶇疆 : ").append(convertProbeList(ipRankingList)).append("\n")
|
||||
.append("褰撳墠缂撳瓨閰嶇疆 : ").append(convertTtlCache(ttlCache)).append("\n")
|
||||
.append("褰撳墠涓荤珯鍩熷悕閰嶇疆 : ").append(convertHostList(hostListWithFixedIp)).append("\n")
|
||||
;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
private static String convertHostList(List<String> hostListWithFixedIp) {
|
||||
if (hostListWithFixedIp == null) {
|
||||
return null;
|
||||
}
|
||||
JSONArray array = new JSONArray();
|
||||
for (String host : hostListWithFixedIp) {
|
||||
array.put(host);
|
||||
}
|
||||
return array.toString();
|
||||
}
|
||||
|
||||
private static String convertTtlCache(HashMap<String, Integer> ttlCache) {
|
||||
if (ttlCache == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
for (String host : ttlCache.keySet()) {
|
||||
try {
|
||||
jsonObject.put(host, ttlCache.get(host));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return jsonObject.toString();
|
||||
}
|
||||
|
||||
private static String convertProbeList(List<IPRankingBean> ipProbeItems) {
|
||||
if (ipProbeItems == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
for (IPRankingBean item : ipProbeItems) {
|
||||
try {
|
||||
jsonObject.put(item.getHostName(), item.getPort());
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return jsonObject.toString();
|
||||
}
|
||||
|
||||
private static ArrayList<IPRankingBean> convertToProbeList(String json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(json);
|
||||
ArrayList<IPRankingBean> list = new ArrayList<>();
|
||||
for (Iterator<String> it = jsonObject.keys(); it.hasNext(); ) {
|
||||
String host = it.next();
|
||||
list.add(new IPRankingBean(host, jsonObject.getInt(host)));
|
||||
}
|
||||
return list;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HashMap<String, Integer> convertToCacheTtlData(String json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(json);
|
||||
HashMap<String, Integer> map = new HashMap<>();
|
||||
for (Iterator<String> it = jsonObject.keys(); it.hasNext(); ) {
|
||||
String host = it.next();
|
||||
map.put(host, jsonObject.getInt(host));
|
||||
}
|
||||
return map;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ArrayList<String> convertToStringList(String json) {
|
||||
if (json != null) {
|
||||
try {
|
||||
JSONArray array = new JSONArray(json);
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
list.add(array.getString(i));
|
||||
}
|
||||
return list;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.HttpDnsService;
|
||||
import com.newsdk.sdk.android.httpdns.ILogger;
|
||||
import com.newsdk.sdk.android.httpdns.log.HttpDnsLog;
|
||||
import com.newsdk.ams.httpdns.demo.utils.SpUtil;
|
||||
|
||||
public class MyApp extends Application {
|
||||
|
||||
private static final String SP_NAME = "HTTPDNS_DEMO";
|
||||
private static final String KEY_INSTANCE = "KEY_INSTANCE";
|
||||
private static final String VALUE_INSTANCE_A = "A";
|
||||
private static final String VALUE_INSTANCE_B = "B";
|
||||
|
||||
public static final String TAG = "HTTPDNS DEMO";
|
||||
private static MyApp instance;
|
||||
|
||||
public static MyApp getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final HttpDnsHolder holderA = new HttpDnsHolder("璇锋浛鎹负娴嬭瘯鐢ˋ瀹炰緥鐨刟ccountId", "璇锋浛鎹负娴嬭瘯鐢ˋ瀹炰緥鐨剆ecret");
|
||||
private final HttpDnsHolder holderB = new HttpDnsHolder("璇锋浛鎹负娴嬭瘯鐢˙瀹炰緥鐨刟ccountId", null);
|
||||
|
||||
private HttpDnsHolder current = holderA;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
instance = this;
|
||||
|
||||
// 寮€鍚痩ogcat 鏃ュ織 榛樿鍏抽棴, 寮€鍙戞祴璇曡繃绋嬩腑鍙互寮€鍚?
|
||||
HttpDnsLog.enable(true);
|
||||
// 娉ㄥ叆鏃ュ織鎺ュ彛锛屾帴鍙梙ttpdns鐨勬棩蹇楋紝寮€鍙戞祴璇曡繃绋嬩腑鍙互寮€鍚? 鍩虹鏃ュ織闇€瑕佸厛enable鎵嶇敓鏁堬紝涓€浜涢敊璇棩蹇椾笉闇€瑕?
|
||||
HttpDnsLog.setLogger(new ILogger() {
|
||||
@Override
|
||||
public void log(String msg) {
|
||||
Log.d("HttpDnsLogger", msg);
|
||||
}
|
||||
});
|
||||
|
||||
// 鍒濆鍖杊ttpdns鐨勯厤缃?
|
||||
holderA.init(this);
|
||||
holderB.init(this);
|
||||
|
||||
SpUtil.readSp(this, SP_NAME, new SpUtil.OnGetSp() {
|
||||
@Override
|
||||
public void onGetSp(SharedPreferences sp) {
|
||||
String flag = sp.getString(KEY_INSTANCE, VALUE_INSTANCE_A);
|
||||
if (flag.equals(VALUE_INSTANCE_A)) {
|
||||
current = holderA;
|
||||
} else {
|
||||
current = holderB;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public HttpDnsHolder getCurrentHolder() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public HttpDnsHolder changeHolder() {
|
||||
if (current == holderA) {
|
||||
current = holderB;
|
||||
SpUtil.writeSp(instance, SP_NAME, new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putString(KEY_INSTANCE, VALUE_INSTANCE_B);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
current = holderA;
|
||||
SpUtil.writeSp(instance, SP_NAME, new SpUtil.OnGetSpEditor() {
|
||||
@Override
|
||||
public void onGetSpEditor(SharedPreferences.Editor editor) {
|
||||
editor.putString(KEY_INSTANCE, VALUE_INSTANCE_A);
|
||||
}
|
||||
});
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
public HttpDnsService getService() {
|
||||
return current.getService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType;
|
||||
|
||||
public interface NetworkRequest {
|
||||
|
||||
/**
|
||||
* 璁剧疆httpdns鐨勯厤缃?
|
||||
*/
|
||||
void updateHttpDnsConfig(boolean async, RequestIpType requestIpType);
|
||||
|
||||
/**
|
||||
* get璇锋眰
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
String httpGet(String url) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType;
|
||||
import com.newsdk.ams.httpdns.demo.base.BaseActivity;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class SDNSActivity extends BaseActivity {
|
||||
|
||||
private static final String[] HOSTS = new String[] {
|
||||
"www.Aliyun.com",
|
||||
"www.taobao.com"
|
||||
};
|
||||
|
||||
private final HashMap<String, String> globalParams = new HashMap<>();
|
||||
private String host = HOSTS[0];
|
||||
private RequestIpType requestIpType = RequestIpType.v4;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addEditTextEditTextButton("key", "value", "Add global param", new OnButtonClickMoreView() {
|
||||
@Override
|
||||
public void onBtnClick(View[] views) {
|
||||
EditText keyView = (EditText) views[0];
|
||||
EditText valueView = (EditText) views[1];
|
||||
String key = keyView.getEditableText().toString();
|
||||
String value = valueView.getEditableText().toString();
|
||||
globalParams.put(key, value);
|
||||
sendLog("Global param added: " + key + "=" + value);
|
||||
}
|
||||
});
|
||||
|
||||
addOneButton("Clear global params", new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
globalParams.clear();
|
||||
sendLog("Global params cleared.");
|
||||
}
|
||||
});
|
||||
|
||||
addFourButton(
|
||||
"IP type v4",
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
requestIpType = RequestIpType.v4;
|
||||
sendLog("Request IP type: v4");
|
||||
}
|
||||
},
|
||||
"IP type v6",
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
requestIpType = RequestIpType.v6;
|
||||
sendLog("Request IP type: v6");
|
||||
}
|
||||
},
|
||||
"IP type both",
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
requestIpType = RequestIpType.both;
|
||||
sendLog("Request IP type: both");
|
||||
}
|
||||
},
|
||||
"IP type auto",
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
requestIpType = RequestIpType.auto;
|
||||
sendLog("Request IP type: auto");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
addAutoCompleteTextViewButton(HOSTS, "Host", "Set host", new OnButtonClick() {
|
||||
@Override
|
||||
public void onBtnClick(View view) {
|
||||
AutoCompleteTextView hostView = (AutoCompleteTextView) view;
|
||||
host = hostView.getEditableText().toString();
|
||||
sendLog("Host set to: " + host);
|
||||
}
|
||||
});
|
||||
|
||||
addEditTextEditTextButton("key", "value", "Resolve with param", new OnButtonClickMoreView() {
|
||||
@Override
|
||||
public void onBtnClick(View[] views) {
|
||||
EditText keyView = (EditText) views[0];
|
||||
EditText valueView = (EditText) views[1];
|
||||
HashMap<String, String> params = new HashMap<>(globalParams);
|
||||
params.put(keyView.getEditableText().toString(), valueView.getEditableText().toString());
|
||||
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(
|
||||
host, requestIpType, params, "sdns-demo");
|
||||
sendLog("SDNS result: " + result);
|
||||
}
|
||||
});
|
||||
|
||||
addTwoButton("Resolve scale1", new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HashMap<String, String> params = new HashMap<>(globalParams);
|
||||
params.put("scale", "scale1");
|
||||
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(
|
||||
host, requestIpType, params, "sdns-demo");
|
||||
sendLog("scale1 result: " + result);
|
||||
}
|
||||
}, "Resolve scale2", new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HashMap<String, String> params = new HashMap<>(globalParams);
|
||||
params.put("scale", "scale2");
|
||||
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(
|
||||
host, requestIpType, params, "sdns-demo");
|
||||
sendLog("scale2 result: " + result);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.net.SSLCertificateSocketFactory;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class WebViewActivity extends Activity {
|
||||
|
||||
private WebView webView;
|
||||
private static final String targetUrl = "http://www.apple.com";
|
||||
|
||||
private static final String TAG = MyApp.TAG + "WebView";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_webview);
|
||||
|
||||
initBar();
|
||||
initHttpDnsWebView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean
|
||||
onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
|
||||
webView.goBack();//杩斿洖涓婁釜椤甸潰
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);//閫€鍑篈ctivity
|
||||
}
|
||||
|
||||
private void initBar() {
|
||||
findViewById(R.id.bar_img).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
WebViewActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
((TextView) findViewById(R.id.bar_text)).setText("HTTPDNS");
|
||||
}
|
||||
|
||||
private void initHttpDnsWebView() {
|
||||
|
||||
webView = (WebView) this.findViewById(R.id.wv_container);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
String scheme = request.getUrl().getScheme().trim();
|
||||
String method = request.getMethod();
|
||||
Map<String, String> headerFields = request.getRequestHeaders();
|
||||
String url = request.getUrl().toString();
|
||||
Log.e(TAG, "url:" + url);
|
||||
// 鏃犳硶鎷︽埅body锛屾嫤鎴柟妗堝彧鑳芥甯稿鐞嗕笉甯ody鐨勮姹傦紱
|
||||
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))
|
||||
&& method.equalsIgnoreCase("get")) {
|
||||
try {
|
||||
URLConnection connection = recursiveRequest(url, headerFields, null);
|
||||
|
||||
if (connection == null) {
|
||||
Log.e(TAG, "connection null");
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
|
||||
// 娉?锛氬浜嶱OST璇锋眰鐨凚ody鏁版嵁锛學ebResourceRequest鎺ュ彛涓苟娌℃湁鎻愪緵锛岃繖閲屾棤娉曞鐞?
|
||||
String contentType = connection.getContentType();
|
||||
String mime = getMime(contentType);
|
||||
String charset = getCharset(contentType);
|
||||
HttpURLConnection httpURLConnection = (HttpURLConnection) connection;
|
||||
int statusCode = httpURLConnection.getResponseCode();
|
||||
String response = httpURLConnection.getResponseMessage();
|
||||
Map<String, List<String>> headers = httpURLConnection.getHeaderFields();
|
||||
Set<String> headerKeySet = headers.keySet();
|
||||
Log.e(TAG, "code:" + httpURLConnection.getResponseCode());
|
||||
Log.e(TAG, "mime:" + mime + "; charset:" + charset);
|
||||
|
||||
|
||||
// 鏃爉ime绫诲瀷鐨勮姹備笉鎷︽埅
|
||||
if (TextUtils.isEmpty(mime)) {
|
||||
Log.e(TAG, "no MIME");
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
} else {
|
||||
// 浜岃繘鍒惰祫婧愭棤闇€缂栫爜淇℃伅
|
||||
if (!TextUtils.isEmpty(charset) || (isBinaryRes(mime))) {
|
||||
WebResourceResponse resourceResponse = new WebResourceResponse(mime, charset, httpURLConnection.getInputStream());
|
||||
resourceResponse.setStatusCodeAndReasonPhrase(statusCode, response);
|
||||
Map<String, String> responseHeader = new HashMap<String, String>();
|
||||
for (String key : headerKeySet) {
|
||||
// HttpUrlConnection鍙兘鍖呭惈key涓簄ull鐨勬姤澶达紝鎸囧悜璇ttp璇锋眰鐘舵€佺爜
|
||||
responseHeader.put(key, httpURLConnection.getHeaderField(key));
|
||||
}
|
||||
resourceResponse.setResponseHeaders(responseHeader);
|
||||
return resourceResponse;
|
||||
} else {
|
||||
Log.e(TAG, "non binary resource for " + mime);
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
// API < 21 鍙兘鎷︽埅URL鍙傛暟
|
||||
return super.shouldInterceptRequest(view, url);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
webView.loadUrl(targetUrl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 浠巆ontentType涓幏鍙朚IME绫诲瀷
|
||||
*
|
||||
* @param contentType
|
||||
* @return
|
||||
*/
|
||||
private String getMime(String contentType) {
|
||||
if (contentType == null) {
|
||||
return null;
|
||||
}
|
||||
return contentType.split(";")[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 浠巆ontentType涓幏鍙栫紪鐮佷俊鎭?
|
||||
*
|
||||
* @param contentType
|
||||
* @return
|
||||
*/
|
||||
private String getCharset(String contentType) {
|
||||
if (contentType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] fields = contentType.split(";");
|
||||
if (fields.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String charset = fields[1];
|
||||
if (!charset.contains("=")) {
|
||||
return null;
|
||||
}
|
||||
charset = charset.substring(charset.indexOf("=") + 1);
|
||||
return charset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 鏄惁鏄簩杩涘埗璧勬簮锛屼簩杩涘埗璧勬簮鍙互涓嶉渶瑕佺紪鐮佷俊鎭?
|
||||
*/
|
||||
private boolean isBinaryRes(String mime) {
|
||||
if (mime.startsWith("image")
|
||||
|| mime.startsWith("audio")
|
||||
|| mime.startsWith("video")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public URLConnection recursiveRequest(String path, Map<String, String> headers, String reffer) {
|
||||
HttpURLConnection conn;
|
||||
URL url = null;
|
||||
try {
|
||||
url = new URL(path);
|
||||
// 寮傛鎺ュ彛鑾峰彇IP
|
||||
String ip = MyApp.getInstance().getService().getIpByHostAsync(url.getHost());
|
||||
if (ip != null) {
|
||||
// 閫氳繃HTTPDNS鑾峰彇IP鎴愬姛锛岃繘琛孶RL鏇挎崲鍜孒OST澶磋缃?
|
||||
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
|
||||
String newUrl = path.replaceFirst(url.getHost(), ip);
|
||||
conn = (HttpURLConnection) new URL(newUrl).openConnection();
|
||||
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> field : headers.entrySet()) {
|
||||
conn.setRequestProperty(field.getKey(), field.getValue());
|
||||
}
|
||||
}
|
||||
// 璁剧疆HTTP璇锋眰澶碒ost鍩?
|
||||
conn.setRequestProperty("Host", url.getHost());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
conn.setConnectTimeout(30000);
|
||||
conn.setReadTimeout(30000);
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
if (conn instanceof HttpsURLConnection) {
|
||||
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) conn;
|
||||
WebviewTlsSniSocketFactory sslSocketFactory = new WebviewTlsSniSocketFactory(
|
||||
(HttpsURLConnection)conn);
|
||||
|
||||
// sni鍦烘櫙锛屽垱寤篠SLScocket
|
||||
httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
|
||||
// https鍦烘櫙锛岃瘉涔︽牎楠?
|
||||
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
String host = httpsURLConnection.getRequestProperty("Host");
|
||||
if (null == host) {
|
||||
host = httpsURLConnection.getURL().getHost();
|
||||
}
|
||||
return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
|
||||
}
|
||||
});
|
||||
}
|
||||
int code = conn.getResponseCode();// Network block
|
||||
if (needRedirect(code)) {
|
||||
// 鍘熸湁鎶ュご涓惈鏈塩ookie锛屾斁寮冩嫤鎴?
|
||||
if (containCookie(headers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String location = conn.getHeaderField("Location");
|
||||
if (location == null) {
|
||||
location = conn.getHeaderField("location");
|
||||
}
|
||||
|
||||
if (location != null) {
|
||||
if (!(location.startsWith("http://") || location
|
||||
.startsWith("https://"))) {
|
||||
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏╱rl
|
||||
URL originalUrl = new URL(path);
|
||||
location = originalUrl.getProtocol() + "://"
|
||||
+ originalUrl.getHost() + location;
|
||||
}
|
||||
Log.e(TAG, "code: " + code + "; location: " + location + "; path " + path);
|
||||
return recursiveRequest(location, headers, path);
|
||||
} else {
|
||||
// 鏃犳硶鑾峰彇location淇℃伅锛岃娴忚鍣ㄨ幏鍙?
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// redirect finish.
|
||||
Log.e(TAG, "redirect finish");
|
||||
return conn;
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
Log.w(TAG, "recursiveRequest MalformedURLException");
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "recursiveRequest IOException");
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "unknow exception");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private boolean needRedirect(int code) {
|
||||
return code >= 300 && code < 400;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* header涓槸鍚﹀惈鏈塩ookie
|
||||
*/
|
||||
private boolean containCookie(Map<String, String> headers) {
|
||||
for (Map.Entry<String, String> headerField : headers.entrySet()) {
|
||||
if (headerField.getKey().contains("Cookie")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static class WebviewTlsSniSocketFactory extends SSLSocketFactory {
|
||||
private final String TAG = WebviewTlsSniSocketFactory.class.getSimpleName();
|
||||
HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
private final HttpsURLConnection conn;
|
||||
|
||||
public WebviewTlsSniSocketFactory(HttpsURLConnection conn) {
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TLS layer
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
|
||||
String peerHost = this.conn.getRequestProperty("Host");
|
||||
if (peerHost == null)
|
||||
peerHost = host;
|
||||
Log.i(TAG, "customized createSocket. host: " + peerHost);
|
||||
InetAddress address = plainSocket.getInetAddress();
|
||||
if (autoClose) {
|
||||
// we don't need the plainSocket
|
||||
plainSocket.close();
|
||||
}
|
||||
// create and connect SSL socket, but don't do hostname/certificate verification yet
|
||||
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
|
||||
SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);
|
||||
|
||||
// enable TLSv1.1/1.2 if available
|
||||
ssl.setEnabledProtocols(ssl.getSupportedProtocols());
|
||||
|
||||
// set up SNI before the handshake
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
Log.i(TAG, "Setting SNI hostname");
|
||||
sslSocketFactory.setHostname(ssl, peerHost);
|
||||
} else {
|
||||
Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
|
||||
try {
|
||||
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
|
||||
setHostnameMethod.invoke(ssl, peerHost);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "SNI not useable", e);
|
||||
}
|
||||
}
|
||||
|
||||
// verify hostname and certificate
|
||||
SSLSession session = ssl.getSession();
|
||||
|
||||
if (!hostnameVerifier.verify(peerHost, session))
|
||||
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);
|
||||
|
||||
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
|
||||
" using " + session.getCipherSuite());
|
||||
|
||||
return ssl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo.base;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.newsdk.ams.httpdns.demo.MyApp;
|
||||
import com.newsdk.ams.httpdns.demo.R;
|
||||
|
||||
public class BaseActivity extends Activity {
|
||||
|
||||
public static final int MSG_WHAT_LOG = 10000;
|
||||
|
||||
private ScrollView logScrollView;
|
||||
private TextView logView;
|
||||
private LinearLayout llContainer;
|
||||
|
||||
private Handler handler;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_base);
|
||||
|
||||
handler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
switch (msg.what) {
|
||||
case MSG_WHAT_LOG:
|
||||
logView.setText(logView.getText() + "\n" + (String) msg.obj);
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
logScrollView.fullScroll(View.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
logScrollView = findViewById(R.id.logScrollView);
|
||||
logView = findViewById(R.id.tvConsoleText);
|
||||
llContainer = findViewById(R.id.llContainer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
handler = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 鍙戦€佹棩蹇楀埌鐣岄潰
|
||||
*
|
||||
* @param log
|
||||
*/
|
||||
protected void sendLog(String log) {
|
||||
Log.d(MyApp.TAG, log);
|
||||
if (handler != null) {
|
||||
Message msg = handler.obtainMessage(MSG_WHAT_LOG, log);
|
||||
handler.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected void cleanLog() {
|
||||
logView.setText("");
|
||||
}
|
||||
|
||||
protected void addView(int layoutId, OnViewCreated created) {
|
||||
FrameLayout container = new FrameLayout(this);
|
||||
View.inflate(this, layoutId, container);
|
||||
llContainer.addView(container, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
created.onViewCreated(container);
|
||||
}
|
||||
|
||||
protected void addOneButton(
|
||||
final String labelOne, final View.OnClickListener clickListenerOne
|
||||
) {
|
||||
addView(R.layout.item_one_button, new OnViewCreated() {
|
||||
@Override
|
||||
public void onViewCreated(View view) {
|
||||
|
||||
Button btnOne = view.findViewById(R.id.btnOne);
|
||||
btnOne.setText(labelOne);
|
||||
btnOne.setOnClickListener(clickListenerOne);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void addTwoButton(
|
||||
final String labelOne, final View.OnClickListener clickListenerOne,
|
||||
final String labelTwo, final View.OnClickListener clickListenerTwo
|
||||
) {
|
||||
addView(R.layout.item_two_button, new OnViewCreated() {
|
||||
@Override
|
||||
public void onViewCreated(View view) {
|
||||
|
||||
Button btnOne = view.findViewById(R.id.btnOne);
|
||||
btnOne.setText(labelOne);
|
||||
btnOne.setOnClickListener(clickListenerOne);
|
||||
|
||||
Button btnTwo = view.findViewById(R.id.btnTwo);
|
||||
btnTwo.setText(labelTwo);
|
||||
btnTwo.setOnClickListener(clickListenerTwo);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void addThreeButton(
|
||||
final String labelOne, final View.OnClickListener clickListenerOne,
|
||||
final String labelTwo, final View.OnClickListener clickListenerTwo,
|
||||
final String labelThree, final View.OnClickListener clickListenerThree
|
||||
) {
|
||||
addView(R.layout.item_three_button, new OnViewCreated() {
|
||||
@Override
|
||||
public void onViewCreated(View view) {
|
||||
|
||||
Button btnOne = view.findViewById(R.id.btnOne);
|
||||
btnOne.setText(labelOne);
|
||||
btnOne.setOnClickListener(clickListenerOne);
|
||||
|
||||
Button btnTwo = view.findViewById(R.id.btnTwo);
|
||||
btnTwo.setText(labelTwo);
|
||||
btnTwo.setOnClickListener(clickListenerTwo);
|
||||
|
||||
Button btnThree = view.findViewById(R.id.btnThree);
|
||||
btnThree.setText(labelThree);
|
||||
btnThree.setOnClickListener(clickListenerThree);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected void addFourButton(
|
||||
final String labelOne, final View.OnClickListener clickListenerOne,
|
||||
final String labelTwo, final View.OnClickListener clickListenerTwo,
|
||||
final String labelThree, final View.OnClickListener clickListenerThree,
|
||||
final String labelFour, final View.OnClickListener clickListenerFour
|
||||
) {
|
||||
addView(R.layout.item_four_button, new OnViewCreated() {
|
||||
@Override
|
||||
public void onViewCreated(View view) {
|
||||
|
||||
Button btnOne = view.findViewById(R.id.btnOne);
|
||||
btnOne.setText(labelOne);
|
||||
btnOne.setOnClickListener(clickListenerOne);
|
||||
|
||||
Button btnTwo = view.findViewById(R.id.btnTwo);
|
||||
btnTwo.setText(labelTwo);
|
||||
btnTwo.setOnClickListener(clickListenerTwo);
|
||||
|
||||
Button btnThree = view.findViewById(R.id.btnThree);
|
||||
btnThree.setText(labelThree);
|
||||
btnThree.setOnClickListener(clickListenerThree);
|
||||
|
||||
Button btnFour = view.findViewById(R.id.btnFour);
|
||||
btnFour.setText(labelFour);
|
||||
btnFour.setOnClickListener(clickListenerFour);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void addEditTextButton(
|
||||
final String hint,
|
||||
final String labelOne, final OnButtonClick clickListenerOne
|
||||
) {
|
||||
addView(R.layout.item_edit_button, new OnViewCreated() {
|
||||
@Override
|
||||
public void onViewCreated(View view) {
|
||||
|
||||
Button btnOne = view.findViewById(R.id.btnOne);
|
||||
btnOne.setText(labelOne);
|
||||
final EditText editText = view.findViewById(R.id.etOne);
|
||||
editText.setHint(hint);
|
||||
btnOne.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
clickListenerOne.onBtnClick(editText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected void addEditTextEditTextButton(
|
||||
final String hintOne, final String hintTwo,
|
||||
final String labelOne, final OnButtonClickMoreView clickListenerOne
|
||||
) {
|
||||
addView(R.layout.item_edit_edit_button, new OnViewCreated() {
|
||||
@Override
|
||||
public void onViewCreated(View view) {
|
||||
|
||||
Button btnOne = view.findViewById(R.id.btnOne);
|
||||
btnOne.setText(labelOne);
|
||||
final EditText editTextOne = view.findViewById(R.id.etOne);
|
||||
editTextOne.setHint(hintOne);
|
||||
final EditText editTextTwo = view.findViewById(R.id.etTwo);
|
||||
editTextTwo.setHint(hintTwo);
|
||||
btnOne.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
clickListenerOne.onBtnClick(new View[]{editTextOne, editTextTwo});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void addAutoCompleteTextViewButton(
|
||||
final String[] strings, final String hint, final String labelOne, final OnButtonClick clickListenerOne
|
||||
) {
|
||||
addView(R.layout.item_autocomplete_button, new OnViewCreated() {
|
||||
@Override
|
||||
public void onViewCreated(View view) {
|
||||
|
||||
Button btnOne = view.findViewById(R.id.btnOne);
|
||||
btnOne.setText(labelOne);
|
||||
|
||||
final AutoCompleteTextView actvOne = view.findViewById(R.id.actvOne);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(),
|
||||
android.R.layout.simple_dropdown_item_1line, strings);
|
||||
actvOne.setAdapter(adapter);
|
||||
actvOne.setHint(hint);
|
||||
|
||||
btnOne.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
clickListenerOne.onBtnClick(actvOne);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface OnViewCreated {
|
||||
void onViewCreated(View view);
|
||||
}
|
||||
|
||||
public interface OnButtonClick {
|
||||
void onBtnClick(View view);
|
||||
}
|
||||
|
||||
public interface OnButtonClickMoreView {
|
||||
void onBtnClick(View[] views);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo.http;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.SSLCertificateSocketFactory;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
|
||||
import com.newsdk.sdk.android.httpdns.NetType;
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType;
|
||||
import com.newsdk.sdk.android.httpdns.SyncService;
|
||||
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector;
|
||||
import com.newsdk.ams.httpdns.demo.MyApp;
|
||||
import com.newsdk.ams.httpdns.demo.NetworkRequest;
|
||||
import com.newsdk.ams.httpdns.demo.utils.Util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* 浣跨敤HttpUrlConnection 瀹炵幇璇锋眰
|
||||
*/
|
||||
public class HttpUrlConnectionRequest implements NetworkRequest {
|
||||
|
||||
public static final String TAG = MyApp.TAG + "HttpUrl";
|
||||
|
||||
private final Context context;
|
||||
private boolean async;
|
||||
private RequestIpType type;
|
||||
|
||||
public HttpUrlConnectionRequest(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHttpDnsConfig(boolean async, RequestIpType requestIpType) {
|
||||
this.async = async;
|
||||
this.type = requestIpType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String httpGet(String url) throws Exception {
|
||||
Log.d(TAG, "浣跨敤httpUrlConnection 璇锋眰" + url + " 寮傛鎺ュ彛 " + async + " ip绫诲瀷 " + type.name());
|
||||
|
||||
HttpURLConnection conn = getConnection(url);
|
||||
InputStream in = null;
|
||||
BufferedReader streamReader = null;
|
||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
in = conn.getErrorStream();
|
||||
String errStr = null;
|
||||
if (in != null) {
|
||||
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
|
||||
errStr = readStringFrom(streamReader).toString();
|
||||
}
|
||||
Log.d(TAG, "璇锋眰澶辫触 " + conn.getResponseCode() + " err " + errStr);
|
||||
throw new Exception("Status Code : " + conn.getResponseCode() + " Msg : " + errStr);
|
||||
} else {
|
||||
in = conn.getInputStream();
|
||||
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
|
||||
String responseStr = readStringFrom(streamReader).toString();
|
||||
Log.d(TAG, "璇锋眰鎴愬姛 " + responseStr);
|
||||
return responseStr;
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection getConnection(String url) throws IOException {
|
||||
final String host = new URL(url).getHost();
|
||||
HttpURLConnection conn = null;
|
||||
HTTPDNSResult result;
|
||||
/* 鍒囨崲涓烘柊鐗堟爣鍑哸pi */
|
||||
if (async) {
|
||||
result = MyApp.getInstance().getService().getHttpDnsResultForHostAsync(host, type);
|
||||
} else {
|
||||
result = MyApp.getInstance().getService().getHttpDnsResultForHostSync(host, type);
|
||||
}
|
||||
Log.d(TAG, "httpdns 瑙f瀽 " + host + " 缁撴灉涓?" + result + " ttl is " + Util.getTtl(result));
|
||||
|
||||
// 杩欓噷闇€瑕佹牴鎹疄闄呮儏鍐甸€夋嫨浣跨敤ipv6鍦板潃 杩樻槸 ipv4鍦板潃锛?涓嬮潰绀轰緥鐨勪唬鐮佷紭鍏堜娇鐢ㄤ簡ipv6鍦板潃
|
||||
if (result.getIpv6s() != null && result.getIpv6s().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v4) {
|
||||
String newUrl = url.replace(host, "[" + result.getIpv6s()[0] + "]");
|
||||
conn = (HttpURLConnection) new URL(newUrl).openConnection();
|
||||
conn.setRequestProperty("Host", host);
|
||||
Log.d(TAG, "浣跨敤ipv6鍦板潃 " + newUrl);
|
||||
} else if (result.getIps() != null && result.getIps().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v6) {
|
||||
String newUrl = url.replace(host, result.getIps()[0]);
|
||||
conn = (HttpURLConnection) new URL(newUrl).openConnection();
|
||||
conn.setRequestProperty("Host", host);
|
||||
Log.d(TAG, "浣跨敤ipv4鍦板潃 " + newUrl);
|
||||
}
|
||||
|
||||
if (conn == null) {
|
||||
Log.d(TAG, "httpdns 鏈繑鍥炶В鏋愮粨鏋滐紝璧發ocaldns");
|
||||
conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
}
|
||||
conn.setConnectTimeout(30000);
|
||||
conn.setReadTimeout(30000);
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
if (conn instanceof HttpsURLConnection) {
|
||||
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) conn;
|
||||
WebviewTlsSniSocketFactory sslSocketFactory = new WebviewTlsSniSocketFactory(
|
||||
(HttpsURLConnection)conn);
|
||||
|
||||
// sni鍦烘櫙锛屽垱寤篠SLSocket
|
||||
httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
|
||||
// https鍦烘櫙锛岃瘉涔︽牎楠?
|
||||
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
String host = httpsURLConnection.getRequestProperty("Host");
|
||||
if (null == host) {
|
||||
host = httpsURLConnection.getURL().getHost();
|
||||
}
|
||||
return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
|
||||
}
|
||||
});
|
||||
}
|
||||
int code = conn.getResponseCode();// Network block
|
||||
if (needRedirect(code)) {
|
||||
//涓存椂閲嶅畾鍚戝拰姘镐箙閲嶅畾鍚憀ocation鐨勫ぇ灏忓啓鏈夊尯鍒?
|
||||
String location = conn.getHeaderField("Location");
|
||||
if (location == null) {
|
||||
location = conn.getHeaderField("location");
|
||||
}
|
||||
if (!(location.startsWith("http://") || location
|
||||
.startsWith("https://"))) {
|
||||
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏╱rl
|
||||
URL originalUrl = new URL(url);
|
||||
location = originalUrl.getProtocol() + "://"
|
||||
+ originalUrl.getHost() + location;
|
||||
}
|
||||
return getConnection(location);
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
private boolean needRedirect(int code) {
|
||||
return code >= 300 && code < 400;
|
||||
}
|
||||
|
||||
static class WebviewTlsSniSocketFactory extends SSLSocketFactory {
|
||||
private final String TAG = WebviewTlsSniSocketFactory.class.getSimpleName();
|
||||
HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
private HttpsURLConnection conn;
|
||||
|
||||
public WebviewTlsSniSocketFactory(HttpsURLConnection conn) {
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TLS layer
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
|
||||
String peerHost = this.conn.getRequestProperty("Host");
|
||||
if (peerHost == null)
|
||||
peerHost = host;
|
||||
Log.i(TAG, "customized createSocket. host: " + peerHost);
|
||||
InetAddress address = plainSocket.getInetAddress();
|
||||
if (autoClose) {
|
||||
// we don't need the plainSocket
|
||||
plainSocket.close();
|
||||
}
|
||||
// create and connect SSL socket, but don't do hostname/certificate verification yet
|
||||
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
|
||||
SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);
|
||||
|
||||
// enable TLSv1.1/1.2 if available
|
||||
ssl.setEnabledProtocols(ssl.getSupportedProtocols());
|
||||
|
||||
// set up SNI before the handshake
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
Log.i(TAG, "Setting SNI hostname");
|
||||
sslSocketFactory.setHostname(ssl, peerHost);
|
||||
} else {
|
||||
Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
|
||||
try {
|
||||
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
|
||||
setHostnameMethod.invoke(ssl, peerHost);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "SNI not useable", e);
|
||||
}
|
||||
}
|
||||
|
||||
// verify hostname and certificate
|
||||
SSLSession session = ssl.getSession();
|
||||
|
||||
if (!hostnameVerifier.verify(peerHost, session))
|
||||
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);
|
||||
|
||||
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
|
||||
" using " + session.getCipherSuite());
|
||||
|
||||
return ssl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stream to string
|
||||
*/
|
||||
public static StringBuilder readStringFrom(BufferedReader streamReader) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = streamReader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo.okhttp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
|
||||
import com.newsdk.sdk.android.httpdns.NetType;
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType;
|
||||
import com.newsdk.sdk.android.httpdns.SyncService;
|
||||
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector;
|
||||
import com.newsdk.ams.httpdns.demo.MyApp;
|
||||
import com.newsdk.ams.httpdns.demo.NetworkRequest;
|
||||
import com.newsdk.ams.httpdns.demo.utils.Util;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.Dns;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* okhttp瀹炵幇缃戠粶璇锋眰
|
||||
*/
|
||||
public class OkHttpRequest implements NetworkRequest {
|
||||
|
||||
public static final String TAG = MyApp.TAG + "Okhttp";
|
||||
private final OkHttpClient client;
|
||||
|
||||
private boolean async;
|
||||
private RequestIpType type;
|
||||
|
||||
public OkHttpRequest(final Context context) {
|
||||
client = new OkHttpClient.Builder()
|
||||
// 杩欓噷閰嶇疆杩炴帴姹狅紝鏄负浜嗘柟渚挎祴璇昲ttpdns鑳藉姏锛屾寮忎唬鐮佽涓嶈閰嶇疆
|
||||
.connectionPool(new ConnectionPool(0, 10 * 1000, TimeUnit.MICROSECONDS))
|
||||
.dns(new Dns() {
|
||||
@Override
|
||||
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
|
||||
HTTPDNSResult result;
|
||||
/* 鍒囨崲涓烘柊鐗堟爣鍑哸pi */
|
||||
if (async) {
|
||||
result = MyApp.getInstance().getService().getHttpDnsResultForHostAsync(hostname, type);
|
||||
} else {
|
||||
result = MyApp.getInstance().getService().getHttpDnsResultForHostSync(hostname, type);
|
||||
}
|
||||
Log.d(TAG, "httpdns 瑙f瀽 " + hostname + " 缁撴灉涓?" + result + " ttl is " + Util.getTtl(result));
|
||||
List<InetAddress> inetAddresses = new ArrayList<>();
|
||||
// 杩欓噷闇€瑕佹牴鎹疄闄呮儏鍐甸€夋嫨浣跨敤ipv6鍦板潃 杩樻槸 ipv4鍦板潃锛?涓嬮潰绀轰緥鐨勪唬鐮佷紭鍏堜娇鐢ㄤ簡ipv6鍦板潃
|
||||
Log.d(TAG, "netType is: " + HttpDnsNetworkDetector.getInstance().getNetType(context));
|
||||
if (result.getIpv6s() != null && result.getIpv6s().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v4) {
|
||||
for (int i = 0; i < result.getIpv6s().length; i++) {
|
||||
inetAddresses.addAll(Arrays.asList(InetAddress.getAllByName(result.getIpv6s()[i])));
|
||||
}
|
||||
Log.d(TAG, "浣跨敤ipv6鍦板潃" + inetAddresses);
|
||||
} else if (result.getIps() != null && result.getIps().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v6) {
|
||||
for (int i = 0; i < result.getIps().length; i++) {
|
||||
inetAddresses.addAll(Arrays.asList(InetAddress.getAllByName(result.getIps()[i])));
|
||||
}
|
||||
Log.d(TAG, "浣跨敤ipv4鍦板潃" + inetAddresses);
|
||||
}
|
||||
if (inetAddresses.size() == 0) {
|
||||
Log.d(TAG, "httpdns 鏈繑鍥濱P锛岃蛋localdns");
|
||||
return Dns.SYSTEM.lookup(hostname);
|
||||
}
|
||||
return inetAddresses;
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHttpDnsConfig(boolean async, RequestIpType requestIpType) {
|
||||
this.async = async;
|
||||
this.type = requestIpType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String httpGet(String url) throws Exception {
|
||||
Log.d(TAG, "浣跨敤okhttp 璇锋眰" + url + " 寮傛鎺ュ彛 " + async + " ip绫诲瀷 " + type.name());
|
||||
Response response = client.newCall(new Request.Builder().url(url).build()).execute();
|
||||
int code = response.code();
|
||||
String body = response.body().string();
|
||||
Log.d(TAG, "浣跨敤okhttp 璇锋眰缁撴灉 code " + code + " body " + body);
|
||||
if (code != HttpURLConnection.HTTP_OK) {
|
||||
throw new Exception("璇锋眰澶辫触 code " + code + " body " + body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class SpUtil {
|
||||
|
||||
public static void readSp(Context context, String name, OnGetSp onGetSp) {
|
||||
SharedPreferences sp = context.getSharedPreferences(name, Context.MODE_PRIVATE);
|
||||
onGetSp.onGetSp(sp);
|
||||
}
|
||||
|
||||
public static void writeSp(Context context, String name, OnGetSpEditor onGetSpEditor) {
|
||||
SharedPreferences sp = context.getSharedPreferences(name, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
onGetSpEditor.onGetSpEditor(editor);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public interface OnGetSp {
|
||||
void onGetSp(SharedPreferences sp);
|
||||
}
|
||||
|
||||
public interface OnGetSpEditor {
|
||||
void onGetSpEditor(SharedPreferences.Editor editor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType;
|
||||
import com.newsdk.sdk.android.httpdns.SyncService;
|
||||
import com.newsdk.ams.httpdns.demo.MyApp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class ThreadUtil {
|
||||
|
||||
public static void multiThreadTest(
|
||||
final String[] validHosts,
|
||||
final int hostCount,
|
||||
final int threadCount,
|
||||
final int executeTime,
|
||||
final boolean async,
|
||||
final RequestIpType type
|
||||
) {
|
||||
new Thread(() -> runMultiThreadTest(validHosts, hostCount, threadCount, executeTime, async, type)).start();
|
||||
}
|
||||
|
||||
private static void runMultiThreadTest(
|
||||
String[] validHosts,
|
||||
int hostCount,
|
||||
int threadCount,
|
||||
int executeTime,
|
||||
boolean async,
|
||||
RequestIpType type
|
||||
) {
|
||||
int validCount = Math.min(validHosts.length, hostCount);
|
||||
Log.d(MyApp.TAG, "Start multiThreadTest, threads=" + threadCount
|
||||
+ ", executeTimeMs=" + executeTime
|
||||
+ ", hostCount=" + hostCount
|
||||
+ ", validHostCount=" + validCount
|
||||
+ ", async=" + async
|
||||
+ ", ipType=" + type.name());
|
||||
|
||||
ArrayList<String> hosts = new ArrayList<>(hostCount);
|
||||
for (int i = 0; i < hostCount - validCount; i++) {
|
||||
hosts.add("test" + i + ".Aliyun.com");
|
||||
}
|
||||
hosts.addAll(Arrays.asList(validHosts).subList(0, validCount));
|
||||
|
||||
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
|
||||
CountDownLatch done = new CountDownLatch(threadCount);
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
pool.execute(() -> {
|
||||
Random random = new Random(Thread.currentThread().getId());
|
||||
long begin = System.currentTimeMillis();
|
||||
int requestCount = 0;
|
||||
int successCount = 0;
|
||||
|
||||
while (System.currentTimeMillis() - begin < executeTime) {
|
||||
String host = hosts.get(random.nextInt(hosts.size()));
|
||||
try {
|
||||
HTTPDNSResult result;
|
||||
if (async) {
|
||||
result = MyApp.getInstance().getService().getIpsByHostAsync(host, type);
|
||||
} else {
|
||||
result = ((SyncService) MyApp.getInstance().getService()).getByHost(host, type);
|
||||
}
|
||||
if (result != null && result.getIps() != null && result.getIps().length > 0) {
|
||||
successCount++;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.w(MyApp.TAG, "multiThreadTest request failed: " + t.getMessage());
|
||||
}
|
||||
requestCount++;
|
||||
}
|
||||
|
||||
Log.w(MyApp.TAG, "multiThreadTest thread=" + Thread.currentThread().getId()
|
||||
+ ", requestCount=" + requestCount
|
||||
+ ", successCount=" + successCount);
|
||||
done.countDown();
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
done.await();
|
||||
} catch (InterruptedException ignored) {
|
||||
} finally {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.newsdk.ams.httpdns.demo.utils;
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.HTTPDNSResult;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class Util {
|
||||
/**
|
||||
* 鑾峰彇ttl锛?
|
||||
* 姝ゆ柟娉曟槸鐢ㄤ簬娴嬭瘯鑷畾涔塼tl鏄惁鐢熸晥
|
||||
*/
|
||||
public static int getTtl(HTTPDNSResult result) {
|
||||
try {
|
||||
Field ttlField = HTTPDNSResult.class.getDeclaredField("ttl");
|
||||
ttlField.setAccessible(true);
|
||||
return ttlField.getInt(result);
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB |
@@ -1,49 +0,0 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="2">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/new_bg" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/logScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvConsoleText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="5dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp" />
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="#EAEAEA">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#ffffff"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:background="#f2f2f2">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bar_img"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@mipmap/back"
|
||||
android:padding="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bar_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text=""
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bar_more"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:text="鏇村"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<WebView
|
||||
android:id="@+id/wv_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/actvOne"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/actvOne"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etOne"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/etOne" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/etOne" />
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/etTwo" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTwo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnThree"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnFour"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTwo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnThree"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTwo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
||||
@@ -1,30 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">閵嗘劙妯嬮柌灞肩隘HttpDns閵嗘厪emo缁嬪绨</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="normal_parse">閺咁噣鈧俺袙閺</string>
|
||||
<string name="request_taobao">鐟欙絾鐎藉ǎ妯虹杺閸╃喎鎮</string>
|
||||
<string name="request_apple">鐟欙絾鐎絘pple閸╃喎鎮</string>
|
||||
<string name="request_douban">鐟欙絾鐎界挒鍡欐憵閸╃喎鎮</string>
|
||||
<string name="https_parse">HTTPS瀵偓閸</string>
|
||||
<string name="timeout">鐠佸墽鐤嗙搾鍛</string>
|
||||
<string name="set_expired">閸忎浇顔忔潻鍥ㄦ埂閸╃喎鎮</string>
|
||||
<string name="set_cache">瀵偓閸氼垱瀵旀稊鍛缂傛挸鐡</string>
|
||||
<string name="set_degration_filter">闂勫秶楠囩粵鏍殣</string>
|
||||
<string name="set_pre_resolve">妫板嫯袙閺</string>
|
||||
<string name="set_region">region</string>
|
||||
<string name="sync_request">閸氬本顒炵憴锝嗙€</string>
|
||||
<string name="multi_sync_request">閸氬本顒炵憴锝嗙€介獮璺哄絺</string>
|
||||
<string name="multi_request">楠炶泛褰傜憴锝嗙€</string>
|
||||
|
||||
<string name="main_about_us">閸忓厖绨幋鎴滄粦</string>
|
||||
<string name="main_helper">鐢喖濮稉顓炵妇</string>
|
||||
<string name="main_clear_text">濞撳懘娅庤ぐ鎾冲濞戝牊浼</string>
|
||||
|
||||
<string name="layout_aboutus_arr">All Rights Reserved.</string>
|
||||
<string name="layout_aboutus_company">闂冨潡鍣锋禍?鏉烆垯娆?閺堝妾洪崗顒€寰冮悧鍫熸綀閹碘偓閺</string>
|
||||
<string name="layout_aboutus_copyright">Copyright 婕?2009 - 2016 Aliyun.com</string>
|
||||
<string name="layout_aboutus_app_version">1.1.3</string>
|
||||
<string name="layout_helpus_content">Q : 娴犫偓娑斿牊妲搁悽銊﹀煕娴f捇鐛橠emo閿涚剠nA : 閻劍鍩涙担鎾荤崣Demo鐏忚鲸妲搁梼鍧楀櫡娴滄垵閽╅崣棰佽礋閹劏鍤滈崝銊ュ灡瀵よ櫣娈戦妴浣烘暏閺夈儰缍嬫瀛抰tpDns閺堝秴濮熼崪灞藉冀妫e牆缂撶拋顔炬暏閻ㄥ嫪绔存稉顏勭毈Demo閿涘矁顔€閹劋缍嬫灞肩┒閹规灚鈧線鐝弫鍫㈡畱HttpDns閺堝秴濮熼妴淇搉\nQ : 婵″倷缍嶉懕鏃傞兇閹存垳婊戦敍鐒卬A : App Demo閻╃鍙ч梻顕€顣介敍宀冾嚞閹兼粎鍌ㄩ柦澶愭嫟缂囥倕褰块敍?1777313</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme"></style>
|
||||
|
||||
<style name="button_allgrade_content">
|
||||
<item name="android:layout_margin">1dip</item>
|
||||
<item name="android:background">#ffffff</item>
|
||||
<item name="android:textSize">18sp</item>
|
||||
<item name="android:clickable">true</item>
|
||||
<item name="android:textColor">#413d41</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true">
|
||||
<New-anchors>
|
||||
<certificates src="system" />
|
||||
</New-anchors>
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
@@ -1,12 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '7.3.1' apply false
|
||||
id 'com.android.library' version '7.3.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||
}
|
||||
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
1
EdgeHttpDNS/sdk/android/demo/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,127 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
gradle.ext {
|
||||
httpVersion = '2.3.4'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.newsdk.ams.httpdns.demo'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.newsdk.ams.httpdns.demo"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField "String", "HTTPDNS_VERSION", "\"${gradle.httpVersion}\""
|
||||
buildConfigField "String", "ACCOUNT_ID", "\"璇锋浛鎹负娴嬭瘯鐢ㄥ疄渚嬬殑accountId\""
|
||||
buildConfigField "String", "SECRET_KEY", "\"璇锋浛鎹负娴嬭瘯鐢ㄥ疄渚嬬殑secret\""
|
||||
buildConfigField "String", "AES_SECRET_KEY", "\"璇锋浛鎹负娴嬭瘯鐢ㄥ疄渚嬬殑aes\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
debug {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
forTest {
|
||||
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慺orTest鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
|
||||
initWith release
|
||||
debuggable true
|
||||
}
|
||||
}
|
||||
|
||||
variantFilter { variant ->
|
||||
def names = variant.flavors*.name
|
||||
def type = variant.buildType.name
|
||||
// To check for a certain build type, use variant.buildType.name == "<buildType>"
|
||||
if ((names.contains("normal") && type.contains("forTest"))
|
||||
|| (names.contains("intl") && type.contains("forTest"))
|
||||
|| (names.contains("end2end") && type.contains("release"))
|
||||
|| (names.contains("end2end") && type.contains("debug"))
|
||||
) {
|
||||
// Gradle ignores any variants that satisfy the conditions above.
|
||||
setIgnore(true)
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
all {
|
||||
jvmArgs '-noverify'
|
||||
systemProperty 'robolectric.logging.enable', true
|
||||
}
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
dataBinding = true
|
||||
}
|
||||
|
||||
flavorDimensions += "version"
|
||||
|
||||
productFlavors {
|
||||
normal {
|
||||
|
||||
}
|
||||
|
||||
intl {
|
||||
|
||||
}
|
||||
|
||||
end2end {
|
||||
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慹nd2end鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
|
||||
|
||||
implementation("com.squareup.okhttp3:okhttp:4.10.0")
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
|
||||
implementation project(path: ':httpdns-sdk')
|
||||
|
||||
implementation('com.newsdk:fastjson:1.1.73.android@jar')
|
||||
// implementation('com.emas.hybrid:emas-hybrid-android:1.1.0.4-public-SNAPSHOT') {
|
||||
// exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||
// exclude group: 'com.taobao.android', module: 'thin_so_release'
|
||||
// }
|
||||
|
||||
implementation 'com.newsdk.ams:new-android-tool:1.1.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
## Project-wide Gradle settings.
|
||||
#
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
#
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
#
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
#Sat Jun 11 21:37:51 CST 2016
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
android.enableD8=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
22
EdgeHttpDNS/sdk/android/demo/proguard-rules.pro
vendored
@@ -1,22 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-keep class com.newsdk.sdk.android.httpdns.**{*;}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.newsdk.ams.emas.demo", appContext.packageName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
|
||||
|
||||
<application
|
||||
android:name="com.newsdk.ams.emas.demo.HttpDnsApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:theme="@style/Theme.NewHttpDnsDemo"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:name="com.newsdk.ams.emas.demo.ui.practice.HttpDnsWebviewGetActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.NewHttpDnsDemo.NoActionBar">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.newsdk.ams.emas.demo.ui.info.list.ListActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.NewHttpDnsDemo.NoActionBar">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.newsdk.ams.emas.demo.MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
<activity android:name="com.newsdk.ams.emas.demo.ui.info.SdnsGlobalSettingActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.NewHttpDnsDemo.NoActionBar" >
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
object BatchResolveCacheHolder {
|
||||
var batchResolveV4List: MutableList<String> = mutableListOf()
|
||||
var batchResolveV6List: MutableList<String> = mutableListOf()
|
||||
var batchResolveBothList: MutableList<String> = mutableListOf()
|
||||
var batchResolveAutoList: MutableList<String> = mutableListOf()
|
||||
fun convertBatchResolveCacheData(cacheData: String?) {
|
||||
if (cacheData == null) {
|
||||
batchResolveBothList.add("www.baidu.com")
|
||||
batchResolveBothList.add("m.baidu.com")
|
||||
batchResolveBothList.add("www.Aliyun.com")
|
||||
batchResolveBothList.add("www.taobao.com")
|
||||
batchResolveBothList.add("www.163.com")
|
||||
batchResolveBothList.add("www.sohu.com")
|
||||
batchResolveBothList.add("www.sina.com.cn")
|
||||
batchResolveBothList.add("www.douyin.com")
|
||||
batchResolveBothList.add("www.qq.com")
|
||||
batchResolveBothList.add("www.chinaamc.com")
|
||||
batchResolveBothList.add("m.chinaamc.com")
|
||||
return
|
||||
}
|
||||
try {
|
||||
val jsonObject = JSONObject(cacheData)
|
||||
val v4Array = jsonObject.optJSONArray("v4")
|
||||
val v6Array = jsonObject.optJSONArray("v6")
|
||||
val bothArray = jsonObject.optJSONArray("both")
|
||||
val autoArray = jsonObject.optJSONArray("auto")
|
||||
if (v4Array != null) {
|
||||
var length = v4Array.length()
|
||||
--length
|
||||
while (length >= 0) {
|
||||
batchResolveV4List.add(0, v4Array.getString(length))
|
||||
--length
|
||||
}
|
||||
}
|
||||
if (v6Array != null) {
|
||||
var length = v6Array.length()
|
||||
--length
|
||||
while (length >= 0) {
|
||||
batchResolveV6List.add(0, v6Array.getString(length))
|
||||
--length
|
||||
}
|
||||
}
|
||||
if (bothArray != null) {
|
||||
var length = bothArray.length()
|
||||
--length
|
||||
while (length >= 0) {
|
||||
batchResolveBothList.add(0, bothArray.getString(length))
|
||||
--length
|
||||
}
|
||||
}
|
||||
if (autoArray != null) {
|
||||
var length = autoArray.length()
|
||||
--length
|
||||
while (length >= 0) {
|
||||
batchResolveAutoList.add(0, autoArray.getString(length))
|
||||
--length
|
||||
}
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
fun convertBatchResolveString(): String {
|
||||
val jsonObject = JSONObject()
|
||||
val v4Array = JSONArray()
|
||||
val v6Array = JSONArray()
|
||||
val bothArray = JSONArray()
|
||||
val autoArray = JSONArray()
|
||||
for (host in batchResolveV4List) {
|
||||
v4Array.put(host)
|
||||
}
|
||||
jsonObject.put("v4", v4Array)
|
||||
for (host in batchResolveV6List) {
|
||||
v6Array.put(host)
|
||||
}
|
||||
jsonObject.put("v6", v6Array)
|
||||
for (host in batchResolveBothList) {
|
||||
bothArray.put(host)
|
||||
}
|
||||
jsonObject.put("both", bothArray)
|
||||
for (host in batchResolveAutoList) {
|
||||
autoArray.put(host)
|
||||
}
|
||||
jsonObject.put("auto", autoArray)
|
||||
return jsonObject.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
|
||||
import android.app.Application
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/5/24
|
||||
*/
|
||||
class HttpDnsApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_ENABLE_AUTH_MODE
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_SECRET_KEY_SET_BY_CONFIG
|
||||
import com.newsdk.sdk.android.httpdns.HttpDns
|
||||
import com.newsdk.sdk.android.httpdns.HttpDnsService
|
||||
import com.newsdk.ams.httpdns.demo.BuildConfig
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/6
|
||||
*/
|
||||
object HttpDnsServiceHolder {
|
||||
|
||||
fun getHttpDnsService(context: Context) : HttpDnsService? {
|
||||
val dnsService = if (!TextUtils.isEmpty(BuildConfig.ACCOUNT_ID)) {
|
||||
val secretKeySetByConfig = getAccountPreference(context).getBoolean(KEY_SECRET_KEY_SET_BY_CONFIG, true)
|
||||
if (secretKeySetByConfig) {
|
||||
HttpDns.getService(BuildConfig.ACCOUNT_ID)
|
||||
} else {
|
||||
val authMode = getAccountPreference(context).getBoolean(KEY_ENABLE_AUTH_MODE, true)
|
||||
if (authMode && !TextUtils.isEmpty(BuildConfig.SECRET_KEY)) HttpDns.getService(
|
||||
context,
|
||||
BuildConfig.ACCOUNT_ID, BuildConfig.SECRET_KEY
|
||||
) else HttpDns.getService(
|
||||
context,
|
||||
BuildConfig.ACCOUNT_ID
|
||||
)
|
||||
}
|
||||
} else null
|
||||
|
||||
return dnsService
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
import com.newsdk.ams.httpdns.demo.databinding.ActivityMainBinding
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
object HttpDns {
|
||||
var inited = false
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val navView: BottomNavigationView = binding.navView
|
||||
|
||||
val navController = findNavController(R.id.nav_host_fragment_activity_main)
|
||||
// Passing each menu ID as a set of Ids because each
|
||||
// menu should be considered as top level destinations.
|
||||
val appBarConfiguration = AppBarConfiguration(
|
||||
setOf(
|
||||
R.id.navigation_basic,
|
||||
R.id.navigation_resolve,
|
||||
R.id.navigation_best_practice,
|
||||
R.id.navigation_information
|
||||
)
|
||||
)
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
|
||||
navView.setOnItemSelectedListener {
|
||||
if (HttpDns.inited) {
|
||||
navController.navigate(it.itemId)
|
||||
true
|
||||
} else {
|
||||
Toast.makeText(this, R.string.init_tip, Toast.LENGTH_SHORT).show()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
object PreResolveCacheHolder {
|
||||
var preResolveV4List: MutableList<String> = mutableListOf()
|
||||
var preResolveV6List: MutableList<String> = mutableListOf()
|
||||
var preResolveBothList: MutableList<String> = mutableListOf()
|
||||
var preResolveAutoList: MutableList<String> = mutableListOf()
|
||||
|
||||
fun convertPreResolveCacheData(cacheData: String?) {
|
||||
if (cacheData == null) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val jsonObject = JSONObject(cacheData)
|
||||
val v4Array = jsonObject.optJSONArray("v4")
|
||||
val v6Array = jsonObject.optJSONArray("v6")
|
||||
val bothArray = jsonObject.optJSONArray("both")
|
||||
val autoArray = jsonObject.optJSONArray("auto")
|
||||
|
||||
if (v4Array != null) {
|
||||
var length = v4Array.length()
|
||||
--length
|
||||
while (length >= 0) {
|
||||
preResolveV4List.add(0, v4Array.getString(length))
|
||||
--length
|
||||
}
|
||||
}
|
||||
|
||||
if (v6Array != null) {
|
||||
var length = v6Array.length()
|
||||
--length
|
||||
while (length >= 0) {
|
||||
preResolveV6List.add(0, v6Array.getString(length))
|
||||
--length
|
||||
}
|
||||
}
|
||||
|
||||
if (bothArray != null) {
|
||||
var length = bothArray.length()
|
||||
--length
|
||||
while (length >= 0) {
|
||||
preResolveBothList.add(0, bothArray.getString(length))
|
||||
--length
|
||||
}
|
||||
}
|
||||
|
||||
if (autoArray != null) {
|
||||
var length = autoArray.length()
|
||||
--length
|
||||
while (length >= 0) {
|
||||
preResolveAutoList.add(0, autoArray.getString(length))
|
||||
--length
|
||||
}
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun convertPreResolveString(): String {
|
||||
val jsonObject = JSONObject()
|
||||
val v4Array = JSONArray()
|
||||
val v6Array = JSONArray()
|
||||
val bothArray = JSONArray()
|
||||
val autoArray = JSONArray()
|
||||
for (host in preResolveV4List) {
|
||||
v4Array.put(host)
|
||||
}
|
||||
jsonObject.put("v4", v4Array)
|
||||
|
||||
for (host in preResolveV6List) {
|
||||
v6Array.put(host)
|
||||
}
|
||||
jsonObject.put("v6", v6Array)
|
||||
|
||||
for (host in preResolveBothList) {
|
||||
bothArray.put(host)
|
||||
}
|
||||
jsonObject.put("both", bothArray)
|
||||
|
||||
for (host in preResolveAutoList) {
|
||||
autoArray.put(host)
|
||||
}
|
||||
jsonObject.put("auto", autoArray)
|
||||
|
||||
return jsonObject.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
class SingleLiveData<T> : MutableLiveData<T>() {
|
||||
|
||||
private val mPending = AtomicBoolean(false)
|
||||
|
||||
@MainThread
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
|
||||
if (hasActiveObservers()) {
|
||||
Log.w("SingleLiveData", "Multiple observers registered but only one will be notified of changes.")
|
||||
}
|
||||
|
||||
// Observe the internal MutableLiveData
|
||||
super.observe(owner, Observer<T> { t ->
|
||||
if (mPending.compareAndSet(true, false)) {
|
||||
observer.onChanged(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun setValue(@Nullable t: T?) {
|
||||
mPending.set(true)
|
||||
super.setValue(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for cases where T is Void, to make calls cleaner.
|
||||
*/
|
||||
@MainThread
|
||||
fun call() {
|
||||
value = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
|
||||
import com.newsdk.sdk.android.httpdns.CacheTtlChanger
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/6
|
||||
*/
|
||||
object TtlCacheHolder {
|
||||
var ttlCache = mutableMapOf<String, Int>()
|
||||
|
||||
val cacheTtlChanger = CacheTtlChanger { host, _, ttl ->
|
||||
if (ttlCache[host] != null) ttlCache[host]!! else ttl
|
||||
}
|
||||
|
||||
fun convertTtlCacheData(cacheData: String?) {
|
||||
if (cacheData == null) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val jsonObject = JSONObject(cacheData)
|
||||
val it = jsonObject.keys()
|
||||
while (it.hasNext()) {
|
||||
val host = it.next()
|
||||
ttlCache[host] = jsonObject.getInt(host)
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun MutableMap<String, Int>?.toJsonString(): String? {
|
||||
if (this == null) {
|
||||
return null
|
||||
}
|
||||
val jsonObject = JSONObject()
|
||||
for (host in this.keys) {
|
||||
try {
|
||||
jsonObject.put(host, this[host])
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return jsonObject.toString()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.newsdk.sdk.android.httpdns.ranking.IPRankingBean
|
||||
import com.newsdk.ams.httpdns.demo.BuildConfig
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/5
|
||||
*/
|
||||
fun String?.toHostList(): MutableList<String>? {
|
||||
if (this == null) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
val array = JSONArray(this)
|
||||
val list = mutableListOf<String>()
|
||||
for (i in 0 until array.length()) {
|
||||
list.add(array.getString(i))
|
||||
}
|
||||
return list
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun String?.toTagList(): MutableList<String>? {
|
||||
if (this == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
val array = JSONArray(this)
|
||||
val list = mutableListOf<String>()
|
||||
for (i in 0 until array.length()) {
|
||||
list.add(array.getString(i))
|
||||
}
|
||||
return list
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun String?.toIPRankingList(): MutableList<IPRankingBean>? {
|
||||
if (this == null) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
val jsonObject = JSONObject(this)
|
||||
val list = mutableListOf<IPRankingBean>()
|
||||
val it = jsonObject.keys()
|
||||
while (it.hasNext()) {
|
||||
val host = it.next()
|
||||
list.add(
|
||||
IPRankingBean(
|
||||
host,
|
||||
jsonObject.getInt(host)
|
||||
)
|
||||
)
|
||||
}
|
||||
return list
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun String?.toTtlCacheMap(): MutableMap<String, Int>? {
|
||||
if (this == null) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
val jsonObject = JSONObject(this)
|
||||
val map = mutableMapOf<String, Int>()
|
||||
val it = jsonObject.keys()
|
||||
while (it.hasNext()) {
|
||||
val host = it.next()
|
||||
val ttl = jsonObject.getInt(host)
|
||||
map[host] = ttl
|
||||
}
|
||||
return map
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun String?.toBlackList(): MutableList<String>? {
|
||||
if (this == null) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
val array = JSONArray(this)
|
||||
val list = mutableListOf<String>()
|
||||
for (i in 0 until array.length()) {
|
||||
list.add(array.getString(i))
|
||||
}
|
||||
return list
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getAccountPreference(context: Context): SharedPreferences {
|
||||
return context.getSharedPreferences(
|
||||
"New_httpdns_${BuildConfig.ACCOUNT_ID}",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
}
|
||||
|
||||
fun convertPreResolveList(preResolveHostList: List<String>?): String? {
|
||||
if (preResolveHostList == null) {
|
||||
return null
|
||||
}
|
||||
val array = JSONArray()
|
||||
for (host in preResolveHostList) {
|
||||
array.put(host)
|
||||
}
|
||||
return array.toString()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readStringFrom(streamReader: BufferedReader): StringBuilder {
|
||||
val sb = StringBuilder()
|
||||
var line: String?
|
||||
while (streamReader.readLine().also { line = it } != null) {
|
||||
sb.append(line)
|
||||
}
|
||||
return sb
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.constant
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/5/24
|
||||
*/
|
||||
|
||||
const val KEY_ENABLE_AUTH_MODE = "enable_auth_mode"
|
||||
|
||||
const val KEY_SECRET_KEY_SET_BY_CONFIG = "secret_key_set_by_config"
|
||||
|
||||
const val KEY_ENABLE_ENCRYPT_MODE = "enable_encrypt_mode"
|
||||
|
||||
const val KEY_ENABLE_EXPIRED_IP = "enable_expired_ip"
|
||||
|
||||
const val KEY_ENABLE_CACHE_IP = "enable_cache_ip"
|
||||
|
||||
const val KEY_CACHE_EXPIRE_TIME = "cache_expire_time"
|
||||
|
||||
const val KEY_ENABLE_HTTPS = "enable_https"
|
||||
|
||||
const val KEY_ENABLE_DEGRADE = "enable_degrade"
|
||||
|
||||
const val KEY_ENABLE_AUTO_REFRESH = "enable_auto_refresh"
|
||||
|
||||
const val KEY_ENABLE_LOG = "enable_log";
|
||||
|
||||
const val KEY_REGION = "region";
|
||||
|
||||
const val KEY_TIMEOUT = "timeout"
|
||||
|
||||
const val KEY_IP_RANKING_ITEMS = "ip_ranking_items"
|
||||
|
||||
const val KEY_TTL_CHANGER = "ttl_changer"
|
||||
|
||||
const val KEY_TAGS = "tags"
|
||||
|
||||
const val KEY_HOST_WITH_FIXED_IP = "host_with_fixed_ip"
|
||||
|
||||
const val KEY_HOST_BLACK_LIST = "host_black_list"
|
||||
|
||||
const val KEY_ASYNC_RESOLVE = "async_resolve"
|
||||
|
||||
const val KEY_SDNS_RESOLVE = "sdns_resolve"
|
||||
|
||||
const val KEY_RESOLVE_IP_TYPE = "resolve_ip_type"
|
||||
|
||||
const val KEY_RESOLVE_METHOD = "resolve_method"
|
||||
|
||||
const val KEY_PRE_RESOLVE_HOST_LIST = "pre_resolve_host_list"
|
||||
|
||||
const val KEY_SDNS_GLOBAL_PARAMS = "sdns_global_params"
|
||||
|
||||
const val KEY_BATCH_RESOLVE_HOST_LIST = "batch_resolve_host_list"
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.net
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.newsdk.ams.emas.demo.HttpDnsServiceHolder
|
||||
import com.newsdk.ams.emas.demo.readStringFrom
|
||||
import com.newsdk.ams.emas.demo.ui.resolve.Response
|
||||
import com.newsdk.sdk.android.httpdns.HTTPDNSResult
|
||||
import com.newsdk.sdk.android.httpdns.HttpDnsCallback
|
||||
import com.newsdk.sdk.android.httpdns.NetType
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType
|
||||
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/5/26
|
||||
*/
|
||||
class HttpURLConnectionRequest(private val context: Context, private val requestIpType: RequestIpType, private val resolveMethod: String,
|
||||
private val isSdns: Boolean, private val sdnsParams: Map<String, String>?, private val cacheKey: String): IRequest {
|
||||
|
||||
override fun get(url: String): Response {
|
||||
val conn: HttpURLConnection = getConnection(url)
|
||||
val inputStream: InputStream?
|
||||
val streamReader: BufferedReader?
|
||||
return if (conn.responseCode != HttpURLConnection.HTTP_OK) {
|
||||
inputStream = conn.errorStream
|
||||
var errStr: String? = null
|
||||
if (inputStream != null) {
|
||||
streamReader = BufferedReader(InputStreamReader(inputStream, "UTF-8"))
|
||||
errStr = readStringFrom(streamReader).toString()
|
||||
}
|
||||
throw Exception("Status Code : " + conn.responseCode + " Msg : " + errStr)
|
||||
} else {
|
||||
inputStream = conn.inputStream
|
||||
streamReader = BufferedReader(InputStreamReader(inputStream, "UTF-8"))
|
||||
val responseStr: String = readStringFrom(streamReader).toString()
|
||||
Response(conn.responseCode, responseStr)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConnection(url: String): HttpURLConnection {
|
||||
val host = URL(url).host
|
||||
val dnsService = HttpDnsServiceHolder.getHttpDnsService(context)
|
||||
|
||||
var ipURL: String? = null
|
||||
dnsService?.let {
|
||||
//鏇挎崲涓烘渶鏂扮殑api
|
||||
Log.d("HttpURLConnection", "start lookup $host via $resolveMethod")
|
||||
var httpDnsResult = HTTPDNSResult("", null, null, null, false, false)
|
||||
if (resolveMethod == "getHttpDnsResultForHostSync(String host, RequestIpType type)") {
|
||||
httpDnsResult = if (isSdns) {
|
||||
dnsService.getHttpDnsResultForHostSync(host, requestIpType, sdnsParams, cacheKey)
|
||||
} else {
|
||||
dnsService.getHttpDnsResultForHostSync(host, requestIpType)
|
||||
}
|
||||
} else if (resolveMethod == "getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback)") {
|
||||
val lock = CountDownLatch(1)
|
||||
if (isSdns) {
|
||||
dnsService.getHttpDnsResultForHostAsync(host, requestIpType, sdnsParams, cacheKey, HttpDnsCallback {
|
||||
httpDnsResult = it
|
||||
lock.countDown()
|
||||
})
|
||||
} else {
|
||||
dnsService.getHttpDnsResultForHostAsync(host, requestIpType, HttpDnsCallback {
|
||||
httpDnsResult = it
|
||||
lock.countDown()
|
||||
})
|
||||
}
|
||||
lock.await()
|
||||
} else if (resolveMethod == "getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type)") {
|
||||
httpDnsResult = if (isSdns) {
|
||||
dnsService.getHttpDnsResultForHostSyncNonBlocking(host, requestIpType, sdnsParams, cacheKey)
|
||||
} else {
|
||||
dnsService.getHttpDnsResultForHostSyncNonBlocking(host, requestIpType)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("HttpURLConnection", "httpdns $host 瑙f瀽缁撴灉 $httpDnsResult")
|
||||
val ipStackType = HttpDnsNetworkDetector.getInstance().getNetType(context)
|
||||
val isV6 = ipStackType == NetType.v6 || ipStackType == NetType.both
|
||||
val isV4 = ipStackType == NetType.v4 || ipStackType == NetType.both
|
||||
|
||||
if (httpDnsResult.ipv6s != null && httpDnsResult.ipv6s.isNotEmpty() && isV6) {
|
||||
ipURL = url.replace(host, "[" + httpDnsResult.ipv6s[0] + "]")
|
||||
} else if (httpDnsResult.ips != null && httpDnsResult.ips.isNotEmpty() && isV4) {
|
||||
ipURL = url.replace(host, httpDnsResult.ips[0])
|
||||
}
|
||||
}
|
||||
|
||||
val conn: HttpURLConnection = URL(ipURL ?: url).openConnection() as HttpURLConnection
|
||||
conn.setRequestProperty("Host", host)
|
||||
|
||||
conn.connectTimeout = 30000
|
||||
conn.readTimeout = 30000
|
||||
conn.instanceFollowRedirects = false
|
||||
|
||||
if (conn is HttpsURLConnection) {
|
||||
val sslSocketFactory = TLSSNISocketFactory(conn)
|
||||
// SNI鍦烘櫙锛屽垱寤篠SLSocket
|
||||
conn.sslSocketFactory = sslSocketFactory
|
||||
// https鍦烘櫙锛岃瘉涔︽牎楠?
|
||||
conn.hostnameVerifier = HostnameVerifier { _, session ->
|
||||
val requestHost = conn.getRequestProperty("Host") ?:conn.getURL().host
|
||||
HttpsURLConnection.getDefaultHostnameVerifier().verify(requestHost, session)
|
||||
}
|
||||
}
|
||||
|
||||
val responseCode = conn.responseCode
|
||||
if (needRedirect(responseCode)) {
|
||||
//涓存椂閲嶅畾鍚戝拰姘镐箙閲嶅畾鍚憀ocation鐨勫ぇ灏忓啓鏈夊尯鍒?
|
||||
var location = conn.getHeaderField("Location")
|
||||
if (location == null) {
|
||||
location = conn.getHeaderField("location")
|
||||
}
|
||||
if (!(location!!.startsWith("http://") || location.startsWith("https://"))) {
|
||||
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏╱rl
|
||||
val originalUrl = URL(url)
|
||||
location = (originalUrl.protocol + "://"
|
||||
+ originalUrl.host + location)
|
||||
}
|
||||
return getConnection(location)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
private fun needRedirect(code: Int): Boolean {
|
||||
return code in 300..399
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.net
|
||||
|
||||
import com.newsdk.ams.emas.demo.ui.resolve.Response
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/5/26
|
||||
*/
|
||||
interface IRequest {
|
||||
fun get(url: String): Response
|
||||
}
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.net
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.newsdk.ams.emas.demo.HttpDnsServiceHolder
|
||||
import com.newsdk.sdk.android.httpdns.HTTPDNSResult
|
||||
import com.newsdk.sdk.android.httpdns.HttpDnsCallback
|
||||
import com.newsdk.sdk.android.httpdns.NetType
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType
|
||||
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
|
||||
import okhttp3.ConnectionPool
|
||||
import okhttp3.Dns
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/14
|
||||
*/
|
||||
class OkHttpClientSingleton private constructor(context: Context
|
||||
) {
|
||||
|
||||
private val mContext = WeakReference(context)
|
||||
|
||||
private var mRequestIpType = RequestIpType.v4
|
||||
private var mResolveMethod: String = "getHttpDnsResultForHostSync(String host, RequestIpType type)"
|
||||
private var mIsSdns: Boolean = false
|
||||
private var mSdnsParams: Map<String, String>? = null
|
||||
private var mCacheKey: String? = null
|
||||
|
||||
private val tag: String = "httpdns:hOkHttpClientSingleton"
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var instance: OkHttpClientSingleton? = null
|
||||
|
||||
fun getInstance(context: Context): OkHttpClientSingleton {
|
||||
if (instance != null) {
|
||||
return instance!!
|
||||
}
|
||||
|
||||
return synchronized(this) {
|
||||
if (instance != null) {
|
||||
instance!!
|
||||
} else {
|
||||
instance = OkHttpClientSingleton(context)
|
||||
instance!!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateConfig(requestIpType: RequestIpType, resolveMethod: String, isSdns: Boolean, params: Map<String, String>?, cacheKey: String): OkHttpClientSingleton {
|
||||
mRequestIpType = requestIpType
|
||||
mResolveMethod = resolveMethod
|
||||
mIsSdns = isSdns
|
||||
mSdnsParams = params
|
||||
mCacheKey = cacheKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun getOkHttpClient(): OkHttpClient {
|
||||
val loggingInterceptor = HttpLoggingInterceptor(OkHttpLog())
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
.connectionPool(ConnectionPool(0, 10 * 1000, TimeUnit.MICROSECONDS))
|
||||
.hostnameVerifier { _, _ ->true }
|
||||
.dns(object : Dns {
|
||||
override fun lookup(hostname: String): List<InetAddress> {
|
||||
Log.d(tag, "start lookup $hostname via $mResolveMethod")
|
||||
val dnsService = HttpDnsServiceHolder.getHttpDnsService(mContext.get()!!)
|
||||
//淇敼涓烘渶鏂扮殑閫氫織鏄撴噦鐨刟pi
|
||||
var httpDnsResult: HTTPDNSResult? = null
|
||||
val inetAddresses = mutableListOf<InetAddress>()
|
||||
if (mResolveMethod == "getHttpDnsResultForHostSync(String host, RequestIpType type)") {
|
||||
httpDnsResult = if (mIsSdns) {
|
||||
dnsService?.getHttpDnsResultForHostSync(hostname, mRequestIpType, mSdnsParams, mCacheKey)
|
||||
} else {
|
||||
dnsService?.getHttpDnsResultForHostSync(hostname, mRequestIpType)
|
||||
}
|
||||
} else if (mResolveMethod == "getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback)") {
|
||||
val lock = CountDownLatch(1)
|
||||
if (mIsSdns) {
|
||||
dnsService?.getHttpDnsResultForHostAsync(
|
||||
hostname,
|
||||
mRequestIpType,
|
||||
mSdnsParams,
|
||||
mCacheKey,
|
||||
HttpDnsCallback {
|
||||
httpDnsResult = it
|
||||
lock.countDown()
|
||||
})
|
||||
} else {
|
||||
dnsService?.getHttpDnsResultForHostAsync(
|
||||
hostname,
|
||||
mRequestIpType,
|
||||
HttpDnsCallback {
|
||||
httpDnsResult = it
|
||||
lock.countDown()
|
||||
})
|
||||
}
|
||||
lock.await()
|
||||
} else if (mResolveMethod == "getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type)") {
|
||||
httpDnsResult = if (mIsSdns) {
|
||||
dnsService?.getHttpDnsResultForHostSyncNonBlocking(hostname, mRequestIpType, mSdnsParams, mCacheKey)
|
||||
} else {
|
||||
dnsService?.getHttpDnsResultForHostSyncNonBlocking(hostname, mRequestIpType)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(tag, "httpdns $hostname 瑙f瀽缁撴灉 $httpDnsResult")
|
||||
httpDnsResult?.let { processDnsResult(it, inetAddresses) }
|
||||
|
||||
if (inetAddresses.isEmpty()) {
|
||||
Log.d(tag, "httpdns 鏈繑鍥濱P锛岃蛋local dns")
|
||||
return Dns.SYSTEM.lookup(hostname)
|
||||
}
|
||||
return inetAddresses
|
||||
}
|
||||
})
|
||||
.addNetworkInterceptor(loggingInterceptor)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun processDnsResult(httpDnsResult: HTTPDNSResult, inetAddresses: MutableList<InetAddress>) {
|
||||
val ipStackType = HttpDnsNetworkDetector.getInstance().getNetType(mContext.get())
|
||||
val isV6 = ipStackType == NetType.v6 || ipStackType == NetType.both
|
||||
val isV4 = ipStackType == NetType.v4 || ipStackType == NetType.both
|
||||
|
||||
if (httpDnsResult.ipv6s != null && httpDnsResult.ipv6s.isNotEmpty() && isV6) {
|
||||
for (i in httpDnsResult.ipv6s.indices) {
|
||||
inetAddresses.addAll(
|
||||
InetAddress.getAllByName(httpDnsResult.ipv6s[i]).toList()
|
||||
)
|
||||
}
|
||||
} else if (httpDnsResult.ips != null && httpDnsResult.ips.isNotEmpty() && isV4) {
|
||||
for (i in httpDnsResult.ips.indices) {
|
||||
inetAddresses.addAll(
|
||||
InetAddress.getAllByName(httpDnsResult.ips[i]).toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.net
|
||||
|
||||
import android.util.Log
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
|
||||
class OkHttpLog: HttpLoggingInterceptor.Logger {
|
||||
override fun log(message: String) {
|
||||
Log.d("Okhttp", message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.net
|
||||
|
||||
import android.content.Context
|
||||
import com.newsdk.ams.emas.demo.ui.resolve.Response
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/5/25
|
||||
*/
|
||||
class OkHttpRequest constructor(
|
||||
private val context: Context,
|
||||
private val requestIpType: RequestIpType,
|
||||
private val resolveMethod: String,
|
||||
private val mIsSdns: Boolean,
|
||||
private val mSdnsParams: Map<String, String>?,
|
||||
private val mCacheKey: String
|
||||
) : IRequest {
|
||||
|
||||
override fun get(url: String): Response {
|
||||
val request = okhttp3.Request.Builder().url(url).build()
|
||||
OkHttpClientSingleton.getInstance(context).updateConfig(requestIpType, resolveMethod, mIsSdns, mSdnsParams, mCacheKey).getOkHttpClient().newCall(request).execute()
|
||||
.use { response -> return Response(response.code, response.body?.string()) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.net
|
||||
|
||||
import android.net.SSLCertificateSocketFactory
|
||||
import android.os.Build
|
||||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import javax.net.ssl.*
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/5/26
|
||||
*/
|
||||
class TLSSNISocketFactory(connection: HttpsURLConnection): SSLSocketFactory() {
|
||||
|
||||
private var mConnection: HttpsURLConnection
|
||||
private var hostnameVerifier: HostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
|
||||
|
||||
init {
|
||||
mConnection = connection
|
||||
}
|
||||
|
||||
override fun createSocket(plainSocket: Socket?, host: String?, port: Int, autoClose: Boolean): Socket? {
|
||||
var peerHost: String? = mConnection.getRequestProperty("Host")
|
||||
if (peerHost == null) peerHost = host
|
||||
val address = plainSocket!!.inetAddress
|
||||
if (autoClose) {
|
||||
// we don't need the plainSocket
|
||||
plainSocket.close()
|
||||
}
|
||||
|
||||
// create and connect SSL socket, but don't do hostname/certificate verification yet
|
||||
// create and connect SSL socket, but don't do hostname/certificate verification yet
|
||||
val sslSocketFactory =
|
||||
SSLCertificateSocketFactory.getDefault(0) as SSLCertificateSocketFactory
|
||||
val ssl = sslSocketFactory.createSocket(address, port) as SSLSocket
|
||||
|
||||
// enable TLSv1.1/1.2 if available
|
||||
|
||||
// enable TLSv1.1/1.2 if available
|
||||
ssl.enabledProtocols = ssl.supportedProtocols
|
||||
// set up SNI before the handshake
|
||||
|
||||
// set up SNI before the handshake
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
sslSocketFactory.setHostname(ssl, peerHost)
|
||||
}
|
||||
|
||||
// verify hostname and certificate
|
||||
|
||||
// verify hostname and certificate
|
||||
val session = ssl.session
|
||||
|
||||
if (!hostnameVerifier.verify(peerHost, session)) throw SSLPeerUnverifiedException(
|
||||
"Cannot verify hostname: $peerHost"
|
||||
)
|
||||
|
||||
return ssl
|
||||
}
|
||||
|
||||
override fun createSocket(host: String?, port: Int): Socket? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun createSocket(host: String?, port: Int, inetAddress: InetAddress?, localPort: Int): Socket? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun createSocket(host: InetAddress?, port: Int): Socket? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun createSocket(host: InetAddress?, port: Int, localHost: InetAddress?, localPot: Int): Socket? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getDefaultCipherSuites(): Array<String?> {
|
||||
return arrayOfNulls(0)
|
||||
}
|
||||
|
||||
override fun getSupportedCipherSuites(): Array<String?> {
|
||||
return arrayOfNulls(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.basic
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_REGION
|
||||
import com.newsdk.ams.emas.demo.getAccountPreference
|
||||
import com.newsdk.ams.emas.demo.ui.info.list.ListActivity
|
||||
import com.newsdk.ams.emas.demo.ui.info.list.kListItemTag
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
import com.newsdk.ams.httpdns.demo.databinding.FragmentBasicSettingBinding
|
||||
|
||||
class BasicSettingFragment : Fragment(), IBasicShowDialog {
|
||||
|
||||
private var _binding: FragmentBasicSettingBinding? = null
|
||||
|
||||
private val binding get() = _binding!!
|
||||
private lateinit var viewModel: BasicSettingViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel =
|
||||
ViewModelProvider(this,)[BasicSettingViewModel::class.java]
|
||||
viewModel.showDialog = this
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
|
||||
_binding = FragmentBasicSettingBinding.inflate(inflater, container, false)
|
||||
binding.lifecycleOwner = this
|
||||
val root: View = binding.root
|
||||
viewModel.initData()
|
||||
|
||||
binding.viewModel = viewModel
|
||||
binding.jumpToAddTag.setOnClickListener {
|
||||
val intent = Intent(activity, ListActivity::class.java)
|
||||
intent.putExtra("list_type", kListItemTag)
|
||||
startActivity(intent)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
override fun showSelectRegionDialog() {
|
||||
val builder = activity?.let { act -> AlertDialog.Builder(act) }
|
||||
builder?.apply {
|
||||
setTitle(R.string.select_region)
|
||||
val china = getString(R.string.china)
|
||||
val chinaHK = getString(R.string.china_hk)
|
||||
val singapore = getString(R.string.singapore)
|
||||
val germany = getString(R.string.germany)
|
||||
val america = getString(R.string.america)
|
||||
val pre = getString(R.string.pre)
|
||||
val items = arrayOf(china, chinaHK, singapore, germany, america, pre)
|
||||
var region = ""
|
||||
val preferences = activity?.let { getAccountPreference(it) }
|
||||
val index = when (preferences?.getString(KEY_REGION, "cn")) {
|
||||
"hk" -> 1
|
||||
"sg" -> 2
|
||||
"de" -> 3
|
||||
"us" -> 4
|
||||
"pre" -> 5
|
||||
else -> 0
|
||||
}
|
||||
setSingleChoiceItems(items, index) { _, which ->
|
||||
region = when (which) {
|
||||
1 -> "hk"
|
||||
2 -> "sg"
|
||||
3 -> "de"
|
||||
4 -> "us"
|
||||
5 -> "pre"
|
||||
else -> "cn"
|
||||
}
|
||||
}
|
||||
setPositiveButton(getString(R.string.confirm)) { dialog, _ ->
|
||||
viewModel.saveRegion(region)
|
||||
dialog.dismiss()
|
||||
}
|
||||
setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
builder?.show()
|
||||
}
|
||||
|
||||
override fun showSetTimeoutDialog() {
|
||||
val input = LayoutInflater.from(activity).inflate(R.layout.dialog_input, null)
|
||||
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
|
||||
editText.hint = getString(R.string.timeout_hint)
|
||||
editText.inputType = EditorInfo.TYPE_CLASS_NUMBER
|
||||
|
||||
val builder = activity?.let { act -> AlertDialog.Builder(act) }
|
||||
builder?.apply {
|
||||
setTitle(getString(R.string.set_timeout))
|
||||
setView(input)
|
||||
setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
when (val timeout = editText.text.toString()) {
|
||||
"" -> Toast.makeText(activity, R.string.timeout_empty, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
else -> viewModel.saveTimeout(timeout.toInt())
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun showInputHostDialog() {
|
||||
val input = LayoutInflater.from(activity).inflate(R.layout.dialog_input, null)
|
||||
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
|
||||
editText.hint = getString(R.string.clear_cache_hint)
|
||||
|
||||
val builder = activity?.let { act -> AlertDialog.Builder(act) }
|
||||
builder?.apply {
|
||||
setTitle(getString(R.string.clear_host_cache))
|
||||
setView(input)
|
||||
setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
viewModel.clearDnsCache(editText.text.toString())
|
||||
dialog.dismiss()
|
||||
}
|
||||
setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showAddPreResolveDialog() {
|
||||
val input = LayoutInflater.from(activity).inflate(R.layout.dialog_input, null)
|
||||
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
|
||||
editText.hint = getString(R.string.add_pre_resolve_hint)
|
||||
|
||||
val builder = activity?.let { act -> AlertDialog.Builder(act) }
|
||||
builder?.apply {
|
||||
setTitle(getString(R.string.add_pre_resolve))
|
||||
setView(input)
|
||||
setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
when (val host = editText.text.toString()) {
|
||||
"" -> Toast.makeText(activity, R.string.pre_resolve_host_is_empty, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
else -> viewModel.addPreResolveDomain(host)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHttpDnsInit() {
|
||||
activity?.runOnUiThread(Runnable {
|
||||
_binding?.initHttpdns?.setText(R.string.inited_httpdns)
|
||||
_binding?.initHttpdns?.isClickable = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,362 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.basic
|
||||
|
||||
import android.app.Application
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.newsdk.ams.emas.demo.*
|
||||
import com.newsdk.ams.emas.demo.constant.*
|
||||
import com.newsdk.sdk.android.httpdns.HttpDns
|
||||
import com.newsdk.sdk.android.httpdns.HttpDnsService
|
||||
import com.newsdk.sdk.android.httpdns.InitConfig
|
||||
import com.newsdk.sdk.android.httpdns.NotUseHttpDnsFilter
|
||||
import com.newsdk.sdk.android.httpdns.RequestIpType
|
||||
import com.newsdk.sdk.android.httpdns.log.HttpDnsLog
|
||||
import com.newsdk.ams.httpdns.demo.BuildConfig
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
|
||||
class BasicSettingViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private const val DAY_MILLS = 24 * 60 * 60 * 1000
|
||||
}
|
||||
|
||||
private val preferences = getAccountPreference(getApplication())
|
||||
|
||||
private var dnsService: HttpDnsService? = null
|
||||
|
||||
var secretKeySetByConfig = true
|
||||
|
||||
/**
|
||||
* 鏄惁寮€鍚壌鏉冩ā寮?
|
||||
*/
|
||||
var enableAuthMode = true
|
||||
|
||||
/**
|
||||
* 鏄惁寮€鍚姞瀵嗘ā寮?
|
||||
*/
|
||||
var enableEncryptMode = true
|
||||
|
||||
/**
|
||||
* 鏄惁鍏佽杩囨湡IP
|
||||
*/
|
||||
var enableExpiredIP = false
|
||||
|
||||
/**
|
||||
* 鏄惁寮€鍚湰鍦扮紦瀛?
|
||||
*/
|
||||
var enableCacheIP = false
|
||||
|
||||
/**
|
||||
* 鏄惁鍏佽HTTPS
|
||||
*/
|
||||
var enableHttps = false
|
||||
|
||||
/**
|
||||
* 鏄惁寮€鍚檷绾?
|
||||
*/
|
||||
var enableDegrade = false
|
||||
|
||||
/**
|
||||
* 鏄惁鍏佽缃戠粶鍒囨崲鑷姩鍒锋柊
|
||||
*/
|
||||
var enableAutoRefresh = false
|
||||
|
||||
/**
|
||||
* 鏄惁鍏佽鎵撳嵃鏃ュ織
|
||||
*/
|
||||
var enableLog = false
|
||||
|
||||
/**
|
||||
* 褰撳墠Region
|
||||
*/
|
||||
var currentRegion = SingleLiveData<String>().apply {
|
||||
value = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 褰撳墠瓒呮椂
|
||||
*/
|
||||
var currentTimeout = SingleLiveData<String>().apply {
|
||||
value = "2000ms"
|
||||
}
|
||||
|
||||
var cacheExpireTime = SingleLiveData<String>().apply {
|
||||
value = "0"
|
||||
}
|
||||
|
||||
var showDialog: IBasicShowDialog? = null
|
||||
|
||||
fun initData() {
|
||||
secretKeySetByConfig = preferences.getBoolean(KEY_SECRET_KEY_SET_BY_CONFIG, true)
|
||||
enableAuthMode = preferences.getBoolean(KEY_ENABLE_AUTH_MODE, true)
|
||||
enableEncryptMode = preferences.getBoolean(KEY_ENABLE_ENCRYPT_MODE, true)
|
||||
enableExpiredIP = preferences.getBoolean(KEY_ENABLE_EXPIRED_IP, false)
|
||||
enableCacheIP = preferences.getBoolean(KEY_ENABLE_CACHE_IP, false)
|
||||
cacheExpireTime.value = preferences.getString(KEY_CACHE_EXPIRE_TIME, "0")
|
||||
enableHttps = preferences.getBoolean(KEY_ENABLE_HTTPS, false)
|
||||
enableDegrade = preferences.getBoolean(KEY_ENABLE_DEGRADE, false)
|
||||
enableAutoRefresh = preferences.getBoolean(KEY_ENABLE_AUTO_REFRESH, false)
|
||||
enableLog = preferences.getBoolean(KEY_ENABLE_LOG, false)
|
||||
when (preferences.getString(KEY_REGION, "cn")) {
|
||||
"cn" -> currentRegion.value = getString(R.string.china)
|
||||
"hk" -> currentRegion.value = getString(R.string.china_hk)
|
||||
"sg" -> currentRegion.value = getString(R.string.singapore)
|
||||
"de" -> currentRegion.value = getString(R.string.germany)
|
||||
"us" -> currentRegion.value = getString(R.string.america)
|
||||
"pre" -> currentRegion.value = getString(R.string.pre)
|
||||
}
|
||||
currentTimeout.value = "${preferences.getInt(KEY_TIMEOUT, 2000)}ms"
|
||||
|
||||
if (MainActivity.HttpDns.inited) {
|
||||
dnsService = HttpDnsServiceHolder.getHttpDnsService(getApplication())
|
||||
showDialog?.onHttpDnsInit()
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleSecretKeySet(button: CompoundButton, checked: Boolean) {
|
||||
secretKeySetByConfig = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_SECRET_KEY_SET_BY_CONFIG, checked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun toggleAuthMode(button: CompoundButton, checked: Boolean) {
|
||||
enableAuthMode = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_ENABLE_AUTH_MODE, checked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun toggleEncryptMode(button: CompoundButton, checked: Boolean) {
|
||||
enableEncryptMode = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_ENABLE_ENCRYPT_MODE, checked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun toggleEnableExpiredIp(button: CompoundButton, checked: Boolean) {
|
||||
enableExpiredIP = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_ENABLE_EXPIRED_IP, checked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun toggleEnableCacheIp(button: CompoundButton, checked: Boolean) {
|
||||
enableCacheIP = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_ENABLE_CACHE_IP, checked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun toggleEnableHttps(button: CompoundButton, checked: Boolean) {
|
||||
enableHttps = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_ENABLE_HTTPS, checked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun toggleEnableDegrade(button: CompoundButton, checked: Boolean) {
|
||||
enableDegrade = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_ENABLE_DEGRADE, checked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun toggleEnableAutoRefresh(button: CompoundButton, checked: Boolean) {
|
||||
enableAutoRefresh = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_ENABLE_AUTO_REFRESH, checked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun toggleEnableLog(button: CompoundButton, checked: Boolean) {
|
||||
enableLog = checked
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(KEY_ENABLE_LOG, checked)
|
||||
editor.apply()
|
||||
HttpDnsLog.enable(checked)
|
||||
}
|
||||
|
||||
fun setRegion() {
|
||||
//寮圭獥閫夋嫨region
|
||||
showDialog?.showSelectRegionDialog()
|
||||
}
|
||||
|
||||
fun saveRegion(region: String) {
|
||||
currentRegion.value = when (region) {
|
||||
"cn" -> getString(R.string.china)
|
||||
"hk" -> getString(R.string.china_hk)
|
||||
"sg" -> getString(R.string.singapore)
|
||||
"de" -> getString(R.string.germany)
|
||||
"pre" -> getString(R.string.pre)
|
||||
else -> getString(R.string.china)
|
||||
}
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_REGION, region)
|
||||
editor.apply()
|
||||
dnsService?.setRegion(region)
|
||||
}
|
||||
|
||||
fun setTimeout() {
|
||||
showDialog?.showSetTimeoutDialog()
|
||||
}
|
||||
|
||||
fun saveTimeout(timeout: Int) {
|
||||
currentTimeout.value = "${timeout}ms"
|
||||
val editor = preferences.edit()
|
||||
editor.putInt(KEY_TIMEOUT, timeout)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun showClearCacheDialog() {
|
||||
showDialog?.showInputHostDialog()
|
||||
}
|
||||
|
||||
fun clearDnsCache(host: String) {
|
||||
if (TextUtils.isEmpty(host)) {
|
||||
dnsService?.cleanHostCache(null)
|
||||
} else {
|
||||
dnsService?.cleanHostCache(mutableListOf(host) as ArrayList<String>)
|
||||
}
|
||||
}
|
||||
|
||||
fun batchResolveHosts() {
|
||||
dnsService?.setPreResolveHosts(BatchResolveCacheHolder.batchResolveV4List, RequestIpType.v4)
|
||||
dnsService?.setPreResolveHosts(BatchResolveCacheHolder.batchResolveV6List, RequestIpType.v6)
|
||||
dnsService?.setPreResolveHosts(BatchResolveCacheHolder.batchResolveAutoList, RequestIpType.auto)
|
||||
dnsService?.setPreResolveHosts(BatchResolveCacheHolder.batchResolveBothList, RequestIpType.both)
|
||||
}
|
||||
|
||||
fun showAddPreResolveDialog() {
|
||||
showDialog?.showAddPreResolveDialog()
|
||||
}
|
||||
|
||||
fun initHttpDns() {
|
||||
if (!TextUtils.isEmpty(BuildConfig.ACCOUNT_ID)) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val aesSecretKey = if (enableEncryptMode && !TextUtils.isEmpty(BuildConfig.AES_SECRET_KEY)) BuildConfig.AES_SECRET_KEY else ""
|
||||
val secretKey = if (enableAuthMode && !TextUtils.isEmpty(BuildConfig.SECRET_KEY)) BuildConfig.SECRET_KEY else ""
|
||||
val enableExpiredIp = preferences.getBoolean(KEY_ENABLE_EXPIRED_IP, false)
|
||||
val enableCacheIp = preferences.getBoolean(KEY_ENABLE_CACHE_IP, false)
|
||||
val enableHttpDns = preferences.getBoolean(KEY_ENABLE_HTTPS, false)
|
||||
val timeout = preferences.getInt(KEY_TIMEOUT, 2000)
|
||||
val region = preferences.getString(KEY_REGION, "cn")
|
||||
val enableDegradationLocalDns = preferences.getBoolean(KEY_ENABLE_DEGRADE, false);
|
||||
//鑷畾涔塼tl
|
||||
val ttlCacheStr = preferences.getString(KEY_TTL_CHANGER, null)
|
||||
TtlCacheHolder.convertTtlCacheData(ttlCacheStr)
|
||||
//IP鎺㈡祴
|
||||
val ipRankingItemJson = preferences.getString(KEY_IP_RANKING_ITEMS, null)
|
||||
//涓荤珯鍩熷悕
|
||||
val hostListWithFixedIpJson =
|
||||
preferences.getString(KEY_HOST_WITH_FIXED_IP, null)
|
||||
val tagsJson = preferences.getString(KEY_TAGS, null)
|
||||
//棰勮В鏋?
|
||||
val preResolveHostList = preferences.getString(KEY_PRE_RESOLVE_HOST_LIST, null)
|
||||
preResolveHostList?.let { Log.d("httpdns:HttpDnsApplication", "pre resolve list: $it") }
|
||||
PreResolveCacheHolder.convertPreResolveCacheData(preResolveHostList)
|
||||
|
||||
//鎵归噺瑙f瀽
|
||||
val batchResolveHostList = preferences.getString(KEY_BATCH_RESOLVE_HOST_LIST, null)
|
||||
BatchResolveCacheHolder.convertBatchResolveCacheData(batchResolveHostList)
|
||||
|
||||
val sdnsGlobalParamStr = preferences.getString(KEY_SDNS_GLOBAL_PARAMS, "")
|
||||
var sdnsGlobalParams: MutableMap<String, String>? = null
|
||||
if (!TextUtils.isEmpty(sdnsGlobalParamStr)) {
|
||||
try {
|
||||
val sdnsJson = JSONObject(sdnsGlobalParamStr)
|
||||
val keys = sdnsJson.keys()
|
||||
sdnsGlobalParams = mutableMapOf()
|
||||
while (keys.hasNext()) {
|
||||
val key = keys.next()
|
||||
sdnsGlobalParams[key] = sdnsJson.getString(key)
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val cacheExpireTimeTemp = cacheExpireTime.value?.toLong() ?: 0
|
||||
|
||||
HttpDnsLog.enable(preferences.getBoolean(KEY_ENABLE_LOG, false))
|
||||
val builder = InitConfig.Builder()
|
||||
.setEnableHttps(enableHttpDns)
|
||||
.setEnableCacheIp(enableCacheIp, cacheExpireTimeTemp * DAY_MILLS)
|
||||
.setEnableExpiredIp(enableExpiredIp)
|
||||
.setRegion(region)
|
||||
.setTimeoutMillis(timeout)
|
||||
.setEnableDegradationLocalDns(enableDegradationLocalDns)
|
||||
.setIPRankingList(ipRankingItemJson.toIPRankingList())
|
||||
.configCacheTtlChanger(TtlCacheHolder.cacheTtlChanger)
|
||||
.configHostWithFixedIp(hostListWithFixedIpJson.toHostList())
|
||||
.setNotUseHttpDnsFilter(NotUseHttpDnsFilter { host ->
|
||||
val blackListStr = preferences.getString(KEY_HOST_BLACK_LIST, null)
|
||||
blackListStr?.let {
|
||||
return@NotUseHttpDnsFilter blackListStr.contains(host)
|
||||
}
|
||||
return@NotUseHttpDnsFilter false
|
||||
})
|
||||
.setSdnsGlobalParams(sdnsGlobalParams)
|
||||
.setBizTags(tagsJson.toTagList())
|
||||
.setAesSecretKey(aesSecretKey)
|
||||
|
||||
if (secretKeySetByConfig) {
|
||||
builder.setContext(this@BasicSettingViewModel.getApplication<HttpDnsApplication>())
|
||||
builder.setSecretKey(secretKey)
|
||||
}
|
||||
HttpDns.init(BuildConfig.ACCOUNT_ID, builder.build())
|
||||
dnsService = HttpDnsServiceHolder.getHttpDnsService(getApplication())
|
||||
dnsService?.setPreResolveHosts(PreResolveCacheHolder.preResolveV4List)
|
||||
dnsService?.setPreResolveHosts(PreResolveCacheHolder.preResolveV6List, RequestIpType.v6)
|
||||
dnsService?.setPreResolveHosts(PreResolveCacheHolder.preResolveBothList, RequestIpType.both)
|
||||
dnsService?.setPreResolveHosts(PreResolveCacheHolder.preResolveAutoList, RequestIpType.auto)
|
||||
showDialog?.onHttpDnsInit()
|
||||
MainActivity.HttpDns.inited = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addPreResolveDomain(host: String) {
|
||||
val preResolveHostListStr = preferences.getString(KEY_PRE_RESOLVE_HOST_LIST, null)
|
||||
val hostList: MutableList<String> = if (preResolveHostListStr == null) {
|
||||
mutableListOf()
|
||||
} else {
|
||||
preResolveHostListStr.toHostList()!!
|
||||
}
|
||||
|
||||
if (hostList.contains(host)) {
|
||||
Toast.makeText(
|
||||
getApplication(),
|
||||
getString(R.string.pre_resolve_host_duplicate, host),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
hostList.add(host)
|
||||
}
|
||||
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_PRE_RESOLVE_HOST_LIST, convertPreResolveList(hostList))
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun getString(resId: Int): String {
|
||||
return getApplication<HttpDnsApplication>().getString(resId)
|
||||
}
|
||||
|
||||
private fun getString(resId: Int, vararg args: String): String {
|
||||
return getApplication<HttpDnsApplication>().getString(resId, *args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.basic
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/5/24
|
||||
*/
|
||||
interface IBasicShowDialog {
|
||||
fun showSelectRegionDialog()
|
||||
|
||||
fun showSetTimeoutDialog()
|
||||
|
||||
fun showInputHostDialog()
|
||||
|
||||
fun showAddPreResolveDialog()
|
||||
|
||||
fun onHttpDnsInit()
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.info
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.newsdk.ams.emas.demo.ui.info.list.*
|
||||
import com.newsdk.ams.httpdns.demo.BuildConfig
|
||||
import com.newsdk.ams.httpdns.demo.databinding.FragmentInfoBinding
|
||||
|
||||
class InfoFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentInfoBinding? = null
|
||||
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var viewModel: InfoViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel = ViewModelProvider(this)[InfoViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentInfoBinding.inflate(inflater, container, false)
|
||||
binding.viewModel = viewModel
|
||||
binding.lifecycleOwner = this
|
||||
|
||||
viewModel.initData()
|
||||
|
||||
binding.infoPkgName.text = activity?.packageName
|
||||
binding.infoSecretView.apply {
|
||||
visibility = if (TextUtils.isEmpty(BuildConfig.SECRET_KEY)) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
binding.jumpToPreResolve.setOnClickListener {
|
||||
val intent = Intent(activity, ListActivity::class.java)
|
||||
intent.putExtra("list_type", kListItemPreResolve)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.jumpToIpRanking.setOnClickListener {
|
||||
val intent = Intent(activity, ListActivity::class.java)
|
||||
intent.putExtra("list_type", kListItemTypeIPRanking)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.jumpToHostFiexIp.setOnClickListener {
|
||||
val intent = Intent(activity, ListActivity::class.java)
|
||||
intent.putExtra("list_type", kListItemTypeHostWithFixedIP)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.jumpToHostBlackList.setOnClickListener {
|
||||
val intent = Intent(activity, ListActivity::class.java)
|
||||
intent.putExtra("list_type", kListItemTypeBlackList)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.jumpToTtlCache.setOnClickListener {
|
||||
val intent = Intent(activity, ListActivity::class.java)
|
||||
intent.putExtra("list_type", kListItemTypeCacheTtl)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.jumpToSdnsGlobalParams.setOnClickListener {
|
||||
val intent = Intent(activity, SdnsGlobalSettingActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.jumpToBatchResolve.setOnClickListener {
|
||||
val intent = Intent(activity, ListActivity::class.java)
|
||||
intent.putExtra("list_type", kListItemBatchResolve)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.info
|
||||
|
||||
import android.app.Application
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.newsdk.ams.emas.demo.HttpDnsApplication
|
||||
import com.newsdk.ams.emas.demo.HttpDnsServiceHolder
|
||||
import com.newsdk.ams.emas.demo.SingleLiveData
|
||||
import com.newsdk.ams.emas.demo.getAccountPreference
|
||||
import com.newsdk.sdk.android.httpdns.NetType
|
||||
import com.newsdk.sdk.android.httpdns.net.HttpDnsNetworkDetector
|
||||
import com.newsdk.ams.httpdns.demo.BuildConfig
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
|
||||
|
||||
class InfoViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
/**
|
||||
* 璐︽埛ID
|
||||
*/
|
||||
val accountId = SingleLiveData<String>().apply {
|
||||
value = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 璐︽埛secret
|
||||
*/
|
||||
val secretKey = SingleLiveData<String?>()
|
||||
|
||||
val currentIpStackType = SingleLiveData<String>().apply {
|
||||
value = "V4"
|
||||
}
|
||||
|
||||
fun initData() {
|
||||
currentIpStackType.value = when (HttpDnsNetworkDetector.getInstance().getNetType(getApplication())) {
|
||||
NetType.v4 -> "V4"
|
||||
NetType.v6 -> "V6"
|
||||
NetType.both -> "V4&V6"
|
||||
else -> getApplication<HttpDnsApplication>().getString(R.string.unknown)
|
||||
}
|
||||
|
||||
accountId.value = BuildConfig.ACCOUNT_ID
|
||||
secretKey.value = BuildConfig.SECRET_KEY
|
||||
}
|
||||
|
||||
fun clearDnsCache() {
|
||||
val httpdnsService = HttpDnsServiceHolder.getHttpDnsService(getApplication())
|
||||
var i = 0;
|
||||
while (i < 500) {
|
||||
httpdnsService?.cleanHostCache(null)
|
||||
++i
|
||||
}
|
||||
}
|
||||
|
||||
fun clearAllCache() {
|
||||
val preferences = getAccountPreference(getApplication())
|
||||
preferences.edit().clear().apply()
|
||||
Toast.makeText(getApplication(), R.string.all_cache_cleared, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.info
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_SDNS_GLOBAL_PARAMS
|
||||
import com.newsdk.ams.emas.demo.getAccountPreference
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
import com.newsdk.ams.httpdns.demo.databinding.ActivitySdnsGlobalSettingBinding
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
class SdnsGlobalSettingActivity: AppCompatActivity() {
|
||||
private lateinit var binding: ActivitySdnsGlobalSettingBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val preferences = getAccountPreference(this)
|
||||
|
||||
binding = ActivitySdnsGlobalSettingBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
binding.toolbar.title = getString(R.string.input_the_sdns_params)
|
||||
|
||||
val params = preferences.getString(KEY_SDNS_GLOBAL_PARAMS, "")
|
||||
binding.sdnsParamsInputLayout.editText?.setText(params)
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
val sdnsParamsStr = binding.sdnsParamsInputLayout.editText?.text.toString()
|
||||
if (!TextUtils.isEmpty(sdnsParamsStr)) {
|
||||
try {
|
||||
val sdnsJson = JSONObject(sdnsParamsStr)
|
||||
preferences.edit().putString(KEY_SDNS_GLOBAL_PARAMS, sdnsParamsStr).apply()
|
||||
onBackPressed()
|
||||
} catch (e: JSONException) {
|
||||
binding.sdnsParamsInputLayout.error = getString(R.string.input_the_sdns_params_error)
|
||||
}
|
||||
} else {
|
||||
preferences.edit().putString(KEY_SDNS_GLOBAL_PARAMS, "").apply()
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,353 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.info.list
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
import com.newsdk.ams.httpdns.demo.databinding.ActivityListBinding
|
||||
|
||||
class ListActivity : AppCompatActivity(), ListAdapter.OnDeleteListener {
|
||||
|
||||
private lateinit var binding: ActivityListBinding
|
||||
|
||||
private val infoList: MutableList<ListItem> = mutableListOf()
|
||||
private lateinit var listAdapter: ListAdapter
|
||||
private var listType: Int = kListItemTypeIPRanking
|
||||
|
||||
private lateinit var viewModel: ListViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
var title = ""
|
||||
intent?.let {
|
||||
listType = intent.getIntExtra("list_type", kListItemTypeIPRanking)
|
||||
title = when (listType) {
|
||||
kListItemTypeCacheTtl -> getString(R.string.ttl_cache_list)
|
||||
kListItemTypeHostWithFixedIP -> getString(R.string.host_fixed_ip_list)
|
||||
kListItemPreResolve -> getString(R.string.pre_resolve_list)
|
||||
kListItemTypeBlackList -> getString(R.string.host_black_list)
|
||||
kListItemBatchResolve -> getString(R.string.batch_resolve_list)
|
||||
kListItemTag -> getString(R.string.add_tag)
|
||||
else -> getString(R.string.ip_probe_list)
|
||||
}
|
||||
}
|
||||
viewModel = ViewModelProvider(this)[ListViewModel::class.java]
|
||||
|
||||
binding = ActivityListBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
binding.infoListToolbar.title = title
|
||||
setSupportActionBar(binding.infoListToolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)//娣诲姞榛樿鐨勮繑鍥炲浘鏍?
|
||||
supportActionBar?.setHomeButtonEnabled(true)
|
||||
|
||||
binding.infoListView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
|
||||
|
||||
viewModel.initData(listType, infoList)
|
||||
|
||||
listAdapter = ListAdapter(this, infoList, this)
|
||||
binding.infoListView.adapter = listAdapter
|
||||
|
||||
binding.fab.setOnClickListener {
|
||||
showAddDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showAddDialog() {
|
||||
when (listType) {
|
||||
kListItemTag -> {
|
||||
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input, null)
|
||||
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
|
||||
editText.hint = getString(R.string.add_tag_hint)
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(getString(R.string.add_tag))
|
||||
.setView(input)
|
||||
.setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
when (val host = editText.text.toString()) {
|
||||
"" -> Toast.makeText(
|
||||
this@ListActivity,
|
||||
R.string.host_fixed_ip_empty,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
else -> {
|
||||
viewModel.toAddTag(host, listAdapter)
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
kListItemTypeHostWithFixedIP -> {
|
||||
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input, null)
|
||||
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
|
||||
editText.hint = getString(R.string.add_host_fixed_ip_hint)
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(getString(R.string.add_host_fixed_ip))
|
||||
.setView(input)
|
||||
.setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
when (val host = editText.text.toString()) {
|
||||
"" -> Toast.makeText(
|
||||
this@ListActivity,
|
||||
R.string.host_fixed_ip_empty,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
else -> {
|
||||
viewModel.toAddHostWithFixedIP(host, listAdapter)
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
kListItemTypeBlackList -> {
|
||||
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input, null)
|
||||
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
|
||||
editText.hint = getString(R.string.add_host_to_black_list_hint)
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(getString(R.string.add_host_to_black_list))
|
||||
.setView(input)
|
||||
.setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
when (val host = editText.text.toString()) {
|
||||
"" -> Toast.makeText(
|
||||
this@ListActivity,
|
||||
R.string.host_to_black_list_empty,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
else -> {
|
||||
viewModel.toAddHostInBlackList(host, listAdapter)
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
kListItemPreResolve -> {
|
||||
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input_3, null)
|
||||
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
|
||||
editText.hint = getString(R.string.add_pre_resolve_hint)
|
||||
val ipTypeGroup = input.findViewById<RadioGroup>(R.id.ip_type)
|
||||
|
||||
var view = createIpTypeRadio(this)
|
||||
view.text = "IPv4"
|
||||
view.isChecked = true
|
||||
view.tag = 0
|
||||
ipTypeGroup.addView(view)
|
||||
|
||||
view = createIpTypeRadio(this)
|
||||
view.text = "IPv6"
|
||||
view.tag = 1
|
||||
ipTypeGroup.addView(view)
|
||||
|
||||
view = createIpTypeRadio(this)
|
||||
view.text = "IPv4&IPv6"
|
||||
view.tag = 2
|
||||
ipTypeGroup.addView(view)
|
||||
|
||||
view = createIpTypeRadio(this)
|
||||
view.text = "鑷姩鍒ゆ柇IP绫诲瀷"
|
||||
view.tag = 3
|
||||
ipTypeGroup.addView(view)
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(getString(R.string.add_pre_resolve))
|
||||
.setView(input)
|
||||
.setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
when (val host = editText.text.toString()) {
|
||||
"" -> Toast.makeText(
|
||||
this@ListActivity,
|
||||
R.string.pre_resolve_host_is_empty,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
else -> {
|
||||
viewModel.toAddPreResolveHost(host, listAdapter, ipTypeGroup.findViewById<RadioButton>(ipTypeGroup.checkedRadioButtonId).tag as Int)
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
kListItemBatchResolve -> {
|
||||
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input_3, null)
|
||||
val editText = input.findViewById<AppCompatEditText>(R.id.add_input)
|
||||
editText.hint = getString(R.string.add_batch_resolve_hint)
|
||||
val ipTypeGroup = input.findViewById<RadioGroup>(R.id.ip_type)
|
||||
var view = createIpTypeRadio(this)
|
||||
view.text = "IPv4"
|
||||
view.isChecked = true
|
||||
view.tag = 0
|
||||
ipTypeGroup.addView(view)
|
||||
view = createIpTypeRadio(this)
|
||||
view.text = "IPv6"
|
||||
view.tag = 1
|
||||
ipTypeGroup.addView(view)
|
||||
view = createIpTypeRadio(this)
|
||||
view.text = "IPv4&IPv6"
|
||||
view.tag = 2
|
||||
ipTypeGroup.addView(view)
|
||||
view = createIpTypeRadio(this)
|
||||
view.text = "鑷姩鍒ゆ柇IP绫诲瀷"
|
||||
view.tag = 3
|
||||
ipTypeGroup.addView(view)
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(getString(R.string.add_batch_resolve))
|
||||
.setView(input)
|
||||
.setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
when (val host = editText.text.toString()) {
|
||||
"" -> Toast.makeText(
|
||||
this@ListActivity,
|
||||
R.string.batch_resolve_host_is_empty,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
else -> {
|
||||
viewModel.toAddBatchResolveHost(host, listAdapter, ipTypeGroup.findViewById<RadioButton>(ipTypeGroup.checkedRadioButtonId).tag as Int)
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
else -> {
|
||||
val isTtl = listType == kListItemTypeCacheTtl
|
||||
val input = LayoutInflater.from(this).inflate(R.layout.dialog_input_2, null)
|
||||
val hostEditText = input.findViewById<AppCompatEditText>(R.id.input_content_1)
|
||||
val intEditText = input.findViewById<AppCompatEditText>(R.id.input_content_2)
|
||||
intEditText.inputType = EditorInfo.TYPE_CLASS_NUMBER
|
||||
|
||||
hostEditText.hint =
|
||||
getString(if (isTtl) R.string.add_ttl_host_hint else R.string.add_ip_probe_host_hint)
|
||||
intEditText.hint =
|
||||
getString(if (isTtl) R.string.add_ttl_ttl_hint else R.string.add_ip_probe_port_hint)
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(getString(if (isTtl) R.string.add_custom_ttl else R.string.add_ip_probe))
|
||||
.setView(input)
|
||||
.setPositiveButton(R.string.confirm) { dialog, _ ->
|
||||
when (val host = hostEditText.text.toString()) {
|
||||
"" -> Toast.makeText(
|
||||
this@ListActivity,
|
||||
R.string.host_is_empty,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
else -> {
|
||||
when (val intValue = intEditText.text.toString()) {
|
||||
"" -> Toast.makeText(
|
||||
this@ListActivity,
|
||||
if (isTtl) R.string.ttl_is_empty else R.string.port_is_empty,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
else -> {
|
||||
try {
|
||||
if (isTtl) {
|
||||
viewModel.toSaveTtlCache(
|
||||
host,
|
||||
intValue.toInt(),
|
||||
listAdapter
|
||||
)
|
||||
} else {
|
||||
viewModel.toSaveIPProbe(
|
||||
host,
|
||||
intValue.toInt(),
|
||||
listAdapter
|
||||
)
|
||||
}
|
||||
} catch (e: NumberFormatException) {
|
||||
Toast.makeText(
|
||||
this@ListActivity,
|
||||
R.string.ttl_is_not_number,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createIpTypeRadio(context: Context): RadioButton {
|
||||
val btn = RadioButton(context)
|
||||
btn.id = View.generateViewId()
|
||||
val params = RadioGroup.LayoutParams(RadioGroup.LayoutParams.MATCH_PARENT, RadioGroup.LayoutParams.WRAP_CONTENT)
|
||||
btn.layoutParams = params
|
||||
|
||||
return btn
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onTagDeleted(position: Int) {
|
||||
viewModel.onTagDeleted(position)
|
||||
}
|
||||
|
||||
override fun onHostWithFixedIPDeleted(position: Int) {
|
||||
//鍙兘閲嶅惎鐢熸晥
|
||||
viewModel.onHostWithFixedIPDeleted(position)
|
||||
}
|
||||
|
||||
override fun onIPRankingItemDeleted(position: Int) {
|
||||
viewModel.onIPProbeItemDeleted(position)
|
||||
}
|
||||
|
||||
override fun onTtlDeleted(host: String) {
|
||||
viewModel.onTtlDeleted(host)
|
||||
}
|
||||
|
||||
override fun onPreResolveDeleted(host: String, intValue: Int) {
|
||||
Log.d("httpdns", "onPreResolveDeleted")
|
||||
viewModel.onPreResolveDeleted(host, intValue)
|
||||
}
|
||||
|
||||
override fun onHostBlackListDeleted(position: Int) {
|
||||
viewModel.onHostBlackListDeleted(position)
|
||||
}
|
||||
|
||||
override fun onBatchResolveDeleted(host: String, intValue: Int) {
|
||||
viewModel.onBatchResolveDeleted(host, intValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.info.list
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
import com.newsdk.ams.httpdns.demo.databinding.InfoListItemBinding;
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/5
|
||||
*/
|
||||
class ListAdapter(private val context: Context,
|
||||
private val itemList: MutableList<ListItem>,
|
||||
private val deleteListener: OnDeleteListener) :
|
||||
RecyclerView.Adapter<ListAdapter.ListViewHolder>() {
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
|
||||
val binding = InfoListItemBinding.inflate(LayoutInflater.from(context))
|
||||
return ListViewHolder(context, binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
|
||||
if (itemList.isEmpty()) {
|
||||
return
|
||||
}
|
||||
holder.setItemValue(itemList[position]) {
|
||||
when (itemList[holder.adapterPosition].type) {
|
||||
kListItemTag -> deleteListener.onTagDeleted(holder.adapterPosition)
|
||||
kListItemTypeHostWithFixedIP -> deleteListener.onHostWithFixedIPDeleted(holder.adapterPosition)
|
||||
kListItemTypeBlackList -> deleteListener.onHostBlackListDeleted(holder.adapterPosition)
|
||||
kListItemTypeCacheTtl -> deleteListener.onTtlDeleted(itemList[holder.adapterPosition].content)
|
||||
kListItemPreResolve -> deleteListener.onPreResolveDeleted(itemList[holder.adapterPosition].content, itemList[holder.adapterPosition].intValue)
|
||||
kListItemBatchResolve -> deleteListener.onBatchResolveDeleted(itemList[holder.adapterPosition].content, itemList[holder.adapterPosition].intValue)
|
||||
else -> deleteListener.onIPRankingItemDeleted(holder.adapterPosition)
|
||||
}
|
||||
itemList.removeAt(holder.adapterPosition)
|
||||
notifyItemRemoved(holder.adapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return itemList.size
|
||||
}
|
||||
|
||||
fun addItemData(item: ListItem) {
|
||||
itemList.add(item)
|
||||
notifyItemInserted(itemList.size - 1)
|
||||
}
|
||||
|
||||
fun getPositionByContent(content: String): Int {
|
||||
for (index in itemList.indices) {
|
||||
if (content == itemList[index].content) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fun updateItemByPosition(content:String, intValue: Int, position: Int) {
|
||||
itemList[position].content = content
|
||||
itemList[position].intValue = intValue
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
class ListViewHolder(private val context: Context, private val binding: InfoListItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
|
||||
fun setItemValue(listItem: ListItem, onDeleteListener: View.OnClickListener) {
|
||||
when (listItem.type) {
|
||||
kListItemTag -> {
|
||||
binding.hostFixedIpContainer.visibility = View.VISIBLE
|
||||
binding.hostAndPortOrTtlContainer.visibility = View.GONE
|
||||
binding.preHostOrWithFixedIp.text = listItem.content
|
||||
}
|
||||
kListItemTypeIPRanking -> {
|
||||
binding.hostFixedIpContainer.visibility = View.GONE
|
||||
binding.hostAndPortOrTtlContainer.visibility = View.VISIBLE
|
||||
binding.hostValue.text = listItem.content
|
||||
binding.portOrTtlValue.text = listItem.intValue.toString()
|
||||
binding.portOrTtlIndicate.text = context.getString(R.string.port)
|
||||
}
|
||||
kListItemTypeCacheTtl -> {
|
||||
binding.hostFixedIpContainer.visibility = View.GONE
|
||||
binding.hostAndPortOrTtlContainer.visibility = View.VISIBLE
|
||||
binding.hostValue.text = listItem.content
|
||||
binding.portOrTtlValue.text = listItem.intValue.toString()
|
||||
binding.portOrTtlIndicate.text = context.getString(R.string.ttl)
|
||||
}
|
||||
kListItemTypeHostWithFixedIP -> {
|
||||
binding.hostFixedIpContainer.visibility = View.VISIBLE
|
||||
binding.hostAndPortOrTtlContainer.visibility = View.GONE
|
||||
binding.preHostOrWithFixedIp.text = listItem.content
|
||||
}
|
||||
kListItemTypeBlackList -> {
|
||||
binding.hostFixedIpContainer.visibility = View.VISIBLE
|
||||
binding.hostAndPortOrTtlContainer.visibility = View.GONE
|
||||
binding.preHostOrWithFixedIp.text = listItem.content
|
||||
}
|
||||
kListItemPreResolve -> {
|
||||
binding.hostFixedIpContainer.visibility = View.GONE
|
||||
binding.hostAndPortOrTtlContainer.visibility = View.VISIBLE
|
||||
binding.hostValue.text = listItem.content
|
||||
binding.portOrTtlValue.text = when (listItem.intValue) {
|
||||
0 -> "IPv4"
|
||||
1 -> "IPv6"
|
||||
2 -> "IPv4&IPv6"
|
||||
else -> "鑷姩鍒ゆ柇IP绫诲瀷"
|
||||
}
|
||||
binding.portOrTtlIndicate.text = context.getString(R.string.ip_type)
|
||||
}
|
||||
kListItemBatchResolve -> {
|
||||
binding.hostFixedIpContainer.visibility = View.GONE
|
||||
binding.hostAndPortOrTtlContainer.visibility = View.VISIBLE
|
||||
binding.hostValue.text = listItem.content
|
||||
binding.portOrTtlValue.text = when (listItem.intValue) {
|
||||
0 -> "IPv4"
|
||||
1 -> "IPv6"
|
||||
2 -> "IPv4&IPv6"
|
||||
else -> "鑷姩鍒ゆ柇IP绫诲瀷"
|
||||
}
|
||||
binding.portOrTtlIndicate.text = context.getString(R.string.ip_type)
|
||||
}
|
||||
}
|
||||
|
||||
binding.slideDeleteMenu.setOnClickListener(onDeleteListener)
|
||||
binding.slideDeleteMenu2.setOnClickListener(onDeleteListener)
|
||||
}
|
||||
}
|
||||
|
||||
interface OnDeleteListener {
|
||||
fun onTagDeleted(position: Int)
|
||||
|
||||
fun onHostWithFixedIPDeleted(position: Int)
|
||||
|
||||
fun onIPRankingItemDeleted(position: Int)
|
||||
|
||||
fun onTtlDeleted(host: String)
|
||||
|
||||
fun onPreResolveDeleted(host: String, intValue: Int)
|
||||
|
||||
fun onHostBlackListDeleted(position: Int)
|
||||
|
||||
fun onBatchResolveDeleted(host: String, intValue: Int)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.info.list
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/5
|
||||
*/
|
||||
|
||||
data class ListItem(var type: Int, var content: String, var intValue: Int)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.info.list
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/5
|
||||
*/
|
||||
|
||||
const val kListItemTypeIPRanking = 0x01
|
||||
|
||||
const val kListItemTypeCacheTtl = 0x02
|
||||
|
||||
const val kListItemTypeHostWithFixedIP = 0x03
|
||||
|
||||
const val kListItemPreResolve = 0x04
|
||||
|
||||
const val kListItemTypeBlackList = 0x05
|
||||
|
||||
const val kListItemBatchResolve = 0x06
|
||||
|
||||
const val kListItemTag = 0x07
|
||||
|
||||
@@ -1,407 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.info.list
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.newsdk.ams.emas.demo.*
|
||||
import com.newsdk.ams.emas.demo.TtlCacheHolder.toJsonString
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_BATCH_RESOLVE_HOST_LIST
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_HOST_BLACK_LIST
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_HOST_WITH_FIXED_IP
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_IP_RANKING_ITEMS
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_PRE_RESOLVE_HOST_LIST
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_TAGS
|
||||
import com.newsdk.ams.emas.demo.constant.KEY_TTL_CHANGER
|
||||
import com.newsdk.sdk.android.httpdns.ranking.IPRankingBean
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/6
|
||||
*/
|
||||
class ListViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private var hostFixedIpList: MutableList<String> = mutableListOf()
|
||||
private var ipRankingList: MutableList<IPRankingBean> = mutableListOf()
|
||||
private var hostBlackList: MutableList<String> = mutableListOf()
|
||||
private var tagsList: MutableList<String> = mutableListOf()
|
||||
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
fun initData(listType: Int, infoList: MutableList<ListItem>) {
|
||||
preferences = getAccountPreference(getApplication())
|
||||
viewModelScope.launch {
|
||||
when (listType) {
|
||||
kListItemTag -> {
|
||||
val tagStr = preferences.getString(KEY_TAGS, null)
|
||||
val list = tagStr.toTagList()
|
||||
list?.let {
|
||||
tagsList.addAll(list)
|
||||
for (tag in tagsList) {
|
||||
infoList.add(ListItem(kListItemTag, tag, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
kListItemTypeHostWithFixedIP -> {
|
||||
val hostFixedIpStr = preferences.getString(KEY_HOST_WITH_FIXED_IP, null)
|
||||
val list = hostFixedIpStr.toHostList()
|
||||
list?.let {
|
||||
hostFixedIpList.addAll(list)
|
||||
for (host in hostFixedIpList) {
|
||||
infoList.add(ListItem(kListItemTypeHostWithFixedIP, host, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
kListItemTypeBlackList -> {
|
||||
val hostBlackListStr = preferences.getString(KEY_HOST_BLACK_LIST, null)
|
||||
val list = hostBlackListStr.toBlackList()
|
||||
list?.let {
|
||||
hostBlackList.addAll(list)
|
||||
for (host in hostBlackList) {
|
||||
infoList.add(ListItem(kListItemTypeBlackList, host, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
kListItemTypeCacheTtl -> {
|
||||
val ttlCacheStr = preferences.getString(KEY_TTL_CHANGER, null)
|
||||
val map = ttlCacheStr.toTtlCacheMap()
|
||||
map?.let {
|
||||
TtlCacheHolder.ttlCache.putAll(map)
|
||||
for ((host, ttl) in TtlCacheHolder.ttlCache) {
|
||||
infoList.add(ListItem(kListItemTypeCacheTtl, host, ttl))
|
||||
}
|
||||
}
|
||||
}
|
||||
kListItemPreResolve -> {
|
||||
for (host in PreResolveCacheHolder.preResolveV4List) {
|
||||
infoList.add(ListItem(kListItemPreResolve, host, 0))
|
||||
}
|
||||
|
||||
for (host in PreResolveCacheHolder.preResolveV6List) {
|
||||
infoList.add(ListItem(kListItemPreResolve, host, 1))
|
||||
}
|
||||
|
||||
for (host in PreResolveCacheHolder.preResolveBothList) {
|
||||
infoList.add(ListItem(kListItemPreResolve, host, 2))
|
||||
}
|
||||
|
||||
for (host in PreResolveCacheHolder.preResolveAutoList) {
|
||||
infoList.add(ListItem(kListItemPreResolve, host, 3))
|
||||
}
|
||||
}
|
||||
kListItemBatchResolve -> {
|
||||
for (host in BatchResolveCacheHolder.batchResolveV4List) {
|
||||
infoList.add(ListItem(kListItemBatchResolve, host, 0))
|
||||
}
|
||||
for (host in BatchResolveCacheHolder.batchResolveV6List) {
|
||||
infoList.add(ListItem(kListItemBatchResolve, host, 1))
|
||||
}
|
||||
for (host in BatchResolveCacheHolder.batchResolveBothList) {
|
||||
infoList.add(ListItem(kListItemBatchResolve, host, 2))
|
||||
}
|
||||
for (host in BatchResolveCacheHolder.batchResolveAutoList) {
|
||||
infoList.add(ListItem(kListItemBatchResolve, host, 3))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val ipRankingListStr = preferences.getString(KEY_IP_RANKING_ITEMS, null)
|
||||
val rankingList = ipRankingListStr.toIPRankingList()
|
||||
rankingList?.let {
|
||||
ipRankingList.addAll(rankingList)
|
||||
for (rankingItem in ipRankingList) {
|
||||
infoList.add(
|
||||
ListItem(
|
||||
kListItemTypeIPRanking,
|
||||
rankingItem.hostName,
|
||||
rankingItem.port
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toAddTag(tag: String, listAdapter: ListAdapter) {
|
||||
tagsList.add(tag)
|
||||
saveTags()
|
||||
|
||||
listAdapter.addItemData(
|
||||
ListItem(
|
||||
kListItemTag,
|
||||
tag,
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun toAddHostWithFixedIP(host: String, listAdapter: ListAdapter) {
|
||||
if (hostFixedIpList.contains(host)) {
|
||||
Toast.makeText(
|
||||
getApplication(),
|
||||
getString(R.string.host_fixed_ip_duplicate, host),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
hostFixedIpList.add(host)
|
||||
saveHostWithFixedIP()
|
||||
listAdapter.addItemData(
|
||||
ListItem(
|
||||
kListItemTypeHostWithFixedIP,
|
||||
host,
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toAddHostInBlackList(host: String, listAdapter: ListAdapter) {
|
||||
if (hostBlackList.contains(host)) {
|
||||
Toast.makeText(
|
||||
getApplication(),
|
||||
getString(R.string.host_black_list_duplicate, host),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
hostBlackList.add(host)
|
||||
saveHostInBlackList()
|
||||
listAdapter.addItemData(
|
||||
ListItem(
|
||||
kListItemTypeBlackList,
|
||||
host,
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveTags() {
|
||||
viewModelScope.launch {
|
||||
val array = JSONArray()
|
||||
for (tag in tagsList) {
|
||||
array.put(tag)
|
||||
}
|
||||
val tagStr = array.toString()
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_TAGS, tagStr)
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveHostWithFixedIP() {
|
||||
viewModelScope.launch {
|
||||
val array = JSONArray()
|
||||
for (host in hostFixedIpList) {
|
||||
array.put(host)
|
||||
}
|
||||
val hostStr = array.toString()
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_HOST_WITH_FIXED_IP, hostStr)
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveHostInBlackList() {
|
||||
viewModelScope.launch {
|
||||
val array = JSONArray()
|
||||
for (host in hostBlackList) {
|
||||
array.put(host)
|
||||
}
|
||||
|
||||
preferences.edit()
|
||||
.putString(KEY_HOST_BLACK_LIST, array.toString())
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun toSaveIPProbe(host: String, port: Int, listAdapter: ListAdapter) {
|
||||
val ipProbeItem =
|
||||
IPRankingBean(host, port)
|
||||
if (ipRankingList.contains(ipProbeItem)) {
|
||||
Toast.makeText(
|
||||
getApplication(),
|
||||
getString(R.string.ip_probe_item_duplicate, host, port.toString()),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
ipRankingList.add(ipProbeItem)
|
||||
saveIPProbe()
|
||||
listAdapter.addItemData(
|
||||
ListItem(
|
||||
kListItemTypeIPRanking,
|
||||
host,
|
||||
port
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveIPProbe() {
|
||||
viewModelScope.launch {
|
||||
val jsonObject = JSONObject()
|
||||
for (item in ipRankingList) {
|
||||
try {
|
||||
jsonObject.put(item.hostName, item.port)
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
val ipProbeStr = jsonObject.toString()
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_IP_RANKING_ITEMS, ipProbeStr)
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun toSaveTtlCache(host: String, ttl: Int, listAdapter: ListAdapter) {
|
||||
viewModelScope.launch {
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_TTL_CHANGER, TtlCacheHolder.ttlCache.toJsonString())
|
||||
editor.apply()
|
||||
}
|
||||
if (TtlCacheHolder.ttlCache.containsKey(host)) {
|
||||
val position = listAdapter.getPositionByContent(host)
|
||||
if (position != -1) {
|
||||
listAdapter.updateItemByPosition(host, ttl, position)
|
||||
}
|
||||
} else {
|
||||
listAdapter.addItemData(
|
||||
ListItem(kListItemTypeCacheTtl, host, ttl)
|
||||
)
|
||||
}
|
||||
TtlCacheHolder.ttlCache[host] = ttl
|
||||
}
|
||||
|
||||
fun toAddPreResolveHost(host: String, listAdapter: ListAdapter, type: Int) {
|
||||
val list: MutableList<String> = when (type) {
|
||||
0 -> PreResolveCacheHolder.preResolveV4List
|
||||
1 -> PreResolveCacheHolder.preResolveV6List
|
||||
2 -> PreResolveCacheHolder.preResolveBothList
|
||||
else -> PreResolveCacheHolder.preResolveAutoList
|
||||
}
|
||||
|
||||
if (list.contains(host)) {
|
||||
Toast.makeText(
|
||||
getApplication(),
|
||||
getString(R.string.pre_resolve_host_duplicate, host),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
list.add(host)
|
||||
savePreResolveHost()
|
||||
listAdapter.addItemData(
|
||||
ListItem(
|
||||
kListItemPreResolve,
|
||||
host,
|
||||
type
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toAddBatchResolveHost(host: String, listAdapter: ListAdapter, type: Int) {
|
||||
val list: MutableList<String> = when (type) {
|
||||
0 -> BatchResolveCacheHolder.batchResolveV4List
|
||||
1 -> BatchResolveCacheHolder.batchResolveV6List
|
||||
2 -> BatchResolveCacheHolder.batchResolveBothList
|
||||
else -> BatchResolveCacheHolder.batchResolveAutoList
|
||||
}
|
||||
if (list.contains(host)) {
|
||||
Toast.makeText(
|
||||
getApplication(),
|
||||
getString(R.string.batch_resolve_host_duplicate, host),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
list.add(host)
|
||||
saveBatchResolveHost()
|
||||
listAdapter.addItemData(
|
||||
ListItem(
|
||||
kListItemBatchResolve,
|
||||
host,
|
||||
type
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun savePreResolveHost() {
|
||||
viewModelScope.launch {
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_PRE_RESOLVE_HOST_LIST, PreResolveCacheHolder.convertPreResolveString())
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveBatchResolveHost() {
|
||||
viewModelScope.launch {
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_BATCH_RESOLVE_HOST_LIST, BatchResolveCacheHolder.convertBatchResolveString())
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun onTagDeleted(position: Int) {
|
||||
tagsList.removeAt(position)
|
||||
saveTags()
|
||||
}
|
||||
|
||||
fun onHostWithFixedIPDeleted(position: Int) {
|
||||
//鍙兘閲嶅惎鐢熸晥
|
||||
val deletedHost = hostFixedIpList.removeAt(position)
|
||||
saveHostWithFixedIP()
|
||||
}
|
||||
|
||||
fun onIPProbeItemDeleted(position: Int) {
|
||||
ipRankingList.removeAt(position)
|
||||
saveIPProbe()
|
||||
}
|
||||
|
||||
fun onTtlDeleted(host: String) {
|
||||
TtlCacheHolder.ttlCache.remove(host)
|
||||
viewModelScope.launch {
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_TTL_CHANGER, TtlCacheHolder.ttlCache.toJsonString())
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun onPreResolveDeleted(host: String, intValue: Int) {
|
||||
val list = when (intValue) {
|
||||
0 -> PreResolveCacheHolder.preResolveV4List
|
||||
1 -> PreResolveCacheHolder.preResolveV6List
|
||||
2 -> PreResolveCacheHolder.preResolveBothList
|
||||
else -> PreResolveCacheHolder.preResolveAutoList
|
||||
}
|
||||
list.remove(host)
|
||||
savePreResolveHost()
|
||||
}
|
||||
|
||||
fun onBatchResolveDeleted(host: String, intValue: Int) {
|
||||
val list = when (intValue) {
|
||||
0 -> BatchResolveCacheHolder.batchResolveV4List
|
||||
1 -> BatchResolveCacheHolder.batchResolveV6List
|
||||
2 -> BatchResolveCacheHolder.batchResolveBothList
|
||||
else -> BatchResolveCacheHolder.batchResolveAutoList
|
||||
}
|
||||
list.remove(host)
|
||||
saveBatchResolveHost()
|
||||
}
|
||||
|
||||
fun onHostBlackListDeleted(position: Int) {
|
||||
hostBlackList.removeAt(position)
|
||||
saveHostInBlackList()
|
||||
}
|
||||
|
||||
private fun getString(resId: Int, vararg args: String): String {
|
||||
return getApplication<HttpDnsApplication>().getString(resId, *args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.newsdk.ams.emas.demo.ui.practice
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.newsdk.ams.httpdns.demo.R
|
||||
import com.newsdk.ams.httpdns.demo.databinding.FragmentBestPracticeBinding
|
||||
|
||||
/**
|
||||
* @author allen.wy
|
||||
* @date 2023/6/14
|
||||
*/
|
||||
class BestPracticeFragment : Fragment(), IBestPracticeShowDialog {
|
||||
|
||||
private var _binding: FragmentBestPracticeBinding? = null
|
||||
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentBestPracticeBinding.inflate(inflater, container, false)
|
||||
|
||||
val viewModel = ViewModelProvider(this)[BestPracticeViewModel::class.java]
|
||||
viewModel.showDialog = this
|
||||
|
||||
binding.viewModel = viewModel
|
||||
binding.openHttpdnsWebview.setOnClickListener {
|
||||
val intent = Intent(activity, HttpDnsWebviewGetActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
// binding.openHttpdnsWebviewPost.setOnClickListener {
|
||||
// val intent = Intent(activity, HttpDnsWVWebViewActivity::class.java)
|
||||
// startActivity(intent)
|
||||
// }
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun showResponseDialog(message: String) {
|
||||
val builder = activity?.let { act -> AlertDialog.Builder(act) }
|
||||
builder?.apply {
|
||||
setTitle(R.string.sni_request)
|
||||
setMessage(message)
|
||||
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
}
|
||||
builder?.show()
|
||||
}
|
||||
|
||||
override fun showNoNetworkDialog() {
|
||||
val builder = activity?.let { act -> AlertDialog.Builder(act) }
|
||||
builder?.apply {
|
||||
setTitle(R.string.tips)
|
||||
setMessage(R.string.network_not_connect)
|
||||
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||
}
|
||||
builder?.show()
|
||||
}
|
||||
}
|
||||
|
||||