阿里sdk

This commit is contained in:
Robin
2026-02-20 17:56:24 +08:00
parent 39524692e5
commit f3af234308
524 changed files with 58345 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
plugins {
id 'com.android.library'
id 'maven-publish'
}
apply from: 'version.gradle'
android {
compileSdk 33
defaultConfig {
minSdkVersion 19
targetSdkVersion 33
versionCode 1
versionName httpdnsDebugVersion
setProperty("archivesBaseName", "alicloud-android-httpdns-$versionName")
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildConfigField "String", "VERSION_NAME", "\"${httpdnsDebugVersion}\""
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile("proguard-android.txt"), 'proguard-rules.pro'
}
forTest {
initWith release
debuggable true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-for-test.pro'
}
}
flavorDimensions += "version"
productFlavors {
normal {
}
intl {
}
end2end {
}
}
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
}
}
}
lintOptions {
abortOnError false
}
}
dependencies {
//noinspection GradleDependency 高版本 jdk和androidx有一些依赖暂时不升级
testEnd2endImplementation "org.robolectric:robolectric:3.8"
//noinspection GradleDependency
testEnd2endImplementation 'junit:junit:4.12'
//noinspection GradleDependency
testEnd2endImplementation 'org.mockito:mockito-core:2.15.0'
testEnd2endImplementation 'com.squareup.okhttp3:mockwebserver:3.9.0'
implementation "com.aliyun.ams:alicloud-android-logger:${loggerVersion}"
implementation "com.aliyun.ams:alicloud-android-crashdefend:${crashDefendVersion}"
implementation "com.aliyun.ams:alicloud-android-ipdetector:${ipdetectorVersion}"
}
ext.getIntlVersion = { version ->
if (version.endsWith("-SNAPSHOT")) {
return version.replace("-SNAPSHOT", "-intl-SNAPSHOT");
} else {
return version + "-intl";
}
}
afterEvaluate {
publishing {
publications {
mavenReleaseAar(MavenPublication) {
from components.normalRelease
groupId 'com.aliyun.ams'
artifactId 'alicloud-android-httpdns'
version "$project.android.defaultConfig.versionName"
}
mavenIntlReleaseAar(MavenPublication) {
from components.intlRelease
groupId 'com.aliyun.ams'
artifactId 'alicloud-android-httpdns'
version getIntlVersion(project.android.defaultConfig.versionName)
}
}
repositories {
if (project.android.defaultConfig.versionName.endsWith("-local-SNAPSHOT")) {
// only maven local
} else if (project.android.defaultConfig.versionName.endsWith("-SNAPSHOT")) {
}
}
}
}

View File

@@ -0,0 +1,50 @@
# 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
-keeppackagenames com.alibaba.sdk.android.httpdns
-keep class com.alibaba.sdk.android.httpdns.HttpDns{*;}
-keep class com.alibaba.sdk.android.httpdns.HttpDnsService{*;}
-keep class com.alibaba.sdk.android.httpdns.SyncService{*;}
-keep class com.alibaba.sdk.android.httpdns.RequestIpType{*;}
-keep class com.alibaba.sdk.android.httpdns.net64.Net64Service{*;}
-keep class com.alibaba.sdk.android.httpdns.DegradationFilter{*;}
-keep class com.alibaba.sdk.android.httpdns.ranking.IPRankingBean{*;}
-keep class com.alibaba.sdk.android.httpdns.ILogger{*;}
-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{*;}
-keep class com.alibaba.sdk.android.httpdns.ApiForTest{*;}
-keep class com.alibaba.sdk.android.httpdns.test.** {*;}
-keep class com.alibaba.sdk.android.httpdns.resolve.ResolveHostResponse{*;}
-keep class com.alibaba.sdk.android.httpdns.utils.CommonUtil{
public <methods>;
public <fields>;
}

View File

@@ -0,0 +1,68 @@
# 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{*;}

View File

@@ -0,0 +1,77 @@
package com.alibaba.sdk.android.httpdns;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
/**
* @author zonglin.nzl
* @date 2020/10/15
*/
public interface ApiForTest {
/**
* 指定初始服务ip
* @param region
* @param ips
* @param ports
* @param ipv6s
* @param v6Ports
*/
void setInitServer(String region, String[] ips, int[] ports, String[] ipv6s, int[] v6Ports);
/**
* 指定httpdns使用的线程池
* @param scheduledExecutorService
*/
void setThread(ScheduledExecutorService scheduledExecutorService);
/**
* 指定 测试使用的socket factory
* @param speedTestSocketFactory
*/
void setSocketFactory(IPRankingTask.SpeedTestSocketFactory speedTestSocketFactory);
/**
* 指定调度接口的调用间歇,避免正常的间歇过长无法测试
* @param timeInterval
*/
void setUpdateServerTimeInterval(int timeInterval);
/**
* 指定 sniff模式的 请求间歇
* @param timeInterval
*/
void setSniffTimeInterval(int timeInterval);
/**
* 获取httpdns的线程池用于控制异常操作
*
* @return
*/
ExecutorService getWorker();
/**
* 设置兜底的调度ip
*
* @param defaultServerIps
* @param ports
*/
void setDefaultUpdateServer(String[] defaultServerIps, int[] ports);
/**
* 设置ipv6的兜底调度IP
*
* @param defaultServerIps
* @param ports
*/
void setDefaultUpdateServerIpv6(String[] defaultServerIps, int[] ports);
/**
* 设置测试用的网络detector
*
* @param networkDetector
*/
void setNetworkDetector(HttpDnsSettings.NetworkDetector networkDetector);
}

View File

@@ -0,0 +1,11 @@
package com.alibaba.sdk.android.httpdns;
/**
* 测试用的初始化接口
* @author zonglin.nzl
* @date 1/14/22
*/
public interface BeforeHttpDnsServiceInit {
void beforeInit(HttpDnsService service);
}

View File

@@ -0,0 +1,69 @@
package com.alibaba.sdk.android.httpdns;
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.utils.CommonUtil;
/**
* Httpdns实例管理
*/
public class HttpDns {
private static HttpDnsInstanceHolder sHolder = new HttpDnsInstanceHolder(new InstanceCreator());
/**
* 获取HttpDnsService对象
*
* @param applicationContext 当前APP的Context
* @param accountID HttpDns控制台分配的AccountID
* @return
*/
public synchronized static HttpDnsService getService(final Context applicationContext, final String accountID) {
return sHolder.get(applicationContext, accountID, null);
}
/**
* 获取HttpDnsService对象并启用鉴权功能
*
* @param applicationContext 当前APP的Context
* @param accountID HttpDns控制台分配的AccountID
* @param secretKey 用户鉴权私钥
* @return
*/
public synchronized static HttpDnsService getService(final Context applicationContext, final String accountID, final String secretKey) {
return sHolder.get(applicationContext, accountID, secretKey);
}
/**
* 获取HttpDnsService对象初始化时不传入任何参数靠统一接入服务获取相关参数
*
* @param applicationContext 当前APP的Context
* @return
*/
public synchronized static HttpDnsService getService(final Context applicationContext) {
return sHolder.get(applicationContext, CommonUtil.getAccountId(applicationContext), CommonUtil.getSecretKey(applicationContext));
}
/**
* 启用或者禁用httpdns理论上这个是内部接口不给外部使用的
* 但是已经对外暴露,所以保留
*
* @param enabled
* @deprecated 启用禁用应该调用实例的方法,而不是控制全部实例的方法
*/
@Deprecated
public synchronized static void switchDnsService(boolean enabled) {
// do nothing as deprecated
}
/**
* 重置httpdns用于一些模拟应用重启的场景
*/
public static void resetInstance() {
sHolder = new HttpDnsInstanceHolder(new InstanceCreator());
NetworkStateManager.getInstance().reset();
}
}

View File

@@ -0,0 +1,34 @@
package com.alibaba.sdk.android.httpdns;
import java.util.HashMap;
/**
* 增加初始化逻辑的单例
* 用于在测试用的httpdns实例中增加测试需要的初始化逻辑
* @author zonglin.nzl
* @date 1/14/22
*/
public class InitManager {
private static class Holder {
private static final InitManager instance = new InitManager();
}
public static InitManager getInstance() {
return Holder.instance;
}
private InitManager() {
}
private HashMap<String, BeforeHttpDnsServiceInit> initThings = new HashMap<>();
public void add(String accountId, BeforeHttpDnsServiceInit beforeHttpDnsServiceInit) {
initThings.put(accountId, beforeHttpDnsServiceInit);
}
public BeforeHttpDnsServiceInit getAndRemove(String accountId) {
return initThings.remove(accountId);
}
}

View File

@@ -0,0 +1,86 @@
package com.alibaba.sdk.android.httpdns.impl;
import android.content.Context;
import com.alibaba.sdk.android.httpdns.ApiForTest;
import com.alibaba.sdk.android.httpdns.BeforeHttpDnsServiceInit;
import com.alibaba.sdk.android.httpdns.HttpDnsSettings;
import com.alibaba.sdk.android.httpdns.InitManager;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
/**
* 测试时 使用的httpdns 实例
* 增加了一些用于测试的api和机制
* @author zonglin.nzl
* @date 2020/10/16
*/
public class HttpDnsServiceTestImpl extends HttpDnsServiceImpl implements ApiForTest {
public HttpDnsServiceTestImpl(Context context, String accountId, String secret) {
super(context, accountId, secret);
}
@Override
protected void beforeInit() {
super.beforeInit();
// 通过InitManager 在httpdns初始化之前 进行一些测试需要前置工作
BeforeHttpDnsServiceInit init = InitManager.getInstance().getAndRemove(mHttpDnsConfig.getAccountId());
if (init != null) {
init.beforeInit(this);
}
}
@Override
protected void initCrashDefend(Context context, HttpDnsConfig config) {
// do nothing for test
mHttpDnsConfig.crashDefend(false);
}
@Override
public void setInitServer(String region, String[] ips, int[] ports, String[] ipv6s, int[] v6Ports) {
mHttpDnsConfig.setInitServers(region, ips, ports, ipv6s, v6Ports);
}
@Override
public void setThread(ScheduledExecutorService scheduledExecutorService) {
mHttpDnsConfig.setWorker(scheduledExecutorService);
}
@Override
public void setSocketFactory(IPRankingTask.SpeedTestSocketFactory speedTestSocketFactory) {
mIpIPRankingService.setSocketFactory(speedTestSocketFactory);
}
@Override
public void setUpdateServerTimeInterval(int timeInterval) {
mScheduleService.setTimeInterval(timeInterval);
}
@Override
public void setSniffTimeInterval(int timeInterval) {
mRequestHandler.setSniffTimeInterval(timeInterval);
}
@Override
public ExecutorService getWorker() {
return mHttpDnsConfig.mWorker;
}
@Override
public void setDefaultUpdateServer(String[] defaultServerIps, int[] ports) {
mHttpDnsConfig.setDefaultUpdateServer(defaultServerIps, ports);
}
@Override
public void setDefaultUpdateServerIpv6(String[] defaultServerIps, int[] ports) {
mHttpDnsConfig.setDefaultUpdateServerIpv6(defaultServerIps, ports);
}
@Override
public void setNetworkDetector(HttpDnsSettings.NetworkDetector networkDetector) {
mHttpDnsConfig.setNetworkDetector(networkDetector);
}
}

View File

@@ -0,0 +1,17 @@
package com.alibaba.sdk.android.httpdns.impl;
import android.content.Context;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
/**
* 改为使用测试实例
* @author zonglin.nzl
* @date 2020/12/4
*/
public class InstanceCreator implements HttpDnsCreator {
@Override
public HttpDnsService create(Context context, String accountId, String secretKey) {
return new HttpDnsServiceTestImpl(context.getApplicationContext(), accountId, secretKey);
}
}

View File

