管理端全部功能跑通
This commit is contained in:
@@ -1,148 +1,77 @@
|
||||
# Alicloud HTTPDNS Android SDK
|
||||
# HTTPDNS Android SDK (SNI Hidden v1.0.0)
|
||||
|
||||
面向 Android 的 HTTP/HTTPS DNS 解析 SDK,提供鉴权与可选 AES 加密、IPv4/IPv6 双栈解析、缓存与调度、预解析等能力。最低支持 Android API 19(Android 4.4)。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 鉴权请求与可选 AES 传输加密
|
||||
- IPv4/IPv6 双栈解析,支持自动/同时解析
|
||||
- 内存 + 持久化缓存与 TTL 控制,可选择复用过期 IP
|
||||
- 预解析、区域路由、网络切换自动刷新
|
||||
- 可定制日志回调与会话追踪 `sessionId`
|
||||
|
||||
## 安装(Gradle)
|
||||
|
||||
在项目的 `build.gradle` 中添加:
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
implementation 'com.aliyun.ams:alicloud-android-httpdns:2.6.7'
|
||||
}
|
||||
```
|
||||
|
||||
请访问 [Android SDK发布说明](https://help.aliyun.com/document_detail/435251.html) 查看最新版本号。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### Java
|
||||
## 1. Init
|
||||
|
||||
```java
|
||||
import com.alibaba.sdk.android.httpdns.HttpDns;
|
||||
import com.alibaba.sdk.android.httpdns.HttpDnsService;
|
||||
import com.alibaba.sdk.android.httpdns.InitConfig;
|
||||
import com.alibaba.sdk.android.httpdns.RequestIpType;
|
||||
|
||||
// 初始化配置
|
||||
String appId = "app1f1ndpo9";
|
||||
|
||||
new InitConfig.Builder()
|
||||
.setContext(context)
|
||||
.setSecretKey("YOUR_SECRET_KEY")
|
||||
.setEnableExpiredIp(true) // 允许返回过期 IP
|
||||
.buildFor("YOUR_ACCOUNT_ID");
|
||||
.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 httpDns = HttpDns.getService("YOUR_ACCOUNT_ID");
|
||||
|
||||
// 预解析热点域名
|
||||
httpDns.setPreResolveHosts(new ArrayList<>(Arrays.asList("www.aliyun.com")));
|
||||
|
||||
// 解析域名
|
||||
HTTPDNSResult result = httpDns.getHttpDnsResultForHostSyncNonBlocking("www.aliyun.com", RequestIpType.auto);
|
||||
String[] ips = result.getIps();
|
||||
HttpDnsService httpDnsService = HttpDns.getService(appId);
|
||||
```
|
||||
|
||||
### Kotlin
|
||||
|
||||
```kotlin
|
||||
import com.alibaba.sdk.android.httpdns.HttpDns
|
||||
import com.alibaba.sdk.android.httpdns.InitConfig
|
||||
import com.alibaba.sdk.android.httpdns.RequestIpType
|
||||
|
||||
// 初始化配置
|
||||
InitConfig.Builder()
|
||||
.setContext(context)
|
||||
.setSecretKey("YOUR_SECRET_KEY")
|
||||
.setEnableExpiredIp(true) // 允许返回过期 IP
|
||||
.buildFor("YOUR_ACCOUNT_ID")
|
||||
|
||||
// 获取实例
|
||||
val httpDns = HttpDns.getService("YOUR_ACCOUNT_ID")
|
||||
|
||||
// 预解析热点域名
|
||||
httpDns.setPreResolveHosts(arrayListOf("www.aliyun.com"))
|
||||
|
||||
// 解析域名
|
||||
val result = httpDns.getHttpDnsResultForHostSyncNonBlocking("www.aliyun.com", RequestIpType.auto)
|
||||
val ips = result.ips
|
||||
```
|
||||
|
||||
### 提示
|
||||
|
||||
- 启动时通过 `setPreResolveHosts()` 预热热点域名
|
||||
- 如需在刷新期间容忍 TTL 过期,可开启 `setEnableExpiredIp(true)`
|
||||
- 使用 `getSessionId()` 并与选用 IP 一同记录,便于排障
|
||||
|
||||
## 源码构建
|
||||
|
||||
```bash
|
||||
./gradlew clean :httpdns-sdk:assembleRelease
|
||||
```
|
||||
|
||||
构建产物位于 `httpdns-sdk/build/outputs/aar/` 目录。
|
||||
|
||||
### 版本说明
|
||||
|
||||
项目使用 `productFlavors` 区分不同版本:
|
||||
- `normal`:中国大陆版本
|
||||
- `intl`:国际版本
|
||||
- `end2end`:用于单元测试
|
||||
|
||||
## 测试
|
||||
|
||||
### 运行单元测试
|
||||
|
||||
```bash
|
||||
./gradlew clean :httpdns-sdk:testEnd2endForTestUnitTest
|
||||
```
|
||||
|
||||
### Demo 应用
|
||||
|
||||
SDK 提供了两个 Demo:
|
||||
|
||||
#### 1. app module(旧版 Demo)
|
||||
|
||||
在 `MyApp.java` 中配置测试账号:
|
||||
## 2. Resolve
|
||||
|
||||
```java
|
||||
private HttpDnsHolder holderA = new HttpDnsHolder("请替换为测试用A实例的accountId", "请替换为测试用A实例的secret");
|
||||
private HttpDnsHolder holderB = new HttpDnsHolder("请替换为测试用B实例的accountId", null);
|
||||
HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(
|
||||
"api.business.com",
|
||||
RequestIpType.auto,
|
||||
null,
|
||||
null
|
||||
);
|
||||
```
|
||||
|
||||
> 两个实例用于测试实例间互不影响,体验时只需配置一个
|
||||
## 3. Official HTTP Adapter (IP + Empty-SNI + Host)
|
||||
|
||||
#### 2. demo module(推荐)
|
||||
```java
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterOptions;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterRequest;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterResponse;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsHttpAdapter;
|
||||
|
||||
使用 Kotlin + MVVM 开发,功能更丰富。在 `demo/build.gradle` 中配置测试账号:
|
||||
HttpDnsHttpAdapter adapter = HttpDns.buildHttpClientAdapter(
|
||||
httpDnsService,
|
||||
new HttpDnsAdapterOptions.Builder()
|
||||
.setConnectTimeoutMillis(3000)
|
||||
.setReadTimeoutMillis(5000)
|
||||
.setRequestIpType(RequestIpType.auto)
|
||||
.setAllowInsecureCertificatesForDebugOnly(false)
|
||||
.build()
|
||||
);
|
||||
|
||||
```groovy
|
||||
buildConfigField "String", "ACCOUNT_ID", "\"请替换为测试用实例的accountId\""
|
||||
buildConfigField "String", "SECRET_KEY", "\"请替换为测试用实例的secret\""
|
||||
buildConfigField "String", "AES_SECRET_KEY", "\"请替换为测试用实例的aes\""
|
||||
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.
|
||||
|
||||
- Android API 19+(Android 4.4+)
|
||||
- 需要权限:`INTERNET`、`ACCESS_NETWORK_STATE`
|
||||
## 4. Public Errors
|
||||
|
||||
## 安全说明
|
||||
- `NO_IP_AVAILABLE`
|
||||
- `TLS_EMPTY_SNI_FAILED`
|
||||
- `HOST_ROUTE_REJECTED`
|
||||
- `RESOLVE_SIGN_INVALID`
|
||||
|
||||
- 切勿提交真实的 AccountID/SecretKey,请通过本地安全配置或 CI 注入
|
||||
- 若担心设备时间偏差影响鉴权,可使用 `setAuthCurrentTime()` 校正时间
|
||||
## 5. Removed Public Params
|
||||
|
||||
## 文档
|
||||
|
||||
官方文档:[Android SDK手册](https://help.aliyun.com/document_detail/435250.html)
|
||||
|
||||
## 感谢
|
||||
|
||||
本项目中 Inet64Util 工具类由 [Shinelw](https://github.com/Shinelw) 贡献支持,感谢。
|
||||
Do not use legacy public parameters:
|
||||
- `accountId`
|
||||
- `serviceDomain`
|
||||
- `endpoint`
|
||||
- `aesSecretKey`
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.content.Context;
|
||||
import com.alibaba.sdk.android.httpdns.impl.HttpDnsInstanceHolder;
|
||||
import com.alibaba.sdk.android.httpdns.impl.InstanceCreator;
|
||||
import com.alibaba.sdk.android.httpdns.net.NetworkStateManager;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterOptions;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsHttpAdapter;
|
||||
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
|
||||
|
||||
/**
|
||||
@@ -66,4 +68,18 @@ public class HttpDns {
|
||||
sHolder = new HttpDnsInstanceHolder(new InstanceCreator());
|
||||
NetworkStateManager.getInstance().reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build official IP-direct + empty-SNI adapter.
|
||||
*/
|
||||
public static HttpDnsHttpAdapter buildHttpClientAdapter(HttpDnsService service) {
|
||||
return new HttpDnsHttpAdapter(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build official IP-direct + empty-SNI adapter with options.
|
||||
*/
|
||||
public static HttpDnsHttpAdapter buildHttpClientAdapter(HttpDnsService service, HttpDnsAdapterOptions options) {
|
||||
return new HttpDnsHttpAdapter(service, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.alibaba.sdk.android.httpdns;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.impl.HttpDnsInstanceHolder;
|
||||
import com.alibaba.sdk.android.httpdns.impl.InstanceCreator;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterOptions;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsHttpAdapter;
|
||||
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -73,6 +75,20 @@ public class HttpDns {
|
||||
InitConfig.addConfig(accountId, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build official IP-direct + empty-SNI adapter.
|
||||
*/
|
||||
public static HttpDnsHttpAdapter buildHttpClientAdapter(HttpDnsService service) {
|
||||
return new HttpDnsHttpAdapter(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build official IP-direct + empty-SNI adapter with options.
|
||||
*/
|
||||
public static HttpDnsHttpAdapter buildHttpClientAdapter(HttpDnsService service, HttpDnsAdapterOptions options) {
|
||||
return new HttpDnsHttpAdapter(service, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用或者禁用httpdns,理论上这个是内部接口,不给外部使用的
|
||||
* 但是已经对外暴露,所以保留
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.alibaba.sdk.android.httpdns;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterOptions;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsHttpAdapter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class HttpDnsV1Client {
|
||||
private HttpDnsV1Client() {
|
||||
}
|
||||
|
||||
public static HttpDnsService init(Context context,
|
||||
String appId,
|
||||
String primaryServiceHost,
|
||||
String backupServiceHost,
|
||||
int servicePort,
|
||||
String signSecret) {
|
||||
InitConfig.Builder builder = new InitConfig.Builder()
|
||||
.setContext(context)
|
||||
.setEnableHttps(true)
|
||||
.setPrimaryServiceHost(primaryServiceHost)
|
||||
.setServicePort(servicePort > 0 ? servicePort : 443);
|
||||
|
||||
if (!TextUtils.isEmpty(backupServiceHost)) {
|
||||
builder.setBackupServiceHost(backupServiceHost);
|
||||
}
|
||||
if (!TextUtils.isEmpty(signSecret)) {
|
||||
builder.setSecretKey(signSecret);
|
||||
}
|
||||
|
||||
builder.buildFor(appId);
|
||||
if (!TextUtils.isEmpty(signSecret)) {
|
||||
return HttpDns.getService(context, appId, signSecret);
|
||||
}
|
||||
return HttpDns.getService(context, appId);
|
||||
}
|
||||
|
||||
public static HTTPDNSResult resolveHost(HttpDnsService service,
|
||||
String host,
|
||||
String qtype,
|
||||
String cip) {
|
||||
if (service == null) {
|
||||
return HTTPDNSResult.empty(host);
|
||||
}
|
||||
RequestIpType requestIpType = RequestIpType.auto;
|
||||
if ("AAAA".equalsIgnoreCase(qtype)) {
|
||||
requestIpType = RequestIpType.v6;
|
||||
}
|
||||
|
||||
Map<String, String> params = null;
|
||||
if (!TextUtils.isEmpty(cip)) {
|
||||
params = new HashMap<>();
|
||||
params.put("cip", cip);
|
||||
}
|
||||
|
||||
return service.getHttpDnsResultForHostSyncNonBlocking(host, requestIpType, params, host);
|
||||
}
|
||||
|
||||
public static HttpDnsHttpAdapter buildHttpClientAdapter(HttpDnsService service) {
|
||||
return HttpDns.buildHttpClientAdapter(service);
|
||||
}
|
||||
|
||||
public static HttpDnsHttpAdapter buildHttpClientAdapter(HttpDnsService service,
|
||||
HttpDnsAdapterOptions options) {
|
||||
return HttpDns.buildHttpClientAdapter(service, options);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,9 @@ public class InitConfig {
|
||||
private final String mBizTags;
|
||||
private final String aesSecretKey;
|
||||
private final String secretKey;
|
||||
private final String primaryServiceHost;
|
||||
private final String backupServiceHost;
|
||||
private final int servicePort;
|
||||
private final Context context;
|
||||
|
||||
private InitConfig(Builder builder) {
|
||||
@@ -82,6 +85,9 @@ public class InitConfig {
|
||||
mEnableObservable = builder.enableObservable;
|
||||
mBizTags = builder.bizTags;
|
||||
aesSecretKey = builder.aesSecretKey;
|
||||
primaryServiceHost = builder.primaryServiceHost;
|
||||
backupServiceHost = builder.backupServiceHost;
|
||||
servicePort = builder.servicePort;
|
||||
context = builder.context;
|
||||
secretKey = builder.secretKey;
|
||||
}
|
||||
@@ -159,6 +165,18 @@ public class InitConfig {
|
||||
return aesSecretKey;
|
||||
}
|
||||
|
||||
public String getPrimaryServiceHost() {
|
||||
return primaryServiceHost;
|
||||
}
|
||||
|
||||
public String getBackupServiceHost() {
|
||||
return backupServiceHost;
|
||||
}
|
||||
|
||||
public int getServicePort() {
|
||||
return servicePort;
|
||||
}
|
||||
|
||||
public Context getContext() {return context;}
|
||||
|
||||
public String getSecretKey() {
|
||||
@@ -184,6 +202,9 @@ public class InitConfig {
|
||||
private Map<String, String> sdnsGlobalParams = null;
|
||||
private String bizTags = null;
|
||||
private String aesSecretKey = null;
|
||||
private String primaryServiceHost = null;
|
||||
private String backupServiceHost = null;
|
||||
private int servicePort = -1;
|
||||
private Context context = null;
|
||||
private String secretKey = null;
|
||||
|
||||
@@ -415,6 +436,43 @@ public class InitConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主服务域名。
|
||||
*/
|
||||
public Builder setPrimaryServiceHost(String host) {
|
||||
this.primaryServiceHost = host;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置备服务域名。
|
||||
*/
|
||||
public Builder setBackupServiceHost(String host) {
|
||||
this.backupServiceHost = host;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置服务域名,支持主备两个。
|
||||
*/
|
||||
public Builder setServiceHosts(List<String> hosts) {
|
||||
if (hosts != null && hosts.size() > 0) {
|
||||
this.primaryServiceHost = hosts.get(0);
|
||||
}
|
||||
if (hosts != null && hosts.size() > 1) {
|
||||
this.backupServiceHost = hosts.get(1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置服务端口,默认 -1 表示使用协议默认端口。
|
||||
*/
|
||||
public Builder setServicePort(int port) {
|
||||
this.servicePort = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置context
|
||||
* @param context 上下文
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.alibaba.sdk.android.httpdns.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -41,6 +42,7 @@ import com.alibaba.sdk.android.httpdns.utils.Constants;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* 域名解析服务 httpdns接口的实现
|
||||
@@ -60,6 +62,7 @@ public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdat
|
||||
private SignService mSignService;
|
||||
private AESEncryptService mAESEncryptService;
|
||||
private boolean resolveAfterNetworkChange = true;
|
||||
private boolean mUseCustomServiceHosts = false;
|
||||
/**
|
||||
* crash defend 默认关闭
|
||||
*/
|
||||
@@ -115,7 +118,9 @@ public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdat
|
||||
|
||||
tryUpdateRegionServer(sContext, accountId);
|
||||
|
||||
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
|
||||
if (!mUseCustomServiceHosts) {
|
||||
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
|
||||
}
|
||||
favorInit(sContext, accountId);
|
||||
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
@@ -163,10 +168,37 @@ public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdat
|
||||
|
||||
mRequestHandler.setSdnsGlobalParams(config.getSdnsGlobalParams());
|
||||
mAESEncryptService.setAesSecretKey(config.getAesSecretKey());
|
||||
applyCustomServiceHosts(config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void applyCustomServiceHosts(InitConfig config) {
|
||||
String primaryHost = config.getPrimaryServiceHost();
|
||||
String backupHost = config.getBackupServiceHost();
|
||||
ArrayList<String> hosts = new ArrayList<>();
|
||||
if (!TextUtils.isEmpty(primaryHost)) {
|
||||
hosts.add(primaryHost.trim());
|
||||
}
|
||||
if (!TextUtils.isEmpty(backupHost) && !backupHost.trim().equalsIgnoreCase(primaryHost == null ? "" : primaryHost.trim())) {
|
||||
hosts.add(backupHost.trim());
|
||||
}
|
||||
if (hosts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] serverHosts = hosts.toArray(new String[0]);
|
||||
int[] ports = new int[serverHosts.length];
|
||||
int servicePort = config.getServicePort();
|
||||
Arrays.fill(ports, servicePort > 0 ? servicePort : -1);
|
||||
|
||||
mHttpDnsConfig.setInitServers(mHttpDnsConfig.getRegion(), serverHosts, ports, serverHosts, ports);
|
||||
mHttpDnsConfig.setDefaultUpdateServer(serverHosts, ports);
|
||||
mHttpDnsConfig.setDefaultUpdateServerIpv6(serverHosts, ports);
|
||||
mHttpDnsConfig.setHTTPSRequestEnabled(true);
|
||||
mUseCustomServiceHosts = true;
|
||||
}
|
||||
|
||||
protected void beforeInit() {
|
||||
// only for test
|
||||
}
|
||||
@@ -218,7 +250,9 @@ public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdat
|
||||
mRequestHandler.resetStatus();
|
||||
|
||||
//服务IP更新,触发服务IP测速
|
||||
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
|
||||
if (!mUseCustomServiceHosts) {
|
||||
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -589,6 +623,12 @@ public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdat
|
||||
|
||||
@Override
|
||||
public void setRegion(String region) {
|
||||
if (mUseCustomServiceHosts) {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.d("ignore setRegion in custom service host mode");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!mHttpDnsConfig.isEnabled()) {
|
||||
HttpDnsLog.i("service is disabled");
|
||||
return;
|
||||
@@ -605,9 +645,13 @@ public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdat
|
||||
if (changed) {
|
||||
mResultRepo.clearMemoryCache();
|
||||
//region变化,服务IP变成对应的预置IP,触发测速
|
||||
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
|
||||
if (!mUseCustomServiceHosts) {
|
||||
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
|
||||
}
|
||||
}
|
||||
if (!mUseCustomServiceHosts) {
|
||||
mScheduleService.updateRegionServerIps(region, Constants.UPDATE_REGION_SERVER_SCENES_REGION_CHANGE);
|
||||
}
|
||||
mScheduleService.updateRegionServerIps(region, Constants.UPDATE_REGION_SERVER_SCENES_REGION_CHANGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -684,7 +728,9 @@ public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdat
|
||||
});
|
||||
|
||||
//网络变化,触发服务IP测速
|
||||
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
|
||||
if (!mUseCustomServiceHosts) {
|
||||
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
@@ -817,6 +863,9 @@ public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdat
|
||||
}
|
||||
|
||||
private void tryUpdateRegionServer(Context context, String accountId) {
|
||||
if (mUseCustomServiceHosts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHttpDnsConfig.getCurrentServer().shouldUpdateServerIp()) {
|
||||
mScheduleService.updateRegionServerIps(Constants.UPDATE_REGION_SERVER_SCENES_INIT);
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.alibaba.sdk.android.httpdns.impl;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
|
||||
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
@@ -67,6 +66,30 @@ public class SignService {
|
||||
return signStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新版 /resolve 请求签名:
|
||||
* appId|lower(domain)|upper(qtype)|exp|nonce
|
||||
*/
|
||||
public String signResolve(String appId, String domain, String qtype, String exp, String nonce) {
|
||||
if (TextUtils.isEmpty(mSecretKey)
|
||||
|| TextUtils.isEmpty(appId)
|
||||
|| TextUtils.isEmpty(domain)
|
||||
|| TextUtils.isEmpty(qtype)
|
||||
|| TextUtils.isEmpty(exp)
|
||||
|| TextUtils.isEmpty(nonce)) {
|
||||
return "";
|
||||
}
|
||||
String raw = appId + "|" + domain.toLowerCase() + "|" + qtype.toUpperCase() + "|" + exp + "|" + nonce;
|
||||
try {
|
||||
return hmacSha256(raw);
|
||||
} catch (Exception e) {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.e("sign resolve fail.", e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String generateV2SignContent(Map<String, String> map) {
|
||||
Map<String, String> sortedMap = new TreeMap<>();
|
||||
for(Map.Entry<String, String> entry : map.entrySet()) {
|
||||
@@ -91,9 +114,17 @@ public class SignService {
|
||||
private String hmacSha256(String content)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Mac mac = Mac.getInstance(ALGORITHM);
|
||||
mac.init(new SecretKeySpec(CommonUtil.decodeHex(mSecretKey), ALGORITHM));
|
||||
mac.init(new SecretKeySpec(mSecretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM));
|
||||
byte[] signedBytes = mac.doFinal(content.getBytes(StandardCharsets.UTF_8));
|
||||
return CommonUtil.encodeHexString(signedBytes);
|
||||
StringBuilder sb = new StringBuilder(signedBytes.length * 2);
|
||||
for (byte b : signedBytes) {
|
||||
String h = Integer.toHexString(b & 0xff);
|
||||
if (h.length() == 1) {
|
||||
sb.append('0');
|
||||
}
|
||||
sb.append(h);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void setCurrentTimestamp(long serverTime) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.alibaba.sdk.android.httpdns.network;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HttpDnsAdapterException extends IOException {
|
||||
private final String errorCode;
|
||||
|
||||
public HttpDnsAdapterException(String errorCode, String message) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public HttpDnsAdapterException(String errorCode, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.alibaba.sdk.android.httpdns.network;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.RequestIpType;
|
||||
|
||||
public class HttpDnsAdapterOptions {
|
||||
private final int connectTimeoutMillis;
|
||||
private final int readTimeoutMillis;
|
||||
private final boolean allowInsecureCertificatesForDebugOnly;
|
||||
private final RequestIpType requestIpType;
|
||||
|
||||
private HttpDnsAdapterOptions(Builder builder) {
|
||||
connectTimeoutMillis = builder.connectTimeoutMillis;
|
||||
readTimeoutMillis = builder.readTimeoutMillis;
|
||||
allowInsecureCertificatesForDebugOnly = builder.allowInsecureCertificatesForDebugOnly;
|
||||
requestIpType = builder.requestIpType;
|
||||
}
|
||||
|
||||
public int getConnectTimeoutMillis() {
|
||||
return connectTimeoutMillis;
|
||||
}
|
||||
|
||||
public int getReadTimeoutMillis() {
|
||||
return readTimeoutMillis;
|
||||
}
|
||||
|
||||
public boolean isAllowInsecureCertificatesForDebugOnly() {
|
||||
return allowInsecureCertificatesForDebugOnly;
|
||||
}
|
||||
|
||||
public RequestIpType getRequestIpType() {
|
||||
return requestIpType;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private int connectTimeoutMillis = 3000;
|
||||
private int readTimeoutMillis = 5000;
|
||||
private boolean allowInsecureCertificatesForDebugOnly = false;
|
||||
private RequestIpType requestIpType = RequestIpType.auto;
|
||||
|
||||
public Builder setConnectTimeoutMillis(int value) {
|
||||
if (value > 0) {
|
||||
connectTimeoutMillis = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setReadTimeoutMillis(int value) {
|
||||
if (value > 0) {
|
||||
readTimeoutMillis = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowInsecureCertificatesForDebugOnly(boolean value) {
|
||||
allowInsecureCertificatesForDebugOnly = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRequestIpType(RequestIpType type) {
|
||||
if (type != null) {
|
||||
requestIpType = type;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpDnsAdapterOptions build() {
|
||||
return new HttpDnsAdapterOptions(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.alibaba.sdk.android.httpdns.network;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpDnsAdapterRequest {
|
||||
private final String method;
|
||||
private final String url;
|
||||
private final Map<String, String> headers;
|
||||
private final byte[] body;
|
||||
|
||||
public HttpDnsAdapterRequest(String method, String url) {
|
||||
this(method, url, null, null);
|
||||
}
|
||||
|
||||
public HttpDnsAdapterRequest(String method, String url, Map<String, String> headers, byte[] body) {
|
||||
this.method = method == null || method.trim().isEmpty() ? "GET" : method.trim().toUpperCase();
|
||||
this.url = url;
|
||||
if (headers == null || headers.isEmpty()) {
|
||||
this.headers = Collections.emptyMap();
|
||||
} else {
|
||||
this.headers = Collections.unmodifiableMap(new LinkedHashMap<>(headers));
|
||||
}
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.alibaba.sdk.android.httpdns.network;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpDnsAdapterResponse {
|
||||
private final int statusCode;
|
||||
private final Map<String, List<String>> headers;
|
||||
private final byte[] body;
|
||||
private final String usedIp;
|
||||
|
||||
public HttpDnsAdapterResponse(int statusCode, Map<String, List<String>> headers, byte[] body, String usedIp) {
|
||||
this.statusCode = statusCode;
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
this.usedIp = usedIp;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getUsedIp() {
|
||||
return usedIp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.alibaba.sdk.android.httpdns.network;
|
||||
|
||||
public final class HttpDnsErrorCode {
|
||||
private HttpDnsErrorCode() {
|
||||
}
|
||||
|
||||
public static final String NO_IP_AVAILABLE = "NO_IP_AVAILABLE";
|
||||
public static final String TLS_EMPTY_SNI_FAILED = "TLS_EMPTY_SNI_FAILED";
|
||||
public static final String HOST_ROUTE_REJECTED = "HOST_ROUTE_REJECTED";
|
||||
public static final String RESOLVE_SIGN_INVALID = "RESOLVE_SIGN_INVALID";
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package com.alibaba.sdk.android.httpdns.network;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.HTTPDNSResult;
|
||||
import com.alibaba.sdk.android.httpdns.HttpDnsService;
|
||||
import com.alibaba.sdk.android.httpdns.RequestIpType;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class HttpDnsHttpAdapter {
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
private final HttpDnsService httpDnsService;
|
||||
private final HttpDnsAdapterOptions options;
|
||||
|
||||
public HttpDnsHttpAdapter(HttpDnsService httpDnsService) {
|
||||
this(httpDnsService, new HttpDnsAdapterOptions.Builder().build());
|
||||
}
|
||||
|
||||
public HttpDnsHttpAdapter(HttpDnsService httpDnsService, HttpDnsAdapterOptions options) {
|
||||
if (httpDnsService == null) {
|
||||
throw new IllegalArgumentException("httpDnsService should not be null");
|
||||
}
|
||||
this.httpDnsService = httpDnsService;
|
||||
this.options = options == null ? new HttpDnsAdapterOptions.Builder().build() : options;
|
||||
}
|
||||
|
||||
public HttpDnsAdapterResponse execute(HttpDnsAdapterRequest request) throws IOException {
|
||||
if (request == null || request.getUrl() == null || request.getUrl().trim().isEmpty()) {
|
||||
throw new HttpDnsAdapterException(HttpDnsErrorCode.HOST_ROUTE_REJECTED,
|
||||
"request or url is empty");
|
||||
}
|
||||
|
||||
URL originalURL = new URL(request.getUrl());
|
||||
String originalHost = originalURL.getHost();
|
||||
if (originalHost == null || originalHost.trim().isEmpty()) {
|
||||
throw new HttpDnsAdapterException(HttpDnsErrorCode.HOST_ROUTE_REJECTED,
|
||||
"invalid original host");
|
||||
}
|
||||
if (!"https".equalsIgnoreCase(originalURL.getProtocol())) {
|
||||
throw new HttpDnsAdapterException(HttpDnsErrorCode.TLS_EMPTY_SNI_FAILED,
|
||||
"only https scheme is supported in empty sni mode");
|
||||
}
|
||||
|
||||
List<String> candidateIps = resolveIps(originalHost);
|
||||
if (candidateIps.isEmpty()) {
|
||||
throw new HttpDnsAdapterException(HttpDnsErrorCode.NO_IP_AVAILABLE,
|
||||
"HTTPDNS returned no ip for host: " + originalHost);
|
||||
}
|
||||
|
||||
IOException lastException = null;
|
||||
for (String ip : candidateIps) {
|
||||
if (ip == null || ip.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String usedIp = ip.trim();
|
||||
HttpsURLConnection connection = null;
|
||||
try {
|
||||
int port = originalURL.getPort() > 0 ? originalURL.getPort() : 443;
|
||||
URL targetURL = new URL("https", usedIp, port, originalURL.getFile());
|
||||
connection = (HttpsURLConnection) targetURL.openConnection();
|
||||
connection.setConnectTimeout(options.getConnectTimeoutMillis());
|
||||
connection.setReadTimeout(options.getReadTimeoutMillis());
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
connection.setRequestMethod(request.getMethod());
|
||||
connection.setSSLSocketFactory(createSocketFactory());
|
||||
connection.setHostnameVerifier(createHostnameVerifier(originalHost));
|
||||
connection.setRequestProperty("Host", originalHost);
|
||||
for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) {
|
||||
if (entry.getKey() == null || entry.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
if ("host".equalsIgnoreCase(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
connection.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
byte[] body = request.getBody();
|
||||
if (body != null && body.length > 0 && allowsRequestBody(request.getMethod())) {
|
||||
connection.setDoOutput(true);
|
||||
connection.getOutputStream().write(body);
|
||||
}
|
||||
|
||||
int code = connection.getResponseCode();
|
||||
byte[] payload = readResponseBytes(connection, code);
|
||||
Map<String, List<String>> headers = sanitizeHeaders(connection.getHeaderFields());
|
||||
return new HttpDnsAdapterResponse(code, headers, payload, usedIp);
|
||||
} catch (IOException e) {
|
||||
lastException = e;
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastException == null) {
|
||||
throw new HttpDnsAdapterException(HttpDnsErrorCode.TLS_EMPTY_SNI_FAILED,
|
||||
"all ip attempts failed with no explicit exception");
|
||||
}
|
||||
|
||||
throw new HttpDnsAdapterException(HttpDnsErrorCode.TLS_EMPTY_SNI_FAILED,
|
||||
"all ip attempts failed", lastException);
|
||||
}
|
||||
|
||||
private List<String> resolveIps(String host) {
|
||||
RequestIpType type = options.getRequestIpType();
|
||||
HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(host, type, null, null);
|
||||
if (result == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> ips = new ArrayList<>();
|
||||
String[] v4 = result.getIps();
|
||||
if (v4 != null) {
|
||||
for (String item : v4) {
|
||||
if (item != null && item.length() > 0) {
|
||||
ips.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
String[] v6 = result.getIpv6s();
|
||||
if (v6 != null) {
|
||||
for (String item : v6) {
|
||||
if (item != null && item.length() > 0) {
|
||||
ips.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ips;
|
||||
}
|
||||
|
||||
private byte[] readResponseBytes(HttpsURLConnection connection, int code) throws IOException {
|
||||
InputStream stream = code >= 400 ? connection.getErrorStream() : connection.getInputStream();
|
||||
if (stream == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int count;
|
||||
while ((count = stream.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, count);
|
||||
}
|
||||
stream.close();
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private Map<String, List<String>> sanitizeHeaders(Map<String, List<String>> source) {
|
||||
if (source == null || source.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, List<String>> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, List<String>> entry : source.entrySet()) {
|
||||
if (entry.getKey() == null) {
|
||||
continue;
|
||||
}
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean allowsRequestBody(String method) {
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
String upper = method.toUpperCase();
|
||||
return "POST".equals(upper) || "PUT".equals(upper) || "PATCH".equals(upper) || "DELETE".equals(upper);
|
||||
}
|
||||
|
||||
private HostnameVerifier createHostnameVerifier(final String originalHost) {
|
||||
if (options.isAllowInsecureCertificatesForDebugOnly()) {
|
||||
return new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
final HostnameVerifier defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
return new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return defaultVerifier.verify(originalHost, session);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private SSLSocketFactory createSocketFactory() throws IOException {
|
||||
try {
|
||||
SSLSocketFactory baseFactory;
|
||||
if (options.isAllowInsecureCertificatesForDebugOnly()) {
|
||||
baseFactory = trustAllSocketFactory();
|
||||
} else {
|
||||
baseFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
}
|
||||
return new NoSniSocketFactory(baseFactory);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IOException("create ssl socket factory failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private SSLSocketFactory trustAllSocketFactory() throws GeneralSecurityException {
|
||||
TrustManager[] managers = new TrustManager[]{new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
}};
|
||||
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, managers, new SecureRandom());
|
||||
return context.getSocketFactory();
|
||||
}
|
||||
|
||||
private static class NoSniSocketFactory extends SSLSocketFactory {
|
||||
private final SSLSocketFactory delegate;
|
||||
|
||||
private NoSniSocketFactory(SSLSocketFactory delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return delegate.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return delegate.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.net.Socket createSocket(java.net.Socket s, String host, int port, boolean autoClose)
|
||||
throws IOException {
|
||||
return sanitize(delegate.createSocket(s, host, port, autoClose));
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.net.Socket createSocket(String host, int port) throws IOException {
|
||||
return sanitize(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.net.Socket createSocket(String host, int port, java.net.InetAddress localHost, int localPort)
|
||||
throws IOException {
|
||||
return sanitize(delegate.createSocket(host, port, localHost, localPort));
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.net.Socket createSocket(java.net.InetAddress host, int port) throws IOException {
|
||||
return sanitize(delegate.createSocket(host, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.net.Socket createSocket(java.net.InetAddress address, int port, java.net.InetAddress localAddress,
|
||||
int localPort) throws IOException {
|
||||
return sanitize(delegate.createSocket(address, port, localAddress, localPort));
|
||||
}
|
||||
|
||||
private java.net.Socket sanitize(java.net.Socket socket) {
|
||||
if (!(socket instanceof SSLSocket)) {
|
||||
return socket;
|
||||
}
|
||||
SSLSocket sslSocket = (SSLSocket) socket;
|
||||
try {
|
||||
SSLParameters params = sslSocket.getSSLParameters();
|
||||
try {
|
||||
java.lang.reflect.Method setServerNames = SSLParameters.class.getMethod("setServerNames", List.class);
|
||||
setServerNames.invoke(params, Collections.emptyList());
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
sslSocket.setSSLParameters(params);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return sslSocket;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,7 @@ import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
|
||||
|
||||
@@ -61,13 +59,7 @@ public class HttpRequest<T> {
|
||||
//设置通用UA
|
||||
conn.setRequestProperty("User-Agent", requestConfig.getUA());
|
||||
if (conn instanceof HttpsURLConnection) {
|
||||
((HttpsURLConnection)conn).setHostnameVerifier(new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return HttpsURLConnection.getDefaultHostnameVerifier().verify(
|
||||
HttpRequestConfig.HTTPS_CERTIFICATE_HOSTNAME, session);
|
||||
}
|
||||
});
|
||||
((HttpsURLConnection) conn).setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
|
||||
}
|
||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
in = conn.getErrorStream();
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package com.alibaba.sdk.android.httpdns.resolve;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.BuildConfig;
|
||||
import com.alibaba.sdk.android.httpdns.NetType;
|
||||
import com.alibaba.sdk.android.httpdns.RequestIpType;
|
||||
import com.alibaba.sdk.android.httpdns.impl.AESEncryptService;
|
||||
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
|
||||
import com.alibaba.sdk.android.httpdns.impl.SignService;
|
||||
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
|
||||
import com.alibaba.sdk.android.httpdns.request.HttpRequestConfig;
|
||||
import com.alibaba.sdk.android.httpdns.track.SessionTrackMgr;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ResolveHostHelper {
|
||||
public static HttpRequestConfig getConfig(HttpDnsConfig config, String host,
|
||||
@@ -28,18 +25,18 @@ public class ResolveHostHelper {
|
||||
Map<String, String> globalParams,
|
||||
SignService signService,
|
||||
AESEncryptService encryptService) {
|
||||
HashMap<String, String> extraArgs = null;
|
||||
if (cacheKey != null) {
|
||||
extraArgs = new HashMap<>();
|
||||
HashMap<String, String> mergedExtras = null;
|
||||
if ((globalParams != null && !globalParams.isEmpty()) || (extras != null && !extras.isEmpty())) {
|
||||
mergedExtras = new HashMap<>();
|
||||
if (globalParams != null) {
|
||||
extraArgs.putAll(globalParams);
|
||||
mergedExtras.putAll(globalParams);
|
||||
}
|
||||
if (extras != null) {
|
||||
extraArgs.putAll(extras);
|
||||
mergedExtras.putAll(extras);
|
||||
}
|
||||
}
|
||||
|
||||
String path = getPath(config, host, type, extraArgs, signService, encryptService);
|
||||
String path = getPath(config, host, type, mergedExtras, signService, encryptService);
|
||||
HttpRequestConfig requestConfig = getHttpRequestConfig(config, path, signService.isSignMode());
|
||||
requestConfig.setUA(config.getUA());
|
||||
requestConfig.setAESEncryptService(encryptService);
|
||||
@@ -50,202 +47,62 @@ public class ResolveHostHelper {
|
||||
Map<String, String> extras,
|
||||
SignService signService,
|
||||
AESEncryptService encryptService) {
|
||||
//参数加密
|
||||
String enc = "";
|
||||
String query = getQuery(type);
|
||||
String version = "1.0";
|
||||
String tags = config.getBizTags();
|
||||
AESEncryptService.EncryptionMode mode = AESEncryptService.EncryptionMode.PLAIN;
|
||||
if (encryptService.isEncryptionMode()) {
|
||||
String encryptJson = buildEncryptionStr(host, query, extras, tags);
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.d("encryptJson:" + encryptJson);
|
||||
}
|
||||
mode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? AESEncryptService.EncryptionMode.AES_GCM : AESEncryptService.EncryptionMode.AES_CBC;
|
||||
enc = encryptService.encrypt(encryptJson, mode);
|
||||
String qtype = getQType(type);
|
||||
StringBuilder query = new StringBuilder();
|
||||
appendQuery(query, "appId", config.getAccountId());
|
||||
appendQuery(query, "dn", host);
|
||||
appendQuery(query, "qtype", qtype);
|
||||
appendQuery(query, "sdk_version", BuildConfig.VERSION_NAME);
|
||||
appendQuery(query, "os", "android");
|
||||
String sid = SessionTrackMgr.getInstance().getSessionId();
|
||||
if (!TextUtils.isEmpty(sid)) {
|
||||
appendQuery(query, "sid", sid);
|
||||
}
|
||||
|
||||
String expireTime = signService.getExpireTime();
|
||||
|
||||
String queryStr = buildQueryStr(config.getAccountId(), mode.getMode(), host,
|
||||
query, extras, enc, expireTime, version, tags);
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.d("query parameter:" + queryStr);
|
||||
}
|
||||
|
||||
//加签
|
||||
if (signService.isSignMode()) {
|
||||
Map<String, String> signParamMap = new HashMap<>();
|
||||
if (encryptService.isEncryptionMode()) {
|
||||
signParamMap.put("enc", enc);
|
||||
signParamMap.put("exp", expireTime);
|
||||
signParamMap.put("id", config.getAccountId());
|
||||
signParamMap.put("m", mode.getMode());
|
||||
signParamMap.put("v", version);
|
||||
}else {
|
||||
signParamMap.put("dn", host);
|
||||
signParamMap.put("exp", expireTime);
|
||||
signParamMap.put("id", config.getAccountId());
|
||||
signParamMap.put("m", mode.getMode());
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
signParamMap.put("q", query);
|
||||
}
|
||||
if (extras != null) {
|
||||
for (Map.Entry<String, String> entry : extras.entrySet()) {
|
||||
signParamMap.put("sdns-" + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(tags)) {
|
||||
signParamMap.put("tags", tags);
|
||||
}
|
||||
signParamMap.put("v", version);
|
||||
}
|
||||
|
||||
String sign = signService.sign(signParamMap);
|
||||
if (TextUtils.isEmpty(sign)) {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.d("param sign fail");
|
||||
}
|
||||
}else {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.d("sign:" + sign);
|
||||
}
|
||||
queryStr += "&s=" + sign;
|
||||
}
|
||||
}
|
||||
String path = "/v2/d?" + queryStr + "&sdk=android_" + BuildConfig.VERSION_NAME + getSid();
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.d("path:" + path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private static String buildQueryStr(String accountId, String mode, String host,
|
||||
String query, Map<String, String> extras, String enc,
|
||||
String expireTime, String version, String tags) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append("id=").append(accountId);
|
||||
stringBuilder.append("&m=").append(mode);
|
||||
if (TextUtils.isEmpty(enc)) {
|
||||
stringBuilder.append("&dn=").append(host);
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
stringBuilder.append("&q=").append(query);
|
||||
}
|
||||
|
||||
String extra = getExtra(extras);
|
||||
if (!TextUtils.isEmpty(extra)) {
|
||||
stringBuilder.append(extra);
|
||||
}
|
||||
if (!TextUtils.isEmpty(tags)) {
|
||||
stringBuilder.append("&tags=").append(tags);
|
||||
}
|
||||
}else {
|
||||
stringBuilder.append("&enc=").append(enc);
|
||||
}
|
||||
stringBuilder.append("&v=").append(version);
|
||||
stringBuilder.append("&exp=").append(expireTime);
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private static String buildEncryptionStr(String host, String query, Map<String, String> extras, String tags) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("dn", host);
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
json.put("q", query);
|
||||
}
|
||||
if (!TextUtils.isEmpty(getExtra(extras))) {
|
||||
for (Map.Entry<String, String> entry : extras.entrySet()) {
|
||||
if (!checkKey(entry.getKey()) || !checkValue(entry.getValue())) {
|
||||
continue;
|
||||
}
|
||||
json.put("sdns-" + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(tags)) {
|
||||
json.put("tags", tags);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.e("encrypt param transfer to json fail.", e);
|
||||
}
|
||||
|
||||
}
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
private static String getQuery(RequestIpType type) {
|
||||
String query = "";
|
||||
switch (type) {
|
||||
case v6:
|
||||
query = "6";
|
||||
break;
|
||||
case both:
|
||||
query = "4,6";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
private static String getExtra(Map<String, String> extras) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean isKey = true;
|
||||
boolean isValue = true;
|
||||
if (extras != null) {
|
||||
for (Map.Entry<String, String> entry : extras.entrySet()) {
|
||||
sb.append("&sdns-");
|
||||
sb.append(entry.getKey());
|
||||
sb.append("=");
|
||||
sb.append(entry.getValue());
|
||||
if (!checkKey(entry.getKey())) {
|
||||
isKey = false;
|
||||
HttpDnsLog.e("设置自定义参数失败,自定义key不合法:" + entry.getKey());
|
||||
break;
|
||||
}
|
||||
if (!checkValue(entry.getValue())) {
|
||||
isValue = false;
|
||||
HttpDnsLog.e("设置自定义参数失败,自定义value不合法:" + entry.getValue());
|
||||
break;
|
||||
}
|
||||
String cip = extras.get("cip");
|
||||
if (!TextUtils.isEmpty(cip)) {
|
||||
appendQuery(query, "cip", cip);
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
if (isKey && isValue) {
|
||||
String extra = sb.toString();
|
||||
if (extra.getBytes(StandardCharsets.UTF_8).length <= 1000) {
|
||||
return extra;
|
||||
} else {
|
||||
HttpDnsLog.e("设置自定义参数失败,自定义参数过长");
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
if (signService.isSignMode()) {
|
||||
String exp = signService.getExpireTime();
|
||||
String nonce = randomNonce();
|
||||
String sign = signService.signResolve(config.getAccountId(), host, qtype, exp, nonce);
|
||||
appendQuery(query, "exp", exp);
|
||||
appendQuery(query, "nonce", nonce);
|
||||
appendQuery(query, "sign", sign);
|
||||
}
|
||||
return "/resolve?" + query;
|
||||
}
|
||||
|
||||
private static boolean checkKey(String s) {
|
||||
return s.matches("[a-zA-Z0-9\\-_]+");
|
||||
private static void appendQuery(StringBuilder query, String key, String value) {
|
||||
if (TextUtils.isEmpty(key) || value == null) {
|
||||
return;
|
||||
}
|
||||
if (query.length() > 0) {
|
||||
query.append("&");
|
||||
}
|
||||
query.append(key).append("=").append(URLEncoder.encode(value, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static boolean checkValue(String s) {
|
||||
return s.matches("[a-zA-Z0-9\\-_=]+");
|
||||
private static String getQType(RequestIpType type) {
|
||||
if (type == RequestIpType.v6) {
|
||||
return "AAAA";
|
||||
}
|
||||
return "A";
|
||||
}
|
||||
|
||||
private static String randomNonce() {
|
||||
return UUID.randomUUID().toString().replace("-", "");
|
||||
}
|
||||
|
||||
public static HttpRequestConfig getConfig(HttpDnsConfig config, ArrayList<String> hostList,
|
||||
RequestIpType type, SignService signService,
|
||||
AESEncryptService encryptService) {
|
||||
//拼接host
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < hostList.size(); i++) {
|
||||
if (i != 0) {
|
||||
stringBuilder.append(",");
|
||||
}
|
||||
stringBuilder.append(hostList.get(i));
|
||||
String host = "";
|
||||
if (hostList != null && hostList.size() > 0) {
|
||||
host = hostList.get(0);
|
||||
}
|
||||
String host = stringBuilder.toString();
|
||||
String path = getPath(config, host, type, null, signService, encryptService);
|
||||
HttpRequestConfig requestConfig = getHttpRequestConfig(config, path, signService.isSignMode());
|
||||
requestConfig.setUA(config.getUA());
|
||||
@@ -268,14 +125,6 @@ public class ResolveHostHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTags(HttpDnsConfig config) {
|
||||
if (TextUtils.isEmpty(config.getBizTags())) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "&tags=" + config.getBizTags();
|
||||
}
|
||||
|
||||
public static String getSid() {
|
||||
String sessionId = SessionTrackMgr.getInstance().getSessionId();
|
||||
if (sessionId == null) {
|
||||
@@ -284,4 +133,14 @@ public class ResolveHostHelper {
|
||||
return "&sid=" + sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容可观测模块透传 tags。
|
||||
*/
|
||||
public static String getTags(HttpDnsConfig config) {
|
||||
if (config == null || TextUtils.isEmpty(config.getBizTags())) {
|
||||
return "";
|
||||
}
|
||||
return "&tags=" + config.getBizTags();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
package com.alibaba.sdk.android.httpdns.resolve;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.RequestIpType;
|
||||
import com.alibaba.sdk.android.httpdns.impl.AESEncryptService;
|
||||
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
|
||||
import com.alibaba.sdk.android.httpdns.impl.SignService;
|
||||
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
|
||||
import com.alibaba.sdk.android.httpdns.request.BatchResolveHttpRequestStatusWatcher;
|
||||
import com.alibaba.sdk.android.httpdns.request.HttpRequest;
|
||||
import com.alibaba.sdk.android.httpdns.request.HttpRequestConfig;
|
||||
import com.alibaba.sdk.android.httpdns.request.HttpRequestTask;
|
||||
import com.alibaba.sdk.android.httpdns.request.HttpRequestWatcher;
|
||||
import com.alibaba.sdk.android.httpdns.request.RequestCallback;
|
||||
import com.alibaba.sdk.android.httpdns.request.RetryHttpRequest;
|
||||
import com.alibaba.sdk.android.httpdns.serverip.RegionServerScheduleService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 发起域名解析请求
|
||||
*/
|
||||
@@ -43,9 +38,18 @@ public class ResolveHostRequestHandler {
|
||||
public void requestResolveHost(final String host, final RequestIpType type,
|
||||
Map<String, String> extras, final String cacheKey,
|
||||
RequestCallback<ResolveHostResponse> callback) {
|
||||
if (type == RequestIpType.both) {
|
||||
requestResolveHostBoth(host, extras, cacheKey, callback);
|
||||
return;
|
||||
}
|
||||
requestResolveHostSingle(host, type, extras, cacheKey, callback);
|
||||
}
|
||||
|
||||
private void requestResolveHostSingle(final String host, final RequestIpType type,
|
||||
Map<String, String> extras, final String cacheKey,
|
||||
RequestCallback<ResolveHostResponse> callback) {
|
||||
HttpRequestConfig requestConfig = ResolveHostHelper.getConfig(mHttpDnsConfig, host, type,
|
||||
extras, cacheKey, mGlobalParams, mSignService, mAESEncryptService);
|
||||
//补充可观测数据
|
||||
requestConfig.setResolvingHost(host);
|
||||
requestConfig.setResolvingIpType(type);
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
@@ -54,30 +58,140 @@ public class ResolveHostRequestHandler {
|
||||
mCategoryController.getCategory().resolve(mHttpDnsConfig, requestConfig, callback);
|
||||
}
|
||||
|
||||
private void requestResolveHostBoth(final String host,
|
||||
final Map<String, String> extras,
|
||||
final String cacheKey,
|
||||
final RequestCallback<ResolveHostResponse> callback) {
|
||||
final ArrayList<ResolveHostResponse.HostItem> mergedItems = new ArrayList<>();
|
||||
final String[] serverIpHolder = new String[]{""};
|
||||
final Throwable[] errorHolder = new Throwable[]{null};
|
||||
|
||||
requestResolveHostSingle(host, RequestIpType.v4, extras, cacheKey, new RequestCallback<ResolveHostResponse>() {
|
||||
@Override
|
||||
public void onSuccess(ResolveHostResponse resolveHostResponse) {
|
||||
mergeResponse(resolveHostResponse, mergedItems, serverIpHolder);
|
||||
requestResolveHostSingle(host, RequestIpType.v6, extras, cacheKey, new RequestCallback<ResolveHostResponse>() {
|
||||
@Override
|
||||
public void onSuccess(ResolveHostResponse resolveHostResponse) {
|
||||
mergeResponse(resolveHostResponse, mergedItems, serverIpHolder);
|
||||
finishBothResolve(callback, mergedItems, serverIpHolder[0], errorHolder[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(Throwable throwable) {
|
||||
if (errorHolder[0] == null) {
|
||||
errorHolder[0] = throwable;
|
||||
}
|
||||
finishBothResolve(callback, mergedItems, serverIpHolder[0], errorHolder[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(Throwable throwable) {
|
||||
errorHolder[0] = throwable;
|
||||
requestResolveHostSingle(host, RequestIpType.v6, extras, cacheKey, new RequestCallback<ResolveHostResponse>() {
|
||||
@Override
|
||||
public void onSuccess(ResolveHostResponse resolveHostResponse) {
|
||||
mergeResponse(resolveHostResponse, mergedItems, serverIpHolder);
|
||||
finishBothResolve(callback, mergedItems, serverIpHolder[0], errorHolder[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(Throwable throwable) {
|
||||
if (errorHolder[0] == null) {
|
||||
errorHolder[0] = throwable;
|
||||
}
|
||||
finishBothResolve(callback, mergedItems, serverIpHolder[0], errorHolder[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void mergeResponse(ResolveHostResponse response,
|
||||
ArrayList<ResolveHostResponse.HostItem> mergedItems,
|
||||
String[] serverIpHolder) {
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
if (response.getItems() != null && !response.getItems().isEmpty()) {
|
||||
mergedItems.addAll(response.getItems());
|
||||
}
|
||||
if (serverIpHolder[0].isEmpty() && response.getServerIp() != null) {
|
||||
serverIpHolder[0] = response.getServerIp();
|
||||
}
|
||||
}
|
||||
|
||||
private void finishBothResolve(RequestCallback<ResolveHostResponse> callback,
|
||||
ArrayList<ResolveHostResponse.HostItem> mergedItems,
|
||||
String serverIp,
|
||||
Throwable error) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
if (!mergedItems.isEmpty()) {
|
||||
callback.onSuccess(new ResolveHostResponse(mergedItems, serverIp));
|
||||
return;
|
||||
}
|
||||
if (error != null) {
|
||||
callback.onFail(error);
|
||||
return;
|
||||
}
|
||||
callback.onSuccess(new ResolveHostResponse(new ArrayList<ResolveHostResponse.HostItem>(), serverIp));
|
||||
}
|
||||
|
||||
public void requestResolveHost(final ArrayList<String> hostList, final RequestIpType type,
|
||||
RequestCallback<ResolveHostResponse> callback) {
|
||||
HttpRequestConfig requestConfig = ResolveHostHelper.getConfig(mHttpDnsConfig, hostList,
|
||||
type, mSignService, mAESEncryptService);
|
||||
requestConfig.setResolvingIpType(type);
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
if (hostList == null || hostList.isEmpty()) {
|
||||
callback.onSuccess(new ResolveHostResponse(new ArrayList<ResolveHostResponse.HostItem>(), ""));
|
||||
return;
|
||||
}
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.d("start resolve hosts async for " + hostList.toString() + " " + type);
|
||||
}
|
||||
|
||||
HttpRequest<ResolveHostResponse> request = new HttpRequest<>(
|
||||
requestConfig, new ResolveHostResponseParser(mAESEncryptService));
|
||||
request = new HttpRequestWatcher<>(request,
|
||||
new BatchResolveHttpRequestStatusWatcher(mHttpDnsConfig.getObservableManager()));
|
||||
// 切换服务IP,更新服务IP
|
||||
request = new HttpRequestWatcher<>(request, new ShiftServerWatcher(mHttpDnsConfig,
|
||||
mScheduleService, mCategoryController));
|
||||
// 重试一次
|
||||
request = new RetryHttpRequest<>(request, 1);
|
||||
try {
|
||||
mHttpDnsConfig.getResolveWorker().execute(
|
||||
new HttpRequestTask<>(request, callback));
|
||||
} catch (Throwable e) {
|
||||
callback.onFail(e);
|
||||
final ArrayList<ResolveHostResponse.HostItem> allItems = new ArrayList<>();
|
||||
final String[] serverIpHolder = new String[]{""};
|
||||
requestResolveHostsSequentially(hostList, 0, type, allItems, serverIpHolder, callback);
|
||||
}
|
||||
|
||||
private void requestResolveHostsSequentially(final ArrayList<String> hostList,
|
||||
final int index,
|
||||
final RequestIpType type,
|
||||
final ArrayList<ResolveHostResponse.HostItem> allItems,
|
||||
final String[] serverIpHolder,
|
||||
final RequestCallback<ResolveHostResponse> callback) {
|
||||
if (index >= hostList.size()) {
|
||||
callback.onSuccess(new ResolveHostResponse(allItems, serverIpHolder[0]));
|
||||
return;
|
||||
}
|
||||
final String host = hostList.get(index);
|
||||
requestResolveHost(host, type, null, null, new RequestCallback<ResolveHostResponse>() {
|
||||
@Override
|
||||
public void onSuccess(ResolveHostResponse resolveHostResponse) {
|
||||
if (resolveHostResponse != null) {
|
||||
if (resolveHostResponse.getItems() != null) {
|
||||
allItems.addAll(resolveHostResponse.getItems());
|
||||
}
|
||||
if (serverIpHolder[0].isEmpty()) {
|
||||
serverIpHolder[0] = resolveHostResponse.getServerIp();
|
||||
}
|
||||
}
|
||||
requestResolveHostsSequentially(hostList, index + 1, type, allItems, serverIpHolder, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(Throwable throwable) {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.w("batch resolve host fail: " + host, throwable);
|
||||
}
|
||||
requestResolveHostsSequentially(hostList, index + 1, type, allItems, serverIpHolder, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,14 +202,14 @@ public class ResolveHostRequestHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置嗅探模式的请求时间间隔
|
||||
* 设置嗅探模式请求时间间隔
|
||||
*/
|
||||
public void setSniffTimeInterval(int timeInterval) {
|
||||
mCategoryController.setSniffTimeInterval(timeInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置sdns的全局参数
|
||||
* 设置 SDNS 全局参数(当前服务端不使用,保留兼容)
|
||||
*/
|
||||
public void setSdnsGlobalParams(Map<String, String> params) {
|
||||
this.mGlobalParams.clear();
|
||||
@@ -105,10 +219,9 @@ public class ResolveHostRequestHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除sdns的全局参数
|
||||
* 清除 SDNS 全局参数
|
||||
*/
|
||||
public void clearSdnsGlobalParams() {
|
||||
this.mGlobalParams.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -122,78 +123,47 @@ public class ResolveHostResponse {
|
||||
}
|
||||
|
||||
public static ResolveHostResponse fromResponse(String serverIp, String body) throws JSONException {
|
||||
|
||||
ArrayList<HostItem> items = new ArrayList<>();
|
||||
JSONObject jsonObject = new JSONObject(body);
|
||||
|
||||
if (jsonObject.has("answers")) {
|
||||
JSONArray answers = jsonObject.getJSONArray("answers");
|
||||
for (int i = 0; i < answers.length(); i++) {
|
||||
JSONObject answer = answers.getJSONObject(i);
|
||||
String hostName = null;
|
||||
int ttl = 0;
|
||||
String extra = null;
|
||||
String[] ips = null;
|
||||
String[] ipsv6 = null;
|
||||
String noIpCode = null;
|
||||
if (answer.has("dn")) {
|
||||
hostName = answer.getString("dn");
|
||||
JSONObject responseObject = new JSONObject(body);
|
||||
JSONObject data = responseObject.optJSONObject("data");
|
||||
if (data == null) {
|
||||
return new ResolveHostResponse(items, serverIp);
|
||||
}
|
||||
String hostName = data.optString("domain");
|
||||
int ttl = data.optInt("ttl", 60);
|
||||
String summary = data.optString("summary", null);
|
||||
JSONArray records = data.optJSONArray("records");
|
||||
HashMap<RequestIpType, ArrayList<String>> typedIPs = new HashMap<>();
|
||||
if (records != null) {
|
||||
for (int i = 0; i < records.length(); i++) {
|
||||
JSONObject record = records.optJSONObject(i);
|
||||
if (record == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (answer.has("v4")) {
|
||||
JSONObject ipv4 = answer.getJSONObject("v4");
|
||||
if (ipv4.has("ips")) {
|
||||
JSONArray ipArray = ipv4.getJSONArray("ips");
|
||||
if (ipArray.length() != 0) {
|
||||
ips = new String[ipArray.length()];
|
||||
for (int j = 0; j < ipArray.length(); j++) {
|
||||
ips[j] = ipArray.getString(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ipv4.has("ttl")) {
|
||||
ttl = ipv4.getInt("ttl");
|
||||
}
|
||||
if (ipv4.has("extra")) {
|
||||
extra = ipv4.getString("extra");
|
||||
}
|
||||
if (ipv4.has("no_ip_code")) {
|
||||
noIpCode = ipv4.getString("no_ip_code");
|
||||
}
|
||||
items.add(new HostItem(hostName, RequestIpType.v4, ips, ttl, extra, noIpCode));
|
||||
if (!TextUtils.isEmpty(noIpCode)) {
|
||||
HttpDnsLog.w("RESOLVE FAIL, HOST:" + hostName + ", QUERY:4, "
|
||||
+ "Msg:" + noIpCode);
|
||||
}
|
||||
String ip = record.optString("ip");
|
||||
if (TextUtils.isEmpty(ip)) {
|
||||
continue;
|
||||
}
|
||||
if (answer.has("v6")) {
|
||||
JSONObject ipv6 = answer.getJSONObject("v6");
|
||||
if (ipv6.has("ips")) {
|
||||
JSONArray ipArray = ipv6.getJSONArray("ips");
|
||||
if (ipArray.length() != 0) {
|
||||
ipsv6 = new String[ipArray.length()];
|
||||
for (int j = 0; j < ipArray.length(); j++) {
|
||||
ipsv6[j] = ipArray.getString(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ipv6.has("ttl")) {
|
||||
ttl = ipv6.getInt("ttl");
|
||||
}
|
||||
if (ipv6.has("extra")) {
|
||||
extra = ipv6.getString("extra");
|
||||
}
|
||||
if (ipv6.has("no_ip_code")) {
|
||||
noIpCode = ipv6.getString("no_ip_code");
|
||||
}
|
||||
items.add(new HostItem(hostName, RequestIpType.v6, ipsv6, ttl, extra, noIpCode));
|
||||
if (!TextUtils.isEmpty(noIpCode)) {
|
||||
HttpDnsLog.w("RESOLVE FAIL, HOST:" + hostName + ", QUERY:6, "
|
||||
+ "Msg:" + noIpCode);
|
||||
}
|
||||
String recordType = record.optString("type", data.optString("qtype", "A")).toUpperCase();
|
||||
RequestIpType ipType = "AAAA".equals(recordType) ? RequestIpType.v6 : RequestIpType.v4;
|
||||
ArrayList<String> list = typedIPs.get(ipType);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
typedIPs.put(ipType, list);
|
||||
}
|
||||
list.add(ip);
|
||||
}
|
||||
}
|
||||
if (typedIPs.isEmpty()) {
|
||||
String qtype = data.optString("qtype", "A");
|
||||
RequestIpType ipType = "AAAA".equalsIgnoreCase(qtype) ? RequestIpType.v6 : RequestIpType.v4;
|
||||
items.add(new HostItem(hostName, ipType, null, ttl, summary, null));
|
||||
} else {
|
||||
for (RequestIpType type : typedIPs.keySet()) {
|
||||
ArrayList<String> ipList = typedIPs.get(type);
|
||||
String[] ips = ipList == null ? null : ipList.toArray(new String[0]);
|
||||
items.add(new HostItem(hostName, type, ips, ttl, summary, null));
|
||||
}
|
||||
|
||||
}
|
||||
return new ResolveHostResponse(items, serverIp);
|
||||
}
|
||||
|
||||
@@ -1,68 +1,27 @@
|
||||
package com.alibaba.sdk.android.httpdns.resolve;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.impl.AESEncryptService;
|
||||
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
|
||||
import com.alibaba.sdk.android.httpdns.request.ResponseParser;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class ResolveHostResponseParser implements
|
||||
ResponseParser<ResolveHostResponse> {
|
||||
|
||||
private final AESEncryptService mAESEncryptService;
|
||||
public class ResolveHostResponseParser implements ResponseParser<ResolveHostResponse> {
|
||||
|
||||
public ResolveHostResponseParser(AESEncryptService aesEncryptService) {
|
||||
mAESEncryptService = aesEncryptService;
|
||||
// 新版协议固定 HTTPS,不使用 AES 响应体解密,保留构造参数仅为兼容调用方。
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolveHostResponse parse(String serverIp, String response) throws Throwable {
|
||||
String data = "";
|
||||
JSONObject jsonResponse = new JSONObject(response);
|
||||
if (jsonResponse.has("code")) {
|
||||
String code = jsonResponse.getString("code");
|
||||
if (TextUtils.equals(code, "success")) {
|
||||
if (jsonResponse.has("data")) {
|
||||
data = jsonResponse.getString("data");
|
||||
if (!TextUtils.isEmpty(data)) {
|
||||
//解密
|
||||
AESEncryptService.EncryptionMode mode = AESEncryptService.EncryptionMode.PLAIN;
|
||||
if (jsonResponse.has("mode")) {
|
||||
String serverEncryptMode = jsonResponse.getString("mode");
|
||||
if (TextUtils.equals(serverEncryptMode, AESEncryptService.EncryptionMode.AES_GCM.getMode())) {
|
||||
mode = AESEncryptService.EncryptionMode.AES_GCM;
|
||||
} else if (TextUtils.equals(serverEncryptMode, AESEncryptService.EncryptionMode.AES_CBC.getMode())) {
|
||||
mode = AESEncryptService.EncryptionMode.AES_CBC;
|
||||
}
|
||||
}
|
||||
data = mAESEncryptService.decrypt(data, mode);
|
||||
if (TextUtils.isEmpty(data)) {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.e("response data decrypt fail");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.e("response data is empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.e("解析失败,原因为" + code);
|
||||
}
|
||||
throw new Exception(code);
|
||||
}
|
||||
}else {
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.e("response don't have code");
|
||||
JSONObject responseObject = new JSONObject(response);
|
||||
String code = responseObject.optString("code");
|
||||
if (!"SUCCESS".equalsIgnoreCase(code)) {
|
||||
String message = responseObject.optString("message");
|
||||
if (message == null) {
|
||||
message = "";
|
||||
}
|
||||
throw new Exception(code + ":" + message);
|
||||
}
|
||||
if (HttpDnsLog.isPrint()) {
|
||||
HttpDnsLog.d("request success " + data);
|
||||
}
|
||||
return ResolveHostResponse.fromResponse(serverIp, data);
|
||||
return ResolveHostResponse.fromResponse(serverIp, response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.content.Context;
|
||||
|
||||
import com.alibaba.sdk.android.httpdns.impl.HttpDnsInstanceHolder;
|
||||
import com.alibaba.sdk.android.httpdns.impl.InstanceCreator;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterOptions;
|
||||
import com.alibaba.sdk.android.httpdns.network.HttpDnsHttpAdapter;
|
||||
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
|
||||
|
||||
/**
|
||||
@@ -74,6 +76,20 @@ public class HttpDns {
|
||||
InitConfig.addConfig(accountId, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build official IP-direct + empty-SNI adapter.
|
||||
*/
|
||||
public static HttpDnsHttpAdapter buildHttpClientAdapter(HttpDnsService service) {
|
||||
return new HttpDnsHttpAdapter(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build official IP-direct + empty-SNI adapter with options.
|
||||
*/
|
||||
public static HttpDnsHttpAdapter buildHttpClientAdapter(HttpDnsService service, HttpDnsAdapterOptions options) {
|
||||
return new HttpDnsHttpAdapter(service, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用或者禁用httpdns,理论上这个是内部接口,不给外部使用的
|
||||
* 但是已经对外暴露,所以保留
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
ext {
|
||||
httpdnsDebugVersion = '2.6.7'
|
||||
httpdnsDebugVersion = '1.0.0'
|
||||
|
||||
loggerVersion = '1.2.0'
|
||||
crashDefendVersion = '0.0.6'
|
||||
utdidVersion = '2.6.0'
|
||||
ipdetectorVersion = '1.2.0'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user