目录名称 |
说明 |
适用范围 |
HttpDnsLibs |
HttpDns Android SDK库目录 |
所有业务 |
README.md |
HTTPDNS Android客户端接入文档 |
接入文档 |
SDK接入是否成功验证方法.docx |
HTTPDNS Android SDK接入验证 |
SDK开发人员维护 |
VERSION.md |
HTTPDNS Android SDK历史版本修改记录 |
SDK开发人员维护 |
数据报表申请方法.docx |
数据报表申请方法 |
SDK开发维护人员 |
HttpDns的主要功能是为了有效的避免由于运营商传统LocalDns解析导致的无法访问最佳接入点的方案。原理为使用Http加密协议替代传统的DNS协议,整个过程不使用域名,大大减少劫持的可能性。
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
将HttpDnsLibs\httpdns_xxxx.jar库文件拷贝至应用libs相应的位置;
2.3 接入依赖库:(注意:已经接入了腾讯灯塔(beacon)组件的应用忽略此步)
将HttpDnsLibs\beacon_android_vxxxx.jar灯塔库拷贝至游戏libs相应的位置;
// 初始化灯塔:如果已经接入MSDK或者IMSDK或者单独接入了腾讯灯塔(Beacon)则不需再初始化该接口
try {
// ***注意:这里业务需要输入自己的灯塔AppKey
UserAction.setAppKey(AppKey);
UserAction.initUserAction(MainActivity.this);
} catch (Exception e) {
Logger.e("init beacon error:" + e.getMessage());
}
/**
* 初始化HttpDns
*
* @param1 context:当前Activity
* @param2 isCooperator:合作厂商开关,腾讯及代理业务为false,非腾讯业务为true
* @param3 appID:,腾讯灯塔的AppKey,非腾讯业务需申请获得,注册后由系统或管理员分配;腾讯业务为AppID
* @param4 timeout:解析超时时间,建议设置为1000ms
* @param5 id:HTTPDNS服务端分配的id,云官网注册获得,腾讯内部业务输入"NULL"
* @param6 key:HTTPDNS服务端分配的key,云官网注册获得,腾讯内部业务输入"NULL"
* @param7 debug:sdk日志开关,true打开,false关闭,***上线时一定要设置为false***
* */
WGGetHostByName.getInstance().init(MainActivity.this, true, "0I000LT6GW1YGCP7", 1000, "xxx", "xxxxxxxx", true);
/**
* 设置OpenId,已接入MSDK业务直接传MSDK OpenId,其它业务传“NULL”
* 注意:该接口返回值是布尔型,在Unity或者Cocos下调用请注意处理返回类型
* @param String openId
*/
WGGetHostByName.getInstance().WGSetDnsOpenId("NULL");
/**
* HttpDns同步解析接口
* 注意:domain只能传入域名不能传入IP,返回结果需要做非空判断
* 首先查询缓存,若存在则返回结果,若不存在则进行同步域名解析请求,
* 解析完成返回最新解析结果,若解析失败返回空对象
* @param domain 域名(如www.qq.com)
* @return 域名对应的解析IP结果集合
*/
String ips = WGGetHostByName.getInstance(). getAddrByName(domain);
3.1 建议调用HttpDns同步接口时最好在子线程调用getAddrByName(domain)接口。
3.2 如果客户端的业务是与host绑定的,比如是绑定了host的http服务或者是cdn的服务,那么在用HTTPDNS返回的IP替换掉URL中的域名以后,还需要指定下Http头的Host字段。
- 以URLConnection为例:
URL oldUrl = new URL(url);
URLConnection connection = oldUrl.openConnection();
// 获取HttpDns域名解析结果
String ips = WGGetHostByName.getInstance().getAddrByName(oldUrl.getHost());
if (ips != null) { // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
String ip;
if (ips.contains(";")) {
ip = ips.substring(0, ips.indexOf(";"));
} else {
ip = ips;
}
String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
connection = (HttpURLConnection) new URL(newUrl).openConnection(); // 设置HTTP请求头Host域名
connection.setRequestProperty("Host", oldUrl.getHost());
}
- 以curl为例:
假设你要访问www.qq.com,通过HTTPDNS解析出来的IP为192.168.0.111,那么通过这个方式来调用即可:
curl -H "Host:www.qq.com" http://192.168.0.111/aaa.txt.
注意:若已接入msdk或者单独接入了腾讯灯塔则不用初始化灯塔。
private static AndroidJavaObject m_dnsJo;
private static AndroidJavaClass sGSDKPlatformClass;
public static void Init() {
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
if (jc == null)
return;
AndroidJavaObject joactivety = jc.GetStatic<AndroidJavaObject>("currentActivity");
if (joactivety == null)
return;
AndroidJavaObject context = joactivety.Call<AndroidJavaObject>("getApplicationContext");
// 初始化HttpDns
AndroidJavaObject joDnsClass = new AndroidJavaObject("com.tencent.msdk.dns.WGGetHostByName");
Debug.Log(" WGGetHostByName ===========" + joDnsClass);
if (joDnsClass == null)
return;
m_dnsJo = joDnsClass.CallStatic<AndroidJavaObject>("getInstance");
Debug.Log(" WGGetHostByName ===========" + m_dnsJo);
if (m_dnsJo == null)
return;
m_dnsJo.Call("init", context);
}
// 该操作建议在子线程中处理
public static string GetHttpDnsIP( string strUrl ) {
string strIp = string.Empty;
// 解析得到IP配置集合
strIp = m_dnsJo.Call<string>("getAddrByName", strUrl);
Debug.Log( strIp );
if( strIp != null )
{
// 取第一个
string[] strIps = strIp.Split(';');
strIp = strIps[0];
}
return strIp;
}
Android原生系统提供了系统API以实现WebView中的网络请求拦截与自定义逻辑注入,我们可以通过上述拦截WebView的各类网络请求,截取URL请求的host,然后调用HttpDns解析该host,通过得到的ip组成新的URL来请求网络地址。
WebSettings webSettings = mWebView.getSettings();
// 使用默认的缓存策略,cache没有过期就用cache
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
// 加载网页图片资源
webSettings.setBlockNetworkImage(false);
// 支持JavaScript脚本
webSettings.setJavaScriptEnabled(true);
// 支持缩放
webSettings.setSupportZoom(true);
mWebView.setWebViewClient(new WebViewClient() {
// API 21及之后使用此方法
@SuppressLint("NewApi")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (request != null && request.getUrl() != null && request.getMethod().equalsIgnoreCase("get")) {
String scheme = request.getUrl().getScheme().trim();
String url = request.getUrl().toString();
Logger.d("url a: " + url);
// HttpDns解析css文件的网络请求及图片请求
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))
&& (url.contains(".css") || url.endsWith(".png") || url.endsWith(".jpg") || url .endsWith(".gif"))) {
try {
URL oldUrl = new URL(url);
URLConnection connection = oldUrl.openConnection();
// 获取HttpDns域名解析结果
String ips = WGGetHostByName.getInstance().getAddrByName(oldUrl.getHost());
if (ips != null) { // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Logger.d("HttpDns ips are: " + ips + " for host: " + oldUrl.getHost());
String ip;
if (ips.contains(";")) {
ip = ips.substring(0, ips.indexOf(";"));
} else {
ip = ips;
}
String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
Logger.d("newUrl a is: " + newUrl);
connection = (HttpURLConnection) new URL(newUrl).openConnection(); // 设置HTTP请求头Host域
connection.setRequestProperty("Host", oldUrl.getHost());
}
Logger.d("ContentType a: " + connection.getContentType());
return new WebResourceResponse("text/css", "UTF-8", connection.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
// API 11至API20使用此方法
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if (!TextUtils.isEmpty(url) && Uri.parse(url).getScheme() != null) {
String scheme = Uri.parse(url).getScheme().trim();
Logger.d("url b: " + url);
// HttpDns解析css文件的网络请求及图片请求
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))
&& (url.contains(".css") || url.endsWith(".png") || url.endsWith(".jpg") || url.endsWith(".gif"))) {
try {
URL oldUrl = new URL(url);
URLConnection connection = oldUrl.openConnection();
// 获取HttpDns域名解析结果
String ips = WGGetHostByName.getInstance().getAddrByName(oldUrl.getHost());
if (ips != null) {
// 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Logger.d("HttpDns ips are: " + ips + " for host: " + oldUrl.getHost());
String ip;
if (ips.contains(";")) {
ip = ips.substring(0, ips.indexOf(";"));
} else {
ip = ips;
}
String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
Logger.d("newUrl b is: " + newUrl);
connection = (HttpURLConnection) new URL(newUrl).openConnection();
// 设置HTTP请求头Host域
connection.setRequestProperty("Host", oldUrl.getHost());
}
Logger.d("ContentType b: " + connection.getContentType());
return new WebResourceResponse("text/css", "UTF-8", connection.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}});
// 加载web资源
mWebView.loadUrl(targetUrl);
// 方案仅做参考
String url = "http://www.qq.com";
Uri uri = Uri.parse(url);
String ip = WGGetHostByName.getInstance().getAddrByName("www.qq.com"); // HttpDns解析域名
if (ip != null) {
if (ip.contains(";")) {
ip = ip.substring(0, ip.indexOf(";"));
}
} else {
return;
}
String mURL = uri.toString().replaceFirst(uri.getHost(), ip);
// OkHttp GET Method
try {
String mGetURL = mURL; // 根据业务自己的服务器地址来封装
Request request = new Request.Builder().url(mGetURL).build();
OkHttpClient client = new OkHttpClient();
Response response = client.newCall(request).execute();
String mGetResult = response.body().string();
System.out.println("OkHttp Get Method result:" + mGetResult);
} catch (Exception e) {
e.printStackTrace();
}
// OkHttp POST Method
try {
String mPostURL = mURL; // 根据业务自己的服务器地址来封装
MediaType mType = MediaType.parse("application/json; charset=utf-8");
// 封装请求参数,根据业务实际情况封装
JSONObject jsonData = new JSONObject();
jsonData.put("os", "Android");
jsonData.put("api_version", 23);
jsonData.put("version", "0.0.1");
......
String data = jsonData.toString();
RequestBody body = RequestBody.create(mType, data);
Request request = new Request.Builder().url(mPostURL).post(body).build();
OkHttpClient client = new OkHttpClient();
Response response = client.newCall(request).execute();
String mPostResult = response.body().string();
System.out.println("OkHttp POST Method result:" + mPostResult);
} catch (Exception e) {
e.printStackTrace();
}
String url = "https://httpdns域名解析得到的IP/d?dn=&clientip=1&ttl=1&id=128"; // 业务自己的请求连接
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setRequestProperty("Host", "原解析的域名");
connection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return HttpsURLConnection.getDefaultHostnameVerifier().verify("原解析的域名", session);
}
});
connection.setConnectTimeout(mTimeOut); // 设置连接超时
connection.setReadTimeout(mTimeOut); // 设置读流超时
connection.connect();
4.2. Https SNI(单IP多HTTPS证书)场景
String url = "https://" + ip + "/pghead/xxxxxxx/140"; // 用HttpDns解析得到的IP封装业务的请求URL
HttpsURLConnection sniConn = null;
try {
sniConn = (HttpsURLConnection) new URL(url).openConnection();
// 设置HTTP请求头Host域
sniConn.setRequestProperty("Host", "原解析的域名");
sniConn.setConnectTimeout(3000);
sniConn.setReadTimeout(3000);
sniConn.setInstanceFollowRedirects(false);
// 定制SSLSocketFactory来带上请求域名 ***关键步骤
SniSSLSocketFactory sslSocketFactory = new SniSSLSocketFactory(sniConn);
sniConn.setSSLSocketFactory(sslSocketFactory);
// 验证主机名和服务器验证方案是否匹配?
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return HttpsURLConnection.getDefaultHostnameVerifier().verify("原解析的域名", session);
}
};
sniConn.setHostnameVerifier(hostnameVerifier);
int code = sniConn.getResponseCode();// Network block
if (code >= 300 && code < 400) {
// 临时重定向和永久重定向location的大小写有区分
String location = sniConn.getHeaderField("Location");
if (location == null) {
location = sniConn.getHeaderField("location");
}
if (!(location.startsWith("http://") || location.startsWith("https://"))) {
URL originalUrl = new URL(url);
location = originalUrl.getProtocol() + "://" + "原解析的域名" + location;
}
showSniHttpsDemo(location, "原解析的域名");
} else {
// 业务自己处理服务端响应结果
DataInputStream dis = new DataInputStream(sniConn.getInputStream());
int len;
byte[] buff = new byte[4096];
StringBuilder response = new StringBuilder();
while ((len = dis.read(buff)) != -1) {
response.append(new String(buff, 0, len));
}
dis.close();
Log.d("WGGetHostByName", "response: " + response.toString());
}
} catch (Exception e) {
Log.w("WGGetHostByName", e.getMessage());
} finally {
if (sniConn != null) {
sniConn.disconnect();
}
}
class SniSSLSocketFactory extends SSLSocketFactory {
private HttpsURLConnection conn;
public SniSSLSocketFactory(HttpsURLConnection conn) {
this.conn = conn;
}
@Override
public Socket createSocket() throws IOException {
return null;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return null;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return null;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return null;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return null;
}
@Override
public String[] getDefaultCipherSuites() {
return new String[0];
}
@Override
public String[] getSupportedCipherSuites() {
return new String[0];
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
String mHost = this.conn.getRequestProperty("Host");
if (mHost == null) {
mHost = host;
}
Log.i("WGGetHostByName", "customized createSocket host is: " + mHost);
InetAddress address = socket.getInetAddress();
if (autoClose) {
socket.close();
}
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);
ssl.setEnabledProtocols(ssl.getSupportedProtocols());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Log.i("WGGetHostByName", "Setting SNI hostname");
sslSocketFactory.setHostname(ssl, mHost);
} else {
Log.d("WGGetHostByName", "No documented SNI support on Android <4.2, trying with reflection");
try {
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
setHostnameMethod.invoke(ssl, mHost);
} catch (Exception e) {
Log.w("WGGetHostByName", "SNI not useable", e);
}
}
// verify hostname and certificate
SSLSession session = ssl.getSession();
HostnameVerifier mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
if (!mHostnameVerifier.verify(mHost, session))
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + mHost);
Log.i("WGGetHostByName",
"Established " + session.getProtocol() + " connection with " + session.getPeerHost() + " using " + session.getCipherSuite());
return ssl;
}
}
5. 检测本地是否使用了HTTP代理,如果使用了HTTP代理,建议不要使用HTTPDNS做域名解析
String host = System.getProperty("http.proxyHost");
String port= System.getProperty("http.proxyPort");
if (host != null && port != null) {
// 使用了本地代理模式
}