@@ -0,0 +1,87 @@
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.utils.CommonUtil;
import android.content.Context;
/**
* Httpdns实例管理
*/
public class HttpDns {
private static final HttpDnsInstanceHolder HOLDER = new HttpDnsInstanceHolder(
new InstanceCreator());
/**
* 获取HttpDnsService对象
* @param accountId HttpDns控制台分配的AccountID
* @return
*/
public synchronized static HttpDnsService getService(final String accountId) {
return HOLDER.get(null, accountId, null);
}
/**
* 获取HttpDnsService对象
* 该方法已弃用,建议使用{@link HttpDns#getService(String)}方法
* @param applicationContext 当前APP的Context
* @param accountID HttpDns控制台分配的AccountID
* @return
*/
@Deprecated
public synchronized static HttpDnsService getService(final Context applicationContext,
final String accountID) {
return HOLDER.get(applicationContext, accountID, null);
}
/**
* 获取HttpDnsService对象并启用鉴权功能
* 该方法已弃用,建议使用{@link HttpDns#getService(String)}方法
* @param applicationContext 当前APP的Context
* @param accountID HttpDns控制台分配的AccountID
* @param secretKey 用户鉴权私钥
* @return
*/
@Deprecated
public synchronized static HttpDnsService getService(final Context applicationContext,
final String accountID,
final String secretKey) {
return HOLDER.get(applicationContext, accountID, secretKey);
}
/**
* 获取HttpDnsService对象初始化时不传入任何参数靠统一接入服务获取相关参数
* 该方法已弃用,建议使用{@link HttpDns#getService(String)}方法
* @param applicationContext 当前APP的Context
* @return
*/
@Deprecated
public synchronized static HttpDnsService getService(final Context applicationContext) {
return HOLDER.get(applicationContext, CommonUtil.getAccountId(applicationContext),
CommonUtil.getSecretKey(applicationContext));
}
/**
* 初始化方法,该方法主要是保存{@link InitConfig},不会真正进行初始化。真正初始化是在{@link HttpDns#getService(Context, String, String)}中
* 这么实现主要是为了兼容{@link InitConfig.Builder#buildFor(String)}方法,新客户使用该方法和旧的方法功能一致
* @param accountId HttpDns控制台分配的AccountID
* @param config {@link InitConfig}
*/
public static void init(String accountId, InitConfig config) {
InitConfig.addConfig(accountId, config);
}
/**
* 启用或者禁用httpdns理论上这个是内部接口不给外部使用的
* 但是已经对外暴露,所以保留
*
* @param enabled
* @deprecated 启用禁用应该调用实例的方法,而不是控制全部实例的方法
*/
@Deprecated
public synchronized static void switchDnsService(boolean enabled) {
// do nothing as deprecated
}
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.sdk.android.httpdns.impl;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
import android.content.Context;
public class InstanceCreator implements HttpDnsCreator {
@Override
public HttpDnsService create(Context context, String accountId, String secretKey) {
return new IntlImpl(context, accountId, secretKey);
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.sdk.android.httpdns.impl;
import android.content.Context;
public class IntlImpl extends HttpDnsServiceImpl {
public IntlImpl(Context context, String accountId, String secret) {
super(context, accountId, secret);
}
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.alibaba.sdk.android.httpdns">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@@ -0,0 +1,18 @@
package com.alibaba.sdk.android.httpdns;
/**
* 修改ttl时长的接口
* 用于用户定制ttl以控制缓存的时长
*/
public interface CacheTtlChanger {
/**
* 根据 域名 ip类型 和 服务的ttl 返回 定制的ttl
*
* @param host 域名
* @param type ip类型
* @param ttl 服务下发的ttl 单位秒
* @return 定制的ttl 单位秒
*/
int changeCacheTtl(String host, RequestIpType type, int ttl);
}

View File

@@ -0,0 +1,13 @@
package com.alibaba.sdk.android.httpdns;
/**
* 降级判断开关接口
*/
@Deprecated
public interface DegradationFilter {
/**
* 是否应该不使用httpdns
*
*/
boolean shouldDegradeHttpDNS(String hostName);
}

View File

@@ -0,0 +1,150 @@
package com.alibaba.sdk.android.httpdns;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.cache.HostRecord;
import com.alibaba.sdk.android.httpdns.utils.Constants;
import java.util.Arrays;
import java.util.List;
/**
* sdns的解析结果
*/
public class HTTPDNSResult {
String host;
String[] ips;
String[] ipv6s;
String extra;
long queryTime;
int ttl;
boolean fromDB;
boolean fromLocalDns;
public HTTPDNSResult(String host) {
this.host = host;
this.ips = Constants.NO_IPS;
this.ipv6s = Constants.NO_IPS;
this.extra = "";
queryTime = 0;
ttl = 0;
fromDB = false;
fromLocalDns = false;
}
public HTTPDNSResult(String host, String[] ips, String[] ipv6s, String extra, boolean expired, boolean isFromDB) {
this.host = host;
this.ips = ips;
this.ipv6s = ipv6s;
this.extra = extra;
this.queryTime = System.currentTimeMillis();
this.ttl = 60;
this.fromDB = isFromDB;
fromLocalDns = false;
}
public HTTPDNSResult (String host, String[] ips, String[] ipv6s, String extra, boolean expired, boolean isFromDB, boolean isFromLocalDns) {
this(host, ips, ipv6s, extra, expired, isFromDB);
fromLocalDns = isFromLocalDns;
}
public static HTTPDNSResult empty(String host) {
return new HTTPDNSResult(host, new String[0], new String[0], "", false, false);
}
public void update(HostRecord record) {
if (record.getHost().equals(host)) {
if (record.getType() == RequestIpType.v4.ordinal()) {
ips = record.getIps();
} else if (record.getType() == RequestIpType.v6.ordinal()) {
ipv6s = record.getIps();
}
extra = TextUtils.isEmpty(record.getExtra()) ? "" : record.getExtra();
queryTime = record.getQueryTime();
ttl = record.getTtl();
fromDB = record.isFromDB();
}
}
public void update(List<HostRecord> records) {
String extra = null;
long queryTime = System.currentTimeMillis();
int ttl = Integer.MAX_VALUE;
boolean fromDB = false;
for (HostRecord record : records) {
if (record.getHost().equals(host)) {
if (record.getType() == RequestIpType.v4.ordinal()) {
ips = record.getIps();
} else if (record.getType() == RequestIpType.v6.ordinal()) {
ipv6s = record.getIps();
}
if (record.getExtra() != null && !record.getExtra().isEmpty()) {
extra = record.getExtra();
}
if (queryTime > record.getQueryTime()) {
queryTime = record.getQueryTime();
}
if (ttl > record.getTtl()) {
ttl = record.getTtl();
}
fromDB |= record.isFromDB();
}
}
this.extra = TextUtils.isEmpty(extra) ? "" : extra;
this.queryTime = queryTime;
this.ttl = ttl;
this.fromDB = fromDB;
}
@Override
public String toString() {
String sb = "host:"
+ host
+ ", ips:"
+ Arrays.toString(ips)
+ ", ipv6s:"
+ Arrays.toString(ipv6s)
+ ", extras:"
+ extra
+ ", expired:"
+ isExpired()
+ ", fromDB:"
+ fromDB
+ ", from Local Dns:"
+ fromLocalDns;
return sb;
}
public String getHost() {
return host;
}
public String[] getIps() {
return ips;
}
public String[] getIpv6s() {
return ipv6s;
}
public String getExtras() {
return extra;
}
public int getTtl() {
return ttl;
}
public boolean isExpired() {
return !fromLocalDns && Math.abs(System.currentTimeMillis() - queryTime) > ttl * 1000L;
}
public boolean isFromDB() {
return fromDB;
}
public boolean isFromLocalDns() {
return fromLocalDns;
}
}

View File

@@ -0,0 +1,61 @@
package com.alibaba.sdk.android.httpdns;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.cache.HostRecord;
import java.util.List;
/**
* 对解析结果进行封装,用于可观测数据的一些解析数据携带,不对外透出
*/
public class HTTPDNSResultWrapper {
private final HTTPDNSResult mHTTPDNSResult;
private String mServerIp;
public HTTPDNSResultWrapper(String host) {
mHTTPDNSResult = new HTTPDNSResult(host);
}
public HTTPDNSResult getHTTPDNSResult() {
return mHTTPDNSResult;
}
public void update(HostRecord record) {
mHTTPDNSResult.update(record);
mServerIp = record.getServerIp();
}
public void update(List<HostRecord> records) {
mHTTPDNSResult.update(records);
for (HostRecord record : records) {
if (record.getHost().equals(mHTTPDNSResult.getHost())) {
if (!TextUtils.isEmpty(record.getServerIp())) {
mServerIp = record.getServerIp();
}
}
}
}
public boolean isExpired() {
return mHTTPDNSResult.isExpired();
}
public String[] getIps() {
return mHTTPDNSResult.getIps();
}
public String[] getIpv6s() {
return mHTTPDNSResult.getIpv6s();
}
public String getServerIp() {
return mServerIp;
}
@Override
public String toString() {
return mHTTPDNSResult.toString();
}
}

View File

@@ -0,0 +1,5 @@
package com.alibaba.sdk.android.httpdns;
public interface HttpDnsCallback {
void onHttpDnsCompleted(HTTPDNSResult result);
}

View File

@@ -0,0 +1,316 @@
package com.alibaba.sdk.android.httpdns;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* HttpDns服务接口
*/
public interface HttpDnsService {
/**
* 设置预解析域名列表默认解析ipv4
*
* @param hostList 预解析的host域名列表
*/
void setPreResolveHosts(List<String> hostList);
/**
* 设置预解析域名列表和解析的ip类型
*
* @param hostList 预解析的host列表
* @param requestIpType {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
*/
void setPreResolveHosts(List<String> hostList, RequestIpType requestIpType);
/**
* 异步解析接口, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
*
* @param host 如www.aliyun.com
* @return 返回ip, 如果没得到解析结果, 则返回null
* @deprecated 该接口已废弃,后续版本可能会被删除,请使用{@link #getIPv4ForHostAsync(String)}
*/
@Deprecated
String getIpByHostAsync(String host);
/**
* 异步解析接口只查询v4地址首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
*
* @param host 要解析的host域名
* @return 返回v4地址
*/
@Deprecated
String getIPv4ForHostAsync(String host);
/**
* 异步解析接口, 获取ipv4列表
*
* @param host 要解析的host域名
* @return 返回v4地址的String 数组, 如果没得到解析结果, 则String 数组的长度为0
* @deprecated 该接口已废弃,后续版本可能会被删除,请使用{@link #getIPv4ListForHostAsync(String)}
*/
@Deprecated
String[] getIpsByHostAsync(String host);
/**
* 异步解析接口, 获取ipv4列表
*
* @param host 要解析的host域名
* @return 返回v4地址列表
*/
@Deprecated
String[] getIPv4ListForHostAsync(String host);
/**
* 异步解析接口获取单个ipv6, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
*
* @param host 要解析的host域名
* @return 返回v6地址
* @deprecated 该接口已废弃,后续版本可能会被删除,请使用{@link #getIPv6ForHostAsync(String)}
*/
@Deprecated
String getIPv6ByHostAsync(String host);
/**
* 异步解析接口获取单个ipv6, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
*
* @param host 要解析的host域名
* @return 返回v6地址
*/
@Deprecated
String getIPv6ForHostAsync(String host);
/**
* 异步解析接口获取ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
*
* @param host 要解析的host域名
* @return 返回v6地址的String 数组, 如果没得到解析结果, 则String 数组的长度为0
* @deprecated 该接口已废弃,后续版本可能会被删除,请使用{@link #getIPv6ListForHostASync(String)}
*/
@Deprecated
String[] getIPv6sByHostAsync(String host);
/**
* 异步解析接口获取ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
*
* @param host 要解析的host域名
* @return 返回v6地址的String 数组, 如果没得到解析结果, 则String 数组的长度为0
*/
@Deprecated
String[] getIPv6ListForHostASync(String host);
/**
* 异步解析接口获取ipv4ipv6列表
*
* @param host 要解析的host域名
* @return {@link HTTPDNSResult}
* @deprecated 该接口已废弃,后续版本可能会被删除,请使用{@link #getHttpDnsResultForHostAsync(String)}
*/
@Deprecated
HTTPDNSResult getAllByHostAsync(String host);
/**
* 异步解析接口获取ipv4 + ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
*
* @param host 要解析的host域名
* @return {@link HTTPDNSResult}
*/
@Deprecated
HTTPDNSResult getHttpDnsResultForHostAsync(String host);
/**
* 异步解析接口获取ipv4 + ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持 指定解析IP类型
*
* @param host 要解析的host域名
* @param type {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
* @return {@link HTTPDNSResult}
* @deprecated 该接口已废弃,后续版本可能会被删除,请使用{@link #getHttpDnsResultForHostAsync(String, RequestIpType)}
*/
@Deprecated
HTTPDNSResult getIpsByHostAsync(String host, RequestIpType type);
/**
* 异步解析接口获取ipv4 + ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持 指定解析IP类型
*
* @param host 要解析的host域名
* @param type {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
* @return {@link HTTPDNSResult}
*/
@Deprecated
HTTPDNSResult getHttpDnsResultForHostAsync(String host, RequestIpType type);
/***
* 校正App签名时间
* @param time time为epoch时间戳1970年1月1日以来的秒数
*/
void setAuthCurrentTime(long time);
/**
* 获取会话id
*
* @return sid
*/
String getSessionId();
//以下针对SDNS
/**
* 异步解析接口, 获取ipv4 + ipv6 列表,首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持配置sdns参数
*
* @param host 要解析的host域名
* @return {@link HTTPDNSResult}
* @deprecated 该接口已废弃,后续版本可能会被删除,请使用{@link #getHttpDnsResultForHostAsync(String, Map, String)}
*/
@Deprecated
HTTPDNSResult getIpsByHostAsync(String host, Map<String, String> params, String cacheKey);
/**
* 异步解析接口, 获取ipv4 + ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持配置sdns参数
*
* @param host 要解析的host域名
* @param params
* @param cacheKey
* @return {@link HTTPDNSResult}
*/
@Deprecated
HTTPDNSResult getHttpDnsResultForHostAsync(String host, Map<String, String> params,
String cacheKey);
/**
* 异步解析接口, 获取ipv4 + ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持配置sdns参数
* 支持指定解析类型
*
* @param host 要解析的host域名
* @param type {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
* @param params
* @param cacheKey
* @return {@link HTTPDNSResult}
* @deprecated 该接口已废弃,后续版本可能会被删除,请使用{@link #getHttpDnsResultForHostAsync(String, RequestIpType,
* Map, String)}
*/
@Deprecated
HTTPDNSResult getIpsByHostAsync(String host, RequestIpType type, Map<String, String> params,
String cacheKey);
/**
* 异步解析接口, 获取ipv4 + ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持配置sdns参数
* 支持指定解析类型
*
* @param host 要解析的host域名
* @param type {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
* @param params 自定义解析参数
* @param cacheKey 缓存的key
* @return {@link HTTPDNSResult}
*/
@Deprecated
HTTPDNSResult getHttpDnsResultForHostAsync(String host, RequestIpType type, Map<String,
String> params, String cacheKey);
/**
* 同步解析接口,支持指定解析类型
* 支持配置sdns参数
* 需要注意的地方:
* 1. 该方法必须在子线程中执行,如果在主线程中调用该方法,方法内部会自动切换成异步执行
* 2. 同步接口会阻塞当前子线程,阻塞时长可以通过{@link InitConfig.Builder#setTimeout(int)}设置,
* 不过阻塞时长的上限是5s如果设置的超时时长超过5s则无效
*
* @param host 要解析的host域名列表
* @param type {@link RequestIpType}
* @param params 自定义解析参数
* @param cacheKey 缓存的key
* @return {@link HTTPDNSResult}
*/
HTTPDNSResult getHttpDnsResultForHostSync(String host, RequestIpType type, Map<String,
String> params, String cacheKey);
/**
* 异步解析接口, 获取ipv4 + ipv6列表, 通过回调返回解析结果,首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持配置sdns参数
* 支持指定解析类型
*
* @param host 要解析的host域名
* @param type {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
* @param params 自定义解析参数
* @param cacheKey 缓存的key
*/
void getHttpDnsResultForHostAsync(String host, RequestIpType type, Map<String,
String> params, String cacheKey, HttpDnsCallback callback);
/**
* 异步解析接口, 获取ipv4 + ipv6列表, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持配置sdns参数
* 支持指定解析类型
*
* @param host 要解析的host域名
* @param type {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
* @param params 自定义解析参数
* @param cacheKey 缓存的key
*/
HTTPDNSResult getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type, Map<String,
String> params, String cacheKey);
//以上针对SDNS
/**
* 设置region海外节点
* 国内版默认是中国大陆节点,国际版默认是新加坡节点
*
* @param region sg(新家坡), hk(中国香港), ""(中国大陆), de(德国), us(美国)
*/
@Deprecated
void setRegion(String region);
/**
* 设置region
* 国内版默认是中国大陆节点
*
* @param region {@link Region}
*/
void setRegion(Region region);
/**
* 立即清除域名端侧内存和本地缓存。
* 后续调用异步接口,会先返回空,触发域名解析
*
* @param hosts host域名列表
*/
void cleanHostCache(ArrayList<String> hosts);
/**
* 同步解析接口,支持指定解析类型
* 需要注意的地方:
* 1. 该方法必须在子线程中执行,如果在主线程中调用该方法,方法内部会自动切换成异步执行
* 2. 同步接口会阻塞当前子线程,阻塞时长可以通过{@link InitConfig.Builder#setTimeout(int)}设置,
* 不过阻塞时长的上限是5s如果设置的超时时长超过5s则无效
*
* @param host 要解析的host域名列表
* @param type {@link RequestIpType}
* @return {@link HTTPDNSResult}
*/
HTTPDNSResult getHttpDnsResultForHostSync(String host, RequestIpType type);
/**
* 异步解析接口根据type获取ip, 通过回调返回解析结果,首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求
* 支持 指定解析IP类型
*
* @param host 要解析的host域名
* @param type {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
*/
void getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback);
/**
* 异步解析接口根据type获取ip, 首先查询缓存, 若存在则返回结果, 若不存在返回null 并且进行异步域名解析请求(不会通过回调给调用方)
* 支持 指定解析IP类型
*
* @param host 要解析的host域名
* @param type {@link RequestIpType} 网络栈类型v4,v6,both,auto(根据当前设备所连的网络自动判断网络栈)
* @return {@link HTTPDNSResult}
*/
HTTPDNSResult getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type);
}

View File

@@ -0,0 +1,47 @@
package com.alibaba.sdk.android.httpdns;
import android.content.Context;
public class HttpDnsSettings {
private static boolean sDailyReport = true;
public static void setDailyReport(boolean dailyReport) {
HttpDnsSettings.sDailyReport = dailyReport;
}
public static boolean isDailyReport() {
return sDailyReport;
}
private static NetworkChecker checker = new NetworkChecker() {
@Override
public boolean isIpv6Only() {
return false;
}
};
@Deprecated
public static void setNetworkChecker(NetworkChecker checker) {
HttpDnsSettings.checker = checker;
}
@Deprecated
public static NetworkChecker getChecker() {
return checker;
}
/**
* 需要外部注入的一些网络环境判断
*/
public interface NetworkChecker {
boolean isIpv6Only();
}
/**
* 获取网络类型的接口
*/
public interface NetworkDetector {
NetType getNetType(Context context);
}
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.sdk.android.httpdns;
/**
* 日志接口
*/
public interface ILogger {
/**
* 日志输出
*/
void log(String msg);
}

View File

@@ -0,0 +1,454 @@
package com.alibaba.sdk.android.httpdns;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
import android.text.format.DateUtils;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.alibaba.sdk.android.httpdns.exception.InitException;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingBean;
import com.alibaba.sdk.android.httpdns.utils.Constants;
/**
* 初始化配置
* 之前的初始化方式,每个配置都是单独设置的,有可能造成一些时序上的问题
* 所以增加统一初始化配置的方法,由内部固定初始化逻辑,避免时序问题
*/
public class InitConfig {
private static final Map<String, InitConfig> configs = new ConcurrentHashMap<>();
static void addConfig(String accountId, InitConfig config) {
configs.put(accountId, config);
}
public static final String NOT_SET = null;
public static InitConfig getInitConfig(String accountId) {
return configs.get(accountId);
}
public static void removeConfig(String accountId) {
if (accountId == null || accountId.isEmpty()) {
configs.clear();
} else {
configs.remove(accountId);
}
}
private final boolean mEnableExpiredIp;
private final boolean mEnableCacheIp;
private final long mExpiredThresholdMillis;
private final int mTimeout;
private final boolean mEnableHttps;
private final List<IPRankingBean> mIPRankingList;
private final String mRegion;
private final CacheTtlChanger mCacheTtlChanger;
private final List<String> mHostListWithFixedIp;
private final boolean mResolveAfterNetworkChange;
private final DegradationFilter mDegradationFilter;
private final NotUseHttpDnsFilter mNotUseHttpDnsFilter;
private final boolean mEnableCrashDefend;
private final Map<String, String> mSdnsGlobalParams;
private final boolean mEnableDegradationLocalDns;
private final boolean mEnableObservable;
private final String mBizTags;
private final String aesSecretKey;
private final String secretKey;
private final Context context;
private InitConfig(Builder builder) {
mEnableExpiredIp = builder.enableExpiredIp;
mEnableCacheIp = builder.enableCacheIp;
mExpiredThresholdMillis = builder.expiredThresholdMillis;
mTimeout = builder.timeout;
mEnableHttps = builder.enableHttps;
mIPRankingList = builder.ipRankingList;
mRegion = builder.region;
mCacheTtlChanger = builder.cacheTtlChanger;
mHostListWithFixedIp = builder.hostListWithFixedIp;
mResolveAfterNetworkChange = builder.resolveAfterNetworkChange;
mDegradationFilter = builder.degradationFilter;
mNotUseHttpDnsFilter = builder.notUseHttpDnsFilter;
mEnableCrashDefend = builder.enableCrashDefend;
mSdnsGlobalParams = builder.sdnsGlobalParams;
mEnableDegradationLocalDns = builder.enableDegradationLocalDns;
mEnableObservable = builder.enableObservable;
mBizTags = builder.bizTags;
aesSecretKey = builder.aesSecretKey;
context = builder.context;
secretKey = builder.secretKey;
}
public boolean isEnableExpiredIp() {
return mEnableExpiredIp;
}
public boolean isEnableCacheIp() {
return mEnableCacheIp;
}
public long getExpiredThresholdMillis() {
return mExpiredThresholdMillis;
}
public boolean isResolveAfterNetworkChange() {
return mResolveAfterNetworkChange;
}
public int getTimeout() {
return mTimeout;
}
public boolean isEnableHttps() {
return mEnableHttps;
}
public boolean isEnableDegradationLocalDns() {
return mEnableDegradationLocalDns;
}
public List<IPRankingBean> getIPRankingList() {
return mIPRankingList;
}
public String getRegion() {
return mRegion;
}
public CacheTtlChanger getCacheTtlChanger() {
return mCacheTtlChanger;
}
public List<String> getHostListWithFixedIp() {
return mHostListWithFixedIp;
}
@Deprecated
public DegradationFilter getDegradationFilter() {
return mDegradationFilter;
}
public NotUseHttpDnsFilter getNotUseHttpDnsFilter() {
return mNotUseHttpDnsFilter;
}
public boolean isEnableCrashDefend() {
return mEnableCrashDefend;
}
public Map<String, String> getSdnsGlobalParams() {
return mSdnsGlobalParams;
}
public boolean isEnableObservable() {
return mEnableObservable;
}
public String getBizTags() {
return mBizTags;
}
public String getAesSecretKey() {
return aesSecretKey;
}
public Context getContext() {return context;}
public String getSecretKey() {
return secretKey;
}
public static class Builder {
private boolean enableExpiredIp = Constants.DEFAULT_ENABLE_EXPIRE_IP;
private boolean enableCacheIp = Constants.DEFAULT_ENABLE_CACHE_IP;
private long expiredThresholdMillis = 0L;
private int timeout = Constants.DEFAULT_TIMEOUT;
private boolean enableDegradationLocalDns = Constants.DEFAULT_ENABLE_DEGRADATION_LOCAL_DNS;
private boolean enableHttps = Constants.DEFAULT_ENABLE_HTTPS;
private List<IPRankingBean> ipRankingList = null;
private String region = NOT_SET;
private CacheTtlChanger cacheTtlChanger = null;
private List<String> hostListWithFixedIp = null;
private boolean resolveAfterNetworkChange = true;
private DegradationFilter degradationFilter = null;
private NotUseHttpDnsFilter notUseHttpDnsFilter = null;
private boolean enableCrashDefend = false;
private boolean enableObservable = true;
private Map<String, String> sdnsGlobalParams = null;
private String bizTags = null;
private String aesSecretKey = null;
private Context context = null;
private String secretKey = null;
/**
* 设置是否允许返回超过ttl 的ip
* @param enableExpiredIp 是否允许返回超过ttl 的ip
* @return {@link Builder}
*/
public Builder setEnableExpiredIp(boolean enableExpiredIp) {
this.enableExpiredIp = enableExpiredIp;
return this;
}
/**
* 设置是否允许使用DB缓存默认不允许
* @param enableCacheIp 是否允许使用DB缓存
* @return {@link Builder}
*/
public Builder setEnableCacheIp(boolean enableCacheIp) {
this.enableCacheIp = enableCacheIp;
return this;
}
public Builder setEnableCacheIp(boolean enableCacheIp, long expiredThresholdMillis) {
this.enableCacheIp = enableCacheIp;
if (expiredThresholdMillis >= 0 && expiredThresholdMillis <= DateUtils.YEAR_IN_MILLIS) {
this.expiredThresholdMillis = expiredThresholdMillis;
}
return this;
}
/**
* 设置请求超时时间,单位ms,默认为2s
* @param timeoutMillis 超时时间单位ms
* @return {@link Builder}
*/
public Builder setTimeoutMillis(int timeoutMillis) {
timeout = timeoutMillis;
return this;
}
/**
* 设置请求超时时间,单位ms,默认为2s
* @param timeout 超时时间单位ms
* @return {@link Builder}
*/
@Deprecated
public Builder setTimeout(int timeout) {
this.timeout = timeout;
return this;
}
/**
* 设置开启/关闭降级到Local Dns在httpdns解析失败或者域名被过滤不走httpdns的时候开启降级会走local dns解析
* @param enableDegradation true, 开启 false, 关闭
* @return {@link Builder}
*/
public Builder setEnableDegradationLocalDns(boolean enableDegradation) {
enableDegradationLocalDns = enableDegradation;
return this;
}
/**
* 设置HTTPDNS域名解析请求类型(HTTP/HTTPS)若不调用该接口默认为HTTP请求
* @param enableHttps 是否使用https
* @return {@link Builder}
*/
public Builder setEnableHttps(boolean enableHttps) {
this.enableHttps = enableHttps;
return this;
}
/**
* 设置要探测的域名列表,默认只会对ipv4的地址进行ip优选
* @param ipRankingList {@link IPRankingBean}
* @return {@link Builder}
*/
public Builder setIPRankingList(List<IPRankingBean> ipRankingList) {
this.ipRankingList = ipRankingList;
return this;
}
@Deprecated
public Builder setRegion(String region) {
this.region = region;
return this;
}
public Builder setRegion(Region region) {
if (region != null) {
this.region = region.getRegion();
}
return this;
}
/**
* 配置自定义ttl的逻辑
*
* @param cacheTtlChanger 修改ttl的接口
*/
public Builder configCacheTtlChanger(CacheTtlChanger cacheTtlChanger) {
this.cacheTtlChanger = cacheTtlChanger;
return this;
}
/**
* 配置主站域名列表
*
* @param hostListWithFixedIp 主站域名列表
*/
public Builder configHostWithFixedIp(List<String> hostListWithFixedIp) {
this.hostListWithFixedIp = hostListWithFixedIp;
return this;
}
/**
* 设置网络切换时是否自动刷新所有域名解析结果,默认自动刷新
* @param enable 是否允许自动刷新域名解析结果
* @return {@link Builder}
*/
public Builder setPreResolveAfterNetworkChanged(boolean enable) {
resolveAfterNetworkChange = enable;
return this;
}
/**
* 设置降级策略, 用户可定制规则降级为原生DNS解析方式
* @param filter {@link DegradationFilter}
* @return {@link Builder}
*/
@Deprecated
public Builder setDegradationFilter(DegradationFilter filter) {
degradationFilter = filter;
return this;
}
/**
* 设置不使用HttpDns的策略, 用户可定制规则指定不走httpdns的域名
* @param filter {@link NotUseHttpDnsFilter}
* @return {@link Builder}
*/
public Builder setNotUseHttpDnsFilter(NotUseHttpDnsFilter filter) {
notUseHttpDnsFilter = filter;
return this;
}
/**
* 是否开启sdk内部的崩溃保护机制默认是关闭的
* @param enabled 开启/关闭
* @return {@link Builder}
*/
public Builder enableCrashDefend(boolean enabled) {
enableCrashDefend = enabled;
return this;
}
/**
* 设置sdns全局参数该全局参数不影响异步解析任务只用于解析接口调用时进行参数合并
* @param params sdn的全局参数
* @return {@link Builder}
*/
public Builder setSdnsGlobalParams(Map<String, String> params) {
sdnsGlobalParams = params;
return this;
}
public Builder enableObservable(boolean enabled) {
enableObservable = enabled;
return this;
}
public Builder setBizTags(List<String> tags) {
if (tags == null) {
return this;
}
if (tags.size() > 5) {
throw new InitException("The number of tags cannot be greater than 5");
}
Pattern p = Pattern.compile("[^a-zA-Z0-9-]");
Matcher matcher;
StringBuilder tmpTag = new StringBuilder();
for (int i = 0; i != tags.size(); ++i) {
String tag = tags.get(i);
if (TextUtils.isEmpty(tag)) {
continue;
}
matcher = p.matcher(tag);
if (matcher.find()) {
throw new InitException("tag can only contain a-z and A-Z and 0-9 and -");
}
if (tmpTag.indexOf(tag) != -1) {
//去重
continue;
}
tmpTag.append(tag);
if (i != tags.size() - 1) {
tmpTag.append(",");
}
}
int lastCommaIndex = tmpTag.lastIndexOf(",");
//最后一位逗号要去掉
if (lastCommaIndex == tmpTag.length() - 1) {
tmpTag.deleteCharAt(lastCommaIndex);
}
if (tmpTag.length() > 64) {
throw new InitException("The length of all tags cannot be greater than 64");
}
bizTags = tmpTag.toString();
return this;
}
/**
* 设置aes加密密钥
* @param aesSecretKey 加密密钥
* @return {@link Builder}
*/
public Builder setAesSecretKey(String aesSecretKey) {
this.aesSecretKey = aesSecretKey;
return this;
}
/**
* 设置context
* @param context 上下文
* @return {@link Builder}
*/
public Builder setContext(Context context) {
if (context instanceof Application) {
this.context = context;
} else {
if (context != null) {
this.context = context.getApplicationContext();
}
}
return this;
}
/**
* 设置加签密钥
* @param secretKey 加签密钥
* @return {@link Builder}
*/
public Builder setSecretKey(String secretKey) {
this.secretKey = secretKey;
return this;
}
public InitConfig build() {
return new InitConfig(this);
}
public InitConfig buildFor(String accountId) {
InitConfig config = new InitConfig(this);
addConfig(accountId, config);
return config;
}
}
}

View File

@@ -0,0 +1,11 @@
package com.alibaba.sdk.android.httpdns;
/**
* 网络类型
*/
public enum NetType {
none,
v4,
v6,
both
}

View File

@@ -0,0 +1,14 @@
package com.alibaba.sdk.android.httpdns;
/**
* 不使用HttpDns的配置接口
*/
public interface NotUseHttpDnsFilter {
/**
* 是否应该不使用httpdns
* @param hostName 域名
* @return true 不走httpdns解析 false 走httpdns解析
*
*/
boolean notUseHttpDns(String hostName);
}

View File

@@ -0,0 +1,20 @@
package com.alibaba.sdk.android.httpdns;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public enum Region {
DEFAULT(""),
HK(Constants.REGION_HK),
SG(Constants.REGION_SG),
DE(Constants.REGION_DE),
US(Constants.REGION_US);
private final String region;
Region(String region) {
this.region = region;
}
public String getRegion() {
return region;
}
}

View File

@@ -0,0 +1,17 @@
package com.alibaba.sdk.android.httpdns;
/**
* 请求的ip类型
*/
public enum RequestIpType {
v4,
v6,
/**
* 表示 两个都要
*/
both,
/**
* 表示根据网络情况自动判断
*/
auto
}

View File

@@ -0,0 +1,13 @@
package com.alibaba.sdk.android.httpdns;
public interface SyncService {
/**
* 同步解析接口,必须在子线程中执行,否则没有效果
*
* @deprecated 该接口已废弃,后续版本可能会删除,请使用
* {@link HttpDnsService#getHttpDnsResultForHostSync(String, RequestIpType)}
*/
@Deprecated
HTTPDNSResult getByHost(String host, RequestIpType type);
}

View File

@@ -0,0 +1,183 @@
package com.alibaba.sdk.android.httpdns.cache;
import java.util.Arrays;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
/**
* ip解析结果记录
* 注意计算hash 和 equal实现没有使用fromDB字段
*/
public class HostRecord {
private long id = -1;
private String region;
private String host;
private String[] ips;
private int type;
private int ttl;
private long queryTime;
private String extra;
private String cacheKey;
private boolean fromDB = false;
private String serverIp;
private String noIpCode;
public static HostRecord create(String region, String host, RequestIpType type, String extra,
String cacheKey, String[] ips, int ttl, String serverIp, String noIpCode) {
HostRecord record = new HostRecord();
record.region = region;
record.host = host;
record.type = type.ordinal();
record.ips = ips;
record.ttl = ttl;
record.queryTime = System.currentTimeMillis();
record.extra = extra;
record.cacheKey = cacheKey;
record.serverIp = serverIp;
record.noIpCode = noIpCode;
return record;
}
public boolean isExpired() {
return System.currentTimeMillis() > queryTime + ttl * 1000L;
}
public void setFromDB(boolean fromDB) {
this.fromDB = fromDB;
}
public boolean isFromDB() {
return fromDB;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String[] getIps() {
return ips;
}
public void setIps(String[] ips) {
this.ips = ips;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getTtl() {
return ttl;
}
public void setTtl(int ttl) {
this.ttl = ttl;
}
public long getQueryTime() {
return queryTime;
}
public void setQueryTime(long queryTime) {
this.queryTime = queryTime;
}
public String getExtra() {
return extra;
}
public void setExtra(String extra) {
this.extra = extra;
}
public String getCacheKey() {
return cacheKey;
}
public void setCacheKey(String cacheKey) {
this.cacheKey = cacheKey;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public void setServerIp(String ip) {
serverIp = ip;
}
public String getServerIp() {
return serverIp;
}
public void setNoIpCode(String noIpCode) {
this.noIpCode = noIpCode;
}
public String getNoIpCode() {
return noIpCode;
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
HostRecord that = (HostRecord)o;
return id == that.id &&
type == that.type &&
ttl == that.ttl &&
queryTime == that.queryTime &&
region.equals(that.region) &&
host.equals(that.host) &&
Arrays.equals(ips, that.ips) &&
CommonUtil.equals(extra, that.extra) &&
CommonUtil.equals(cacheKey, that.cacheKey) &&
CommonUtil.equals(noIpCode, that.noIpCode);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(
new Object[] {id, region, host, type, ttl, queryTime, extra, cacheKey, noIpCode});
result = 31 * result + Arrays.hashCode(ips);
return result;
}
@Override
public String toString() {
return "HostRecord{" +
"id=" + id +
", region='" + region + '\'' +
", host='" + host + '\'' +
", ips=" + Arrays.toString(ips) +
", type=" + type +
", ttl=" + ttl +
", queryTime=" + queryTime +
", extra='" + extra + '\'' +
", cacheKey='" + cacheKey + '\'' +
", fromDB=" + fromDB +
", noIpCode=" + noIpCode +
'}';
}
}

View File

@@ -0,0 +1,260 @@
package com.alibaba.sdk.android.httpdns.cache;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* 数据库存取操作
*/
public class RecordDBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "aliclound_httpdns_v3_";
private static final int DB_VERSION = 0x03;
static class HOST {
static final String TABLE_NAME = "host";
static final String COL_ID = "id";
static final String COL_REGION = "region";
static final String COL_HOST = "host";
static final String COL_IPS = "ips";
static final String COL_TYPE = "type";
static final String COL_TIME = "time";
static final String COL_TTL = "ttl";
static final String COL_EXTRA = "extra";
static final String COL_CACHE_KEY = "cache_key";
// 旧版本 用于存储网络标识的字段,由于合规的影响,删除了相关代码,此字段变为固定字段,目前已经没有意义
static final String COL_SP = "sp";
static final String COL_NO_IP_CODE = "no_ip_code";
static final String CREATE_HOST_TABLE_SQL = "CREATE TABLE " + TABLE_NAME + " ("
+ COL_ID + " INTEGER PRIMARY KEY,"
+ COL_REGION + " TEXT,"
+ COL_HOST + " TEXT,"
+ COL_IPS + " TEXT,"
+ COL_TYPE + " INTEGER,"
+ COL_TIME + " INTEGER,"
+ COL_TTL + " INTEGER,"
+ COL_EXTRA + " TEXT,"
+ COL_CACHE_KEY + " TEXT,"
+ COL_SP + " TEXT,"
+ COL_NO_IP_CODE + " TEXT"
+ ");";
}
private final String mAccountId;
private final Object mLock = new Object();
private SQLiteDatabase mDb;
public RecordDBHelper(Context context, String accountId) {
super(context, DB_NAME + accountId + ".db", null, DB_VERSION);
this.mAccountId = accountId;
}
private SQLiteDatabase getDB() {
if (mDb == null) {
try {
mDb = getWritableDatabase();
} catch (Exception e) {
}
}
return mDb;
}
@Override
protected void finalize() throws Throwable {
if (mDb != null) {
try {
mDb.close();
} catch (Exception ignored) {
}
}
super.finalize();
}
/**
* 从数据库获取全部数据
*
* @param region
* @return
*/
public List<HostRecord> readFromDb(String region) {
synchronized (mLock) {
ArrayList<HostRecord> hosts = new ArrayList<>();
SQLiteDatabase db = null;
Cursor cursor = null;
try {
db = getDB();
cursor = db.query(HOST.TABLE_NAME, null, HOST.COL_REGION + " = ?",
new String[] {region}, null, null, null);
if (cursor != null && cursor.getCount() > 0) {
cursor.moveToFirst();
do {
HostRecord hostRecord = new HostRecord();
hostRecord.setId(cursor.getLong(cursor.getColumnIndex(HOST.COL_ID)));
hostRecord.setRegion(
cursor.getString(cursor.getColumnIndex(HOST.COL_REGION)));
hostRecord.setHost(cursor.getString(cursor.getColumnIndex(HOST.COL_HOST)));
hostRecord.setIps(CommonUtil.parseStringArray(
cursor.getString(cursor.getColumnIndex(HOST.COL_IPS))));
hostRecord.setType(cursor.getInt(cursor.getColumnIndex(HOST.COL_TYPE)));
hostRecord.setTtl(cursor.getInt(cursor.getColumnIndex(HOST.COL_TTL)));
hostRecord.setQueryTime(
cursor.getLong(cursor.getColumnIndex(HOST.COL_TIME)));
hostRecord.setExtra(
cursor.getString(cursor.getColumnIndex(HOST.COL_EXTRA)));
hostRecord.setCacheKey(
cursor.getString(cursor.getColumnIndex(HOST.COL_CACHE_KEY)));
hostRecord.setFromDB(true);
hostRecord.setNoIpCode(cursor.getString(cursor.getColumnIndex(HOST.COL_NO_IP_CODE)));
hosts.add(hostRecord);
} while (cursor.moveToNext());
}
} catch (Exception e) {
HttpDnsLog.w("read from db fail " + mAccountId, e);
} finally {
try {
if (cursor != null) {
cursor.close();
}
} catch (Exception ignored) {
}
}
return hosts;
}
}
/**
* 从数据库删除数据
*/
public void delete(List<HostRecord> records) {
if (records == null || records.isEmpty()) {
return;
}
synchronized (mLock) {
SQLiteDatabase db = null;
try {
db = getDB();
db.beginTransaction();
for (HostRecord record : records) {
db.delete(HOST.TABLE_NAME, HOST.COL_ID + " = ? ",
new String[] {String.valueOf(record.getId())});
}
db.setTransactionSuccessful();
} catch (Exception e) {
HttpDnsLog.w("delete record fail " + mAccountId, e);
} finally {
if (db != null) {
try {
db.endTransaction();
} catch (Exception ignored) {
}
}
}
}
}
/**
* 更新数据
*/
public void insertOrUpdate(List<HostRecord> records) {
synchronized (mLock) {
SQLiteDatabase db = null;
try {
db = getDB();
db.beginTransaction();
for (HostRecord record : records) {
ContentValues cv = new ContentValues();
cv.put(HOST.COL_REGION, record.getRegion());
cv.put(HOST.COL_HOST, record.getHost());
cv.put(HOST.COL_IPS, CommonUtil.translateStringArray(record.getIps()));
cv.put(HOST.COL_CACHE_KEY, record.getCacheKey());
cv.put(HOST.COL_EXTRA, record.getExtra());
cv.put(HOST.COL_TIME, record.getQueryTime());
cv.put(HOST.COL_TYPE, record.getType());
cv.put(HOST.COL_TTL, record.getTtl());
cv.put(HOST.COL_NO_IP_CODE, record.getNoIpCode());
if (record.getId() != -1) {
db.update(HOST.TABLE_NAME, cv, HOST.COL_ID + " = ?",
new String[] {String.valueOf(record.getId())});
} else {
long id = db.insert(HOST.TABLE_NAME, null, cv);
record.setId(id);
}
}
db.setTransactionSuccessful();
} catch (Exception e) {
HttpDnsLog.w("insertOrUpdate record fail " + mAccountId, e);
} finally {
if (db != null) {
try {
db.endTransaction();
} catch (Exception ignored) {
}
}
}
}
}
@Override
public void onCreate(SQLiteDatabase db) {
try {
db.execSQL(HOST.CREATE_HOST_TABLE_SQL);
} catch (Exception e) {
HttpDnsLog.w("create db fail " + mAccountId, e);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
try {
db.beginTransaction();
db.execSQL("DROP TABLE IF EXISTS " + HOST.TABLE_NAME + ";");
db.setTransactionSuccessful();
db.endTransaction();
onCreate(db);
} catch (Exception e) {
HttpDnsLog.w("upgrade db fail " + mAccountId, e);
}
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
try {
db.beginTransaction();
db.execSQL("DROP TABLE IF EXISTS " + HOST.TABLE_NAME + ";");
db.setTransactionSuccessful();
db.endTransaction();
onCreate(db);
} catch (Exception e) {
HttpDnsLog.w("downgrade db fail " + mAccountId, e);
}
}
}
}

View File

@@ -0,0 +1,65 @@
package com.alibaba.sdk.android.httpdns.config;
import java.util.concurrent.atomic.AtomicBoolean;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.utils.Constants;
import android.content.Context;
import android.content.SharedPreferences;
/**
* 辅助配置的缓存写入和读取
*/
public class ConfigCacheHelper {
private final AtomicBoolean mSaving = new AtomicBoolean(false);
public void restoreFromCache(Context context, HttpDnsConfig config) {
SharedPreferences sp = context.getSharedPreferences(
Constants.CONFIG_CACHE_PREFIX + config.getAccountId(), Context.MODE_PRIVATE);
SpCacheItem[] items = config.getCacheItem();
for (SpCacheItem item : items) {
item.restoreFromCache(sp);
}
}
public void saveConfigToCache(Context context, HttpDnsConfig config) {
if (mSaving.compareAndSet(false, true)) {
try {
config.getWorker().execute(new WriteCacheTask(context, config, this));
} catch (Exception ignored) {
mSaving.set(false);
}
}
}
static class WriteCacheTask implements Runnable {
private final Context mContext;
private final HttpDnsConfig mHttpDnsConfig;
private final ConfigCacheHelper mCacheHelper;
public WriteCacheTask(Context context, HttpDnsConfig config, ConfigCacheHelper helper) {
this.mContext = context;
this.mHttpDnsConfig = config;
this.mCacheHelper = helper;
}
@Override
public void run() {
mCacheHelper.mSaving.set(false);
SharedPreferences.Editor editor = mContext.getSharedPreferences(
Constants.CONFIG_CACHE_PREFIX + mHttpDnsConfig.getAccountId(),
Context.MODE_PRIVATE)
.edit();
SpCacheItem[] items = mHttpDnsConfig.getCacheItem();
for (SpCacheItem item : items) {
item.saveToCache(editor);
}
// 虽然提示建议使用apply但是实践证明apply是把写文件操作推迟到了一些界面切换等时机反而影响了UI线程。不如直接在子线程写文件
editor.commit();
}
}
}

View File

@@ -0,0 +1,117 @@
package com.alibaba.sdk.android.httpdns.config;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
import java.util.Arrays;
public class RegionServer {
/**
* HttpDns的服务IP
*/
private String[] mServerIps;
/**
* HttpDns的服务端口线上都是默认端口 80 或者 443
* 此处是为了测试场景指定端口
* 下标和{@link #mServerIps} 对应
* 如果为null 表示没有指定端口
*/
private int[] mPorts;
private String mRegion;
private String[] mIpv6ServerIps;
private int[] mIpv6Ports;
public RegionServer(String[] serverIps, int[] ports, String[] ipv6ServerIps, int[] ipv6Ports, String region) {
this.mServerIps = serverIps == null ? new String[0] : serverIps;
this.mPorts = ports;
this.mRegion = region;
this.mIpv6ServerIps = ipv6ServerIps == null ? new String[0] : ipv6ServerIps;
this.mIpv6Ports = ipv6Ports;
}
public String[] getServerIps() {
return mServerIps;
}
public int[] getPorts() {
return mPorts;
}
public String getRegion() {
return mRegion;
}
public String[] getIpv6ServerIps() {
return mIpv6ServerIps;
}
public int[] getIpv6Ports() {
return mIpv6Ports;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RegionServer that = (RegionServer) o;
return Arrays.equals(mServerIps, that.mServerIps) &&
Arrays.equals(mPorts, that.mPorts) &&
Arrays.equals(mIpv6ServerIps, that.mIpv6ServerIps) &&
Arrays.equals(mIpv6Ports, that.mIpv6Ports) &&
CommonUtil.equals(mRegion, that.mRegion);
}
public boolean serverEquals(RegionServer that) {
return Arrays.equals(mServerIps, that.mServerIps) &&
Arrays.equals(mPorts, that.mPorts) &&
Arrays.equals(mIpv6ServerIps, that.mIpv6ServerIps) &&
Arrays.equals(mIpv6Ports, that.mIpv6Ports) &&
CommonUtil.equals(mRegion, that.mRegion);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(new Object[]{mRegion});
result = 31 * result + Arrays.hashCode(mServerIps);
result = 31 * result + Arrays.hashCode(mPorts);
result = 31 * result + Arrays.hashCode(mIpv6ServerIps);
result = 31 * result + Arrays.hashCode(mIpv6Ports);
return result;
}
public boolean updateIpv6(String[] ips, int[] ports) {
boolean same = CommonUtil.isSameServer(this.mIpv6ServerIps, this.mIpv6Ports, ips, ports);
if (same) {
return false;
}
this.mIpv6ServerIps = ips;
this.mIpv6Ports = ports;
return true;
}
public boolean updateRegionAndIpv4(String region, String[] ips, int[] ports) {
boolean same = CommonUtil.isSameServer(this.mServerIps, this.mPorts, ips, ports);
if (same && region.equals(this.mRegion)) {
return false;
}
this.mRegion = region;
this.mServerIps = ips;
this.mPorts = ports;
return true;
}
public boolean updateAll(String region, String[] ips, int[] ports, String[] ipv6s, int[] v6ports) {
boolean same = CommonUtil.isSameServer(this.mServerIps, this.mPorts, ips, ports);
boolean v6same = CommonUtil.isSameServer(this.mIpv6ServerIps, this.mIpv6Ports, ipv6s, v6ports);
if (same && v6same && region.equals(this.mRegion)) {
return false;
}
this.mRegion = region;
this.mServerIps = ips;
this.mPorts = ports;
this.mIpv6ServerIps = ipv6s;
this.mIpv6Ports = v6ports;
return true;
}
}

View File

@@ -0,0 +1,392 @@
package com.alibaba.sdk.android.httpdns.config;
import java.util.Arrays;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
import com.alibaba.sdk.android.httpdns.utils.Constants;
import android.content.SharedPreferences;
import android.text.TextUtils;
/**
* 服务节点配置
* 维护 服务节点的一些状态
*/
public class ServerConfig extends RegionServer implements SpCacheItem {
private final HttpDnsConfig mHttpDnsConfig;
private int mLastOkServerIndex = 0;
private int mCurrentServerIndex = 0;
private long mServerIpsLastUpdatedTime = 0;
private int mLastOkServerIndexForV6 = 0;
private int mCurrentServerIndexForV6 = 0;
public ServerConfig(HttpDnsConfig config, String[] serverIps, int[] ports, String[] ipv6ServerIps, int[] ipv6Ports) {
super(serverIps, ports, ipv6ServerIps, ipv6Ports, config.getRegion());
mHttpDnsConfig = config;
}
/**
* 获取当前使用的服务IP
*/
public String getServerIp() {
final String[] serverIps = getServerIps();
if (serverIps == null || mCurrentServerIndex >= serverIps.length
|| mCurrentServerIndex < 0) {
return null;
}
return serverIps[mCurrentServerIndex];
}
/**
* 获取当前使用的服务IP ipv6
*/
public String getServerIpForV6() {
final String[] serverIps = getIpv6ServerIps();
if (serverIps == null || mCurrentServerIndexForV6 >= serverIps.length
|| mCurrentServerIndexForV6 < 0) {
return null;
}
return serverIps[mCurrentServerIndexForV6];
}
/**
* 获取当前使用的服务端口
*/
public int getPort() {
final int[] ports = getPorts();
if (ports == null || mCurrentServerIndex >= ports.length || mCurrentServerIndex < 0) {
return CommonUtil.getPort(-1, mHttpDnsConfig.getSchema());
}
return CommonUtil.getPort(ports[mCurrentServerIndex], mHttpDnsConfig.getSchema());
}
/**
* 获取当前使用的服务端口
*/
public int getPortForV6() {
final int[] ports = getIpv6Ports();
if (ports == null || mCurrentServerIndexForV6 >= ports.length
|| mCurrentServerIndexForV6 < 0) {
return CommonUtil.getPort(-1, mHttpDnsConfig.getSchema());
}
return CommonUtil.getPort(ports[mCurrentServerIndexForV6], mHttpDnsConfig.getSchema());
}
/**
* 是否应该更新服务IP
*/
public boolean shouldUpdateServerIp() {
return System.currentTimeMillis() - mServerIpsLastUpdatedTime >= 24 * 60 * 60 * 1000;
}
/**
* 设置服务IP
*
* @return false 表示 前后服务一直,没有更新
*/
public synchronized boolean setServerIps(String region, String[] serverIps, int[] ports,
String[] serverV6Ips, int[] v6Ports) {
region = CommonUtil.fixRegion(region);
if (serverIps == null || serverIps.length == 0) {
serverIps = mHttpDnsConfig.getInitServer().getServerIps();
ports = mHttpDnsConfig.getInitServer().getPorts();
}
if (serverV6Ips == null || serverV6Ips.length == 0) {
serverV6Ips = mHttpDnsConfig.getInitServer().getIpv6ServerIps();
v6Ports = mHttpDnsConfig.getInitServer().getIpv6Ports();
}
boolean changed = updateRegionAndIpv4(region, serverIps, ports);
boolean v6changed = updateIpv6(serverV6Ips, v6Ports);
if (changed) {
this.mLastOkServerIndex = 0;
this.mCurrentServerIndex = 0;
}
if (v6changed) {
this.mLastOkServerIndexForV6 = 0;
this.mCurrentServerIndexForV6 = 0;
}
if (!CommonUtil.isSameServer(serverIps, ports,
mHttpDnsConfig.getInitServer().getServerIps(),
mHttpDnsConfig.getInitServer().getPorts())
|| !CommonUtil.isSameServer(serverV6Ips, v6Ports,
mHttpDnsConfig.getInitServer().getIpv6ServerIps(),
mHttpDnsConfig.getInitServer().getIpv6Ports())) {
// 非初始化IP才认为是真正的更新了服务IP
this.mServerIpsLastUpdatedTime = System.currentTimeMillis();
// 非初始IP才有缓存的必要
mHttpDnsConfig.saveToCache();
}
return changed || v6changed;
}
public synchronized void updateServerIpv4sRank(String[] sortedIps, int[] ports) {
String[] serverIps = getServerIps();
int[] serverPorts = getPorts();
String region = getRegion();
//对比和当前的region server是否是同一批避免测速完已经被更新
if (serverIps.length != sortedIps.length) {
//ip数量不一致数据已经被更新
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("abort rank server ip count changed, current ips: " + Arrays.toString(serverIps)
+ ", sorted ips: " + Arrays.toString(sortedIps));
}
return;
}
boolean contain;
//如果排序的ip都在当前Server ip列表中认为是一批服务ipip和端口需要一起判断
for (int i = 0; i != sortedIps.length; ++i) {
contain = isContainServiceIp(serverIps, serverPorts, sortedIps[i], ports == null ? -1 : ports[i]);
if (!contain) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("abort rank server ip as changed, current ips: " + Arrays.toString(serverIps)
+ ", ports: " + Arrays.toString(serverPorts)
+ ", sorted ips: " + Arrays.toString(sortedIps)
+ ", ports: " + Arrays.toString(ports)
);
}
return;
}
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("update ranked server ips: " + Arrays.toString(sortedIps)
+ ", ports: " + Arrays.toString(ports));
}
//仅更新内存
boolean changed = updateRegionAndIpv4(region, sortedIps, ports);
if (changed) {
this.mLastOkServerIndex = 0;
this.mCurrentServerIndex = 0;
}
}
public synchronized void updateServerIpv6sRank(String[] sortedIps, int[] ports) {
//和当前ip进行对比看看是不是已经被更新了如果被更新了那此次排序结果不使用
String[] serverIps = getIpv6ServerIps();
int[] serverPorts = getIpv6Ports();
//对比和当前的region server是否是同一批避免测速完已经被更新
if (serverIps.length != sortedIps.length) {
//ip数量不一致数据已经被更新
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("abort rank server ip count changed, current ipv6s: " + Arrays.toString(serverIps)
+ ", sorted ipv6s: " + Arrays.toString(sortedIps));
}
return;
}
boolean contain;
//如果排序的ip都在当前Server ip列表中认为是一批服务ip
for (int i = 0; i != sortedIps.length; ++i) {
contain = isContainServiceIp(serverIps, serverPorts, sortedIps[i], ports == null ? -1 : ports[i]);
if (!contain) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("abort rank server ip as changed, current ipv6s: " + Arrays.toString(serverIps)
+ ", ports: " + Arrays.toString(serverPorts)
+ ", sorted ipv6s: " + Arrays.toString(sortedIps)
+ ", ports: " + Arrays.toString(ports)
);
}
return;
}
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("update ranked server ipv6s: " + Arrays.toString(sortedIps)
+ ", ports: " + Arrays.toString(ports));
}
//仅更新内存
boolean v6changed = updateIpv6(sortedIps, ports);
if (v6changed) {
mLastOkServerIndexForV6 = 0;
mCurrentServerIndexForV6 = 0;
}
}
private boolean isContainServiceIp(String[] sourceIps, int[] sourcePorts, String targetIp, int targetPort) {
if (sourceIps == null || sourceIps.length == 0) {
return false;
}
for (int i = 0; i != sourceIps.length; ++i) {
if (TextUtils.equals(sourceIps[i], targetIp)) {
if (sourcePorts == null) {
return targetPort <= 0;
}
if (i < sourcePorts.length) {
if (sourcePorts[i] == targetPort) {
return true;
}
} else {
if (targetPort <= 0) {
return true;
}
}
}
}
return false;
}
/**
* 切换域名解析服务
*
* @param ip 请求失败的服务IP
* @param port 请求失败的服务端口
* @return 是否切换回了最开始的服务。当请求切换的ip和port不是当前ip和port时说明这个切换请求是无效的不切换返回false 认为没有切换回最开始的ip
*/
public boolean shiftServer(String ip, int port) {
return shiftServerV4(ip, port);
}
private boolean shiftServerV4(String ip, int port) {
final String[] serverIps = getServerIps();
final int[] ports = getPorts();
if (serverIps == null) {
return false;
}
if (!(ip.equals(serverIps[mCurrentServerIndex]) && (ports == null
|| ports[mCurrentServerIndex] == port))) {
return false;
}
mCurrentServerIndex++;
if (mCurrentServerIndex >= serverIps.length) {
mCurrentServerIndex = 0;
}
return mCurrentServerIndex == mLastOkServerIndex;
}
public boolean shiftServerV6(String ip, int port) {
final String[] serverIps = getIpv6ServerIps();
final int[] ports = getIpv6Ports();
if (serverIps == null) {
return false;
}
if (!(ip.equals(serverIps[mCurrentServerIndexForV6]) && (ports == null
|| ports[mCurrentServerIndexForV6] == port))) {
return false;
}
mCurrentServerIndexForV6++;
if (mCurrentServerIndexForV6 >= serverIps.length) {
mCurrentServerIndexForV6 = 0;
}
return mCurrentServerIndexForV6 == mLastOkServerIndexForV6;
}
/**
* 标记当前好用的域名解析服务
*
* @return 标记成功与否
*/
public boolean markOkServer(String serverIp, int port) {
final String[] serverIps = getServerIps();
final int[] ports = getPorts();
if (serverIps == null) {
return false;
}
if (serverIps[mCurrentServerIndex].equals(serverIp) && (ports == null
|| ports[mCurrentServerIndex] == port)) {
if (mLastOkServerIndex != mCurrentServerIndex) {
mLastOkServerIndex = mCurrentServerIndex;
mHttpDnsConfig.saveToCache();
}
return true;
}
return false;
}
/**
* 标记当前好用的域名解析服务
*
* @return 标记成功与否
*/
public boolean markOkServerV6(String serverIp, int port) {
final String[] serverIps = getIpv6ServerIps();
final int[] ports = getIpv6Ports();
if (serverIps == null) {
return false;
}
if (serverIps[mCurrentServerIndexForV6].equals(serverIp) && (ports == null
|| ports[mCurrentServerIndexForV6] == port)) {
if (mLastOkServerIndexForV6 != mCurrentServerIndexForV6) {
mLastOkServerIndexForV6 = mCurrentServerIndexForV6;
mHttpDnsConfig.saveToCache();
}
return true;
}
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
if (!super.equals(o)) {return false;}
ServerConfig that = (ServerConfig)o;
return mLastOkServerIndex == that.mLastOkServerIndex &&
mCurrentServerIndex == that.mCurrentServerIndex &&
mLastOkServerIndexForV6 == that.mLastOkServerIndexForV6 &&
mCurrentServerIndexForV6 == that.mCurrentServerIndexForV6 &&
mServerIpsLastUpdatedTime == that.mServerIpsLastUpdatedTime &&
mHttpDnsConfig.equals(that.mHttpDnsConfig);
}
@Override
public int hashCode() {
return Arrays.hashCode(
new Object[] {super.hashCode(), mHttpDnsConfig, mLastOkServerIndex,
mCurrentServerIndex,
mLastOkServerIndexForV6, mCurrentServerIndexForV6, mServerIpsLastUpdatedTime});
}
@Override
public void restoreFromCache(SharedPreferences sp) {
String cachedServerRegion = sp.getString(Constants.CONFIG_CURRENT_SERVER_REGION,
getRegion());
//初始化region和缓存server region一致的情况使用缓存的服务IP。否则初始化的region优先级更高
if (CommonUtil.regionEquals(cachedServerRegion, getRegion())) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("restore service ip of " + (TextUtils.isEmpty(cachedServerRegion) ? "default" : cachedServerRegion));
}
String[] serverIps = CommonUtil.parseStringArray(sp.getString(Constants.CONFIG_KEY_SERVERS,
CommonUtil.translateStringArray(getServerIps())));
int[] ports = CommonUtil.parsePorts(
sp.getString(Constants.CONFIG_KEY_PORTS, CommonUtil.translateIntArray(getPorts())));
String[] serverV6Ips = CommonUtil.parseStringArray(
sp.getString(Constants.CONFIG_KEY_SERVERS_IPV6,
CommonUtil.translateStringArray(getIpv6ServerIps())));
int[] v6Ports = CommonUtil.parsePorts(sp.getString(Constants.CONFIG_KEY_PORTS_IPV6,
CommonUtil.translateIntArray(getIpv6Ports())));
updateAll(cachedServerRegion, serverIps, ports, serverV6Ips, v6Ports);
mServerIpsLastUpdatedTime = sp.getLong(Constants.CONFIG_SERVERS_LAST_UPDATED_TIME, 0);
}
}
@Override
public void saveToCache(SharedPreferences.Editor editor) {
editor.putString(Constants.CONFIG_KEY_SERVERS,
CommonUtil.translateStringArray(getServerIps()));
editor.putString(Constants.CONFIG_KEY_PORTS, CommonUtil.translateIntArray(getPorts()));
editor.putInt(Constants.CONFIG_CURRENT_INDEX, mCurrentServerIndex);
editor.putInt(Constants.CONFIG_LAST_INDEX, mLastOkServerIndex);
editor.putString(Constants.CONFIG_KEY_SERVERS_IPV6,
CommonUtil.translateStringArray(getIpv6ServerIps()));
editor.putString(Constants.CONFIG_KEY_PORTS_IPV6,
CommonUtil.translateIntArray(getIpv6Ports()));
editor.putInt(Constants.CONFIG_CURRENT_INDEX_IPV6, mCurrentServerIndexForV6);
editor.putInt(Constants.CONFIG_LAST_INDEX_IPV6, mLastOkServerIndexForV6);
editor.putLong(Constants.CONFIG_SERVERS_LAST_UPDATED_TIME, mServerIpsLastUpdatedTime);
editor.putString(Constants.CONFIG_CURRENT_SERVER_REGION, getRegion());
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.sdk.android.httpdns.config;
import android.content.SharedPreferences;
public interface SpCacheItem {
void restoreFromCache(SharedPreferences sp);
void saveToCache(SharedPreferences.Editor editor);
}

View File

@@ -0,0 +1,32 @@
package com.alibaba.sdk.android.httpdns.config.region;
import com.alibaba.sdk.android.httpdns.config.RegionServer;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public class AmericaRegionServer {
private static final String [] SERVER_IPS = new String [] {
"47.246.131.175",
"47.246.131.141"
};
private static final int[] PORTS = null;
private static final String[] IPV6_SERVER_IPS = new String [] {
"2404:2280:4000::2bb",
"2404:2280:4000::23e"
};
private static final int[] IPV6_PORTS = null;
private static final String[] UPDATE_SERVER = new String[] {
"resolvers-us.httpdns.aliyuncs.com"
};
private static final String[] IPV6_UPDATE_SERVER = new String[] {
"resolvers-us.httpdns.aliyuncs.com"
};
public static RegionServer getInitServer() {
return new RegionServer(SERVER_IPS, PORTS, IPV6_SERVER_IPS, IPV6_PORTS, Constants.REGION_US);
}
public static RegionServer getUpdateServer() {
return new RegionServer(UPDATE_SERVER, Constants.NO_PORTS, IPV6_UPDATE_SERVER, Constants.NO_PORTS, Constants.REGION_US);
}
}

View File

@@ -0,0 +1,38 @@
package com.alibaba.sdk.android.httpdns.config.region;
import com.alibaba.sdk.android.httpdns.config.RegionServer;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public final class DefaultRegionServer {
private static final String [] SERVER_IPS = new String [] {
"203.107.1.1",
"203.107.1.97",
"203.107.1.100",
"203.119.238.240",
"106.11.25.239",
"59.82.99.47"
};
private static final int[] PORTS = null;
private static final String[] IPV6_SERVER_IPS = new String [] {
"2401:b180:7001::31d",
"2408:4003:1f40::30a",
"2401:b180:2000:20::10",
"2401:b180:2000:30::1c"
};
private static final int[] IPV6_PORTS = null;
private static final String[] UPDATE_SERVER = new String[] {
"resolvers-cn.httpdns.aliyuncs.com"
};
private static final String[] IPV6_UPDATE_SERVER = new String[] {
"resolvers-cn.httpdns.aliyuncs.com"
};
public static RegionServer getInitServer() {
return new RegionServer(SERVER_IPS, PORTS, IPV6_SERVER_IPS, IPV6_PORTS, Constants.REGION_DEFAULT);
}
public static RegionServer getUpdateServer() {
return new RegionServer(UPDATE_SERVER, Constants.NO_PORTS, IPV6_UPDATE_SERVER, Constants.NO_PORTS, Constants.REGION_DEFAULT);
}
}

View File

@@ -0,0 +1,33 @@
package com.alibaba.sdk.android.httpdns.config.region;
import com.alibaba.sdk.android.httpdns.config.RegionServer;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public class GermanyRegionServer {
private static final String [] SERVER_IPS = new String [] {
"47.89.80.182",
"47.246.146.77"
};
private static final int[] PORTS = null;
private static final String[] IPV6_SERVER_IPS = new String [] {
"2404:2280:3000::176",
"2404:2280:3000::188"
};
private static final int[] IPV6_PORTS = null;
private static final String[] UPDATE_SERVER = new String[] {
"resolvers-de.httpdns.aliyuncs.com"
};
private static final String[] IPV6_UPDATE_SERVER = new String[] {
"resolvers-de.httpdns.aliyuncs.com"
};
public static RegionServer getInitServer() {
return new RegionServer(SERVER_IPS, PORTS, IPV6_SERVER_IPS, IPV6_PORTS, Constants.REGION_DE);
}
public static RegionServer getUpdateServer() {
return new RegionServer(UPDATE_SERVER, Constants.NO_PORTS, IPV6_UPDATE_SERVER, Constants.NO_PORTS, Constants.REGION_DE);
}
}

View File

@@ -0,0 +1,32 @@
package com.alibaba.sdk.android.httpdns.config.region;
import com.alibaba.sdk.android.httpdns.config.RegionServer;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public class HongKongRegionServer {
private static final String[] SERVER_IPS = new String[] {
"47.56.234.194",
"47.56.119.115"
};
private static final int[] PORTS = null;
private static final String[] IPV6_SERVER_IPS = new String[] {
"240b:4000:f10::178",
"240b:4000:f10::188"
};
private static final int[] IPV6_PORTS = null;
private static final String[] UPDATE_SERVER = new String[] {
"resolvers-hk.httpdns.aliyuncs.com"
};
private static final String[] IPV6_UPDATE_SERVER = new String[] {
"resolvers-hk.httpdns.aliyuncs.com"
};
public static RegionServer getInitServer() {
return new RegionServer(SERVER_IPS, PORTS, IPV6_SERVER_IPS, IPV6_PORTS, Constants.REGION_HK);
}
public static RegionServer getUpdateServer() {
return new RegionServer(UPDATE_SERVER, Constants.NO_PORTS, IPV6_UPDATE_SERVER, Constants.NO_PORTS, Constants.REGION_HK);
}
}

View File

@@ -0,0 +1,30 @@
package com.alibaba.sdk.android.httpdns.config.region;
import com.alibaba.sdk.android.httpdns.config.RegionServer;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public class PreReleaseRegionServer {
private static final String [] SERVER_IPS = new String [] {
"106.11.190.18"
};
private static final int[] PORTS = null;
private static final String[] IPV6_SERVER_IPS = new String [] {
"resolvers-cn-pre.httpdns.aliyuncs.com"
};
private static final int[] IPV6_PORTS = null;
private static final String[] UPDATE_SERVER = new String[] {
"resolvers-cn-pre.httpdns.aliyuncs.com"
};
private static final String[] IPV6_UPDATE_SERVER = new String[] {
"resolvers-cn-pre.httpdns.aliyuncs.com"
};
public static RegionServer getInitServer() {
return new RegionServer(SERVER_IPS, PORTS, IPV6_SERVER_IPS, IPV6_PORTS, Constants.REGION_DEBUG_PRE);
}
public static RegionServer getUpdateServer() {
return new RegionServer(UPDATE_SERVER, Constants.NO_PORTS, IPV6_UPDATE_SERVER, Constants.NO_PORTS, Constants.REGION_DEBUG_PRE);
}
}

View File

@@ -0,0 +1,76 @@
package com.alibaba.sdk.android.httpdns.config.region;
import com.alibaba.sdk.android.httpdns.config.RegionServer;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public class RegionServerManager {
public static RegionServer getInitServer(String region) {
RegionServer regionServer = null;
switch (region) {
case Constants.REGION_HK:
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("use hk region");
}
regionServer = HongKongRegionServer.getInitServer();
break;
case Constants.REGION_SG:
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("use sg region");
}
regionServer = SingaporeRegionServer.getInitServer();
break;
case Constants.REGION_DE:
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("use de region");
}
regionServer = GermanyRegionServer.getInitServer();
break;
case Constants.REGION_US:
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("use us region");
}
regionServer = AmericaRegionServer.getInitServer();
break;
case Constants.REGION_DEBUG_PRE:
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("use pre region");
}
regionServer = PreReleaseRegionServer.getInitServer();
break;
default:
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("use default region");
}
regionServer = DefaultRegionServer.getInitServer();
break;
}
return regionServer;
}
public static RegionServer getUpdateServer(String region) {
RegionServer regionServer = null;
switch (region) {
case Constants.REGION_HK:
regionServer = HongKongRegionServer.getUpdateServer();
break;
case Constants.REGION_SG:
regionServer = SingaporeRegionServer.getUpdateServer();
break;
case Constants.REGION_DE:
regionServer = GermanyRegionServer.getUpdateServer();
break;
case Constants.REGION_US:
regionServer = AmericaRegionServer.getUpdateServer();
break;
case Constants.REGION_DEBUG_PRE:
regionServer = PreReleaseRegionServer.getUpdateServer();
break;
default:
regionServer = DefaultRegionServer.getUpdateServer();
}
return regionServer;
}
}

View File

@@ -0,0 +1,32 @@
package com.alibaba.sdk.android.httpdns.config.region;
import com.alibaba.sdk.android.httpdns.config.RegionServer;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public class SingaporeRegionServer {
private static final String[] SERVER_IPS = new String[] {
"161.117.200.122",
"47.74.222.190"
};
private static final int[] PORTS = null;
private static final String[] IPV6_SERVER_IPS = new String[] {
"240b:4000:f10::178",
"240b:4000:f10::188"
};
private static final int[] IPV6_PORTS = null;
private static final String[] UPDATE_SERVER = new String[] {
"resolvers-sg.httpdns.aliyuncs.com"
};
private static final String[] IPV6_UPDATE_SERVER = new String[] {
"resolvers-sg.httpdns.aliyuncs.com"
};
public static RegionServer getInitServer() {
return new RegionServer(SERVER_IPS, PORTS, IPV6_SERVER_IPS, IPV6_PORTS, Constants.REGION_SG);
}
public static RegionServer getUpdateServer() {
return new RegionServer(UPDATE_SERVER, Constants.NO_PORTS, IPV6_UPDATE_SERVER, Constants.NO_PORTS, Constants.REGION_SG);
}
}

View File

@@ -0,0 +1,21 @@
package com.alibaba.sdk.android.httpdns.exception;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
public class HttpDnsUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
// 处理所有未捕获异常
public void uncaughtException(Thread thread, Throwable ex) {
try {
HttpDnsLog.e("Catch an uncaught exception, " + thread.getName() + ", error message: "
+ ex.getMessage(), ex);
reportUncaughtError(ex);
ex.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void reportUncaughtError(Throwable ex) {
}
}

View File

@@ -0,0 +1,8 @@
package com.alibaba.sdk.android.httpdns.exception;
public class InitException extends RuntimeException {
public InitException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,163 @@
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 javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
/**
* AES 加密解密工具类
* @author renwei
* @date 2025/3/26
*/
public class AESEncryptService {
private static final int GCM_IV_LENGTH = 12;
private static final int CBC_IV_LENGTH = 16;
private static final int GCM_TAG_LENGTH = 128;
private String aesSecretKey;
/**
* 加密方法
*/
public String encrypt(String data, EncryptionMode mode) {
if (EncryptionMode.PLAIN == mode) {
return "";
}
if (TextUtils.isEmpty(aesSecretKey)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("aesSecretKey为空");
}
return "";
}
if (TextUtils.isEmpty(data)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("待加密数据为空");
}
return "";
}
// 生成IV
SecureRandom random = new SecureRandom();
byte[] iv = new byte[mode == EncryptionMode.AES_GCM ? GCM_IV_LENGTH : CBC_IV_LENGTH];
random.nextBytes(iv);
String encryptStr = "";
try {
byte[] encrypted = mode == EncryptionMode.AES_GCM ? aesGcmEncrypt(data, iv) : aesCbcEncrypt(data, iv);
// 组合IV和密文
byte[] combined = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
encryptStr = CommonUtil.encodeHexString(combined);
} catch (Exception e) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.e("加密失败, 加密内容:" + data);
}
}
return encryptStr;
}
private byte[] aesGcmEncrypt(String plainText, byte[] iv) throws Exception {
byte[] key = CommonUtil.decodeHex(aesSecretKey);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKey secretKey = new SecretKeySpec(key, "AES");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); // 认证标签长度为 128 位
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
return cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
}
private byte[] aesCbcEncrypt(String plainText, byte[] iv) throws Exception {
byte[] key = CommonUtil.decodeHex(aesSecretKey);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //
SecretKey secretKey = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv); // IV 必须为 16 字节
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
return cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
}
/**
* 解密方法
*/
public String decrypt(String encryptData, EncryptionMode mode) {
if (EncryptionMode.PLAIN == mode) {
return encryptData;
}
if (TextUtils.isEmpty(aesSecretKey)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("aesSecretKey为空");
}
return "";
}
if (TextUtils.isEmpty(encryptData)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("待解密数据为空");
}
return "";
}
String plainData = "";
try {
byte[] binaryencrypted = CommonUtil.decodeBase64(encryptData);
plainData = EncryptionMode.AES_GCM == mode ? aesGcmDecrypt(binaryencrypted): aesCbcDecrypt(binaryencrypted);
} catch (Exception e) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.e("解密失败, 待解密数据: " + encryptData);
}
}
return plainData;
}
private String aesCbcDecrypt(byte[] binaryencrypted) throws Exception {
byte[] key = CommonUtil.decodeHex(aesSecretKey);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(Arrays.copyOfRange(binaryencrypted, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decrypted = cipher.doFinal(Arrays.copyOfRange(binaryencrypted, 16, binaryencrypted.length));
return new String(decrypted, StandardCharsets.UTF_8);
}
private String aesGcmDecrypt(byte[] binaryencrypted) throws Exception {
byte[] key = CommonUtil.decodeHex(aesSecretKey);
SecretKey secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, Arrays.copyOfRange(binaryencrypted, 0, 12)); // 认证标签长度为 128 位
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
byte[] decrypted = cipher.doFinal(Arrays.copyOfRange(binaryencrypted, 12, binaryencrypted.length));
return new String(decrypted, StandardCharsets.UTF_8);
}
public Boolean isEncryptionMode(){
return !TextUtils.isEmpty(aesSecretKey);
}
public void setAesSecretKey(String aesSecretKey) {
this.aesSecretKey = aesSecretKey;
}
public enum EncryptionMode {
PLAIN("0"), AES_CBC("1"), AES_GCM("2");
private final String mode;
EncryptionMode(String mode) {
this.mode = mode;
}
public String getMode(){
return mode;
}
}
}

View File

@@ -0,0 +1,178 @@
package com.alibaba.sdk.android.httpdns.impl;
import com.alibaba.sdk.android.httpdns.HTTPDNSResult;
import com.alibaba.sdk.android.httpdns.HttpDnsCallback;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
import com.alibaba.sdk.android.httpdns.Region;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.SyncService;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.utils.Constants;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ErrorImpl implements HttpDnsService, SyncService {
@Override
public void setPreResolveHosts(List<String> hostList) {
}
@Override
public void setPreResolveHosts(List<String> hostList, RequestIpType requestIpType) {
}
@Override
public String getIpByHostAsync(String host) {
HttpDnsLog.w("init error");
return null;
}
@Override
public String getIPv4ForHostAsync(String host) {
return null;
}
@Override
public String[] getIpsByHostAsync(String host) {
HttpDnsLog.w("init error");
return new String[0];
}
@Override
public String[] getIPv4ListForHostAsync(String host) {
return new String[0];
}
@Override
public String[] getIPv6sByHostAsync(String host) {
HttpDnsLog.w("init error");
return new String[0];
}
@Override
public String[] getIPv6ListForHostASync(String host) {
return new String[0];
}
@Override
public HTTPDNSResult getAllByHostAsync(String host) {
HttpDnsLog.w("init error");
return Constants.EMPTY;
}
@Override
public HTTPDNSResult getHttpDnsResultForHostAsync(String host) {
return Constants.EMPTY;
}
@Override
public HTTPDNSResult getIpsByHostAsync(String host, RequestIpType type) {
return Constants.EMPTY;
}
@Override
public HTTPDNSResult getHttpDnsResultForHostAsync(String host, RequestIpType type) {
return Constants.EMPTY;
}
@Override
public void setAuthCurrentTime(long time) {
}
@Override
public String getSessionId() {
return null;
}
@Override
public HTTPDNSResult getIpsByHostAsync(String host, Map<String, String> params,
String cacheKey) {
HttpDnsLog.w("init error");
return Constants.EMPTY;
}
@Override
public HTTPDNSResult getHttpDnsResultForHostAsync(String host, Map<String, String> params,
String cacheKey) {
return Constants.EMPTY;
}
@Override
public HTTPDNSResult getIpsByHostAsync(String host, RequestIpType type,
Map<String, String> params, String cacheKey) {
HttpDnsLog.w("init error");
return Constants.EMPTY;
}
@Override
public HTTPDNSResult getHttpDnsResultForHostAsync(String host, RequestIpType type,
Map<String, String> params, String cacheKey) {
return Constants.EMPTY;
}
@Override
public HTTPDNSResult getHttpDnsResultForHostSync(String host, RequestIpType type, Map<String, String> params, String cacheKey) {
return Constants.EMPTY;
}
@Override
public void getHttpDnsResultForHostAsync(String host, RequestIpType type, Map<String, String> params, String cacheKey, HttpDnsCallback callback) {
}
@Override
public HTTPDNSResult getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type, Map<String, String> params, String cacheKey) {
return Constants.EMPTY;
}
@Override
public void setRegion(String region) {
}
@Override
public void setRegion(Region region) {
}
@Override
public String getIPv6ByHostAsync(String host) {
HttpDnsLog.w("init error");
return null;
}
@Override
public String getIPv6ForHostAsync(String host) {
return null;
}
@Override
public HTTPDNSResult getByHost(String host, RequestIpType type) {
HttpDnsLog.w("init error");
return Constants.EMPTY;
}
@Override
public void cleanHostCache(ArrayList<String> hosts) {
}
@Override
public HTTPDNSResult getHttpDnsResultForHostSync(String host, RequestIpType type) {
return Constants.EMPTY;
}
@Override
public void getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback) {
}
@Override
public HTTPDNSResult getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type) {
return Constants.EMPTY;
}
}

View File

@@ -0,0 +1,161 @@
package com.alibaba.sdk.android.httpdns.impl;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import com.alibaba.sdk.android.httpdns.RequestIpType;
public class HostResolveLocker {
private static class Recorder {
private final HashSet<String> mV4ResolvingHost = new HashSet<>();
private final HashSet<String> mV6ResolvingHost = new HashSet<>();
private final HashSet<String> mBothResolvingHost = new HashSet<>();
private final HashMap<String, CountDownLatch> mV4Latchs = new HashMap<>();
private final HashMap<String, CountDownLatch> mV6Latchs = new HashMap<>();
private final HashMap<String, CountDownLatch> mBothLatchs = new HashMap<>();
private final Object mLock = new Object();
public boolean beginResolve(String host, RequestIpType type) {
if (type == RequestIpType.both) {
if (mBothResolvingHost.contains(host)) {
// 正在解析
return false;
} else {
synchronized (mLock) {
if (mBothResolvingHost.contains(host)) {
// 正在解析
return false;
} else {
mBothResolvingHost.add(host);
createLatch(host, mBothLatchs);
return true;
}
}
}
} else if (type == RequestIpType.v4) {
if (mV4ResolvingHost.contains(host)) {
return false;
} else {
synchronized (mLock) {
if (mV4ResolvingHost.contains(host)) {
return false;
} else {
mV4ResolvingHost.add(host);
createLatch(host, mV4Latchs);
return true;
}
}
}
} else if (type == RequestIpType.v6) {
if (mV6ResolvingHost.contains(host)) {
return false;
} else {
synchronized (mLock) {
if (mV6ResolvingHost.contains(host)) {
return false;
} else {
mV6ResolvingHost.add(host);
createLatch(host, mV6Latchs);
return true;
}
}
}
}
return false;
}
private void createLatch(String host, HashMap<String, CountDownLatch> latchs) {
CountDownLatch countDownLatch = new CountDownLatch(1);
latchs.put(host, countDownLatch);
}
private void countDownLatch(String host, HashMap<String, CountDownLatch> latchs) {
CountDownLatch latch = latchs.get(host);
if (latch != null) {
latch.countDown();
}
}
private CountDownLatch getLatch(String host, RequestIpType type) {
switch (type) {
case v4:
return mV4Latchs.get(host);
case v6:
return mV6Latchs.get(host);
case both:
return mBothLatchs.get(host);
}
return null;
}
public void endResolve(String host, RequestIpType type) {
switch (type) {
case v4:
mV4ResolvingHost.remove(host);
countDownLatch(host, mV4Latchs);
break;
case v6:
mV6ResolvingHost.remove(host);
countDownLatch(host, mV6Latchs);
break;
case both:
mBothResolvingHost.remove(host);
countDownLatch(host, mBothLatchs);
break;
}
}
public boolean await(String host, RequestIpType type, long timeout, TimeUnit unit)
throws InterruptedException {
CountDownLatch countDownLatch = getLatch(host, type);
if (countDownLatch != null) {
return countDownLatch.await(timeout, unit);
} else {
return true;
}
}
}
private final Object mLock = new Object();
private final Recorder mDefaultRecorder = new Recorder();
private final HashMap<String, Recorder> mRecorders = new HashMap<>();
public boolean beginResolve(String host, RequestIpType type, String cacheKey) {
Recorder recorder = getRecorder(cacheKey);
return recorder.beginResolve(host, type);
}
private Recorder getRecorder(String cacheKey) {
Recorder recorder;
if (cacheKey == null || cacheKey.isEmpty()) {
recorder = mDefaultRecorder;
} else {
recorder = mRecorders.get(cacheKey);
if (recorder == null) {
synchronized (mLock) {
recorder = mRecorders.get(cacheKey);
if (recorder == null) {
recorder = new Recorder();
mRecorders.put(cacheKey, recorder);
}
}
}
}
return recorder;
}
public void endResolve(String host, RequestIpType type, String cacheKey) {
Recorder recorder = getRecorder(cacheKey);
recorder.endResolve(host, type);
}
public boolean await(String host, RequestIpType type, String cacheKey, long timeout,
TimeUnit unit) throws InterruptedException {
Recorder recorder = getRecorder(cacheKey);
return recorder.await(host, type, timeout, unit);
}
}

View File

@@ -0,0 +1,119 @@
package com.alibaba.sdk.android.httpdns.impl;
import java.util.HashMap;
import java.util.HashSet;
import com.alibaba.sdk.android.httpdns.RequestIpType;
public class HostResolveRecorder {
private static class Recorder {
private final HashSet<String> mV4ResolvingHost = new HashSet<>();
private final HashSet<String> mV6ResolvingHost = new HashSet<>();
private final HashSet<String> mBothResolvingHost = new HashSet<>();
private final Object mLock = new Object();
public boolean beginResolve(String host, RequestIpType type) {
if (type == RequestIpType.both) {
if (mBothResolvingHost.contains(host) || (mV4ResolvingHost.contains(host)
&& mV6ResolvingHost.contains(host))) {
// 正在解析
return false;
} else {
synchronized (mLock) {
if (mBothResolvingHost.contains(host) || (mV4ResolvingHost.contains(host)
&& mV6ResolvingHost.contains(host))) {
// 正在解析
return false;
} else {
mBothResolvingHost.add(host);
return true;
}
}
}
} else if (type == RequestIpType.v4) {
if (mV4ResolvingHost.contains(host) || mBothResolvingHost.contains(host)) {
return false;
} else {
synchronized (mLock) {
if (mV4ResolvingHost.contains(host) || mBothResolvingHost.contains(host)) {
return false;
} else {
mV4ResolvingHost.add(host);
return true;
}
}
}
} else if (type == RequestIpType.v6) {
if (mV6ResolvingHost.contains(host) || mBothResolvingHost.contains(host)) {
return false;
} else {
synchronized (mLock) {
if (mV6ResolvingHost.contains(host) || mBothResolvingHost.contains(host)) {
return false;
} else {
mV6ResolvingHost.add(host);
return true;
}
}
}
}
return false;
}
public void endResolve(String host, RequestIpType type) {
switch (type) {
case v4:
mV4ResolvingHost.remove(host);
break;
case v6:
mV6ResolvingHost.remove(host);
break;
case both:
mBothResolvingHost.remove(host);
break;
}
}
}
private final Object mLock = new Object();
private final Recorder mDefaultRecorder = new Recorder();
private final HashMap<String, Recorder> mRecorderHashMap = new HashMap<>();
public boolean beginResolve(String host, RequestIpType type) {
return beginResolve(host, type, null);
}
public void endResolve(String host, RequestIpType type) {
endResolve(host, type, null);
}
public boolean beginResolve(String host, RequestIpType type, String cacheKey) {
Recorder recorder = getRecorder(cacheKey);
return recorder.beginResolve(host, type);
}
private Recorder getRecorder(String cacheKey) {
Recorder recorder = null;
if (cacheKey == null || cacheKey.isEmpty()) {
recorder = mDefaultRecorder;
} else {
recorder = mRecorderHashMap.get(cacheKey);
if (recorder == null) {
synchronized (mLock) {
recorder = mRecorderHashMap.get(cacheKey);
if (recorder == null) {
recorder = new Recorder();
mRecorderHashMap.put(cacheKey, recorder);
}
}
}
}
return recorder;
}
public void endResolve(String host, RequestIpType type, String cacheKey) {
Recorder recorder = getRecorder(cacheKey);
recorder.endResolve(host, type);
}
}

View File

@@ -0,0 +1,431 @@
package com.alibaba.sdk.android.httpdns.impl;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import com.alibaba.sdk.android.httpdns.BuildConfig;
import com.alibaba.sdk.android.httpdns.HttpDnsSettings;
import com.alibaba.sdk.android.httpdns.InitConfig;
import com.alibaba.sdk.android.httpdns.config.ConfigCacheHelper;
import com.alibaba.sdk.android.httpdns.config.RegionServer;
import com.alibaba.sdk.android.httpdns.config.ServerConfig;
import com.alibaba.sdk.android.httpdns.config.region.RegionServerManager;
import com.alibaba.sdk.android.httpdns.config.SpCacheItem;
import com.alibaba.sdk.android.httpdns.observable.ObservableConfig;
import com.alibaba.sdk.android.httpdns.observable.ObservableConstants;
import com.alibaba.sdk.android.httpdns.observable.ObservableManager;
import com.alibaba.sdk.android.httpdns.request.HttpRequestConfig;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
import com.alibaba.sdk.android.httpdns.utils.Constants;
import com.alibaba.sdk.android.httpdns.utils.ThreadUtil;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
/**
* httpdns的配置
*/
public class HttpDnsConfig implements SpCacheItem {
private final Context mContext;
private boolean mEnabled = Constants.DEFAULT_SDK_ENABLE;
/**
* 初始服务节点
*/
private RegionServer mInitServer;
/**
* 兜底的调度服务IP用于应对国际版服务IP有可能不稳定的情况
*/
private RegionServer mDefaultUpdateServer;
/**
* 当前服务节点
*/
private final ServerConfig mCurrentServer;
/**
* 用户的accountId
*/
private final String mAccountId;
/**
* 当前请求使用的schema
*/
private String mSchema = Constants.DEFAULT_SCHEMA;
/**
* 当前region
*/
private String mRegion;
/**
* 超时时长
*/
private int mTimeout = Constants.DEFAULT_TIMEOUT;
/**
* 是否禁用服务,以避免崩溃
*/
private boolean mHitCrashDefend;
/**
* 是否远程禁用服务
*/
private boolean mRemoteDisabled = false;
/**
* 是否禁用probe能力
*/
private boolean mIPRankingDisabled = false;
/**
* 是否开启降级到Local Dns
*/
private boolean mEnableDegradationLocalDns = Constants.DEFAULT_ENABLE_DEGRADATION_LOCAL_DNS;
/**
* 网络探测接口
*/
private HttpDnsSettings.NetworkDetector mNetworkDetector = null;
private final ConfigCacheHelper mCacheHelper;
protected ExecutorService mWorker = ThreadUtil.createExecutorService();
protected ExecutorService mResolveWorker = ThreadUtil.createResolveExecutorService();
protected ExecutorService mDbWorker = ThreadUtil.createDBExecutorService();
private ObservableConfig mObservableConfig;
private ObservableManager mObservableManager;
private float mObservableSampleBenchMarks;
private String mBizTags;
private String mUA;
public HttpDnsConfig(Context context, String accountId, String secret) {
mContext = context;
mAccountId = accountId;
if (mContext != null) {
mUA = buildUA();
}
//region提前设置
mRegion = getInitRegion(accountId);
mInitServer = RegionServerManager.getInitServer(mRegion);
mDefaultUpdateServer = RegionServerManager.getUpdateServer(mRegion);
mCurrentServer = new ServerConfig(this, mInitServer.getServerIps(), mInitServer.getPorts(), mInitServer.getIpv6ServerIps(), mInitServer.getIpv6Ports());
mObservableConfig = new ObservableConfig();
// 先从缓存读取数据再赋值cacheHelper 避免在读取缓存过程中,触发写缓存操作
ConfigCacheHelper helper = new ConfigCacheHelper();
if (context != null) {
helper.restoreFromCache(context, this);
}
mCacheHelper = helper;
mObservableManager = new ObservableManager(this, secret);
}
public Context getContext() {
return mContext;
}
public String getAccountId() {
return mAccountId;
}
public ServerConfig getCurrentServer() {
return mCurrentServer;
}
public ObservableConfig getObservableConfig() {
return mObservableConfig;
}
public ObservableManager getObservableManager() {
return mObservableManager;
}
public float getObservableSampleBenchMarks() {
return mObservableSampleBenchMarks;
}
public void setObservableSampleBenchMarks(float benchMarks) {
if (mObservableSampleBenchMarks != benchMarks) {
mObservableSampleBenchMarks = benchMarks;
saveToCache();
}
}
public boolean isCurrentRegionMatch() {
return CommonUtil.regionEquals(mRegion, mCurrentServer.getRegion());
}
public boolean isAllInitServer() {
return mInitServer.serverEquals((RegionServer)mCurrentServer);
}
public String getRegion() {
return mRegion;
}
public boolean isEnabled() {
return mEnabled && !mHitCrashDefend && !mRemoteDisabled;
}
/**
* 是否启用httpdns
* <p>
* 注意是 永久禁用,因为缓存的原因,一旦禁用,就没有机会启用了
*/
public void setEnabled(boolean enabled) {
if (this.mEnabled != enabled) {
this.mEnabled = enabled;
saveToCache();
}
}
public int getTimeout() {
return mTimeout;
}
public void setTimeout(int timeout) {
if (this.mTimeout != timeout) {
this.mTimeout = timeout;
saveToCache();
}
}
public String getBizTags() {
return mBizTags;
}
public void setBizTags(String tags) {
mBizTags = tags;
}
public String getSchema() {
return mSchema;
}
public ExecutorService getWorker() {
return mWorker;
}
public ExecutorService getResolveWorker() {
return mResolveWorker;
}
public ExecutorService getDbWorker() {
return mDbWorker;
}
public void setWorker(ExecutorService worker) {
// 给测试使用
this.mWorker = worker;
this.mDbWorker = worker;
mResolveWorker = worker;
}
/**
* 切换https
*
* @return 配置是否变化
*/
public boolean setHTTPSRequestEnabled(boolean enabled) {
String oldSchema = mSchema;
if (enabled) {
mSchema = HttpRequestConfig.HTTPS_SCHEMA;
} else {
mSchema = HttpRequestConfig.HTTP_SCHEMA;
}
if (!mSchema.equals(oldSchema)) {
saveToCache();
}
return !mSchema.equals(oldSchema);
}
public void setEnableDegradationLocalDns(boolean enable) {
mEnableDegradationLocalDns = enable;
}
public boolean isEnableDegradationLocalDns() {
return mEnableDegradationLocalDns;
}
/**
* 设置用户切换的region
*/
public boolean setRegion(String region) {
if (!mRegion.equals(region)) {
mRegion = region;
mInitServer = RegionServerManager.getInitServer(mRegion);
mDefaultUpdateServer = RegionServerManager.getUpdateServer(mRegion);
mCurrentServer.setServerIps(mRegion, mInitServer.getServerIps(), mInitServer.getPorts(), mInitServer.getIpv6ServerIps(), mInitServer.getIpv6Ports());
return true;
}
return false;
}
/**
* 获取ipv6的服务节点
*/
public String[] getIpv6ServerIps() {
return this.mCurrentServer.getIpv6ServerIps();
}
public RegionServer getInitServer() {
return this.mInitServer;
}
public RegionServer getDefaultUpdateServer() {
return mDefaultUpdateServer;
}
public String[] getDefaultIpv6UpdateServer() {
return mDefaultUpdateServer.getIpv6ServerIps();
}
public String getUA() {
return mUA;
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
HttpDnsConfig that = (HttpDnsConfig)o;
return mEnabled == that.mEnabled &&
mTimeout == that.mTimeout &&
mHitCrashDefend == that.mHitCrashDefend &&
mRemoteDisabled == that.mRemoteDisabled &&
mIPRankingDisabled == that.mIPRankingDisabled &&
CommonUtil.equals(mContext, that.mContext) &&
CommonUtil.equals(mInitServer, that.mInitServer) &&
CommonUtil.equals(mDefaultUpdateServer, that.mDefaultUpdateServer) &&
CommonUtil.equals(mCurrentServer, that.mCurrentServer) &&
CommonUtil.equals(mAccountId, that.mAccountId) &&
CommonUtil.equals(mSchema, that.mSchema) &&
CommonUtil.equals(mRegion, that.mRegion) &&
CommonUtil.equals(mCacheHelper, that.mCacheHelper) &&
CommonUtil.equals(mWorker, that.mWorker) &&
CommonUtil.equals(mResolveWorker, that.mResolveWorker) &&
CommonUtil.equals(mDbWorker, that.mDbWorker);
}
@Override
public int hashCode() {
return Arrays.hashCode(
new Object[] {mContext, mEnabled, mInitServer, mDefaultUpdateServer, mCurrentServer,
mAccountId, mSchema, mRegion, mTimeout, mHitCrashDefend, mRemoteDisabled,
mIPRankingDisabled,
mCacheHelper, mWorker, mResolveWorker, mDbWorker});
}
/**
* 设置初始服务IP
* <p>
* 线上SDK 初始化服务IP是内置写死的。
* 本API主要用于一些测试代码使用
*
*/
public void setInitServers(String initRegion, String[] initIps, int[] initPorts,
String[] initIpv6s, int[] initV6Ports) {
this.mRegion = initRegion;
if (initIps == null) {
return;
}
String[] oldInitServerIps = this.mInitServer.getServerIps();
int[] oldInitPorts = this.mInitServer.getPorts();
String[] oldInitIpv6ServerIps = this.mInitServer.getIpv6ServerIps();
int[] oldInitIpv6Ports = this.mInitServer.getIpv6Ports();
this.mInitServer.updateAll(initRegion, initIps, initPorts, initIpv6s, initV6Ports);
if (mCurrentServer.getServerIps() == null
|| mCurrentServer.getIpv6ServerIps() == null
|| (CommonUtil.isSameServer(oldInitServerIps, oldInitPorts,
mCurrentServer.getServerIps(), mCurrentServer.getPorts())
&& CommonUtil.isSameServer(oldInitIpv6ServerIps, oldInitIpv6Ports,
mCurrentServer.getIpv6ServerIps(), mCurrentServer.getIpv6Ports()))) {
mCurrentServer.setServerIps(initRegion, initIps, initPorts, initIpv6s, initV6Ports);
}
}
/**
* 设置兜底的调度IP
* 测试代码使用
*/
public void setDefaultUpdateServer(String[] ips, int[] ports) {
this.mDefaultUpdateServer.updateRegionAndIpv4(this.mInitServer.getRegion(), ips, ports);
}
public void setDefaultUpdateServerIpv6(String[] defaultServerIps, int[] ports) {
this.mDefaultUpdateServer.updateIpv6(defaultServerIps, ports);
}
public void crashDefend(boolean crashDefend) {
this.mHitCrashDefend = crashDefend;
}
public void remoteDisable(boolean disable) {
this.mRemoteDisabled = disable;
}
public void ipRankingDisable(boolean disable) {
this.mIPRankingDisabled = disable;
}
public boolean isIPRankingDisabled() {
return mIPRankingDisabled;
}
public HttpDnsSettings.NetworkDetector getNetworkDetector() {
return mNetworkDetector;
}
public void setNetworkDetector(HttpDnsSettings.NetworkDetector networkDetector) {
this.mNetworkDetector = networkDetector;
}
// 缓存相关的 处理,暂时放这里
public void saveToCache() {
if (mCacheHelper != null && mContext != null) {
mCacheHelper.saveConfigToCache(mContext, this);
}
}
public SpCacheItem[] getCacheItem() {
return new SpCacheItem[] {this, mCurrentServer, mObservableConfig};
}
@Override
public void restoreFromCache(SharedPreferences sp) {
mEnabled = sp.getBoolean(Constants.CONFIG_ENABLE, Constants.DEFAULT_SDK_ENABLE);
mObservableSampleBenchMarks = sp.getFloat(Constants.CONFIG_OBSERVABLE_BENCH_MARKS, -1);
}
@Override
public void saveToCache(SharedPreferences.Editor editor) {
editor.putBoolean(Constants.CONFIG_ENABLE, mEnabled);
editor.putFloat(Constants.CONFIG_OBSERVABLE_BENCH_MARKS, mObservableSampleBenchMarks);
}
private String getInitRegion(String accountId) {
InitConfig config = InitConfig.getInitConfig(accountId);
if (config == null) {
return Constants.REGION_DEFAULT;
}
return CommonUtil.fixRegion(config.getRegion());
}
private String buildUA() {
if (mContext == null) {
return "";
}
String versionName = ObservableConstants.UNKNOWN;
try {
versionName = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
}
return Build.BRAND + "/" + Build.MODEL
+ ";" + "Android" + "/" + Build.VERSION.RELEASE
+ ";" + mContext.getPackageName() + "/" + versionName
+ ";" + "HTTPDNS" + "/" + BuildConfig.VERSION_NAME;
}
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.sdk.android.httpdns.impl;
import android.content.Context;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
/**
* httpdns服务创建接口
*/
public interface HttpDnsCreator {
HttpDnsService create(Context context, String accountId, String secretKey);
}

View File

@@ -0,0 +1,42 @@
package com.alibaba.sdk.android.httpdns.impl;
import android.content.Context;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import java.util.HashMap;
/**
* HttpDnsService 实例持有者
*/
public class HttpDnsInstanceHolder {
private final HttpDnsCreator mHttpDnsCreator;
private final HashMap<String, HttpDnsService> mInstances;
private final ErrorImpl mError = new ErrorImpl();
public HttpDnsInstanceHolder(HttpDnsCreator creator) {
this.mHttpDnsCreator = creator;
mInstances = new HashMap<>();
}
public HttpDnsService get(Context context, String account, String secretKey) {
if (account == null || account.equals("")) {
HttpDnsLog.e("init httpdns with emtpy account!!");
return mError;
}
HttpDnsService service = mInstances.get(account);
if (service == null) {
service = mHttpDnsCreator.create(context, account, secretKey);
mInstances.put(account, service);
} else {
if (service instanceof HttpDnsServiceImpl) {
((HttpDnsServiceImpl) service).setSecret(secretKey);
}
}
return service;
}
}

View File

@@ -0,0 +1,909 @@
package com.alibaba.sdk.android.httpdns.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.sdk.android.crashdefend.CrashDefendApi;
import com.alibaba.sdk.android.crashdefend.CrashDefendCallback;
import com.alibaba.sdk.android.httpdns.BuildConfig;
import com.alibaba.sdk.android.httpdns.HTTPDNSResult;
import com.alibaba.sdk.android.httpdns.HttpDnsCallback;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
import com.alibaba.sdk.android.httpdns.HttpDnsSettings;
import com.alibaba.sdk.android.httpdns.InitConfig;
import com.alibaba.sdk.android.httpdns.Region;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.SyncService;
import com.alibaba.sdk.android.httpdns.cache.RecordDBHelper;
import com.alibaba.sdk.android.httpdns.observable.ObservableConstants;
import com.alibaba.sdk.android.httpdns.observable.event.CleanHostCacheEvent;
import com.alibaba.sdk.android.httpdns.resolve.HostFilter;
import com.alibaba.sdk.android.httpdns.resolve.ResolveHostCache;
import com.alibaba.sdk.android.httpdns.resolve.ResolveHostCacheGroup;
import com.alibaba.sdk.android.httpdns.resolve.ResolveHostRequestHandler;
import com.alibaba.sdk.android.httpdns.resolve.ResolveHostResultRepo;
import com.alibaba.sdk.android.httpdns.resolve.ResolveHostService;
import com.alibaba.sdk.android.httpdns.resolve.BatchResolveHostService;
import com.alibaba.sdk.android.httpdns.HTTPDNSResultWrapper;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.net.HttpDnsNetworkDetector;
import com.alibaba.sdk.android.httpdns.net.NetworkStateManager;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingService;
import com.alibaba.sdk.android.httpdns.serverip.RegionServerScheduleService;
import com.alibaba.sdk.android.httpdns.serverip.RegionServerScheduleService.OnRegionServerIpUpdate;
import com.alibaba.sdk.android.httpdns.serverip.ranking.RegionServerRankingService;
import com.alibaba.sdk.android.httpdns.track.SessionTrackMgr;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
import com.alibaba.sdk.android.httpdns.utils.Constants;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
/**
* 域名解析服务 httpdns接口的实现
*/
public class HttpDnsServiceImpl implements HttpDnsService, OnRegionServerIpUpdate,
NetworkStateManager.OnNetworkChange, SyncService {
protected HttpDnsConfig mHttpDnsConfig;
private ResolveHostResultRepo mResultRepo;
protected ResolveHostRequestHandler mRequestHandler;
protected RegionServerScheduleService mScheduleService;
protected IPRankingService mIpIPRankingService;
protected RegionServerRankingService mRegionServerRankingService;
protected ResolveHostService mResolveHostService;
protected BatchResolveHostService mBatchResolveHostService;
private HostFilter mFilter;
private SignService mSignService;
private AESEncryptService mAESEncryptService;
private boolean resolveAfterNetworkChange = true;
/**
* crash defend 默认关闭
*/
private boolean mCrashDefendEnabled = false;
public static Context sContext;
public HttpDnsServiceImpl(Context context, final String accountId, String secret) {
try {
InitConfig config = InitConfig.getInitConfig(accountId);
sContext = (config != null && config.getContext() != null) ? config.getContext() : context;
secret = (config != null && config.getSecretKey() != null) ? config.getSecretKey() : secret;
mHttpDnsConfig = new HttpDnsConfig(sContext, accountId, secret);
if (sContext == null) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.e("init httpdns with null context!!");
}
mHttpDnsConfig.setEnabled(false);
return;
}
mFilter = new HostFilter();
mSignService = new SignService(secret);
mAESEncryptService = new AESEncryptService();
mIpIPRankingService = new IPRankingService(this.mHttpDnsConfig);
mRegionServerRankingService = new RegionServerRankingService(mHttpDnsConfig);
mResultRepo = new ResolveHostResultRepo(this.mHttpDnsConfig, this.mIpIPRankingService,
new RecordDBHelper(this.mHttpDnsConfig.getContext(), this.mHttpDnsConfig.getAccountId()),
new ResolveHostCacheGroup());
mScheduleService = new RegionServerScheduleService(this.mHttpDnsConfig, this);
mRequestHandler = new ResolveHostRequestHandler(mHttpDnsConfig, mScheduleService,
mSignService, mAESEncryptService);
HostResolveLocker asyncLocker = new HostResolveLocker();
mResolveHostService = new ResolveHostService(this.mHttpDnsConfig,
mIpIPRankingService,
mRequestHandler, mResultRepo, mFilter, asyncLocker);
mBatchResolveHostService = new BatchResolveHostService(this.mHttpDnsConfig, mResultRepo,
mRequestHandler,
mIpIPRankingService, mFilter, asyncLocker);
mHttpDnsConfig.setNetworkDetector(HttpDnsNetworkDetector.getInstance());
beforeInit();
setupInitConfig(accountId);
if (mCrashDefendEnabled) {
initCrashDefend(sContext, mHttpDnsConfig);
}
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.w("init fail, crash defend");
return;
}
NetworkStateManager.getInstance().init(sContext);
NetworkStateManager.getInstance().addListener(this);
tryUpdateRegionServer(sContext, accountId);
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
favorInit(sContext, accountId);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("httpdns service is init " + accountId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void setupInitConfig(final String accountId) {
InitConfig config = InitConfig.getInitConfig(accountId);
if (config != null) {
// 先设置和网络相关的内容
this.mHttpDnsConfig.setTimeout(config.getTimeout());
this.mHttpDnsConfig.setHTTPSRequestEnabled(config.isEnableHttps());
mHttpDnsConfig.setBizTags(config.getBizTags());
mHttpDnsConfig.setEnableDegradationLocalDns(config.isEnableDegradationLocalDns());
// 再设置一些可以提前,没有副作用的内容
mResolveHostService.setEnableExpiredIp(config.isEnableExpiredIp());
if (config.getIPRankingList() != null) {
mIpIPRankingService.setIPRankingList(config.getIPRankingList());
}
// 设置region 必须在 读取缓存之前。2.4.1版本开始region初始化提前到HttpDnsConfig初始化
// 设置 主站域名 需要在 读取缓存之前
this.mResultRepo.setHostListWhichIpFixed(config.getHostListWithFixedIp());
// 设置缓存控制,并读取缓存
mResultRepo.setCachedIPEnabled(config.isEnableCacheIp(), config.getExpiredThresholdMillis());
this.mResultRepo.setCacheTtlChanger(config.getCacheTtlChanger());
resolveAfterNetworkChange = config.isResolveAfterNetworkChange();
if (config.getDegradationFilter() != null) {
mFilter.setFilter(config.getDegradationFilter());
}
if (config.getNotUseHttpDnsFilter() != null) {
mFilter.setFilter(config.getNotUseHttpDnsFilter());
}
mHttpDnsConfig.getObservableManager().positiveEnableObservable(config.isEnableObservable());
mCrashDefendEnabled = config.isEnableCrashDefend();
mRequestHandler.setSdnsGlobalParams(config.getSdnsGlobalParams());
mAESEncryptService.setAesSecretKey(config.getAesSecretKey());
}
}
protected void beforeInit() {
// only for test
}
protected void favorInit(Context context, String accountId) {
// for different favor init
}
protected void initCrashDefend(Context context, final HttpDnsConfig config) {
CrashDefendApi.registerCrashDefendSdk(context, "httpdns", BuildConfig.VERSION_NAME, 2, 7,
new CrashDefendCallback() {
@Override
public void onSdkStart(int limitCount, int crashCount, int restoreCount) {
config.crashDefend(false);
}
@Override
public void onSdkStop(int limitCount, int crashCount, int restoreCount,
long nextRestoreInterval) {
config.crashDefend(true);
HttpDnsLog.w("sdk is not safe to run");
}
@Override
public void onSdkClosed(int restoreCount) {
config.crashDefend(true);
HttpDnsLog.e("sdk will not run any more");
}
});
}
public void setSecret(String secret) {
if (!mHttpDnsConfig.isEnabled()) {
return;
}
InitConfig config = InitConfig.getInitConfig(mHttpDnsConfig.getAccountId());
secret = (config != null && config.getSecretKey() != null) ? config.getSecretKey() : secret;
this.mSignService.setSecretKey(secret);
}
@Override
public void serverIpUpdated(boolean regionUpdated) {
if (!mHttpDnsConfig.isEnabled()) {
return;
}
if (regionUpdated) {
mResultRepo.clearMemoryCache();
}
mRequestHandler.resetStatus();
//服务IP更新触发服务IP测速
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
}
@Override
public void setPreResolveHosts(List<String> hostList) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return;
}
if (hostList == null || hostList.size() == 0) {
HttpDnsLog.i("setPreResolveHosts empty list");
return;
}
setPreResolveHosts(hostList, RequestIpType.v4);
}
@Override
public void setPreResolveHosts(List<String> hostList, RequestIpType requestIpType) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return;
}
if (hostList == null || hostList.size() == 0) {
HttpDnsLog.i("setPreResolveHosts empty list");
return;
}
requestIpType = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), requestIpType);
mBatchResolveHostService.batchResolveHostAsync(hostList, requestIpType);
}
@Override
public String getIpByHostAsync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return null;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return null;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return null;
}
String[] ips = getIPv4ListForHostAsync(host);
if (ips == null || ips.length == 0) {
return null;
}
return ips[0];
}
@Override
public String getIPv4ForHostAsync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return null;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return null;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return null;
}
String[] ips = getIPv4ListForHostAsync(host);
if (ips == null || ips.length == 0) {
return null;
}
return ips[0];
}
@Override
public String[] getIpsByHostAsync(final String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return new String[0];
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return new String[0];
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return new String[0];
}
return mResolveHostService.resolveHostSyncNonBlocking(host, RequestIpType.v4, null, getCurrentNetworkKey()).getIps();
}
@Override
public String[] getIPv4ListForHostAsync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return new String[0];
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return new String[0];
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return new String[0];
}
return mResolveHostService.resolveHostSyncNonBlocking(host, RequestIpType.v4, null, getCurrentNetworkKey()).getIps();
}
@Override
public String[] getIPv6sByHostAsync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return new String[0];
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return new String[0];
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return new String[0];
}
return mResolveHostService.resolveHostSyncNonBlocking(host, RequestIpType.v6, null, getCurrentNetworkKey())
.getIpv6s();
}
@Override
public String[] getIPv6ListForHostASync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return new String[0];
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return new String[0];
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return new String[0];
}
return mResolveHostService.resolveHostSyncNonBlocking(host, RequestIpType.v6, null, getCurrentNetworkKey())
.getIpv6s();
}
@Override
public HTTPDNSResult getAllByHostAsync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
return mResolveHostService.resolveHostSyncNonBlocking(host, RequestIpType.both, null, getCurrentNetworkKey());
}
@Override
public HTTPDNSResult getHttpDnsResultForHostAsync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
return mResolveHostService.resolveHostSyncNonBlocking(host, RequestIpType.both, null, getCurrentNetworkKey());
}
@Override
public HTTPDNSResult getIpsByHostAsync(String host, RequestIpType type) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
return mResolveHostService.resolveHostSyncNonBlocking(host, type, null, getCurrentNetworkKey());
}
@Override
public HTTPDNSResult getHttpDnsResultForHostAsync(String host, RequestIpType type) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
return mResolveHostService.resolveHostSyncNonBlocking(host, type, null, getCurrentNetworkKey());
}
@Override
public void setAuthCurrentTime(long time) {
if (!mHttpDnsConfig.isEnabled()) {
return;
}
mSignService.setCurrentTimestamp(time);
}
@Override
public String getSessionId() {
return SessionTrackMgr.getInstance().getSessionId();
}
@Override
public HTTPDNSResult getIpsByHostAsync(String host, Map<String, String> params,
String cacheKey) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
return mResolveHostService.resolveHostSyncNonBlocking(host, RequestIpType.v4, params, cacheKey);
}
@Override
public HTTPDNSResult getHttpDnsResultForHostAsync(String host, Map<String, String> params,
String cacheKey) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
return mResolveHostService.resolveHostSyncNonBlocking(host, RequestIpType.v4, params, cacheKey);
}
@Override
public HTTPDNSResult getIpsByHostAsync(String host, RequestIpType type,
Map<String, String> params, String cacheKey) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
return mResolveHostService.resolveHostSyncNonBlocking(host, type, params, cacheKey);
}
@Override
public HTTPDNSResult getHttpDnsResultForHostAsync(String host, RequestIpType type,
Map<String, String> params, String cacheKey) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
return mResolveHostService.resolveHostSyncNonBlocking(host, type, params, cacheKey);
}
@Override
public HTTPDNSResult getHttpDnsResultForHostSync(String host, RequestIpType type, Map<String, String> params, String cacheKey) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
if (Looper.getMainLooper() == Looper.myLooper()) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request in main thread, use async request");
}
return mResolveHostService.resolveHostSyncNonBlocking(host, type, null, getCurrentNetworkKey());
}
return mResolveHostService.resolveHostSync(host, type, params, cacheKey);
}
@Override
public void getHttpDnsResultForHostAsync(String host, RequestIpType type, Map<String, String> params, String cacheKey, HttpDnsCallback callback) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
if (callback != null) {
callback.onHttpDnsCompleted(Constants.EMPTY);
}
return;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
if (callback != null) {
callback.onHttpDnsCompleted(Constants.EMPTY);
}
return;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
if (callback != null) {
callback.onHttpDnsCompleted(Constants.EMPTY);
}
return;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
mResolveHostService.resolveHostAsync(host, type, params, cacheKey, callback);
}
@Override
public HTTPDNSResult getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type, Map<String, String> params, String cacheKey) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
return mResolveHostService.resolveHostSyncNonBlocking(host, type, params, cacheKey);
}
@Override
public void setRegion(String region) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return;
}
region = CommonUtil.fixRegion(region);
if (CommonUtil.regionEquals(this.mHttpDnsConfig.getRegion(), region)
&& !this.mHttpDnsConfig.isAllInitServer()) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("region " + region + " is same, do not update serverIps");
}
return;
}
boolean changed = mHttpDnsConfig.setRegion(region);
if (changed) {
mResultRepo.clearMemoryCache();
//region变化服务IP变成对应的预置IP触发测速
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
}
mScheduleService.updateRegionServerIps(region, Constants.UPDATE_REGION_SERVER_SCENES_REGION_CHANGE);
}
@Override
public void setRegion(Region region) {
setRegion(region == null ? "" : region.getRegion());
}
@Override
public String getIPv6ByHostAsync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return null;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return null;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return null;
}
String[] ips = getIPv6sByHostAsync(host);
if (ips == null || ips.length == 0) {
return null;
}
return ips[0];
}
@Override
public String getIPv6ForHostAsync(String host) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return null;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return null;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return null;
}
String[] ips = getIPv6sByHostAsync(host);
if (ips == null || ips.length == 0) {
return null;
}
return ips[0];
}
@Override
public void onNetworkChange(String networkType) {
if (!mHttpDnsConfig.isEnabled()) {
return;
}
try {
mHttpDnsConfig.getWorker().execute(new Runnable() {
@Override
public void run() {
// 获取当前网络标识
String requestNetworkKey = getCurrentNetworkKey();
// 获取历史域名
HashMap<String, RequestIpType> allHost = mResultRepo.getAllHostWithoutFixedIP();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("network change to " + requestNetworkKey + ", smart resolve hosts");
}
// 智能增量解析
if (resolveAfterNetworkChange && mHttpDnsConfig.isEnabled()) {
smartBatchResolve(allHost, requestNetworkKey);
}
}
});
//网络变化触发服务IP测速
mRegionServerRankingService.rankServiceIp(mHttpDnsConfig.getCurrentServer());
} catch (Exception e) {
}
}
@Override
public HTTPDNSResult getByHost(String host, RequestIpType type) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
if (Looper.getMainLooper() == Looper.myLooper()) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request in main thread, use async request");
}
return mResolveHostService.resolveHostSyncNonBlocking(host, type, null, getCurrentNetworkKey());
}
return mResolveHostService.resolveHostSync(host, type, null, getCurrentNetworkKey());
}
private RequestIpType changeTypeWithNetType(HttpDnsSettings.NetworkDetector networkDetector,
RequestIpType type) {
if (type == RequestIpType.auto) {
if (networkDetector != null) {
switch (networkDetector.getNetType(mHttpDnsConfig.getContext())) {
case v4:
return RequestIpType.v4;
default:
return RequestIpType.both;
}
}
return RequestIpType.both;
}
return type;
}
public void cleanHostCache(ArrayList<String> hosts) {
CleanHostCacheEvent cleanHostCacheEvent = new CleanHostCacheEvent();
if (hosts == null || hosts.size() == 0) {
// 清理所有host
mResultRepo.clear();
cleanHostCacheEvent.setTag(ObservableConstants.CLEAN_ALL_HOST_CACHE);
} else {
// 清理选中的host
cleanHostCacheEvent.setTag(ObservableConstants.CLEAN_SPECIFY_HOST_CACHE);
mResultRepo.clear(hosts);
}
cleanHostCacheEvent.setStatusCode(200);
cleanHostCacheEvent.setCostTime((int) (System.currentTimeMillis() - cleanHostCacheEvent.getTimestamp()));
mHttpDnsConfig.getObservableManager().addObservableEvent(cleanHostCacheEvent);
}
@Override
public HTTPDNSResult getHttpDnsResultForHostSync(String host, RequestIpType type) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
if (Looper.getMainLooper() == Looper.myLooper()) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request in main thread, use async request");
}
return mResolveHostService.resolveHostSyncNonBlocking(host, type, null, getCurrentNetworkKey());
}
return mResolveHostService.resolveHostSync(host, type, null, getCurrentNetworkKey());
}
@Override
public void getHttpDnsResultForHostAsync(String host, RequestIpType type, HttpDnsCallback callback) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
if (callback != null) {
callback.onHttpDnsCompleted(Constants.EMPTY);
}
return;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
if (callback != null) {
callback.onHttpDnsCompleted(Constants.EMPTY);
}
return;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
if (callback != null) {
callback.onHttpDnsCompleted(Constants.EMPTY);
}
return;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
mResolveHostService.resolveHostAsync(host, type, null, getCurrentNetworkKey(), callback);
}
@Override
public HTTPDNSResult getHttpDnsResultForHostSyncNonBlocking(String host, RequestIpType type) {
if (!mHttpDnsConfig.isEnabled()) {
HttpDnsLog.i("service is disabled");
return Constants.EMPTY;
}
if (!CommonUtil.isAHost(host)) {
HttpDnsLog.i("host is invalid. " + host);
return Constants.EMPTY;
}
if (CommonUtil.isAnIP(host)) {
HttpDnsLog.i("host is ip. " + host);
return Constants.EMPTY;
}
type = changeTypeWithNetType(mHttpDnsConfig.getNetworkDetector(), type);
return mResolveHostService.resolveHostSyncNonBlocking(host, type, null, getCurrentNetworkKey());
}
private void tryUpdateRegionServer(Context context, String accountId) {
if (mHttpDnsConfig.getCurrentServer().shouldUpdateServerIp()) {
mScheduleService.updateRegionServerIps(Constants.UPDATE_REGION_SERVER_SCENES_INIT);
} else {
InitConfig config = InitConfig.getInitConfig(accountId);
String initRegion = Constants.REGION_DEFAULT;
if (config != null) {
initRegion = CommonUtil.fixRegion(config.getRegion());
}
SharedPreferences sp = context.getSharedPreferences(
Constants.CONFIG_CACHE_PREFIX + accountId, Context.MODE_PRIVATE);
String cachedRegion = sp.getString(Constants.CONFIG_CURRENT_SERVER_REGION,
Constants.REGION_DEFAULT);
if (!CommonUtil.regionEquals(cachedRegion, initRegion)) {
mScheduleService.updateRegionServerIps(Constants.UPDATE_REGION_SERVER_SCENES_REGION_CHANGE);
}
}
}
/**
* 智能增量解析:只解析当前网络环境下缺失的域名
*
* @param allHosts 所有历史域名
* @param requestNetworkKey 请求时的网络标识
*/
private void smartBatchResolve(HashMap<String, RequestIpType> allHosts, String requestNetworkKey) {
ArrayList<String> v4List = new ArrayList<>();
ArrayList<String> v6List = new ArrayList<>();
ArrayList<String> bothList = new ArrayList<>();
// 检查当前网络环境下是否需要解析
for (Map.Entry<String, RequestIpType> entry : allHosts.entrySet()) {
String host = entry.getKey();
RequestIpType type = entry.getValue();
// 使用请求时的网络标识检查缓存
if (needsResolveInNetwork(host, type, requestNetworkKey)) {
if (type == RequestIpType.v4) {
v4List.add(host);
} else if (type == RequestIpType.v6) {
v6List.add(host);
} else {
bothList.add(host);
}
}
}
// 使用带网络标识的批量解析方法
if (v4List.size() > 0) {
mBatchResolveHostService.batchResolveHostAsync(v4List, RequestIpType.v4);
}
if (v6List.size() > 0) {
mBatchResolveHostService.batchResolveHostAsync(v6List, RequestIpType.v6);
}
if (bothList.size() > 0) {
mBatchResolveHostService.batchResolveHostAsync(bothList, RequestIpType.both);
}
if (v4List.size() > 0 || v6List.size() > 0 || bothList.size() > 0) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("smart resolve " + (v4List.size() + v6List.size() + bothList.size()) + " hosts for network " + requestNetworkKey);
}
}
}
/**
* 检查指定网络环境下是否需要解析域名
*
* @param host 域名
* @param type 解析类型
* @param networkKey 网络标识
* @return 是否需要解析
*/
private boolean needsResolveInNetwork(String host, RequestIpType type, String networkKey) {
// 检查指定网络环境下是否有有效缓存
ResolveHostCache cache = mResultRepo.getCacheGroup().getCache(networkKey);
HTTPDNSResultWrapper result = cache.getResult(host, type);
return result == null || result.isExpired();
}
/**
* 获取当前网络标识,用于网络隔离缓存
*
* @return 当前网络标识
*/
private String getCurrentNetworkKey() {
return NetworkStateManager.getInstance().getCurrentNetworkKey();
}
}

View File

@@ -0,0 +1,110 @@
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;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class SignService {
private static final String ALGORITHM = "HmacSHA256";
public static final int EXPIRATION_TIME = 10 * 60;
private static final String SDNS_PREFIX = "sdns-";
private static final Set<String> includeParamKeys = new HashSet<>();
private String mSecretKey;
private long mOffset = 0L;
public SignService(String secretKey) {
mSecretKey = secretKey;
includeParamKeys.addAll(Arrays.asList("id", "m", "dn", "cip", "q", "enc", "exp", "tags", "v"));
}
/**
* 设置密钥
*/
public void setSecretKey(String secretKey) {
mSecretKey = secretKey;
}
/**
* 使用 HMAC-SHA256 生成十六进制格式的签名
*/
public String sign(Map<String, String> param) {
if (TextUtils.isEmpty(mSecretKey)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("secretKey为空.");
}
return "";
}
String signStr;
try {
signStr = generateV2SignContent(param);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("signStr:" + signStr);
}
signStr = hmacSha256(signStr);
} catch (Exception e) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.e("sign fail.", e);
}
signStr = "";
}
return signStr;
}
private static String generateV2SignContent(Map<String, String> map) {
Map<String, String> sortedMap = new TreeMap<>();
for(Map.Entry<String, String> entry : map.entrySet()) {
String paramKey = entry.getKey();
String paramValue = entry.getValue();
if (includeParamKeys.contains(paramKey) || paramKey.startsWith(SDNS_PREFIX)) {
if(!TextUtils.isEmpty(paramValue)) {
sortedMap.put(paramKey, paramValue);
}
}
}
StringBuilder signContent = new StringBuilder();
for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
if (signContent.length() > 0) {
signContent.append("&");
}
signContent.append(entry.getKey()).append("=").append(entry.getValue());
}
return signContent.toString();
}
private String hmacSha256(String content)
throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance(ALGORITHM);
mac.init(new SecretKeySpec(CommonUtil.decodeHex(mSecretKey), ALGORITHM));
byte[] signedBytes = mac.doFinal(content.getBytes(StandardCharsets.UTF_8));
return CommonUtil.encodeHexString(signedBytes);
}
public void setCurrentTimestamp(long serverTime) {
mOffset = serverTime - System.currentTimeMillis() / 1000;
}
public String getExpireTime(){
return Long.toString(System.currentTimeMillis() / 1000 + EXPIRATION_TIME + mOffset);
}
public Boolean isSignMode(){
return !TextUtils.isEmpty(mSecretKey);
}
}

View File

@@ -0,0 +1,144 @@
package com.alibaba.sdk.android.httpdns.log;
import java.util.HashSet;
import com.alibaba.sdk.android.httpdns.ILogger;
import android.util.Log;
/**
* 日志工具类
*/
public class HttpDnsLog {
public static final String TAG = "httpdns";
private static boolean printToLogcat = false;
private static final HashSet<ILogger> LOGGERS = new HashSet<>();
/**
* 设置日志接口
* 不受{@link #printToLogcat} 控制
*/
public static void setLogger(ILogger logger) {
if (logger != null) {
LOGGERS.add(logger);
}
}
/**
* 移除日志接口
*/
public static void removeLogger(ILogger logger) {
if (logger != null) {
LOGGERS.remove(logger);
}
}
/**
* logcat开关
*
* @param enable
*/
public static void enable(boolean enable) {
HttpDnsLog.printToLogcat = enable;
}
public static boolean isPrint() {
return HttpDnsLog.printToLogcat;
}
public static void e(String errLog) {
if (HttpDnsLog.printToLogcat) {
Log.e(TAG, errLog);
}
if (HttpDnsLog.LOGGERS.size() > 0) {
for (ILogger logger : HttpDnsLog.LOGGERS) {
logger.log("[E]" + errLog);
}
}
}
public static void i(String infoLog) {
if (HttpDnsLog.printToLogcat) {
Log.i(TAG, infoLog);
}
if (HttpDnsLog.LOGGERS.size() > 0) {
for (ILogger logger : HttpDnsLog.LOGGERS) {
logger.log("[I]" + infoLog);
}
}
}
public static void d(String debugLog) {
if (HttpDnsLog.printToLogcat) {
Log.d(TAG, debugLog);
}
if (HttpDnsLog.LOGGERS.size() > 0) {
for (ILogger logger : HttpDnsLog.LOGGERS) {
logger.log("[D]" + debugLog);
}
}
}
public static void w(String warnLog) {
if (HttpDnsLog.printToLogcat) {
Log.w(TAG, warnLog);
}
if (HttpDnsLog.LOGGERS.size() > 0) {
for (ILogger logger : HttpDnsLog.LOGGERS) {
logger.log("[W]" + warnLog);
}
}
}
public static void e(String errLog, Throwable throwable) {
if (HttpDnsLog.printToLogcat) {
Log.e(TAG, errLog, throwable);
}
if (HttpDnsLog.LOGGERS.size() > 0) {
for (ILogger logger : HttpDnsLog.LOGGERS) {
logger.log("[E]" + errLog);
}
printStackTrace(throwable);
}
}
public static void w(String warnLog, Throwable throwable) {
if (HttpDnsLog.printToLogcat) {
Log.e(TAG, warnLog, throwable);
}
if (HttpDnsLog.LOGGERS.size() > 0) {
for (ILogger logger : HttpDnsLog.LOGGERS) {
logger.log("[W]" + warnLog);
}
printStackTrace(throwable);
}
}
private static void printStackTrace(Throwable throwable) {
if (HttpDnsLog.LOGGERS.size() > 0) {
for (ILogger logger : HttpDnsLog.LOGGERS) {
logger.log(Log.getStackTraceString(throwable));
}
}
}
/**
* ip数组转成字符串方便输出
*
* @param ips
* @return
*/
public static String wrap(String[] ips) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (int i = 0; i < ips.length; i++) {
if (i != 0) {
stringBuilder.append(",");
}
stringBuilder.append(ips[i]);
}
stringBuilder.append("]");
return stringBuilder.toString();
}
}

View File

@@ -0,0 +1,131 @@
package com.alibaba.sdk.android.httpdns.net;
import java.util.concurrent.ExecutorService;
import com.alibaba.sdk.android.httpdns.HttpDnsSettings;
import com.alibaba.sdk.android.httpdns.NetType;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.utils.ThreadUtil;
import android.content.Context;
import com.aliyun.ams.ipdetector.Inet64Util;
public class HttpDnsNetworkDetector implements HttpDnsSettings.NetworkDetector {
private static class Holder {
private static final HttpDnsNetworkDetector INSTANCE = new HttpDnsNetworkDetector();
}
public static HttpDnsNetworkDetector getInstance() {
return Holder.INSTANCE;
}
private HttpDnsNetworkDetector() {
}
private final ExecutorService mWorker = ThreadUtil.createSingleThreadService("NetType");
private boolean mCheckInterface = true;
private String mHostToCheckNetType = "www.taobao.com";
private NetType mCache = NetType.none;
private boolean mDisableCache = false;
private Context mContext;
/**
* 网络变化时,清除缓存
*/
public void cleanCache(final boolean connected) {
if (mDisableCache) {
return;
}
mCache = NetType.none;
if (connected && this.mContext != null) {
mWorker.execute(new Runnable() {
@Override
public void run() {
// 异步探测一下
if (mContext != null) {
mCache = detectNetType(mContext);
}
}
});
}
}
/**
* 是否禁用缓存,默认不禁用
* 不确定是否存在网络链接不变的情况下,网络情况会发生变化的情况,所以提供了此开关
*/
public void disableCache(boolean disable) {
this.mDisableCache = disable;
}
/**
* 如果不能检查本地网关ip,可以调用此接口关闭
*/
public void setCheckInterface(boolean checkInterface) {
this.mCheckInterface = checkInterface;
}
/**
* 有些场景需要通过本地解析来确认网络类型,默认使用 www.taobao.com
*/
public void setHostToCheckNetType(String hostToCheckNetType) {
this.mHostToCheckNetType = hostToCheckNetType;
}
@Override
public NetType getNetType(Context context) {
if (mDisableCache) {
NetType tmp = detectNetType(context);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("ipdetector type is " + tmp.name());
}
return tmp;
}
if (mCache != NetType.none) {
return mCache;
}
mCache = detectNetType(context);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("ipdetector type is " + mCache.name());
}
return mCache;
}
private NetType detectNetType(Context context) {
this.mContext = context.getApplicationContext();
try {
int type;// 不检查本地IP的情况下无法过滤ipv6只有本地ip的情况需要通过其它方式检测下。
// 没有网络?
if (mCheckInterface) {
type = Inet64Util.getIpStack(context);
} else {
type = Inet64Util.getIpStackCheckLocal(context);
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("ip detector type is " + type);
}
if (type == IP_DUAL_STACK) {
return NetType.both;
} else if (type == IPV4_ONLY) {
return NetType.v4;
} else if (type == IPV6_ONLY) {
return NetType.v6;
} else {
// 没有网络?
return NetType.none;
}
} catch (Throwable e) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("ip detector not exist.");
}
return NetType.none;
}
}
private final static int IPV4_ONLY = 1;
private final static int IPV6_ONLY = 2;
private final static int IP_DUAL_STACK = 3;
}

View File

@@ -0,0 +1,237 @@
package com.alibaba.sdk.android.httpdns.net;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.utils.ThreadUtil;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Network;
import android.net.LinkProperties;
import android.net.LinkAddress;
import android.os.Build;
import android.os.Process;
public class NetworkStateManager {
public static final String NONE_NETWORK = "None_Network";
private static final String NETWORK_KEY_PREFIX = "emasInner_";
private Context mContext;
private String mLastConnectedNetwork = NONE_NETWORK;
private volatile String mCurrentNetworkKey = NONE_NETWORK;
private final ArrayList<OnNetworkChange> mListeners = new ArrayList<>();
private final ExecutorService mWorker = ThreadUtil.createSingleThreadService("network");
private static class Holder {
private static final NetworkStateManager instance = new NetworkStateManager();
}
public static NetworkStateManager getInstance() {
return Holder.instance;
}
private NetworkStateManager() {
}
public void init(Context ctx) {
if (ctx == null) {
throw new IllegalStateException("Context can't be null");
}
if (this.mContext != null) {
// inited
return;
}
this.mContext = ctx.getApplicationContext();
// 立即检测当前网络状态
mWorker.execute(() -> {
try {
String currentNetwork = detectCurrentNetwork();
if (!currentNetwork.equals(NONE_NETWORK)) {
mLastConnectedNetwork = currentNetwork;
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("[NetworkStateManager.init] - Initial network detected: " + currentNetwork);
}
} catch (Exception e) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.e("[NetworkStateManager.init] - Failed to detect initial network", e);
}
}
});
BroadcastReceiver bcReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
try {
mWorker.execute(new Runnable() {
@Override
public void run() {
try {
if (isInitialStickyBroadcast()) { // no need to handle initial
// sticky broadcast
return;
}
String action = intent.getAction();
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)
&& hasNetInfoPermission(context)) {
String currentNetwork = detectCurrentNetwork();
HttpDnsNetworkDetector.getInstance().cleanCache(
!currentNetwork.equals(NONE_NETWORK));
if (!currentNetwork.equals(NONE_NETWORK)
&& !currentNetwork.equalsIgnoreCase(mLastConnectedNetwork)) {
for (OnNetworkChange onNetworkChange : mListeners) {
onNetworkChange.onNetworkChange(currentNetwork);
}
}
if (!currentNetwork.equals(NONE_NETWORK)) {
mLastConnectedNetwork = currentNetwork;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception ignored) {
}
}
};
try {
if (hasNetInfoPermission(mContext)) {
IntentFilter mFilter = new IntentFilter();
mFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(bcReceiver, mFilter);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private String detectCurrentNetwork() {
try {
ConnectivityManager connectivityManager = (ConnectivityManager)mContext
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info != null && info.isAvailable() && info.isConnected()) {
String name = info.getTypeName();
String ipPrefix = getLocalIpPrefix();
if (ipPrefix != null) {
name = name + "_" + ipPrefix;
}
// 增加前缀防止与用户cacheKey冲突
mCurrentNetworkKey = (name == null) ? NONE_NETWORK : (NETWORK_KEY_PREFIX + name);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("[detectCurrentNetwork] - Network key:" + mCurrentNetworkKey + " subType "
+ "name: "
+ info.getSubtypeName());
}
return mCurrentNetworkKey;
}
} catch (Exception e) {
e.printStackTrace();
}
mCurrentNetworkKey = NONE_NETWORK;
return NONE_NETWORK;
}
private String getLocalIpPrefix() {
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return null;
}
ConnectivityManager connectivityManager = (ConnectivityManager) mContext
.getSystemService(Context.CONNECTIVITY_SERVICE);
Network activeNetwork = connectivityManager.getActiveNetwork();
if (activeNetwork == null) {
return null;
}
LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNetwork);
if (linkProperties == null) {
return null;
}
String ipv6Backup = null;
for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) {
InetAddress address = linkAddress.getAddress();
if (address.isLoopbackAddress() || address.isLinkLocalAddress()) {
continue;
}
if (address instanceof Inet4Address) {
String ip = address.getHostAddress();
String[] parts = ip.split("\\.");
if (parts.length >= 3) {
return parts[0] + "." + parts[1] + "." + parts[2];
}
} else if (address instanceof Inet6Address && ipv6Backup == null) {
String ipv6 = address.getHostAddress();
if (ipv6.contains("%")) {
ipv6 = ipv6.substring(0, ipv6.indexOf("%"));
}
String[] parts = ipv6.split(":");
if (parts.length >= 4) {
ipv6Backup = parts[0] + ":" + parts[1] + ":" + parts[2] + ":" + parts[3];
}
}
}
return ipv6Backup;
} catch (Exception e) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("[getLocalIpPrefix] - Failed to get IP prefix: " + e.getMessage());
}
}
return null;
}
public String getCurrentNetworkKey() {
return mCurrentNetworkKey;
}
public void addListener(OnNetworkChange change) {
this.mListeners.add(change);
}
public interface OnNetworkChange {
void onNetworkChange(String networkType);
}
public void reset() {
mContext = null;
mLastConnectedNetwork = NONE_NETWORK;
mCurrentNetworkKey = NONE_NETWORK;
mListeners.clear();
}
private static boolean hasNetInfoPermission(Context context) {
try {
return checkSelfPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)
== PackageManager.PERMISSION_GRANTED;
} catch (Throwable e) {
HttpDnsLog.w("check network info permission fail", e);
}
return false;
}
private static int checkSelfPermission(Context context, String permission) {
return context.checkPermission(permission, Process.myPid(), Process.myUid());
}
}

View File

@@ -0,0 +1,117 @@
package com.alibaba.sdk.android.httpdns.observable;
import android.content.SharedPreferences;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.config.SpCacheItem;
import com.alibaba.sdk.android.httpdns.utils.Constants;
import org.json.JSONException;
import org.json.JSONObject;
public class ObservableConfig implements SpCacheItem {
//默认关闭
public boolean enable = false;
public double sampleRatio = 1.0;
public String endpoint = "";
public int batchReportMaxSize = 50;
public int batchReportIntervalTime = 60;
public int maxReportsPerMinute = 4;
public JSONObject raw = null;
public boolean updateConfig(ObservableConfig config) {
if (config == null) {
//调度接口没有下发配置,关闭可观测
if (enable) {
enable = false;
raw = null;
return true;
}
return false;
}
if (isSameConfig(config)) {
return false;
}
enable = config.enable;
sampleRatio = config.sampleRatio;
endpoint = config.endpoint;
batchReportMaxSize = config.batchReportMaxSize;
batchReportIntervalTime = config.batchReportIntervalTime;
maxReportsPerMinute = config.maxReportsPerMinute;
raw = config.raw;
return true;
}
private boolean isSameConfig(ObservableConfig config) {
return enable == config.enable
&& sampleRatio == config.sampleRatio
&& TextUtils.equals(endpoint, config.endpoint)
&& batchReportMaxSize == config.batchReportMaxSize
&& batchReportIntervalTime == config.batchReportIntervalTime
&& maxReportsPerMinute == config.maxReportsPerMinute;
}
public static ObservableConfig fromJson(JSONObject json) {
if (json == null) {
return null;
}
ObservableConfig observableConfig = new ObservableConfig();
if (json.has("enable")) {
observableConfig.enable = json.optBoolean("enable");
}
if (json.has("sample_ratio")) {
observableConfig.sampleRatio = json.optDouble("sample_ratio");
}
String endpoint = json.optString("endpoint");
if (!TextUtils.isEmpty(endpoint)) {
observableConfig.endpoint = endpoint;
}
observableConfig.batchReportMaxSize = json.optInt("batch_report_max_size", 50);
if (observableConfig.batchReportMaxSize <= 0) {
observableConfig.batchReportMaxSize = 50;
}
observableConfig.batchReportIntervalTime = json.optInt("batch_report_interval_time", 60);
if (observableConfig.batchReportIntervalTime <= 0) {
observableConfig.batchReportIntervalTime = 60;
}
observableConfig.maxReportsPerMinute = json.optInt("max_reports_per_minute", 4);
if (observableConfig.maxReportsPerMinute <= 0) {
observableConfig.maxReportsPerMinute = 4;
}
observableConfig.raw = json;
return observableConfig;
}
@Override
public void restoreFromCache(SharedPreferences sp) {
String cachedObservableConfig = sp.getString(Constants.CONFIG_OBSERVABLE_CONFIG, null);
if (!TextUtils.isEmpty(cachedObservableConfig)) {
try {
JSONObject json = new JSONObject(cachedObservableConfig);
ObservableConfig cachedConfig = fromJson(json);
enable = cachedConfig.enable;
sampleRatio = cachedConfig.sampleRatio;
endpoint = cachedConfig.endpoint;
batchReportMaxSize = cachedConfig.batchReportMaxSize;
batchReportIntervalTime = cachedConfig.batchReportIntervalTime;
maxReportsPerMinute = cachedConfig.maxReportsPerMinute;
raw = cachedConfig.raw;
} catch (JSONException e) {
}
}
}
@Override
public void saveToCache(SharedPreferences.Editor editor) {
if (raw != null) {
editor.putString(Constants.CONFIG_OBSERVABLE_CONFIG, raw.toString());
} else {
editor.remove(Constants.CONFIG_OBSERVABLE_CONFIG);
}
}
}

View File

@@ -0,0 +1,38 @@
package com.alibaba.sdk.android.httpdns.observable;
public final class ObservableConstants {
public static final int REQUEST_IP_TYPE_AUTO = 0x00;
public static final int REQUEST_IP_TYPE_V4 = 0x01;
public static final int REQUEST_IP_TYPE_V6 = 0x02;
public static final int REQUEST_IP_TYPE_BOTH = 0x03;
public static final int RESOLVE_API_UNKNOWN = 0x00;
public static final int RESOLVE_API_SYNC = 0x04;
public static final int RESOLVE_API_ASYNC = 0x08;
public static final int RESOLVE_API_SYN_NON_BLOCKING = 0x0C;
public static final int REMOTE_CACHE_NULL = 0x00;
public static final int REMOTE_CACHE_EXPIRED = 0x10;
public static final int CACHE_NONE = 0x00;
public static final int CACHE_NOT_EXPIRED = 0x10;
public static final int CACHE_EXPIRED_NOT_USE = 0x20;
public static final int CACHE_EXPIRED_USE = 0x30;
public static final int REQUEST_NOT_RETRY = 0x00;
public static final int REQUEST_RETRY = 0x10;
public static final String UNKNOWN = "unknown";
public static final int HTTP_REQUEST = 0x00;
public static final int HTTPS_REQUEST = 0x04;
public static final int NOT_SIGN_MODE_REQUEST = 0x00;
public static final int SIGN_MODE_REQUEST = 0x08;
public static final int CLEAN_ALL_HOST_CACHE = 1;
public static final int CLEAN_SPECIFY_HOST_CACHE = 2;
public static final int EMPTY_RESULT = 0x00;
public static final int NOT_EMPTY_RESULT = 0x40;
}

View File

@@ -0,0 +1,98 @@
package com.alibaba.sdk.android.httpdns.observable;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.request.HttpRequestWrapper;
import com.alibaba.sdk.android.httpdns.request.ResponseParser;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
public class ObservableHttpRequest<T> extends HttpRequestWrapper<T> {
private String url;
private ResponseParser<T> parser;
private Map<String, String> mHeaders;
private String mBody;
public ObservableHttpRequest(String url, ResponseParser<T> parser, Map<String, String> headers, String body) {
super(null);
this.url = url;
this.parser = parser;
mHeaders = headers;
mBody = body;
}
@Override
public T request() throws Throwable {
HttpURLConnection conn = null;
InputStream in = null;
BufferedReader streamReader = null;
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request url " + url);
}
try {
conn = (HttpURLConnection)new URL(url).openConnection();
conn.setReadTimeout(10000);
conn.setConnectTimeout(10000);
if (mHeaders != null) {
for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
}
if (!TextUtils.isEmpty(mBody)) {
conn.setRequestMethod("POST");
conn.setDoOutput(true);
OutputStream outputStream = conn.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
bw.write(mBody);
bw.close();
}
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
in = conn.getInputStream();
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String responseStr = readStringFrom(streamReader).toString();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request success " + responseStr);
}
return parser.parse(null, responseStr);
}
} catch (Throwable e) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d(e.getMessage());
}
} finally {
if (conn != null) {
conn.disconnect();
}
try {
if (in != null) {
in.close();
}
if (streamReader != null) {
streamReader.close();
}
} catch (IOException ignored) {
}
}
return null;
}
}

View File

@@ -0,0 +1,375 @@
package com.alibaba.sdk.android.httpdns.observable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.text.TextUtils;
import android.text.format.DateUtils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.observable.event.GroupEvent;
import com.alibaba.sdk.android.httpdns.observable.event.LocalDnsEvent;
import com.alibaba.sdk.android.httpdns.observable.event.ObservableEvent;
import com.alibaba.sdk.android.httpdns.observable.event.ReportingRateExceptionEvent;
import com.alibaba.sdk.android.httpdns.request.HttpRequest;
import com.alibaba.sdk.android.httpdns.request.HttpRequestTask;
import com.alibaba.sdk.android.httpdns.request.RequestCallback;
import com.alibaba.sdk.android.httpdns.request.ResponseParser;
import com.alibaba.sdk.android.httpdns.request.RetryHttpRequest;
import com.alibaba.sdk.android.httpdns.resolve.ResolveHostHelper;
import com.alibaba.sdk.android.httpdns.track.SessionTrackMgr;
import com.alibaba.sdk.android.httpdns.utils.ThreadUtil;
/**
* utils data upload manager
*/
public class ObservableManager {
private static final int MESSAGE_REPORT = 101;
private HttpDnsConfig mHttpDnsConfig;
private String mSecret = null;
private int mReportBatchIndex = 0;
private List<ObservableEvent> mCacheEvents = new ArrayList<>();
private boolean mPositiveEnableObservable = true;
private boolean mReportingRateException = false;
private AtomicBoolean initMessage = new AtomicBoolean(false);
private ArrayDeque<Long> mReportsTime = new ArrayDeque<>();
private AtomicBoolean mReporting = new AtomicBoolean(false);
private final Handler mHandler;
private static final ExecutorService sWorker = ThreadUtil.createObservableExecutorService();
private static final ExecutorService mReportWorker = ThreadUtil.createObservableReportExecutorService();
private static final char[] hexChar = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public ObservableManager(HttpDnsConfig httpDnsConfig, String secret) {
mHttpDnsConfig = httpDnsConfig;
mSecret = secret;
if (mHttpDnsConfig.getObservableSampleBenchMarks() < 0) {
mHttpDnsConfig.setObservableSampleBenchMarks(generateSampleBenchMarks());
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("observable: " + mHttpDnsConfig.getObservableSampleBenchMarks());
}
HandlerThread handlerThread = new HandlerThread("httpdns_observable");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_REPORT) {
report();
if (isEnableObservable() || !mCacheEvents.isEmpty()) {
mHandler.sendEmptyMessageDelayed(MESSAGE_REPORT, mHttpDnsConfig.getObservableConfig().batchReportIntervalTime * DateUtils.SECOND_IN_MILLIS);
}
}
}
};
}
public void positiveEnableObservable(boolean enable) {
mPositiveEnableObservable = enable;
}
private boolean isEnableReport() {
return mPositiveEnableObservable && !TextUtils.isEmpty(mHttpDnsConfig.getObservableConfig().endpoint);
}
/**
* 是否开启可观测,如果关闭,则打点和上报都关闭
* @return
*/
private boolean isEnableObservable() {
return mPositiveEnableObservable && mHttpDnsConfig.getObservableConfig().enable
&& mHttpDnsConfig.getObservableSampleBenchMarks() < mHttpDnsConfig.getObservableConfig().sampleRatio
&& !mReportingRateException;
}
public void addObservableEvent(final ObservableEvent event) {
if (!isEnableObservable()) {
return;
}
if (event == null) {
return;
}
if (event instanceof LocalDnsEvent) {
//需要local dns的event在这里统一执行local dns解析
try {
sWorker.execute(new Runnable() {
@Override
public void run() {
((LocalDnsEvent) event).localDns(event.getHostName());
addObservableEventInner(event);
}
});
} catch (Exception e) {
//线程池异常,也要添加事件
addObservableEventInner(event);
}
} else {
addObservableEventInner(event);
}
}
private void addObservableEventInner(final ObservableEvent event) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (event instanceof GroupEvent) {
ObservableEvent temp;
GroupEvent targetGroupEvent = null;
for (int i= 0; i != mCacheEvents.size(); ++i) {
temp = mCacheEvents.get(i);
if (temp instanceof GroupEvent) {
if (((GroupEvent) temp).isSameGroup(event)) {
targetGroupEvent = (GroupEvent) temp;
break;
}
}
}
if (targetGroupEvent != null) {
targetGroupEvent.groupWith(event);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("group event: " + targetGroupEvent.toString());
}
} else {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("add group event: " + event.toString());
}
addEventInner(event);
tryReport();
}
} else {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("add event: " + event.toString());
}
addEventInner(event);
tryReport();
}
}
});
if (initMessage.compareAndSet(false, true)) {
mHandler.sendEmptyMessageDelayed(MESSAGE_REPORT, mHttpDnsConfig.getObservableConfig().batchReportIntervalTime * DateUtils.SECOND_IN_MILLIS);
}
}
private void addEventInner(ObservableEvent event) {
mCacheEvents.add(event);
if (mCacheEvents.size() >= 400) {
ReportingRateExceptionEvent reportingRateExceptionEvent = new ReportingRateExceptionEvent();
reportingRateExceptionEvent.setStatusCode(200);
Map<Integer, Integer> eventNumMap = new HashMap<>();
for (ObservableEvent event1 : mCacheEvents) {
Integer num = eventNumMap.get(event1.getEventName());
if (num == null) {
num = 0;
}
eventNumMap.put(event1.getEventName(), num + 1);
}
int eventName = -1;
int tmpNum = 0;
for (Map.Entry<Integer, Integer> entry : eventNumMap.entrySet()) {
if (entry.getValue() > tmpNum) {
tmpNum = entry.getValue();
eventName = entry.getKey();
}
}
reportingRateExceptionEvent.setTag(eventName);
addObservableEvent(reportingRateExceptionEvent);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.w("observable report rate exception.");
}
//关闭日志采集
mReportingRateException = true;
}
}
private void tryReport() {
if (mCacheEvents.size() >= mHttpDnsConfig.getObservableConfig().batchReportMaxSize) {
//达到上报数量
mHandler.removeMessages(MESSAGE_REPORT);
mHandler.sendEmptyMessage(MESSAGE_REPORT);
}
}
private void report() {
if (TextUtils.isEmpty(mHttpDnsConfig.getObservableConfig().endpoint)) {
return;
}
if (mCacheEvents.isEmpty()) {
return;
}
if (!isEnableReport()) {
mCacheEvents.clear();
++mReportBatchIndex;
return;
}
//上报流量控制判断
long current = System.currentTimeMillis();
if (reachingReportingLimit(current)) {
return;
}
if (mReporting.compareAndSet(false, true)) {
String body = buildReportBody();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("observable: " + body);
}
int num = Math.min(mHttpDnsConfig.getObservableConfig().batchReportMaxSize, mCacheEvents.size());
for (int i = 0; i != num; ++i) {
mCacheEvents.remove(0);
}
Map<String, String> headers = getHeader(mReportBatchIndex++);
try {
String url = getUrl(body);
if (TextUtils.isEmpty(url)) {
return;
}
//添加上报时间点,用于上报流量控制
mReportsTime.addLast(current);
HttpRequest<String> request = new ObservableHttpRequest<>(url, new ResponseParser<String>() {
@Override
public String parse(String serverIp, String response) throws Throwable {
return response;
}
}, headers, body);
// 重试2次
request = new RetryHttpRequest<>(request, 2);
mReportWorker.execute(new HttpRequestTask<>(request, new RequestCallback<String>() {
@Override
public void onSuccess(String response) {
mReporting.set(false);
mHandler.removeMessages(MESSAGE_REPORT);
mHandler.sendEmptyMessage(MESSAGE_REPORT);
}
@Override
public void onFail(Throwable throwable) {
mReporting.set(false);
}
}));
} catch (Throwable e) {
mReporting.set(false);
}
}
}
private String buildReportBody() {
StringBuilder sb = new StringBuilder();
int num = Math.min(mHttpDnsConfig.getObservableConfig().batchReportMaxSize, mCacheEvents.size());
for (int i = 0; i != num; ++i) {
sb.append(mCacheEvents.get(i).toString());
sb.append("\n");
}
return sb.toString();
}
private String getUrl(String content) throws UnsupportedEncodingException, NoSuchAlgorithmException {
ObservableConfig config = mHttpDnsConfig.getObservableConfig();
String url = config.endpoint;
if (TextUtils.isEmpty(url)) {
return null;
}
if (!TextUtils.isEmpty(url) && (!url.startsWith("http://") && !url.startsWith("https://"))) {
url = "https://" + url;
}
if (!url.endsWith("/")) {
url += "/";
}
url += mHttpDnsConfig.getAccountId() + "/metrics";
long time = System.currentTimeMillis();
url += "?t=" + time;
url += "&ssk=" + (TextUtils.isEmpty(mSecret) ? 0 : 1);
//tags
url += ResolveHostHelper.getTags(mHttpDnsConfig);
String sign = md5(md5(content) + "-" + (TextUtils.isEmpty(mSecret) ? mHttpDnsConfig.getAccountId() : mSecret) + "-" + time);
url += "&s=" + sign;
return url;
}
private Map<String, String> getHeader(int reportBatchIndex) {
Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", mHttpDnsConfig.getUA());
headers.put("X-HTTPDNS-REQUEST-ID", SessionTrackMgr.getInstance().getSessionId() + "-" + reportBatchIndex);
return headers;
}
private String md5(String content) throws NoSuchAlgorithmException, UnsupportedEncodingException {
byte[] hash;
hash = MessageDigest.getInstance("MD5").digest(content.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder(hash.length * 2);
for (byte aByte : hash) {
sb.append(hexChar[(aByte & 0xf0) >>> 4]);
sb.append(hexChar[aByte & 0x0f]);
}
return sb.toString();
}
private float generateSampleBenchMarks() {
Random random = new Random(System.currentTimeMillis());
return random.nextFloat();
}
private boolean reachingReportingLimit(long current) {
int limit = mHttpDnsConfig.getObservableConfig().maxReportsPerMinute;
if (mReportsTime.size() < limit) {
return false;
}
//先移除超过每分钟上报次数的上报时间点
while (mReportsTime.size() > limit) {
mReportsTime.pollFirst();
}
Long first = mReportsTime.peekFirst();
if (first == null) {
return false;
}
return current - first < DateUtils.MINUTE_IN_MILLIS;
}
}

View File

@@ -0,0 +1,24 @@
package com.alibaba.sdk.android.httpdns.observable.event;
public class BatchQueryHttpDnsApiEvent extends QueryHttpDnsApiEvent {
public BatchQueryHttpDnsApiEvent() {
super();
mEventName = 2;
}
@Override
public void setHostName(String hostName) {
//do nothing
}
@Override
public void setHttpDnsIps(String[] ipv4s, String[] ipv6s) {
//do nothing
}
@Override
public void setLocalDnsIps(String localDnsIps) {
//do nothing
}
}

View File

@@ -0,0 +1,82 @@
package com.alibaba.sdk.android.httpdns.observable.event;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.observable.ObservableConstants;
public class CallSdkApiEvent extends ObservableEvent implements GroupEvent, LocalDnsEvent {
public CallSdkApiEvent(long startTime) {
super();
mTimestamp = startTime;
mEventName = 5;
mCount = 1;
}
public void setRequestType(RequestIpType queryType) {
if (queryType == RequestIpType.v4) {
mTag |= ObservableConstants.REQUEST_IP_TYPE_V4;
} else if (queryType == RequestIpType.v6) {
mTag |= ObservableConstants.REQUEST_IP_TYPE_V6;
} else if (queryType == RequestIpType.both) {
mTag |= ObservableConstants.REQUEST_IP_TYPE_BOTH;
}
}
public void setInvokeApi(int api) {
mTag |= api;
}
public void setCacheScene(int scene) {
mTag |= scene;
}
public void setResultStatus(int status) {
mTag |= status;
}
public void setTimestamp(long timestamp) {
mTimestamp = timestamp;
}
@Override
public void setErrorCode(String errorCode) {
//do nothing
}
private void incrementCount() {
++mCount;
}
@Override
public boolean isSameGroup(ObservableEvent event) {
if (event == null) {
return false;
}
return TextUtils.equals(getHostName(), event.getHostName())
&& getTag() == event.getTag();
}
@Override
public void groupWith(ObservableEvent event) {
setTimestamp(event.getTimestamp());
mServerIp = event.getServerIp();
int totalCostTime = getCostTime() * getCount();
incrementCount();
setCostTime((totalCostTime + event.getCostTime()) / getCount());
if (!TextUtils.isEmpty(event.getHttpDnsIps())) {
mHttpDnsIps = event.getHttpDnsIps();
}
if (!TextUtils.isEmpty(event.getLocalDnsIps())) {
mLocalDnsIps = event.getLocalDnsIps();
mLocalDnsCost = event.getLocalDnsCost();
}
}
@Override
public int getRequestIpType() {
return mTag;
}
}

View File

@@ -0,0 +1,44 @@
package com.alibaba.sdk.android.httpdns.observable.event;
public class CleanHostCacheEvent extends ObservableEvent {
public CleanHostCacheEvent() {
super();
mEventName = 6;
mCount = 1;
}
public void setTag(int cleanType) {
mTag = cleanType;
}
@Override
public void setHostName(String hostName) {
//do nothing
}
@Override
public void setServerIp(String serverIp) {
//do nothing
}
@Override
public void setIpType(int type) {
//do nothing
}
@Override
public void setErrorCode(String errorCode) {
//do nothing
}
@Override
public void setHttpDnsIps(String[] ipv4s, String[] ipv6s) {
//do nothing
}
@Override
public void setLocalDnsIps(String localDnsIps) {
//do nothing
}
}

View File

@@ -0,0 +1,10 @@
package com.alibaba.sdk.android.httpdns.observable.event;
/**
* 需要聚合的事件
*/
public interface GroupEvent {
boolean isSameGroup(ObservableEvent event);
void groupWith(ObservableEvent event);
}

View File

@@ -0,0 +1,67 @@
package com.alibaba.sdk.android.httpdns.observable.event;
import com.alibaba.sdk.android.httpdns.observable.ObservableConstants;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
public interface LocalDnsEvent {
default void localDns(String host) {
try {
long localDnsStart = System.currentTimeMillis();
InetAddress[] addresses = InetAddress.getAllByName(host);
long localDnsCost = System.currentTimeMillis() - localDnsStart;
if (addresses != null && addresses.length > 0) {
List<String> ips = new ArrayList<>();
List<String> ipv6s = new ArrayList<>();
for (InetAddress address : addresses) {
if (address instanceof Inet4Address) {
ips.add(address.getHostAddress());
} else if (address instanceof Inet6Address) {
ipv6s.add(address.getHostAddress());
}
}
StringBuilder sb = new StringBuilder();
int ipType = getRequestIpType();
if ((ipType & ObservableConstants.REQUEST_IP_TYPE_V4) == ObservableConstants.REQUEST_IP_TYPE_V4) {
for (int i = 0; i != ips.size(); ++i) {
sb.append(ips.get(i));
if (i != ips.size() - 1) {
sb.append(",");
}
}
}
if ((ipType & ObservableConstants.REQUEST_IP_TYPE_V6) ==ObservableConstants.REQUEST_IP_TYPE_V6) {
if (!ipv6s.isEmpty() && sb.length() > 0) {
sb.append(";");
}
for (int i = 0; i != ipv6s.size(); ++i) {
sb.append(ipv6s.get(i));
if (i != ipv6s.size() - 1) {
sb.append(",");
}
}
}
if (this instanceof ObservableEvent) {
((ObservableEvent) this).setLocalDnsIps(sb.toString());
((ObservableEvent) this).setLocalDnsCost(localDnsCost);
}
}
} catch (Exception e) {
}
}
/**
* 返回值和tag中的请求类型保持一致
* @return
*/
abstract int getRequestIpType();
}

View File

@@ -0,0 +1,176 @@
package com.alibaba.sdk.android.httpdns.observable.event;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsServiceImpl;
import com.alibaba.sdk.android.httpdns.observable.ObservableConstants;
import com.alibaba.sdk.android.httpdns.utils.NetworkUtil;
public abstract class ObservableEvent {
private static final String DIVIDER = "|";
protected int mEventName;
protected int mTag;
protected String mHostName;
protected long mTimestamp;
protected String mServerIp;
protected int mCostTime;
private String mIpType;
protected int mStatusCode;
protected String mErrorCode;
protected int mCount;
private String mNetworkType;
protected String mHttpDnsIps;
protected String mLocalDnsIps;
protected long mLocalDnsCost;
protected ObservableEvent() {
mTimestamp = System.currentTimeMillis();
mNetworkType = String.valueOf(NetworkUtil.getNetworkType(HttpDnsServiceImpl.sContext));
}
public int getEventName() {
return mEventName;
}
public int getTag() {
return mTag;
}
public void setHostName(String hostName) {
mHostName = hostName;
}
public String getHostName() {
return mHostName;
}
public long getTimestamp() {
return mTimestamp;
}
public void setServerIp(String serverIp) {
mServerIp = serverIp;
}
public String getServerIp() {
return mServerIp;
}
public void setCostTime(int time) {
mCostTime = time;
}
public int getCostTime() {
return mCostTime;
}
public void setIpType(int type) {
mIpType = String.valueOf(type);
}
public int getIpType() {
return Integer.parseInt(mIpType);
}
public void setStatusCode(int statusCode) {
mStatusCode = statusCode;
}
public int getStatusCode() {
return mStatusCode;
}
public void setErrorCode(String errorCode) {
if (TextUtils.isEmpty(errorCode)) {
return;
}
if (errorCode.length() > 128) {
mErrorCode = errorCode.substring(0, 128);
} else {
mErrorCode = errorCode;
}
}
public String getErrorCode() {
return mErrorCode;
}
public int getCount() {
return mCount;
}
public void setHttpDnsIps(String[] ipv4s, String[] ipv6s) {
int ipType = 0;
if (ipv4s != null && ipv4s.length > 0) {
ipType |= ObservableConstants.REQUEST_IP_TYPE_V4;
}
if (ipv6s != null && ipv6s.length > 0) {
ipType |= ObservableConstants.REQUEST_IP_TYPE_V6;
}
setIpType(ipType);
StringBuilder sb = new StringBuilder();
if (ipv4s != null) {
for (int i = 0; i != ipv4s.length; ++i) {
sb.append(ipv4s[i]);
if (i != ipv4s.length - 1) {
sb.append(",");
}
}
}
if (ipv6s != null) {
if (ipv6s.length != 0 && sb.length() > 0) {
sb.append(";");
}
for (int i = 0; i != ipv6s.length; ++i) {
sb.append(ipv6s[i]);
if (i != ipv6s.length - 1) {
sb.append(",");
}
}
}
mHttpDnsIps = sb.toString();
}
public String getHttpDnsIps() {
return mHttpDnsIps;
}
public void setLocalDnsIps(String localDnsIps) {
mLocalDnsIps = localDnsIps;
}
public String getLocalDnsIps() {
return mLocalDnsIps;
}
public void setLocalDnsCost(long localDnsCost) {
mLocalDnsCost = localDnsCost;
}
public long getLocalDnsCost() {
return mLocalDnsCost;
}
public String toString() {
return mEventName +
DIVIDER + mTag +
DIVIDER + (TextUtils.isEmpty(mHostName) ? "" : mHostName) +
DIVIDER + mTimestamp +
DIVIDER + (TextUtils.isEmpty(mServerIp) ? "" : mServerIp) +
DIVIDER + mCostTime +
DIVIDER + (TextUtils.isEmpty(mNetworkType) ? "" : mNetworkType) + //预留的网络类型
DIVIDER + ((TextUtils.isEmpty(mIpType)) ? "" : mIpType) +
DIVIDER + mStatusCode +
DIVIDER + (TextUtils.isEmpty(mErrorCode) ? "" : mErrorCode) +
DIVIDER + mCount +
DIVIDER + (TextUtils.isEmpty(mHttpDnsIps) ? "" : mHttpDnsIps) +
DIVIDER + (TextUtils.isEmpty(mLocalDnsIps) ? "" : mLocalDnsIps) +
DIVIDER + mLocalDnsCost;
}
}

View File

@@ -0,0 +1,36 @@
package com.alibaba.sdk.android.httpdns.observable.event;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.observable.ObservableConstants;
public class QueryHttpDnsApiEvent extends ObservableEvent implements LocalDnsEvent {
public QueryHttpDnsApiEvent() {
super();
mEventName = 3;
mCount = 1;
}
public void setTag(boolean isRetry, RequestIpType queryType, boolean http, boolean signMode) {
mTag = isRetry ? ObservableConstants.REQUEST_RETRY : ObservableConstants.REQUEST_NOT_RETRY;
if (queryType == RequestIpType.v4) {
mTag |= ObservableConstants.REQUEST_IP_TYPE_V4;
} else if (queryType == RequestIpType.v6) {
mTag |= ObservableConstants.REQUEST_IP_TYPE_V6;
} else if (queryType == RequestIpType.both) {
mTag |= ObservableConstants.REQUEST_IP_TYPE_BOTH;
}
if (!http) {
mTag |= ObservableConstants.HTTPS_REQUEST;
}
if (signMode) {
mTag |= ObservableConstants.SIGN_MODE_REQUEST;
}
}
@Override
public int getRequestIpType() {
return mTag;
}
}

View File

@@ -0,0 +1,44 @@
package com.alibaba.sdk.android.httpdns.observable.event;
public class ReportingRateExceptionEvent extends ObservableEvent {
public ReportingRateExceptionEvent() {
super();
mEventName = 8;
mCount = 1;
}
public void setTag(int eventName) {
mTag = eventName;
}
@Override
public void setHostName(String hostName) {
//do nothing
}
@Override
public void setCostTime(int time) {
mCostTime = 0;
}
@Override
public void setIpType(int type) {
//do nothing
}
@Override
public void setErrorCode(String errorCode) {
//do nothing
}
@Override
public void setHttpDnsIps(String[] ipv4s, String[] ipv6s) {
//do nothing
}
@Override
public void setLocalDnsIps(String localDnsIps) {
//do nothing
}
}

View File

@@ -0,0 +1,52 @@
package com.alibaba.sdk.android.httpdns.observable.event;
import com.alibaba.sdk.android.httpdns.utils.Constants;
public class UpdateRegionServerIpsEvent extends ObservableEvent {
public UpdateRegionServerIpsEvent() {
super();
mEventName = 7;
mCount = 1;
}
public void setTag(int scenes) {
if (scenes == Constants.UPDATE_REGION_SERVER_SCENES_INIT) {
mTag = 3;
} else if (scenes == Constants.UPDATE_REGION_SERVER_SCENES_REGION_CHANGE) {
mTag = 2;
} else if (scenes == Constants.UPDATE_REGION_SERVER_SCENES_SERVER_UNAVAILABLE) {
mTag = 1;
}
}
@Override
public void setHostName(String hostName) {
//do nothing
}
@Override
public void setServerIp(String serverIp) {
//do nothing
}
@Override
public void setIpType(int type) {
//do nothing
}
@Override
public void setErrorCode(String errorCode) {
//do nothing
}
@Override
public void setHttpDnsIps(String[] ipv4s, String[] ipv6s) {
//do nothing
}
@Override
public void setLocalDnsIps(String localDnsIps) {
//do nothing
}
}

View File

@@ -0,0 +1,48 @@
package com.alibaba.sdk.android.httpdns.ranking;
/**
* IP优选配置项
*/
public class IPRankingBean {
/**
* 进行ip优选的域名
*/
String hostName;
/**
* 用于测试速度的端口
*/
int port;
public IPRankingBean(String hostName, int port) {
this.hostName = hostName;
this.port = port;
}
public String getHostName() {
return hostName;
}
public int getPort() {
return port;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
IPRankingBean that = (IPRankingBean)o;
return port == that.port && hostName != null && hostName.equals(that.hostName);
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + (hostName == null ? 0 : hostName.hashCode());
hash = 31 * hash + port;
return hash;
}
}

View File

@@ -0,0 +1,8 @@
package com.alibaba.sdk.android.httpdns.ranking;
/**
* IP优选的结果回调
*/
public interface IPRankingCallback {
void onResult(String host, String[] sortedIps);
}

View File

@@ -0,0 +1,79 @@
package com.alibaba.sdk.android.httpdns.ranking;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentSkipListSet;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
/**
* IP优选服务
*/
public class IPRankingService {
private final HttpDnsConfig mHttpDnsConfig;
private List<IPRankingBean> mIPRankingBeanList;
private IPRankingTask.SpeedTestSocketFactory mSocketFactory
= new IPRankingTask.SpeedTestSocketFactory() {
@Override
public Socket create() {
return new Socket();
}
};
private final ConcurrentSkipListSet<String> mProbingHosts = new ConcurrentSkipListSet<>();
public IPRankingService(HttpDnsConfig config) {
this.mHttpDnsConfig = config;
}
/**
* 进行ipv4优选
*/
public void probeIpv4(String host, String[] ips, final IPRankingCallback IPRankingCallback) {
if (mHttpDnsConfig.isIPRankingDisabled()) {
return;
}
IPRankingBean ipRankingBean = getIPRankingBean(host);
if (ipRankingBean != null && ips != null && ips.length > 1) {
if (mProbingHosts.contains(host)) {
return;
}
mProbingHosts.add(host);
try {
mHttpDnsConfig.getWorker().execute(
new IPRankingTask(mSocketFactory, host, ips, ipRankingBean, new IPRankingCallback() {
@Override
public void onResult(String host, String[] sortedIps) {
mProbingHosts.remove(host);
if (IPRankingCallback != null) {
IPRankingCallback.onResult(host, sortedIps);
}
}
}));
} catch (Exception e) {
mProbingHosts.remove(host);
}
}
}
public void setIPRankingList(List<IPRankingBean> ipRankingBeanList) {
this.mIPRankingBeanList = ipRankingBeanList;
}
public void setSocketFactory(IPRankingTask.SpeedTestSocketFactory socketFactory) {
this.mSocketFactory = socketFactory;
}
private IPRankingBean getIPRankingBean(String host) {
if (mIPRankingBeanList != null && mIPRankingBeanList.size() > 0) {
ArrayList<IPRankingBean> list = new ArrayList<>(mIPRankingBeanList);
for (IPRankingBean item : list) {
if (host.equals(item.getHostName())) {
return item;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,63 @@
package com.alibaba.sdk.android.httpdns.ranking;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
/**
* ip优选实现
*/
public class IPRankingTask implements Runnable {
public interface SpeedTestSocketFactory {
Socket create();
}
private final SpeedTestSocketFactory mSocketFactory;
private final String mHost;
private final String[] mIps;
private final IPRankingBean mIPRankingBean;
private final IPRankingCallback mIPRankingCallback;
public IPRankingTask(SpeedTestSocketFactory socketFactory, String host, String[] ips,
IPRankingBean ipRankingBean, IPRankingCallback IPRankingCallback) {
this.mSocketFactory = socketFactory;
this.mHost = host;
this.mIps = ips;
this.mIPRankingBean = ipRankingBean;
this.mIPRankingCallback = IPRankingCallback;
}
@Override
public void run() {
int[] speeds = new int[mIps.length];
for (int i = 0; i < mIps.length; i++) {
speeds[i] = testConnectSpeed(mIps[i], mIPRankingBean.getPort());
}
String[] result = CommonUtil.sortIpsWithSpeeds(mIps, speeds);
if (mIPRankingCallback != null) {
mIPRankingCallback.onResult(mHost, result);
}
}
private int testConnectSpeed(String ip, int port) {
long start = System.currentTimeMillis();
long end = Long.MAX_VALUE;
try (Socket socket = mSocketFactory.create()) {
SocketAddress remoteAddress = new InetSocketAddress(ip, port);
socket.connect(remoteAddress, 5 * 1000);
end = System.currentTimeMillis();
} catch (IOException e) {
e.printStackTrace();
}
if (end == Long.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int) (end - start);
}
}

View File

@@ -0,0 +1,33 @@
package com.alibaba.sdk.android.httpdns.request;
/**
* 数据请求转异步
*/
public abstract class AsyncRequestTask<T> implements Runnable {
private final RequestCallback<T> mCallback;
public AsyncRequestTask(RequestCallback<T> callback) {
this.mCallback = callback;
}
/**
* 请求数据,不需要直接调用,除非想要同步获取请求数据
*/
public abstract T request() throws Throwable;
@Override
public void run() {
try {
T t = request();
if (mCallback != null) {
mCallback.onSuccess(t);
}
} catch (Throwable throwable) {
throwable.printStackTrace();
if (mCallback != null) {
mCallback.onFail(throwable);
}
}
}
}

View File

@@ -0,0 +1,73 @@
package com.alibaba.sdk.android.httpdns.request;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.observable.ObservableConstants;
import com.alibaba.sdk.android.httpdns.observable.ObservableManager;
import com.alibaba.sdk.android.httpdns.observable.event.BatchQueryHttpDnsApiEvent;
import com.alibaba.sdk.android.httpdns.resolve.ResolveHostResponse;
public class BatchResolveHttpRequestStatusWatcher implements HttpRequestWatcher.Watcher {
ObservableManager observableManager;
private BatchQueryHttpDnsApiEvent mBatchQueryHttpDnsApiEvent;
public BatchResolveHttpRequestStatusWatcher(ObservableManager observableManager) {
this.observableManager = observableManager;
}
@Override
public void onStart(HttpRequestConfig config) {
mBatchQueryHttpDnsApiEvent = new BatchQueryHttpDnsApiEvent();
}
@Override
public void onSuccess(HttpRequestConfig config, Object data) {
if (mBatchQueryHttpDnsApiEvent != null && observableManager != null) {
mBatchQueryHttpDnsApiEvent.setTag(config.isRetry(), config.getResolvingIpType(),
HttpRequestConfig.HTTP_SCHEMA.equals(config.getSchema()), config.isSignMode());
mBatchQueryHttpDnsApiEvent.setServerIp(config.getIp());
mBatchQueryHttpDnsApiEvent.setCostTime((int) (System.currentTimeMillis() - mBatchQueryHttpDnsApiEvent.getTimestamp()));
int resultIpType = 0x00;
if (data instanceof ResolveHostResponse) {
for (ResolveHostResponse.HostItem item : ((ResolveHostResponse) data).getItems()) {
if (item.getIpType() == RequestIpType.v4) {
resultIpType |= ObservableConstants.REQUEST_IP_TYPE_V4;
} else if (item.getIpType() == RequestIpType.v6) {
resultIpType |= ObservableConstants.REQUEST_IP_TYPE_V6;
}
}
}
mBatchQueryHttpDnsApiEvent.setIpType(resultIpType);
mBatchQueryHttpDnsApiEvent.setStatusCode(resultIpType == 0x00 ? 204 : 200);
observableManager.addObservableEvent(mBatchQueryHttpDnsApiEvent);
mBatchQueryHttpDnsApiEvent = null;
}
}
@Override
public void onFail(HttpRequestConfig config, Throwable throwable) {
if (mBatchQueryHttpDnsApiEvent != null && observableManager != null) {
mBatchQueryHttpDnsApiEvent.setTag(config.isRetry(), config.getResolvingIpType(),
HttpRequestConfig.HTTP_SCHEMA.equals(config.getSchema()), config.isSignMode());
mBatchQueryHttpDnsApiEvent.setServerIp(config.getIp());
mBatchQueryHttpDnsApiEvent.setCostTime((int) (System.currentTimeMillis() - mBatchQueryHttpDnsApiEvent.getTimestamp()));
mBatchQueryHttpDnsApiEvent.setIpType(0x00);
if (throwable instanceof HttpException) {
mBatchQueryHttpDnsApiEvent.setStatusCode(((HttpException) throwable).getCode());
String errorCode = throwable.getMessage();
mBatchQueryHttpDnsApiEvent.setErrorCode(TextUtils.isEmpty(errorCode) ? "Unknown" : errorCode);
} else {
mBatchQueryHttpDnsApiEvent.setStatusCode(-1);
mBatchQueryHttpDnsApiEvent.setErrorCode(throwable.getClass().getSimpleName() + ":" + throwable.getMessage());
}
observableManager.addObservableEvent(mBatchQueryHttpDnsApiEvent);
mBatchQueryHttpDnsApiEvent = null;
}
}
}

View File

@@ -0,0 +1,155 @@
package com.alibaba.sdk.android.httpdns.request;
import java.util.Arrays;
import org.json.JSONException;
import org.json.JSONObject;
/**
* 网络请求失败的异常
*/
public class HttpException extends Exception {
public static final int ERROR_CODE_403 = 403;
public static final int ERROR_CODE_400 = 400;
public static final int ERROR_CODE_500 = 500;
/**
* 用户服务level不匹配
* 用户有不同的level高level有一些专用的服务节点如果一个低level的用户向一个高level专用的服务节点请求就会返回此错误
* <p>
* 需要重试 切换服务IP
*/
public static final String ERROR_MSG_SERVICE_LEVEL_DENY = "ServiceLevelDeny";
/**
* 未用签名访问
* 不重试 不切换
* 生成空解析 缓存1小时
*/
public static final String ERROR_MSG_UNSIGNED = "UnsignedInterfaceDisabled";
/**
* 签名过期
* 不重试 不切换 sdk逻辑保证不应该过期
*/
public static final String ERROR_MSG_SIGNATURE_EXPIRED = "SignatureExpired";
/**
* 签名验证失败
* 不重试 不切换
*/
public static final String ERROR_MSG_INVALID_SIGNATURE = "InvalidSignature";
/**
* 账户服务level缺失
* 不重试 不切换
* 生成空解析 缓存1小时
*/
public static final String ERROR_MSG_INVALID_ACCOUNT = "InvalidAccount";
/**
* 账户不存在或者禁用
* 不重试 不切换
* 生成空解析 缓存1小时
*/
public static final String ERROR_MSG_ACCOUNT_NOT_EXISTS = "AccountNotExists";
/**
* 签名有效时间过长
* 不重试 不切换
*/
public static final String ERROR_MSG_INVALID_DURATION = "InvalidDuration";
/**
* 无效域名
* 不重试 不切换
*/
public static final String ERROR_MSG_INVALID_HOST = "InvalidHost";
public static final String ERROR_MSG_TAG = "code";
private final int mCode;
private HttpException(int code, String message) {
super(message);
this.mCode = code;
}
/**
* Http status code
*/
public int getCode() {
return mCode;
}
/**
* 创建异常
*
* @param code
* @param message 服务器返回的特定格式的数据
* @return
*/
public static HttpException create(int code, String message) {
return new HttpException(code, tryTranslateMessage(message));
}
private static String tryTranslateMessage(String message) {
JSONObject object;
try {
object = new JSONObject(message);
return object.getString(ERROR_MSG_TAG);
} catch (JSONException ignored) {
}
return message;
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
HttpException that = (HttpException)o;
return mCode == that.mCode && getMessage().equals(that.getMessage());
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] {mCode, getMessage()});
}
/**
* 是不是要切换服务IP
*/
public boolean shouldShiftServer() {
String msg = getMessage();
return !ERROR_MSG_UNSIGNED.equals(msg)
&& !ERROR_MSG_SIGNATURE_EXPIRED.equals(msg)
&& !ERROR_MSG_INVALID_SIGNATURE.equals(msg)
&& !ERROR_MSG_INVALID_ACCOUNT.equals(msg)
&& !ERROR_MSG_ACCOUNT_NOT_EXISTS.equals(msg)
&& !ERROR_MSG_INVALID_DURATION.equals(msg)
&& !ERROR_MSG_INVALID_HOST.equals(msg);
}
/**
* 这个错误是否建议重试
*/
public boolean shouldRetry() {
String msg = getMessage();
return !ERROR_MSG_UNSIGNED.equals(msg)
&& !ERROR_MSG_SIGNATURE_EXPIRED.equals(msg)
&& !ERROR_MSG_INVALID_SIGNATURE.equals(msg)
&& !ERROR_MSG_INVALID_ACCOUNT.equals(msg)
&& !ERROR_MSG_ACCOUNT_NOT_EXISTS.equals(msg)
&& !ERROR_MSG_INVALID_DURATION.equals(msg)
&& !ERROR_MSG_INVALID_HOST.equals(msg);
}
/**
* 这个错误是否要创建空缓存
*/
public boolean shouldCreateEmptyCache() {
String msg = getMessage();
return ERROR_MSG_UNSIGNED.equals(msg)
|| ERROR_MSG_INVALID_ACCOUNT.equals(msg)
|| ERROR_MSG_ACCOUNT_NOT_EXISTS.equals(msg);
}
}

View File

@@ -0,0 +1,121 @@
package com.alibaba.sdk.android.httpdns.request;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
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;
/**
* 网络请求
*/
public class HttpRequest<T> {
private HttpRequestConfig requestConfig;
private ResponseParser<T> translator;
protected HttpRequest() {
}
public HttpRequest(HttpRequestConfig requestConfig, ResponseParser<T> translator) {
this.requestConfig = requestConfig;
this.translator = translator;
}
/**
* 获取本次请求的配置
*
* @return
*/
public HttpRequestConfig getRequestConfig() {
return requestConfig;
}
/**
* 发起请求
*
* @return 返回的数据
* @throws Throwable 发生的错误
*/
public T request() throws Throwable {
HttpURLConnection conn = null;
InputStream in = null;
BufferedReader streamReader = null;
long start = System.currentTimeMillis();
String serverIp = requestConfig.getIp();
String url = requestConfig.url();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request url " + url);
}
try {
conn = (HttpURLConnection)new URL(url).openConnection();
conn.setReadTimeout(requestConfig.getTimeout());
conn.setConnectTimeout(requestConfig.getTimeout());
//设置通用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);
}
});
}
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
in = conn.getErrorStream();
if (in != null) {
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String errStr = readStringFrom(streamReader).toString();
throw HttpException.create(conn.getResponseCode(), errStr);
} else {
throw HttpException.create(conn.getResponseCode(), "");
}
} else {
in = conn.getInputStream();
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String responseStr = readStringFrom(streamReader).toString();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request success " + responseStr);
}
return translator.parse(serverIp, responseStr);
}
} catch (Throwable e) {
long cost = System.currentTimeMillis() - start;
HttpDnsLog.w("request " + url + " fail, cost " + cost, e);
throw e;
} finally {
if (conn != null) {
conn.disconnect();
}
try {
if (in != null) {
in.close();
}
if (streamReader != null) {
streamReader.close();
}
} catch (IOException ignored) {
}
}
}
/**
* 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;
}
}

View File

@@ -0,0 +1,169 @@
package com.alibaba.sdk.android.httpdns.request;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.impl.AESEncryptService;
import com.alibaba.sdk.android.httpdns.utils.Constants;
/**
* 网络请求的配置
*/
public class HttpRequestConfig {
public static final String HTTP_SCHEMA = "http://";
public static final String HTTPS_SCHEMA = "https://";
public static final String HTTPS_CERTIFICATE_HOSTNAME = "203.107.1.1";
/**
* 请求协议
*/
private String mSchema = HTTP_SCHEMA;
/**
* 服务器ip
*/
private String mIp;
/**
* 服务器端口
*/
private int mPort;
/**
* 请求路径包含query参数
*/
private String mPath;
/**
* 请求超时时间
*/
private int mTimeout = Constants.DEFAULT_TIMEOUT;
/**
* 服务器IP类型 只会是 v4 或者 v6
*/
private RequestIpType mIpType = RequestIpType.v4;
private String mUA = "";
//待解析的域名
private String mResolvingHost;
//待解析的域名的ip类型
private RequestIpType mResolvingIpType;
//是不是重试
private boolean isRetry = false;
private boolean isSignMode = false;
private AESEncryptService mAESEncryptService;
public HttpRequestConfig(String ip, int port, String path) {
this.mIp = ip;
this.mPort = port;
this.mPath = path;
}
public HttpRequestConfig(String schema, String ip, int port, String path, int timeout, RequestIpType ipType) {
this.mSchema = schema;
this.mIp = ip;
this.mPort = port;
this.mPath = path;
this.mTimeout = timeout;
this.mIpType = ipType;
}
public HttpRequestConfig(String schema, String ip, int port, String path, int timeout, RequestIpType ipType, boolean signMode) {
this.mSchema = schema;
this.mIp = ip;
this.mPort = port;
this.mPath = path;
this.mTimeout = timeout;
this.mIpType = ipType;
isSignMode = signMode;
}
public void setSchema(String schema) {
this.mSchema = schema;
}
public void setIp(String ip) {
this.mIp = ip;
}
public void setPort(int port) {
this.mPort = port;
}
public String getSchema() {
return this.mSchema;
}
public String getIp() {
return mIp;
}
public int getPort() {
return mPort;
}
public void setTimeout(int timeout) {
this.mTimeout = timeout;
}
public int getTimeout() {
return mTimeout;
}
public RequestIpType getIpType() {
return mIpType;
}
public String url() {
if (mIpType == RequestIpType.v6) {
//域名兜底,这里需要对域名做特殊处理
if (!TextUtils.isEmpty(mIp) && mIp.contains(".")) {
return mSchema + mIp + ":" + mPort + mPath;
}
return mSchema + "[" + mIp + "]" + ":" + mPort + mPath;
} else {
return mSchema + mIp + ":" + mPort + mPath;
}
}
public void setUA(String ua) {
mUA = ua;
}
public String getUA() {
return mUA;
}
public void setResolvingHost(String host) {
mResolvingHost = host;
}
public String getResolvingHost() {
return mResolvingHost;
}
public void setResolvingIpType(RequestIpType type) {
mResolvingIpType = type;
}
public RequestIpType getResolvingIpType() {
return mResolvingIpType;
}
public void setRetry() {
isRetry = true;
}
public boolean isRetry() {
return isRetry;
}
public boolean isSignMode() {
return isSignMode;
}
public AESEncryptService getAESEncryptService() {
return mAESEncryptService;
}
public void setAESEncryptService(AESEncryptService mAESEncryptService) {
this.mAESEncryptService = mAESEncryptService;
}
}

View File

@@ -0,0 +1,25 @@
package com.alibaba.sdk.android.httpdns.request;
import com.alibaba.sdk.android.httpdns.observable.ObservableManager;
public class HttpRequestFailWatcher implements HttpRequestWatcher.Watcher {
public HttpRequestFailWatcher(ObservableManager reportManager) {
}
@Override
public void onStart(HttpRequestConfig config) {
}
@Override
public void onSuccess(HttpRequestConfig config, Object data) {
}
@Override
public void onFail(HttpRequestConfig config, Throwable throwable) {
}
}

View File

@@ -0,0 +1,19 @@
package com.alibaba.sdk.android.httpdns.request;
/**
* 网络请求的异步任务
*/
public class HttpRequestTask<T> extends AsyncRequestTask<T> {
private final HttpRequest<T> mHttpRequest;
public HttpRequestTask(HttpRequest<T> httpRequest, RequestCallback<T> callback) {
super(callback);
this.mHttpRequest = httpRequest;
}
@Override
public T request() throws Throwable {
return mHttpRequest.request();
}
}

View File

@@ -0,0 +1,53 @@
package com.alibaba.sdk.android.httpdns.request;
/**
* 监听网络请求,用于附加业务逻辑
*/
public class HttpRequestWatcher<T> extends HttpRequestWrapper<T> {
private final Watcher mWatcher;
public HttpRequestWatcher(HttpRequest<T> request, Watcher watcher) {
super(request);
this.mWatcher = watcher;
}
@Override
public T request() throws Throwable {
try {
if (mWatcher != null) {
try {
mWatcher.onStart(getRequestConfig());
} catch (Exception e) {
e.printStackTrace();
}
}
T t = super.request();
if (mWatcher != null) {
try {
mWatcher.onSuccess(getRequestConfig(), t);
} catch (Exception e) {
e.printStackTrace();
}
}
return t;
} catch (Throwable throwable) {
if (mWatcher != null) {
try {
mWatcher.onFail(getRequestConfig(), throwable);
} catch (Exception e) {
e.printStackTrace();
}
}
throw throwable;
}
}
public interface Watcher {
void onStart(HttpRequestConfig config);
void onSuccess(HttpRequestConfig config, Object data);
void onFail(HttpRequestConfig config, Throwable throwable);
}
}

View File

@@ -0,0 +1,23 @@
package com.alibaba.sdk.android.httpdns.request;
/**
* 网络请求的包装类
*/
public class HttpRequestWrapper<T> extends HttpRequest<T> {
private final HttpRequest<T> mHttpRequest;
public HttpRequestWrapper(HttpRequest<T> request) {
this.mHttpRequest = request;
}
@Override
public HttpRequestConfig getRequestConfig() {
return mHttpRequest.getRequestConfig();
}
@Override
public T request() throws Throwable {
return mHttpRequest.request();
}
}

View File

@@ -0,0 +1,10 @@
package com.alibaba.sdk.android.httpdns.request;
/**
* http请求结果回调
*/
public interface RequestCallback<T> {
void onSuccess(T response);
void onFail(Throwable throwable);
}

View File

@@ -0,0 +1,8 @@
package com.alibaba.sdk.android.httpdns.request;
/**
* http响应解析
*/
public interface ResponseParser<T> {
T parse(String serverIp, String response) throws Throwable;
}

View File

@@ -0,0 +1,44 @@
package com.alibaba.sdk.android.httpdns.request;
/**
* 失败时 重试请求
*/
public class RetryHttpRequest<T> extends HttpRequestWrapper<T> {
private int retryCount;
public RetryHttpRequest(HttpRequest<T> request, int retryCount) {
super(request);
this.retryCount = retryCount;
}
@Override
public T request() throws Throwable {
while (true) {
try {
return super.request();
} catch (Throwable throwable) {
if (shouldRetry(throwable)) {
if (retryCount > 0) {
//可观测需要
getRequestConfig().setRetry();
retryCount--;
} else {
throw throwable;
}
} else {
throw throwable;
}
}
}
}
private boolean shouldRetry(Throwable throwable) {
if (throwable instanceof HttpException) {
return ((HttpException)throwable).shouldRetry();
} else {
// 其它异常都可以重试
return true;
}
}
}

View File

@@ -0,0 +1,81 @@
package com.alibaba.sdk.android.httpdns.request;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.observable.ObservableManager;
import com.alibaba.sdk.android.httpdns.observable.event.QueryHttpDnsApiEvent;
import com.alibaba.sdk.android.httpdns.resolve.ResolveHostResponse;
public class SingleResolveHttpRequestStatusWatcher implements HttpRequestWatcher.Watcher {
private final ObservableManager observableManager;
private QueryHttpDnsApiEvent mQueryHttpDnsApiEvent = null;
public SingleResolveHttpRequestStatusWatcher(ObservableManager observableManager) {
this.observableManager = observableManager;
}
@Override
public void onStart(HttpRequestConfig config) {
mQueryHttpDnsApiEvent = new QueryHttpDnsApiEvent();
}
@Override
public void onSuccess(HttpRequestConfig config, Object data) {
if (mQueryHttpDnsApiEvent != null && observableManager != null) {
mQueryHttpDnsApiEvent.setTag(config.isRetry(), config.getResolvingIpType(),
HttpRequestConfig.HTTP_SCHEMA.equals(config.getSchema()), config.isSignMode());
mQueryHttpDnsApiEvent.setHostName(config.getResolvingHost());
mQueryHttpDnsApiEvent.setServerIp(config.getIp());
mQueryHttpDnsApiEvent.setCostTime((int) (System.currentTimeMillis() - mQueryHttpDnsApiEvent.getTimestamp()));
int resultIpType = 0x00;
if (data instanceof ResolveHostResponse) {
ResolveHostResponse response = (ResolveHostResponse) data;
String[] ips = null, ipv6s = null;
for (ResolveHostResponse.HostItem item : response.getItems()) {
if (item.getIpType() == RequestIpType.v4) {
resultIpType |= 0x01;
ips = item.getIps();
} else if (item.getIpType() == RequestIpType.v6) {
resultIpType |= 0x02;
ipv6s = item.getIps();
}
}
mQueryHttpDnsApiEvent.setHttpDnsIps(ips, ipv6s);
}
mQueryHttpDnsApiEvent.setIpType(resultIpType);
mQueryHttpDnsApiEvent.setStatusCode(resultIpType == 0x00 ? 204 : 200);
observableManager.addObservableEvent(mQueryHttpDnsApiEvent);
mQueryHttpDnsApiEvent = null;
}
}
@Override
public void onFail(HttpRequestConfig config, Throwable throwable) {
if (mQueryHttpDnsApiEvent != null && observableManager != null) {
mQueryHttpDnsApiEvent.setTag(config.isRetry(), config.getResolvingIpType(),
HttpRequestConfig.HTTP_SCHEMA.equals(config.getSchema()), config.isSignMode());
mQueryHttpDnsApiEvent.setHostName(config.getResolvingHost());
mQueryHttpDnsApiEvent.setServerIp(config.getIp());
mQueryHttpDnsApiEvent.setCostTime((int) (System.currentTimeMillis() - mQueryHttpDnsApiEvent.getTimestamp()));
mQueryHttpDnsApiEvent.setIpType(0x00);
if (throwable instanceof HttpException) {
mQueryHttpDnsApiEvent.setStatusCode(((HttpException) throwable).getCode());
String errorCode = throwable.getMessage();
mQueryHttpDnsApiEvent.setErrorCode(TextUtils.isEmpty(errorCode) ? "Unknown" : errorCode);
} else {
mQueryHttpDnsApiEvent.setStatusCode(-1);
mQueryHttpDnsApiEvent.setErrorCode(throwable.getClass().getSimpleName() + ":" + throwable.getMessage());
}
observableManager.addObservableEvent(mQueryHttpDnsApiEvent);
mQueryHttpDnsApiEvent = null;
}
}
}

View File

@@ -0,0 +1,52 @@
package com.alibaba.sdk.android.httpdns.request;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.observable.ObservableManager;
import com.alibaba.sdk.android.httpdns.observable.event.UpdateRegionServerIpsEvent;
public class UpdateRegionServerHttpRequestStatusWatcher implements HttpRequestWatcher.Watcher {
private final ObservableManager observableManager;
private int mScenes;
private UpdateRegionServerIpsEvent mUpdateRegionServerIpsEvent;
public UpdateRegionServerHttpRequestStatusWatcher(int scenes, ObservableManager observableManager) {
this.observableManager = observableManager;
mScenes = scenes;
}
@Override
public void onStart(HttpRequestConfig config) {
mUpdateRegionServerIpsEvent = new UpdateRegionServerIpsEvent();
mUpdateRegionServerIpsEvent.setTag(mScenes);
}
@Override
public void onSuccess(HttpRequestConfig config, Object data) {
if (mUpdateRegionServerIpsEvent != null && observableManager != null) {
mUpdateRegionServerIpsEvent.setCostTime((int) (System.currentTimeMillis() - mUpdateRegionServerIpsEvent.getTimestamp()));
mUpdateRegionServerIpsEvent.setStatusCode(200);
observableManager.addObservableEvent(mUpdateRegionServerIpsEvent);
mUpdateRegionServerIpsEvent = null;
}
}
@Override
public void onFail(HttpRequestConfig config, Throwable throwable) {
if (mUpdateRegionServerIpsEvent != null && observableManager != null) {
mUpdateRegionServerIpsEvent.setCostTime((int) (System.currentTimeMillis() - mUpdateRegionServerIpsEvent.getTimestamp()));
if (throwable instanceof HttpException) {
mUpdateRegionServerIpsEvent.setStatusCode(((HttpException) throwable).getCode());
String errorCode = throwable.getMessage();
mUpdateRegionServerIpsEvent.setErrorCode(TextUtils.isEmpty(errorCode) ? "Unknown" : errorCode);
} else {
mUpdateRegionServerIpsEvent.setStatusCode(-1);
mUpdateRegionServerIpsEvent.setErrorCode(throwable.getClass().getSimpleName() + ":" + throwable.getMessage());
}
observableManager.addObservableEvent(mUpdateRegionServerIpsEvent);
mUpdateRegionServerIpsEvent = null;
}
}
}

View File

@@ -0,0 +1,191 @@
package com.alibaba.sdk.android.httpdns.resolve;
import com.alibaba.sdk.android.httpdns.HTTPDNSResultWrapper;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.impl.HostResolveLocker;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.net.NetworkStateManager;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingCallback;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingService;
import com.alibaba.sdk.android.httpdns.request.HttpException;
import com.alibaba.sdk.android.httpdns.request.RequestCallback;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
import java.util.ArrayList;
import java.util.List;
/**
* 批量解析域名
*
* @author zonglin.nzl
* @date 2020/12/11
*/
public class BatchResolveHostService {
private final HttpDnsConfig mHttpDnsConfig;
private final ResolveHostResultRepo mResultRepo;
private final ResolveHostRequestHandler mRequestHandler;
private final IPRankingService mIpIPRankingService;
private final HostFilter mHostFilter;
private final HostResolveLocker mAsyncLocker;
public BatchResolveHostService(HttpDnsConfig config, ResolveHostResultRepo repo,
ResolveHostRequestHandler requestHandler,
IPRankingService ipIPRankingService, HostFilter filter,
HostResolveLocker locker) {
this.mHttpDnsConfig = config;
this.mResultRepo = repo;
this.mRequestHandler = requestHandler;
this.mIpIPRankingService = ipIPRankingService;
this.mHostFilter = filter;
this.mAsyncLocker = locker;
}
/**
* 批量解析域名
*
* @param hostList 待解析域名列表
* @param type 解析类型
*/
public void batchResolveHostAsync(final List<String> hostList, final RequestIpType type) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("batch resolve host " + hostList.toString() + " " + type);
}
ArrayList<String> hostsRequestV4 = new ArrayList<>();
ArrayList<String> hostsRequestV6 = new ArrayList<>();
ArrayList<String> hostsRequestBoth = new ArrayList<>();
for (String host : hostList) {
if (!CommonUtil.isAHost(host)
|| CommonUtil.isAnIP(host)
|| this.mHostFilter.isFiltered(host)) {
// 过滤掉不需要的域名
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("resolve ignore host as not invalid " + host);
}
continue;
}
// 过滤掉有缓存的域名
String networkKey = NetworkStateManager.getInstance().getCurrentNetworkKey();
if (type == RequestIpType.v4) {
HTTPDNSResultWrapper result = mResultRepo.getIps(host, type, networkKey);
if (result == null || result.isExpired()) {
// 需要解析
hostsRequestV4.add(host);
}
} else if (type == RequestIpType.v6) {
HTTPDNSResultWrapper result = mResultRepo.getIps(host, type, networkKey);
if (result == null || result.isExpired()) {
// 需要解析
hostsRequestV6.add(host);
}
} else {
HTTPDNSResultWrapper resultV4 = mResultRepo.getIps(host, RequestIpType.v4, networkKey);
HTTPDNSResultWrapper resultV6 = mResultRepo.getIps(host, RequestIpType.v6, networkKey);
if ((resultV4 == null || resultV4.isExpired()) && (resultV6 == null || resultV6.isExpired())) {
// 都需要解析
hostsRequestBoth.add(host);
} else if (resultV4 == null || resultV4.isExpired()) {
hostsRequestV4.add(host);
} else if (resultV6 == null || resultV6.isExpired()) {
hostsRequestV6.add(host);
}
}
}
batchResolveHost(hostsRequestV4, RequestIpType.v4);
batchResolveHost(hostsRequestV6, RequestIpType.v6);
batchResolveHost(hostsRequestBoth, RequestIpType.both);
}
private void batchResolveHost(ArrayList<String> hostList, final RequestIpType type) {
if (hostList == null || hostList.isEmpty()) {
return;
}
ArrayList<String> allHosts = new ArrayList<>(hostList);
// 预解析每次最多5个域名
final int maxCountPerRequest = 5;
int requestCount = (hostList.size() + maxCountPerRequest - 1) / maxCountPerRequest;
for (int i = 0; i < requestCount; i++) {
final ArrayList<String> targetHost = new ArrayList<>();
while (targetHost.size() < maxCountPerRequest && allHosts.size() > 0) {
String host = allHosts.remove(0);
if (mAsyncLocker.beginResolve(host, type, null)) {
targetHost.add(host);
} else {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("resolve ignore host as already interpret " + host);
}
}
}
if (targetHost.size() <= 0) {
continue;
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("resolve host " + targetHost.toString() + " " + type);
}
final String region = mHttpDnsConfig.getRegion();
mRequestHandler.requestResolveHost(targetHost, type,
new RequestCallback<ResolveHostResponse>() {
@Override
public void onSuccess(final ResolveHostResponse resolveHostResponse) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("resolve hosts for " + targetHost.toString() + " " + type
+ " return " + resolveHostResponse.toString());
}
String networkKey = NetworkStateManager.getInstance().getCurrentNetworkKey();
mResultRepo.save(region, resolveHostResponse, networkKey);
if (type == RequestIpType.v4 || type == RequestIpType.both) {
for (final ResolveHostResponse.HostItem item
: resolveHostResponse.getItems()) {
if (item.getIpType() == RequestIpType.v4) {
mIpIPRankingService.probeIpv4(item.getHost(), item.getIps(),
new IPRankingCallback() {
@Override
public void onResult(String host, String[] sortedIps) {
String networkKey = NetworkStateManager.getInstance().getCurrentNetworkKey();
mResultRepo.update(item.getHost(),
item.getIpType(),
networkKey, sortedIps);
}
});
}
}
}
for (String host : targetHost) {
mAsyncLocker.endResolve(host, type, null);
}
}
@Override
public void onFail(Throwable throwable) {
HttpDnsLog.w("resolve hosts for " + targetHost.toString() + " fail",
throwable);
if (throwable instanceof Exception) {
String query = "4";
if (type == RequestIpType.v6) {
query = "6";
}else if (type == RequestIpType.both) {
query = "4,6";
}
String errorMsg = (throwable instanceof HttpException) ? throwable.getMessage() : throwable.toString();
HttpDnsLog.w("RESOLVE FAIL, HOST:" + targetHost + ", QUERY:" + query
+ ", Msg:" + errorMsg);
}
if (throwable instanceof HttpException
&& ((HttpException)throwable).shouldCreateEmptyCache()) {
ResolveHostResponse emptyResponse = ResolveHostResponse.createEmpty(
targetHost, type, 60 * 60);
String networkKey = NetworkStateManager.getInstance().getCurrentNetworkKey();
mResultRepo.save(region, emptyResponse, networkKey);
}
for (String host : targetHost) {
mAsyncLocker.endResolve(host, type, null);
}
}
});
}
}
}

View File

@@ -0,0 +1,70 @@
package com.alibaba.sdk.android.httpdns.resolve;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.serverip.RegionServerScheduleService;
/**
* 域名解析策略控制
*/
public class CategoryController implements StatusControl {
private Status mStatus = Status.NORMAL;
private final NormalResolveCategory mNormal;
private final SniffResolveCategory mSniff;
public CategoryController(HttpDnsConfig config, RegionServerScheduleService scheduleService) {
mNormal = new NormalResolveCategory(config, scheduleService, this);
mSniff = new SniffResolveCategory(config, scheduleService, this);
}
public ResolveHostCategory getCategory() {
if (mStatus == Status.DISABLE) {
return mSniff;
}
return mNormal;
}
@Override
public void turnDown() {
switch (mStatus) {
case NORMAL:
mStatus = Status.PRE_DISABLE;
break;
case PRE_DISABLE:
mStatus = Status.DISABLE;
break;
default:
break;
}
}
@Override
public void turnUp() {
mStatus = Status.NORMAL;
}
/**
* 重置策略
*/
public void reset() {
mStatus = Status.NORMAL;
mSniff.reset();
}
/**
* 设置嗅探模式请求间隔
*
*/
public void setSniffTimeInterval(int timeInterval) {
mSniff.setInterval(timeInterval);
}
/**
* 策略状态只有disable状态会使用嗅探模式
*/
enum Status {
NORMAL,
PRE_DISABLE,
DISABLE
}
}

View File

@@ -0,0 +1,22 @@
package com.alibaba.sdk.android.httpdns.resolve;
import com.alibaba.sdk.android.httpdns.DegradationFilter;
import com.alibaba.sdk.android.httpdns.NotUseHttpDnsFilter;
public class HostFilter {
DegradationFilter mFilter;
NotUseHttpDnsFilter mNotUseHttpDnsFilter;
public boolean isFiltered(String host) {
return (mNotUseHttpDnsFilter != null && mNotUseHttpDnsFilter.notUseHttpDns(host)) || (mFilter != null && mFilter.shouldDegradeHttpDNS(host));
}
@Deprecated
public void setFilter(DegradationFilter filter) {
this.mFilter = filter;
}
public void setFilter(NotUseHttpDnsFilter filter) {
mNotUseHttpDnsFilter = filter;
}
}

View File

@@ -0,0 +1,47 @@
package com.alibaba.sdk.android.httpdns.resolve;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
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.request.SingleResolveHttpRequestStatusWatcher;
import com.alibaba.sdk.android.httpdns.serverip.RegionServerScheduleService;
/**
* 域名解析的一般策略
*/
public class NormalResolveCategory implements ResolveHostCategory {
private final HttpDnsConfig mHttpDnsConfig;
private final StatusControl mStatusControl;
private final RegionServerScheduleService mScheduleService;
public NormalResolveCategory(HttpDnsConfig config, RegionServerScheduleService scheduleService, StatusControl statusControl) {
this.mScheduleService = scheduleService;
this.mStatusControl = statusControl;
mHttpDnsConfig = config;
}
@Override
public void resolve(HttpDnsConfig config, HttpRequestConfig requestConfig,
RequestCallback<ResolveHostResponse> callback) {
HttpRequest<ResolveHostResponse> request = new HttpRequest<>(requestConfig,
new ResolveHostResponseParser(requestConfig.getAESEncryptService()));
request = new HttpRequestWatcher<>(request, new SingleResolveHttpRequestStatusWatcher(
mHttpDnsConfig.getObservableManager()));
// 切换服务IP更新服务IP
request = new HttpRequestWatcher<>(request, new ShiftServerWatcher(config,
mScheduleService,
mStatusControl));
// 重试一次
request = new RetryHttpRequest<>(request, 1);
try {
config.getResolveWorker().execute(new HttpRequestTask<>(request, callback));
} catch (Throwable e) {
callback.onFail(e);
}
}
}

View File

@@ -0,0 +1,242 @@
package com.alibaba.sdk.android.httpdns.resolve;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.sdk.android.httpdns.HTTPDNSResultWrapper;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.cache.HostRecord;
public class ResolveHostCache {
/**
* v4的解析记录
*/
private final ConcurrentHashMap<String, HostRecord> mV4Records = new ConcurrentHashMap<>();
/**
* v6的解析记录
*/
private final ConcurrentHashMap<String, HostRecord> mV6Records = new ConcurrentHashMap<>();
/**
* v4的返回结果
*/
private final ConcurrentHashMap<String, HTTPDNSResultWrapper> mV4HttpDnsResults
= new ConcurrentHashMap<>();
/**
* v6的返回结果
*/
private final ConcurrentHashMap<String, HTTPDNSResultWrapper> mV6HttpDnsResults
= new ConcurrentHashMap<>();
/**
* 同时解析4 6的结果
*/
private final ConcurrentHashMap<String, HTTPDNSResultWrapper> mBothHttpDnsResults =
new ConcurrentHashMap<>();
public HTTPDNSResultWrapper getResult(String host, RequestIpType type) {
HTTPDNSResultWrapper result = obtainHttpResult(host, type);
result = buildHttpResult(host, type, result);
cacheResult(host, type, result);
return result;
}
private HTTPDNSResultWrapper obtainHttpResult(String host, RequestIpType type) {
HTTPDNSResultWrapper result = null;
switch (type) {
case v6:
result = mV6HttpDnsResults.get(host);
break;
case v4:
result = mV4HttpDnsResults.get(host);
break;
case both:
result = mBothHttpDnsResults.get(host);
break;
}
return result;
}
private void cacheResult(String host, RequestIpType type, HTTPDNSResultWrapper result) {
if (result != null) {
switch (type) {
case v6:
mV6HttpDnsResults.put(host, result);
break;
case v4:
mV4HttpDnsResults.put(host, result);
break;
case both:
mBothHttpDnsResults.put(host, result);
break;
}
}
}
private HTTPDNSResultWrapper buildHttpResult(String host, RequestIpType type, HTTPDNSResultWrapper result) {
HostRecord record;
switch (type) {
case v6:
record = mV6Records.get(host);
if (record != null) {
if (result == null) {
result = new HTTPDNSResultWrapper(host);
}
result.update(record);
}
break;
case v4:
record = mV4Records.get(host);
if (record != null) {
if (result == null) {
result = new HTTPDNSResultWrapper(host);
}
result.update(record);
}
break;
default:
record = mV4Records.get(host);
HostRecord recordv6 = mV6Records.get(host);
if (record == null || recordv6 == null) {
return result;
}
if (result == null) {
result = new HTTPDNSResultWrapper(host);
}
ArrayList<HostRecord> records = new ArrayList<>();
records.add(record);
records.add(recordv6);
result.update(records);
break;
}
return result;
}
public HostRecord update(String region, String host, RequestIpType type, String extra,
String cacheKey, String[] ips, int ttl, String serverIp, String noIpCode) {
HostRecord record = null;
switch (type) {
case v4:
record = mV4Records.get(host);
if (record == null) {
record = HostRecord.create(region, host, type, extra, cacheKey, ips, ttl, serverIp, noIpCode);
mV4Records.put(host, record);
} else {
record.setRegion(region);
record.setQueryTime(System.currentTimeMillis());
record.setIps(ips);
record.setTtl(ttl);
record.setExtra(extra);
record.setFromDB(false);
record.setServerIp(serverIp);
record.setNoIpCode(noIpCode);
}
break;
case v6:
record = mV6Records.get(host);
if (record == null) {
record = HostRecord.create(region, host, type, extra, cacheKey, ips, ttl, serverIp, noIpCode);
mV6Records.put(host, record);
} else {
record.setRegion(region);
record.setQueryTime(System.currentTimeMillis());
record.setIps(ips);
record.setTtl(ttl);
record.setExtra(extra);
record.setFromDB(false);
record.setServerIp(serverIp);
record.setNoIpCode(noIpCode);
}
break;
default:
throw new IllegalStateException("type should be v4 or b6");
}
return record;
}
public void put(HostRecord record) {
if (record.getType() == RequestIpType.v4.ordinal()) {
mV4Records.put(record.getHost(), record);
} else if (record.getType() == RequestIpType.v6.ordinal()) {
mV6Records.put(record.getHost(), record);
}
}
public HostRecord updateIps(String host, RequestIpType type, String[] ips) {
HostRecord record = null;
switch (type) {
case v4:
record = mV4Records.get(host);
if (record == null) {
return null;
}
record.setIps(ips);
break;
case v6:
record = mV6Records.get(host);
if (record == null) {
return null;
}
record.setIps(ips);
break;
default:
throw new IllegalStateException("type should be v4 or b6");
}
return record;
}
public List<HostRecord> clear() {
ArrayList<HostRecord> list = new ArrayList<>();
list.addAll(mV4Records.values());
list.addAll(mV6Records.values());
mV4Records.clear();
mV6Records.clear();
mV4HttpDnsResults.clear();
mV6HttpDnsResults.clear();
mBothHttpDnsResults.clear();
return list;
}
public List<HostRecord> clear(List<String> hosts) {
ArrayList<HostRecord> records = new ArrayList<>();
for (String host : hosts) {
HostRecord tmp = mV4Records.remove(host);
if (tmp != null) {
records.add(tmp);
}
}
for (String host : hosts) {
HostRecord tmp = mV6Records.remove(host);
if (tmp != null) {
records.add(tmp);
}
}
for (String host : hosts) {
mV4HttpDnsResults.remove(host);
mV6HttpDnsResults.remove(host);
mBothHttpDnsResults.remove(host);
}
return records;
}
public HashMap<String, RequestIpType> getAllHostNotEmptyResult() {
HashMap<String, RequestIpType> all = new HashMap<>();
for (HostRecord record : mV4Records.values()) {
if (record.getIps() != null && record.getIps().length > 0) {
all.put(record.getHost(), RequestIpType.v4);
}
}
for (HostRecord record : mV6Records.values()) {
if (record.getIps() != null && record.getIps().length > 0) {
RequestIpType type = all.get(record.getHost());
if (type == null) {
all.put(record.getHost(), RequestIpType.v6);
} else {
all.put(record.getHost(), RequestIpType.both);
}
}
}
return all;
}
}

View File

@@ -0,0 +1,114 @@
package com.alibaba.sdk.android.httpdns.resolve;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.cache.HostRecord;
import com.alibaba.sdk.android.httpdns.net.NetworkStateManager;
/**
* 缓存组管理器,支持网络隔离缓存
*/
public class ResolveHostCacheGroup {
/**
* 所有缓存统一管理包括网络隔离缓存和SDNS缓存
*/
private final HashMap<String, ResolveHostCache> mCaches = new HashMap<>();
private final Object lock = new Object();
private static final String NETWORK_KEY_PREFIX = "emasInner_";
public ResolveHostCache getCache(String cacheKey) {
if (cacheKey == null || cacheKey.isEmpty()) {
// 普通解析使用当前网络标识
cacheKey = NetworkStateManager.getInstance().getCurrentNetworkKey();
}
// 统一的缓存获取逻辑
ResolveHostCache cache = mCaches.get(cacheKey);
if (cache == null) {
synchronized (lock) {
cache = mCaches.get(cacheKey);
if (cache == null) {
cache = new ResolveHostCache();
mCaches.put(cacheKey, cache);
}
}
}
return cache;
}
/**
* 获取所有网络缓存中的域名排除SDNS缓存
*/
public HashMap<String, RequestIpType> getAllHostFromNetworkCaches() {
HashMap<String, RequestIpType> allHosts = new HashMap<>();
synchronized (lock) {
for (Map.Entry<String, ResolveHostCache> entry : mCaches.entrySet()) {
String cacheKey = entry.getKey();
if (!cacheKey.startsWith(NETWORK_KEY_PREFIX)) {
continue;
}
ResolveHostCache cache = entry.getValue();
HashMap<String, RequestIpType> hosts = cache.getAllHostNotEmptyResult();
for (Map.Entry<String, RequestIpType> hostEntry : hosts.entrySet()) {
String host = hostEntry.getKey();
RequestIpType type = hostEntry.getValue();
if (!allHosts.containsKey(host)) {
allHosts.put(host, type);
continue;
}
RequestIpType mergedType = mergeIpTypes(allHosts.get(host), type);
allHosts.put(host, mergedType);
}
}
}
return allHosts;
}
private RequestIpType mergeIpTypes(RequestIpType type1, RequestIpType type2) {
if (type1 == RequestIpType.both || type2 == RequestIpType.both) {
return RequestIpType.both;
}
if ((type1 == RequestIpType.v4 && type2 == RequestIpType.v6) ||
(type1 == RequestIpType.v6 && type2 == RequestIpType.v4)) {
return RequestIpType.both;
}
return type1;
}
public List<HostRecord> clearAll() {
ArrayList<HostRecord> records = new ArrayList<>();
if (mCaches.size() > 0) {
synchronized (lock) {
for (ResolveHostCache cache : mCaches.values()) {
records.addAll(cache.clear());
}
}
}
return records;
}
public List<HostRecord> clearAll(List<String> hosts) {
ArrayList<HostRecord> records = new ArrayList<>();
if (mCaches.size() > 0) {
synchronized (lock) {
for (ResolveHostCache cache : mCaches.values()) {
records.addAll(cache.clear(hosts));
}
}
}
return records;
}
}

View File

@@ -0,0 +1,19 @@
package com.alibaba.sdk.android.httpdns.resolve;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.request.HttpRequestConfig;
import com.alibaba.sdk.android.httpdns.request.RequestCallback;
/**
* 域名解析策略接口
*/
public interface ResolveHostCategory {
/**
* 解析域名
* @param config {@link HttpDnsConfig}
* @param requestConfig {@link HttpRequestConfig}
* @param callback {@link RequestCallback<ResolveHostResponse>}
*/
void resolve(HttpDnsConfig config, HttpRequestConfig requestConfig,
RequestCallback<ResolveHostResponse> callback);
}

View File

@@ -0,0 +1,287 @@
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;
public class ResolveHostHelper {
public static HttpRequestConfig getConfig(HttpDnsConfig config, String host,
RequestIpType type,
Map<String, String> extras, String cacheKey,
Map<String, String> globalParams,
SignService signService,
AESEncryptService encryptService) {
HashMap<String, String> extraArgs = null;
if (cacheKey != null) {
extraArgs = new HashMap<>();
if (globalParams != null) {
extraArgs.putAll(globalParams);
}
if (extras != null) {
extraArgs.putAll(extras);
}
}
String path = getPath(config, host, type, extraArgs, signService, encryptService);
HttpRequestConfig requestConfig = getHttpRequestConfig(config, path, signService.isSignMode());
requestConfig.setUA(config.getUA());
requestConfig.setAESEncryptService(encryptService);
return requestConfig;
}
public static String getPath(HttpDnsConfig config, String host, RequestIpType type,
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 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;
}
}
} 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 "";
}
}
private static boolean checkKey(String s) {
return s.matches("[a-zA-Z0-9\\-_]+");
}
private static boolean checkValue(String s) {
return s.matches("[a-zA-Z0-9\\-_=]+");
}
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 = stringBuilder.toString();
String path = getPath(config, host, type, null, signService, encryptService);
HttpRequestConfig requestConfig = getHttpRequestConfig(config, path, signService.isSignMode());
requestConfig.setUA(config.getUA());
requestConfig.setAESEncryptService(encryptService);
return requestConfig;
}
private static HttpRequestConfig getHttpRequestConfig(HttpDnsConfig config, String path, Boolean isSignMode) {
if (config.getNetworkDetector() != null && config.getNetworkDetector().getNetType(
config.getContext()) == NetType.v6) {
return new HttpRequestConfig(config.getSchema(),
config.getCurrentServer().getServerIpForV6(),
config.getCurrentServer().getPortForV6(), path, config.getTimeout(),
RequestIpType.v6, isSignMode);
} else {
return new HttpRequestConfig(config.getSchema(),
config.getCurrentServer().getServerIp(), config.getCurrentServer().getPort(), path,
config.getTimeout(), RequestIpType.v4, isSignMode);
}
}
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) {
return "";
} else {
return "&sid=" + sessionId;
}
}
}

View File

@@ -0,0 +1,114 @@
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;
/**
* 发起域名解析请求
*/
public class ResolveHostRequestHandler {
private final HttpDnsConfig mHttpDnsConfig;
private final RegionServerScheduleService mScheduleService;
private final CategoryController mCategoryController;
private final HashMap<String, String> mGlobalParams;
private final SignService mSignService;
private final AESEncryptService mAESEncryptService;
public ResolveHostRequestHandler(HttpDnsConfig config, RegionServerScheduleService scheduleService,
SignService signService, AESEncryptService aesEncryptService) {
this.mHttpDnsConfig = config;
this.mScheduleService = scheduleService;
this.mCategoryController = new CategoryController(config, scheduleService);
this.mGlobalParams = new HashMap<>();
this.mSignService = signService;
this.mAESEncryptService = aesEncryptService;
}
public void requestResolveHost(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()) {
HttpDnsLog.d("start resolve ip request for " + host + " " + type);
}
mCategoryController.getCategory().resolve(mHttpDnsConfig, requestConfig, callback);
}
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 (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);
}
}
/**
* 重置状态
*/
public void resetStatus() {
mCategoryController.reset();
}
/**
* 设置嗅探模式的请求时间间隔
*/
public void setSniffTimeInterval(int timeInterval) {
mCategoryController.setSniffTimeInterval(timeInterval);
}
/**
* 设置sdns的全局参数
*/
public void setSdnsGlobalParams(Map<String, String> params) {
this.mGlobalParams.clear();
if (params != null) {
this.mGlobalParams.putAll(params);
}
}
/**
* 清除sdns的全局参数
*/
public void clearSdnsGlobalParams() {
this.mGlobalParams.clear();
}
}

View File

@@ -0,0 +1,214 @@
package com.alibaba.sdk.android.httpdns.resolve;
import android.text.TextUtils;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* 解析的结果
*/
public class ResolveHostResponse {
private final ArrayList<HostItem> mHostItems;
private String mServerIp;
public ResolveHostResponse(ArrayList<HostItem> items, String serverIp) {
this.mHostItems = items;
mServerIp = serverIp;
}
public HostItem getHostItem(String host, RequestIpType type) {
for (HostItem item : mHostItems) {
if (item.mHost.equals(host) && item.mIpType == type) {
return item;
}
}
return null;
}
public List<HostItem> getItems() {
return mHostItems;
}
public String getServerIp() {
return mServerIp;
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder();
if (mHostItems != null) {
for (int i = 0; i < mHostItems.size(); i++) {
ret.append(mHostItems.get(i).toString());
if (i != mHostItems.size() - 1) {
ret.append("\n");
}
}
}
return ret.toString();
}
public static class HostItem {
private final String mHost;
private final RequestIpType mIpType;
private final String[] mIps;
private final int mTtl;
private final String mExtra;
private final String noIpCode;
public HostItem(String host, RequestIpType type, String[] ips, int ttl, String extra, String noIpCode) {
this.mHost = host;
this.mIpType = type;
this.mIps = ips;
if (ttl <= 0) {
this.mTtl = 60;
} else {
this.mTtl = ttl;
}
this.mExtra = extra;
this.noIpCode = noIpCode;
}
public String getHost() {
return mHost;
}
public RequestIpType getIpType() {
return mIpType;
}
public String[] getIps() {
return mIps;
}
public int getTtl() {
return mTtl;
}
public String getExtra() {
return mExtra;
}
public String getNoIpCode() {
return noIpCode;
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder(
"host: " + mHost + " ip cnt: " + (mIps != null ? mIps.length : 0) + " ttl: "
+ mTtl);
if (mIps != null) {
for (String ip : mIps) {
ret.append("\n ip: ").append(ip);
}
}
ret.append("\n extra: ").append(mExtra);
ret.append("\n noIpCode: ").append(noIpCode);
return ret.toString();
}
}
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");
}
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);
}
}
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);
}
}
}
}
return new ResolveHostResponse(items, serverIp);
}
public static ResolveHostResponse createEmpty(List<String> hostList, RequestIpType type,
int ttl) {
ArrayList<HostItem> list = new ArrayList<>();
for (String host : hostList) {
if (type == RequestIpType.v4 || type == RequestIpType.both) {
list.add(new HostItem(host, RequestIpType.v4, null, ttl, null, null));
}
if (type == RequestIpType.v6 || type == RequestIpType.both) {
list.add(new HostItem(host, RequestIpType.v6, null, ttl, null, null));
}
}
return new ResolveHostResponse(list, "");
}
}

View File

@@ -0,0 +1,68 @@
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 ResolveHostResponseParser(AESEncryptService aesEncryptService) {
mAESEncryptService = aesEncryptService;
}
@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");
}
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request success " + data);
}
return ResolveHostResponse.fromResponse(serverIp, data);
}
}

View File

@@ -0,0 +1,313 @@
package com.alibaba.sdk.android.httpdns.resolve;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.alibaba.sdk.android.httpdns.CacheTtlChanger;
import com.alibaba.sdk.android.httpdns.HTTPDNSResultWrapper;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.cache.HostRecord;
import com.alibaba.sdk.android.httpdns.cache.RecordDBHelper;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingCallback;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingService;
/**
* 域名解析结果
*/
public class ResolveHostResultRepo {
private final RecordDBHelper mDBHelper;
private boolean mEnableCache = false;
private final HttpDnsConfig mHttpDnsConfig;
private final IPRankingService mIpIPRankingService;
private final ResolveHostCacheGroup mCacheGroup;
private CacheTtlChanger mCacheTtlChanger;
private final ArrayList<String> mHostListWhichIpFixed = new ArrayList<>();
private volatile boolean mFinishReadFromDb = false;
private final Object mDbLock = new Object();
private long mExpiredThresholdMillis = 0L;
public ResolveHostResultRepo(HttpDnsConfig config, IPRankingService ipIPRankingService,
RecordDBHelper dbHelper, ResolveHostCacheGroup cacheGroup) {
this.mHttpDnsConfig = config;
this.mIpIPRankingService = ipIPRankingService;
this.mDBHelper = dbHelper;
this.mCacheGroup = cacheGroup;
}
public void ensureReadFromDB() {
if (!mEnableCache) {
return;
}
if (mFinishReadFromDb) {
return;
}
synchronized (mDbLock) {
if (mFinishReadFromDb) {
return;
}
readFromDB(mExpiredThresholdMillis);
mFinishReadFromDb = true;
}
}
private void readFromDB(long expiredThresholdMillis) {
final String region = mHttpDnsConfig.getRegion();
List<HostRecord> records = mDBHelper.readFromDb(region);
for (HostRecord record : records) {
// 过期缓存不加载到内存中
if (System.currentTimeMillis() >= record.getQueryTime() + record.getTtl() * 1000L + expiredThresholdMillis) {
continue;
}
if (record.getIps() == null || record.getIps().length == 0) {
// 空解析按照ttl来不区分是否来自数据库
record.setFromDB(false);
}
if (mHostListWhichIpFixed.contains(record.getHost())) {
// 固定IP按照ttl来不区分是否来自数据库
record.setFromDB(false);
}
ResolveHostCache cache = mCacheGroup.getCache(record.getCacheKey());
cache.put(record);
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("cache ready");
}
//清理db
ArrayList<HostRecord> expired = new ArrayList<>();
for (HostRecord record : records) {
//达到过期阈值
if (System.currentTimeMillis() >= record.getQueryTime() + record.getTtl() * 1000L + expiredThresholdMillis) {
expired.add(record);
}
}
mDBHelper.delete(expired);
if (!mHttpDnsConfig.getRegion().equals(region)) {
// 防止刚读取完region变化了
mCacheGroup.clearAll();
} else {
for (final HostRecord record : records) {
RequestIpType type = RequestIpType.values()[record.getType()];
if (!record.isExpired() && type == RequestIpType.v4) {
mIpIPRankingService.probeIpv4(record.getHost(), record.getIps(),
new IPRankingCallback() {
@Override
public void onResult(String host, String[] sortedIps) {
update(host, RequestIpType.v4, record.getCacheKey(), sortedIps);
}
});
}
}
}
}
private HostRecord save(String region, String host, RequestIpType type, String extra,
String cacheKey, String[] ips, int ttl, String serverIp, String noIpCode) {
if (mCacheTtlChanger != null) {
ttl = mCacheTtlChanger.changeCacheTtl(host, type, ttl);
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("save host " + host + " for type " + type.name() + " ttl is " + ttl);
}
ResolveHostCache cache = mCacheGroup.getCache(cacheKey);
return cache.update(region, host, type, extra, cacheKey, ips, ttl, serverIp, noIpCode);
}
private void updateInner(String host, RequestIpType type, String cacheKey, String[] ips) {
ResolveHostCache cache = mCacheGroup.getCache(cacheKey);
HostRecord record = cache.updateIps(host, type, ips);
if (mEnableCache || mHostListWhichIpFixed.contains(host)) {
final ArrayList<HostRecord> records = new ArrayList<>();
records.add(record);
try {
mHttpDnsConfig.getDbWorker().execute(new Runnable() {
@Override
public void run() {
mDBHelper.insertOrUpdate(records);
}
});
} catch (Exception ignored) {
}
}
}
/**
* 保存预解析结果
*/
public void save(String region, ResolveHostResponse resolveHostResponse, String cacheKey) {
final ArrayList<HostRecord> records = new ArrayList<>();
for (ResolveHostResponse.HostItem item : resolveHostResponse.getItems()) {
HostRecord record = save(region, item.getHost(), item.getIpType(), item.getExtra(), cacheKey,
item.getIps(), item.getTtl(), resolveHostResponse.getServerIp(), item.getNoIpCode());
if (mEnableCache
|| mHostListWhichIpFixed.contains(item.getHost())
|| (item.getIps() == null || item.getIps().length == 0)
) {
records.add(record);
}
}
if (records.size() > 0) {
try {
mHttpDnsConfig.getDbWorker().execute(new Runnable() {
@Override
public void run() {
mDBHelper.insertOrUpdate(records);
}
});
} catch (Exception ignored) {
}
}
}
/**
* 更新ip, 一般用于更新ip的顺序
*/
public void update(String host, RequestIpType type, String cacheKey, String[] ips) {
switch (type) {
case v4:
updateInner(host, RequestIpType.v4, cacheKey, ips);
break;
case v6:
updateInner(host, RequestIpType.v6, cacheKey, ips);
break;
default:
HttpDnsLog.e("update both is impossible for " + host);
break;
}
}
/**
* 获取之前保存的解析结果
*/
public HTTPDNSResultWrapper getIps(String host, RequestIpType type, String cacheKey) {
ensureReadFromDB();
ResolveHostCache cache = mCacheGroup.getCache(cacheKey);
return cache.getResult(host, type);
}
/**
* 仅清除内容缓存
*/
public void clearMemoryCache() {
mCacheGroup.clearAll();
}
/**
* 仅清除非主站域名的内存缓存
*/
public void clearMemoryCacheForHostWithoutFixedIP() {
mCacheGroup.clearAll(new ArrayList<String>(getAllHostWithoutFixedIP().keySet()));
}
/**
* 清除所有已解析结果
*/
public void clear() {
final List<HostRecord> recordsToBeDeleted = mCacheGroup.clearAll();
if (mEnableCache && recordsToBeDeleted.size() > 0) {
try {
mHttpDnsConfig.getDbWorker().execute(new Runnable() {
@Override
public void run() {
mDBHelper.delete(recordsToBeDeleted);
}
});
} catch (Exception ignored) {
}
}
}
/**
* 清除指定域名的已解析结果
*/
public void clear(ArrayList<String> hosts) {
if (hosts == null || hosts.size() == 0) {
clear();
return;
}
final List<HostRecord> recordsToBeDeleted = mCacheGroup.clearAll(hosts);
if (recordsToBeDeleted.size() > 0 && mEnableCache) {
try {
mHttpDnsConfig.getDbWorker().execute(new Runnable() {
@Override
public void run() {
mDBHelper.delete(recordsToBeDeleted);
}
});
} catch (Exception ignored) {
}
}
}
/**
* 获取当前所有已缓存结果的域名
*/
public HashMap<String, RequestIpType> getAllHostWithoutFixedIP() {
HashMap<String, RequestIpType> result = mCacheGroup.getAllHostFromNetworkCaches();
for (String host : mHostListWhichIpFixed) {
result.remove(host);
}
return result;
}
/**
* 配置 本地缓存开关,触发缓存读取逻辑
*/
public void setCachedIPEnabled(boolean enable, long expiredThresholdMillis) {
mEnableCache = enable;
mExpiredThresholdMillis = expiredThresholdMillis;
try {
mHttpDnsConfig.getDbWorker().execute(new Runnable() {
@Override
public void run() {
ensureReadFromDB();
}
});
} catch (Throwable ignored) {
}
}
/**
* 设置自定义ttl的接口用于控制缓存的时长
*/
public void setCacheTtlChanger(CacheTtlChanger changer) {
mCacheTtlChanger = changer;
}
/**
* 设置主站域名,主站域名的缓存策略和其它域名不同
* 1. 内存缓存不轻易清除
* 2. 默认本地缓存本地缓存的ttl有效
*/
public void setHostListWhichIpFixed(List<String> hostListWhichIpFixed) {
this.mHostListWhichIpFixed.clear();
if (hostListWhichIpFixed != null) {
this.mHostListWhichIpFixed.addAll(hostListWhichIpFixed);
}
}
/**
* 获取缓存组,供外部访问网络缓存
*
* @return 缓存组
*/
public ResolveHostCacheGroup getCacheGroup() {
return mCacheGroup;
}
}

View File

@@ -0,0 +1,636 @@
package com.alibaba.sdk.android.httpdns.resolve;
import android.text.TextUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.alibaba.sdk.android.httpdns.HTTPDNSResult;
import com.alibaba.sdk.android.httpdns.HTTPDNSResultWrapper;
import com.alibaba.sdk.android.httpdns.HttpDnsCallback;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.impl.HostResolveLocker;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.log.HttpDnsLog;
import com.alibaba.sdk.android.httpdns.observable.ObservableConstants;
import com.alibaba.sdk.android.httpdns.observable.event.CallSdkApiEvent;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingCallback;
import com.alibaba.sdk.android.httpdns.ranking.IPRankingService;
import com.alibaba.sdk.android.httpdns.request.HttpException;
import com.alibaba.sdk.android.httpdns.request.RequestCallback;
import com.alibaba.sdk.android.httpdns.utils.CommonUtil;
import com.alibaba.sdk.android.httpdns.utils.Constants;
/**
* 域名解析服务
*/
public class ResolveHostService {
private final HttpDnsConfig mHttpDnsConfig;
private final ResolveHostResultRepo mResultRepo;
private final ResolveHostRequestHandler mRequestHandler;
private final IPRankingService mIpIPRankingService;
private final HostFilter mFilter;
private boolean mEnableExpiredIp = true;
private final HostResolveLocker mAsyncLocker;
private final HostResolveLocker mLocker;
public ResolveHostService(HttpDnsConfig config, IPRankingService ipIPRankingService,
ResolveHostRequestHandler requestHandler,
ResolveHostResultRepo repo, HostFilter filter,
HostResolveLocker locker) {
this.mHttpDnsConfig = config;
this.mIpIPRankingService = ipIPRankingService;
this.mRequestHandler = requestHandler;
this.mResultRepo = repo;
this.mFilter = filter;
this.mAsyncLocker = locker;
this.mLocker = new HostResolveLocker();
}
/**
* 解析域名
*/
public HTTPDNSResult resolveHostSyncNonBlocking(final String host, final RequestIpType type,
final Map<String, String> extras,
final String cacheKey) {
if (mFilter.isFiltered(host)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request host " + host + ", which is filtered");
}
return Constants.EMPTY;
}
long start = System.currentTimeMillis();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d(
"sync non blocking request host " + host + " with type " + type + " extras : " + CommonUtil.toString(
extras) + " cacheKey " + cacheKey);
}
HTTPDNSResultWrapper result = mResultRepo.getIps(host, type, cacheKey);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("host " + host + " result is " + CommonUtil.toString(result));
}
if (result == null || result.isExpired()) {
if (type == RequestIpType.both) {
// 过滤掉 未过期的请求
HTTPDNSResultWrapper resultV4 = mResultRepo.getIps(host, RequestIpType.v4, cacheKey);
HTTPDNSResultWrapper resultV6 = mResultRepo.getIps(host, RequestIpType.v6, cacheKey);
boolean v4Invalid =
resultV4 == null || resultV4.isExpired();
boolean v6Invalid =
resultV6 == null || resultV6.isExpired();
if (v4Invalid && v6Invalid) {
// 都过期,不过滤
asyncResolveHostInner(host, type, extras, cacheKey);
} else if (v4Invalid) {
// 仅v4过期
asyncResolveHostInner(host, RequestIpType.v4, extras, cacheKey);
} else if (v6Invalid) {
// 仅v6过期
asyncResolveHostInner(host, RequestIpType.v6, extras, cacheKey);
}
} else {
asyncResolveHostInner(host, type, extras, cacheKey);
}
}
if (result != null && (!result.isExpired() || mEnableExpiredIp)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i(
"request host " + host + " for " + type + " and return " + result.toString()
+ " immediately");
}
addCallSdkApiEvent(getCallSdkApiEvent(ObservableConstants.RESOLVE_API_SYN_NON_BLOCKING, host, type, true, result, start),
result);
return result.getHTTPDNSResult();
} else {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("request host " + host + " and return empty immediately");
}
addCallSdkApiEvent(getCallSdkApiEvent(ObservableConstants.RESOLVE_API_SYN_NON_BLOCKING, host, type, false, result, start),
null);
return Constants.EMPTY;
}
}
private void asyncResolveHostInner(final String host, final RequestIpType type,
Map<String, String> extras, final String cacheKey) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("async start request for " + host + " " + type);
}
if (mAsyncLocker.beginResolve(host, type, cacheKey)) {
final String region = mHttpDnsConfig.getRegion();
mRequestHandler.requestResolveHost(host, type, extras, cacheKey,
new RequestCallback<ResolveHostResponse>() {
@Override
public void onSuccess(final ResolveHostResponse resolveHostResponse) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("ip request for " + host + " " + type + " return "
+ resolveHostResponse.toString());
}
mResultRepo.save(region, resolveHostResponse, cacheKey);
if (type == RequestIpType.v4 || type == RequestIpType.both) {
for (final ResolveHostResponse.HostItem item :
resolveHostResponse.getItems()) {
if (item.getIpType() == RequestIpType.v4) {
mIpIPRankingService.probeIpv4(host, item.getIps(),
new IPRankingCallback() {
@Override
public void onResult(String host, String[] sortedIps) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i(
"ip probe for " + host + " " + type + " return "
+ CommonUtil.translateStringArray(sortedIps));
}
mResultRepo.update(host, RequestIpType.v4, cacheKey,
sortedIps);
}
});
}
}
}
mAsyncLocker.endResolve(host, type, cacheKey);
}
@Override
public void onFail(Throwable throwable) {
HttpDnsLog.w("ip request for " + host + " fail", throwable);
if (throwable instanceof Exception) {
String query = "4";
if (type == RequestIpType.v6) {
query = "6";
}else if (type == RequestIpType.both) {
query = "4,6";
}
String errorMsg = (throwable instanceof HttpException) ? throwable.getMessage() : throwable.toString();
HttpDnsLog.w("RESOLVE FAIL, HOST:" + host + ", QUERY:" + query
+ ", Msg:" + errorMsg);
}
if (throwable instanceof HttpException
&& ((HttpException)throwable).shouldCreateEmptyCache()) {
ArrayList<String> targetHost = new ArrayList<>();
targetHost.add(host);
ResolveHostResponse emptyResponse = ResolveHostResponse.createEmpty(
targetHost, type, 60 * 60);
mResultRepo.save(region, emptyResponse, cacheKey);
}
mAsyncLocker.endResolve(host, type, cacheKey);
}
});
}
}
/**
* 解析域名
*/
public HTTPDNSResult resolveHostSync(final String host, final RequestIpType type,
final Map<String, String> extras, final String cacheKey) {
if (mFilter.isFiltered(host)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request host " + host + ", which is filtered");
}
return degradationLocalDns(host);
}
long start = System.currentTimeMillis();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("sync request host " + host + " with type " + type + " extras : "
+ CommonUtil.toString(extras) + " cacheKey " + cacheKey);
}
HTTPDNSResultWrapper result = mResultRepo.getIps(host, type, cacheKey);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("host " + host + " result in cache is " + CommonUtil.toString(result));
}
if (result != null && (!result.isExpired() || mEnableExpiredIp)) {
//有缓存,如果没过期或者允许使用过期解析结果,直接返回
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i(
"request host " + host + " for " + type + " and return " + result.toString()
+ " immediately");
}
if (result.isExpired()) {
//如果缓存已经过期了,发起异步解析更新缓存
asyncResolveHostInner(host, type, extras, cacheKey);
}
//可能是空结果如果开启降级local dns走local dns解析
if ((result.getIps() == null || result.getIps().length == 0)
&& (result.getIpv6s() == null || result.getIpv6s().length == 0)) {
if (mHttpDnsConfig.isEnableDegradationLocalDns()) {
return degradationLocalDns(host);
}
}
addCallSdkApiEvent(getCallSdkApiEvent(ObservableConstants.RESOLVE_API_SYNC, host, type, true, result, start), result);
return result.getHTTPDNSResult();
}
//如果没有缓存,或者有过期缓存但是不允许使用过期缓存
if (result == null || result.isExpired()) {
// 没有缓存,或者缓存过期,或者是从数据库读取的 需要解析
if (type == RequestIpType.both) {
// 过滤掉 未过期的请求
HTTPDNSResultWrapper resultV4 = mResultRepo.getIps(host, RequestIpType.v4, cacheKey);
HTTPDNSResultWrapper resultV6 = mResultRepo.getIps(host, RequestIpType.v6, cacheKey);
boolean v4Invalid =
resultV4 == null || resultV4.isExpired();
boolean v6Invalid =
resultV6 == null || resultV6.isExpired();
if (v4Invalid && v6Invalid) {
// 都过期,不过滤
syncResolveHostInner(host, type, extras, cacheKey, result);
} else if (v4Invalid) {
// 仅v4过期
syncResolveHostInner(host, RequestIpType.v4, extras, cacheKey, result);
} else if (v6Invalid) {
// 仅v6过期
syncResolveHostInner(host, RequestIpType.v6, extras, cacheKey, result);
}
} else {
syncResolveHostInner(host, type, extras, cacheKey, result);
}
}
CallSdkApiEvent callSdkApiEvent = getCallSdkApiEvent(ObservableConstants.RESOLVE_API_SYNC, host, type, false, result, start);
result = mResultRepo.getIps(host, type, cacheKey);
if (result != null && (!result.isExpired() || mEnableExpiredIp)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i(
"request host " + host + " for " + type + " and return " + result.toString()
+ " after request");
}
//可能是空结果如果开启降级local dns走local dns解析
if ((result.getIps() == null || result.getIps().length == 0)
&& (result.getIpv6s() == null || result.getIpv6s().length == 0)) {
if (mHttpDnsConfig.isEnableDegradationLocalDns()) {
return degradationLocalDns(host);
}
}
addCallSdkApiEvent(callSdkApiEvent, result);
return result.getHTTPDNSResult();
} else {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("request host " + host + " and return empty after request");
}
addCallSdkApiEvent(callSdkApiEvent, null);
return degradationLocalDns(host);
}
}
private HTTPDNSResult degradationLocalDns(String host) {
if (mHttpDnsConfig.isEnableDegradationLocalDns()) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request host " + host + " via local dns");
}
try {
InetAddress[] addresses = InetAddress.getAllByName(host);
if (addresses != null && addresses.length > 0) {
List<String> ips = new ArrayList<>();
List<String> ipv6s = new ArrayList<>();
for (InetAddress address : addresses) {
if (address instanceof Inet4Address) {
ips.add(address.getHostAddress());
} else if (address instanceof Inet6Address) {
ipv6s.add(address.getHostAddress());
}
}
HTTPDNSResult result = new HTTPDNSResult(host, ips.toArray(new String[0]), ipv6s.toArray(new String[0]), null, false, false, true);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("request host " + host + " via local dns return " + result);
}
return result;
}
} catch (UnknownHostException e) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.e("failed request host " + host + " via local dns", e);
}
}
}
return Constants.EMPTY;
}
private void syncResolveHostInner(final String host, final RequestIpType type,
Map<String, String> extras, final String cacheKey,
HTTPDNSResultWrapper result) {
long start = System.currentTimeMillis();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("sync start request for " + host + " " + type);
}
if (mLocker.beginResolve(host, type, cacheKey)) {
final String region = mHttpDnsConfig.getRegion();
// 没有正在进行的解析,发起新的解析
mRequestHandler.requestResolveHost(host, type, extras, cacheKey,
new RequestCallback<ResolveHostResponse>() {
@Override
public void onSuccess(final ResolveHostResponse resolveHostResponse) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("ip request for " + host + " " + type + " return "
+ resolveHostResponse.toString());
}
mResultRepo.save(region, resolveHostResponse, cacheKey);
if (type == RequestIpType.v4 || type == RequestIpType.both) {
for (final ResolveHostResponse.HostItem item :
resolveHostResponse.getItems()) {
if (item.getIpType() == RequestIpType.v4) {
mIpIPRankingService.probeIpv4(host, item.getIps(),
new IPRankingCallback() {
@Override
public void onResult(String host, String[] sortedIps) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i(
"ip probe for " + host + " " + type + " return "
+ CommonUtil.translateStringArray(sortedIps));
}
mResultRepo.update(host, RequestIpType.v4, cacheKey,
sortedIps);
}
});
}
}
}
mLocker.endResolve(host, type, cacheKey);
}
@Override
public void onFail(Throwable throwable) {
HttpDnsLog.w("ip request for " + host + " fail", throwable);
if (throwable instanceof Exception) {
String query = "4";
if (type == RequestIpType.v6) {
query = "6";
}else if (type == RequestIpType.both) {
query = "4,6";
}
String errorMsg = (throwable instanceof HttpException) ? throwable.getMessage() : throwable.toString();
HttpDnsLog.w("RESOLVE FAIL, HOST:" + host + ", QUERY:" + query
+ ", Msg:" + errorMsg);
}
if (throwable instanceof HttpException
&& ((HttpException)throwable).shouldCreateEmptyCache()) {
ArrayList<String> targetHost = new ArrayList<>();
targetHost.add(host);
ResolveHostResponse emptyResponse = ResolveHostResponse.createEmpty(
targetHost, type, 60 * 60);
mResultRepo.save(region, emptyResponse, cacheKey);
}
mLocker.endResolve(host, type, cacheKey);
}
});
}
if (result == null || !mEnableExpiredIp) {
// 有结果,但是过期了,不允许返回过期结果,等请求结束
/* 同步锁超时时间不能大于5s不论请求是否已结束保证同步锁都会在至多5s内结束 */
int timeout = mHttpDnsConfig.getTimeout();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("the httpDnsConfig timeout is: " + timeout);
}
//锁的超时上限不能大于5s
timeout = Math.min(timeout, Constants.SYNC_TIMEOUT_MAX_LIMIT);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("final timeout is: " + timeout);
}
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("wait for request finish");
}
try {
boolean waitResult = mLocker.await(host, type, cacheKey, timeout, TimeUnit.MILLISECONDS);
if (!waitResult) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("lock await timeout finished");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// 不管什么情况释放锁
mLocker.endResolve(host, type, cacheKey);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("sync resolve time is: " + (System.currentTimeMillis() - start));
}
}
}
public void resolveHostAsync(final String host, final RequestIpType type, final Map<String, String> extras,
final String cacheKey, HttpDnsCallback callback) {
if (mFilter.isFiltered(host)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("request host " + host + ", which is filtered");
}
if (callback != null) {
if (mHttpDnsConfig.isEnableDegradationLocalDns()) {
mHttpDnsConfig.getResolveWorker().execute(new Runnable() {
@Override
public void run() {
callback.onHttpDnsCompleted(degradationLocalDns(host));
}
});
} else {
callback.onHttpDnsCompleted(Constants.EMPTY);
}
}
return;
}
long start = System.currentTimeMillis();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d(
"async request host " + host + " with type " + type + " extras : " + CommonUtil.toString(
extras) + " cacheKey " + cacheKey);
}
HTTPDNSResultWrapper result = mResultRepo.getIps(host, type, cacheKey);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("host " + host + " result in cache is " + CommonUtil.toString(result));
}
if (result != null && (!result.isExpired() || mEnableExpiredIp)) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i(
"request host " + host + " for " + type + " and return " + result.toString()
+ " immediately");
}
if (result.isExpired()) {
//如果缓存已经过期了,发起异步解析更新缓存
asyncResolveHostInner(host, type, extras, cacheKey);
}
//可能是空结果如果开启降级local dns走local dns解析
if ((result.getIps() == null || result.getIps().length == 0)
&& (result.getIpv6s() == null || result.getIpv6s().length == 0)) {
if (mHttpDnsConfig.isEnableDegradationLocalDns()) {
mHttpDnsConfig.getResolveWorker().execute(new Runnable() {
@Override
public void run() {
callback.onHttpDnsCompleted(degradationLocalDns(host));
}
});
return;
}
}
addCallSdkApiEvent(getCallSdkApiEvent(ObservableConstants.RESOLVE_API_ASYNC, host, type, true, result, start), result);
if (callback != null) {
callback.onHttpDnsCompleted(result.getHTTPDNSResult());
}
return;
}
CallSdkApiEvent callSdkApiEvent = getCallSdkApiEvent(ObservableConstants.RESOLVE_API_ASYNC, host, type, false, result, start);
//没有可用的解析结果,需要发起请求
if (type == RequestIpType.both) {
// 过滤掉 未过期的请求
HTTPDNSResultWrapper resultV4 = mResultRepo.getIps(host, RequestIpType.v4, cacheKey);
HTTPDNSResultWrapper resultV6 = mResultRepo.getIps(host, RequestIpType.v6, cacheKey);
boolean v4Invalid =
resultV4 == null || resultV4.isExpired();
boolean v6Invalid =
resultV6 == null || resultV6.isExpired();
if (v4Invalid && v6Invalid) {
// 都过期,不过滤
asyncResolveHost(host, type, extras, cacheKey, callback, callSdkApiEvent);
} else if (v4Invalid) {
// 仅v4过期
asyncResolveHost(host, RequestIpType.v4, extras, cacheKey, callback, callSdkApiEvent);
} else if (v6Invalid) {
// 仅v6过期
asyncResolveHost(host, RequestIpType.v6, extras, cacheKey, callback, callSdkApiEvent);
}
} else {
asyncResolveHost(host, type, extras, cacheKey, callback, callSdkApiEvent);
}
}
private void asyncResolveHost(String host, RequestIpType type, Map<String, String> extras,
String cacheKey, HttpDnsCallback callback, CallSdkApiEvent event) {
mHttpDnsConfig.getResolveWorker().execute(new Runnable() {
@Override
public void run() {
asyncResolveHostInner(host, type, extras, cacheKey);
/* 同步锁超时时间不能大于5s不论请求是否已结束保证同步锁都会在至多5s内结束 */
int timeout = mHttpDnsConfig.getTimeout();
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("the httpDnsConfig timeout is: " + timeout);
}
//锁的超时上限不能大于5s
timeout = Math.min(timeout, Constants.SYNC_TIMEOUT_MAX_LIMIT);
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("final timeout is: " + timeout);
}
//发起异步请求,有无并发,统一在这里通过锁同步机制,从缓存中处理解析结果
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("wait for request finish");
}
try {
boolean waitResult = mAsyncLocker.await(host, type, cacheKey, timeout, TimeUnit.MILLISECONDS);
if (!waitResult) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.d("lock await timeout finished");
}
}
} catch (InterruptedException e) {
HttpDnsLog.e(e.getMessage(), e);
}
HTTPDNSResultWrapper result = mResultRepo.getIps(host, type, cacheKey);
addCallSdkApiEvent(event, result);
if (result != null && !result.isExpired()) {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i(
"request host " + host + " for " + type + " and return " + result.toString()
+ " after request");
}
if (callback != null) {
//可能是空结果如果开启降级local dns走local dns解析
if ((result.getIps() == null || result.getIps().length == 0)
&& (result.getIpv6s() == null || result.getIpv6s().length == 0)) {
if (mHttpDnsConfig.isEnableDegradationLocalDns()) {
callback.onHttpDnsCompleted(degradationLocalDns(host));
return;
}
}
callback.onHttpDnsCompleted(result.getHTTPDNSResult());
}
} else {
if (HttpDnsLog.isPrint()) {
HttpDnsLog.i("request host " + host + " and return empty after request");
}
if (callback != null) {
callback.onHttpDnsCompleted(degradationLocalDns(host));
}
}
}
});
}
public void setEnableExpiredIp(boolean enableExpiredIp) {
this.mEnableExpiredIp = enableExpiredIp;
}
private void addCallSdkApiEvent(CallSdkApiEvent event, HTTPDNSResultWrapper result) {
event.setCostTime((int) (System.currentTimeMillis() - event.getTimestamp()));
if (result != null) {
event.setServerIp(result.getServerIp());
event.setHttpDnsIps(result.getIps(), result.getIpv6s());
if (TextUtils.isEmpty(event.getHttpDnsIps())) {
event.setStatusCode(204);
} else {
event.setResultStatus(ObservableConstants.NOT_EMPTY_RESULT);
event.setStatusCode(200);
}
} else {
event.setHttpDnsIps(null, null);
event.setStatusCode(204);
}
mHttpDnsConfig.getObservableManager().addObservableEvent(event);
}
private CallSdkApiEvent getCallSdkApiEvent(int api, String host, RequestIpType type, boolean hitCache, HTTPDNSResultWrapper cacheResult, long start) {
CallSdkApiEvent callSdkApiEvent = new CallSdkApiEvent(start);
callSdkApiEvent.setRequestType(type);
callSdkApiEvent.setHostName(host);
callSdkApiEvent.setInvokeApi(api);
int cacheScene = cacheResult == null ? ObservableConstants.CACHE_NONE : (hitCache ? (cacheResult.isExpired() ? ObservableConstants.CACHE_EXPIRED_USE : ObservableConstants.CACHE_NOT_EXPIRED) : ObservableConstants.CACHE_EXPIRED_NOT_USE);
callSdkApiEvent.setCacheScene(cacheScene);
return callSdkApiEvent;
}
}

View File

@@ -0,0 +1,91 @@
package com.alibaba.sdk.android.httpdns.resolve;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import com.alibaba.sdk.android.httpdns.impl.HttpDnsConfig;
import com.alibaba.sdk.android.httpdns.request.HttpException;
import com.alibaba.sdk.android.httpdns.request.HttpRequestConfig;
import com.alibaba.sdk.android.httpdns.request.HttpRequestWatcher;
import com.alibaba.sdk.android.httpdns.serverip.RegionServerScheduleService;
import com.alibaba.sdk.android.httpdns.utils.Constants;
/**
* 请求失败时切换当前Region的服务IP服务IP都切换过更新服务IP
*/
public class ShiftServerWatcher implements HttpRequestWatcher.Watcher {
private final HttpDnsConfig mHttpDnsConfig;
private final RegionServerScheduleService mScheduleService;
private final StatusControl mStatusControl;
private long mBeginRequestTime;
public ShiftServerWatcher(HttpDnsConfig config, RegionServerScheduleService scheduleService,
StatusControl statusControl) {
this.mHttpDnsConfig = config;
this.mScheduleService = scheduleService;
this.mStatusControl = statusControl;
}
@Override
public void onStart(HttpRequestConfig config) {
mBeginRequestTime = System.currentTimeMillis();
}
@Override
public void onSuccess(HttpRequestConfig requestConfig, Object data) {
if (requestConfig.getIpType() == RequestIpType.v6) {
if (this.mHttpDnsConfig.getCurrentServer().markOkServerV6(requestConfig.getIp(),
requestConfig.getPort())) {
if (mStatusControl != null) {
mStatusControl.turnUp();
}
}
} else {
if (this.mHttpDnsConfig.getCurrentServer().markOkServer(requestConfig.getIp(),
requestConfig.getPort())) {
if (mStatusControl != null) {
mStatusControl.turnUp();
}
}
}
}
@Override
public void onFail(HttpRequestConfig requestConfig, Throwable throwable) {
long cost = System.currentTimeMillis() - mBeginRequestTime;
// 是否切换服务IP, 超过超时时间我们也切换ip花费时间太长说明这个ip可能也有问题
if (shouldShiftServer(throwable) || cost > requestConfig.getTimeout()) {
// 切换和更新请求的服务IP
boolean isBackToFirstServer;
if (requestConfig.getIpType() == RequestIpType.v6) {
isBackToFirstServer = this.mHttpDnsConfig.getCurrentServer().shiftServerV6(
requestConfig.getIp(), requestConfig.getPort());
requestConfig.setIp(this.mHttpDnsConfig.getCurrentServer().getServerIpForV6());
requestConfig.setPort(this.mHttpDnsConfig.getCurrentServer().getPortForV6());
} else {
isBackToFirstServer = this.mHttpDnsConfig.getCurrentServer().shiftServer(
requestConfig.getIp(), requestConfig.getPort());
requestConfig.setIp(this.mHttpDnsConfig.getCurrentServer().getServerIp());
requestConfig.setPort(this.mHttpDnsConfig.getCurrentServer().getPort());
}
// 所有服务IP都尝试过了通知上层进一步处理
if (isBackToFirstServer && mScheduleService != null) {
mScheduleService.updateRegionServerIps(Constants.UPDATE_REGION_SERVER_SCENES_SERVER_UNAVAILABLE);
}
if (mStatusControl != null) {
mStatusControl.turnDown();
}
}
}
private boolean shouldShiftServer(Throwable throwable) {
if (throwable instanceof HttpException) {
return ((HttpException)throwable).shouldShiftServer();
}
// 除了特定的一些错误sdk问题或者客户配置问题不是服务网络问题都切换服务IP这边避免个别服务节点真的访问不上
// 一方面尽可能提高可用性另一方面当客户发生异常时也方便根据服务IP来判断是否是真的无网络因为多个服务IP都访问不上的可能性较低
// 还有一方面是 sniff模式是在切换服务IP的前提下触发的这样也提高的sniff模式触发几率在真的网络异常时降低网络请求频次
return true;
}
}

View File

@@ -0,0 +1,8 @@
package com.alibaba.sdk.android.httpdns.resolve;
public class SniffException extends Exception {
public SniffException(String message) {
super(message);
}
}

Some files were not shown because too many files have changed in this diff Show More