IT技术互动交流平台

Android使用OkHttp支持HttpDNS

来源:IT165收集  发布日期:2016-01-18 21:33:39

首先,需要明确一个概念,什么叫做HttpDNS以及为什么要用HttpDNS。

HttpDNS是使用HTTP协议向DNS服务器的80端口进行请求,代替传统的DNS协议向DNS服务器的53端口进行请求。也就是使用Http协议去进行dns解析请求,将服务器返回的解析结果,也就是域名对应的服务器ip获得,直接向该ip发起对应的api服务请求,代替使用域名。

那么为什么要使用HttpDNS呢?主要原因有三点

LocalDNS劫持 平均访问延迟下降 用户连接失败率下降

LocalDNS劫持: 由于HttpDNS是通过ip直接请求http获取服务器A记录地址,不存在向本地运营商询问domain解析过程,所以从根本避免了劫持问题。 (对于http内容tcp/ip层劫持,可以使用验证因子或者数据加密等方式来保证传输数据的可信度)

平均访问延迟下降: 由于是ip直接访问省掉了一次domain解析过程,(即使系统有缓存速度也会稍快一些‘毫秒级’)通过智能算法排序后找到最快节点进行访问。

用户连接失败率下降: 通过算法降低以往失败率过高的服务器排序,通过时间近期访问过的数据提高服务器排序,通过历史访问成功记录提高服务器排序。如果ip(a)访问错误,在下一次返回ip(b)或者ip(c) 排序后的记录。(LocalDNS很可能在一个ttl时间内(或多个ttl)都是返回记录

至于HttpDNS更加详细的内容,可以参考下面这篇文章

那么,在客户端该如何实现httpDNS呢?目前,国内有一部分厂商已经提供了这个解析服务,我们可以使用它们的服务,也可以使用自建服务器进行中转,至于自建服务器上如何实现,是调第三方呢还是自己去解析呢这个属于服务器的事,对于客户端来说是完全透明的。这篇文章主要是为了学习,为了方便起见,我们直接使用第三方服务。目前,提供httpdns解析服务的有:

阿里巴巴 阿里云HttpDNS

DNSPod D+

无论是哪个api,都是直接调用它们暴露的restful api获得解析结果,只不过收费问题不一样,当然也有免费的,免费的是有限制的。

阿里云的HttpDNS服务的api比较标准,直接发一个Get请求,带上请求参数,返回结果以json返回。

服务URL:http://203.107.1.1/d 请求方法:HTTP GET 请求参数
名称 是否必须 描述
host 必须 要查询的域名
ip 可选 用户IP,如果没有这个参数,将使用TCP连接的源IP作为用户IP

实例

http://203.107.1.1/d?host=www.taobao.com&ip=42.120.74.196

请求成功时,返回结果如下

{
  'host': 'www.taobao.com',
  'ips': [
    '115.238.23.241',
    '115.238.23.251'
  ],
  'ttl': 57
}

而DNSPod的API基本上和阿里云的没什么差别,只不过返回结果不是以json返回,而是直接返回ip地址。举个例子:

服务URL:http://119.29.29.29/d 请求方法:HTTP GET 请求参数
名称 是否必须 描述
dn 必须 要查询的域名
ip 可选 用户IP,如果没有这个参数,将使用TCP连接的源IP作为用户IP
ttl 可选 ttl=1 表示要求 D+服务器在响应结果中携带解析结果的 ttl 值,返回的 ttl 和域名解析结果用英文逗号分割

实例:

http://119.29.29.29/d?dn=www.dnspod.cn&ip=1.1.1.1&ttl=1

请求成功则返回ip地址,但不是json格式,如果存在ttl=1,则以逗号分隔,这点个人有点不喜欢

59.37.116.101,60

介于阿里云的api更加标准,这里以阿里云的api为例,进行举例说明。

既然我们可以拿到域名对应的ip了,那么拿到ip后我们需要做两步:

将域名替换为ip地址 将请求头中添加host属性,值为域名对应的ip地址

做完了这两步,我们就可以进行正常的请求了,当然,这只是针对http请求,对于https请求,可能比这个还要复杂。

我们以OkHttp作为网络请求的底层支持,那么这个实现就显得格外的简单,对用户来说可以做到完全透明化,在用户不知情的情况下完成这个操作。没错,答案就是拦截器,在发出请求之前做这个替换。

首先我们需要写一个工具类,完成获得域名对应的ip以及替换操作

public class HttpDNSUtil {
    /**
     * 转换url 主机头为ip地址
     *
     * @param url  原url
     * @param host 主机头
     * @param ip   服务器ip
     * @return
     */
    public static String getIpUrl(String url, String host, String ip) {
        if (url == null) {
            Log.e('TAG', 'URL NULL');
        }
        if (host == null) {
            Log.e('TAG', 'host NULL');
        }
        if (ip == null) {
            Log.e('TAG', 'ip NULL');
        }
        if (url == null || host == null || ip == null) return url;
        String ipUrl = url.replaceFirst(host, ip);
        return ipUrl;
    }
    /**
     * 根据url获得ip,此方法只是最简单的模拟,实际情况很复杂,需要做缓存处理
     *
     * @param host
     * @return
     */
    public static String getIPByHost(String host) {
        HttpUrl httpUrl = new HttpUrl.Builder()
                .scheme('http')
                .host('203.107.1.1')
                .addPathSegment('d')
                .addQueryParameter('host', host)
                .build();
        //与我们正式请求独立,所以这里新建一个OkHttpClient
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url(httpUrl)
                .get()
                .build();
        try {
            String result = null;
            /**
             * 子线程中同步去获取
             */
            Response response = okHttpClient.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject jsonObject = new JSONObject(body);
                JSONArray ips = jsonObject.optJSONArray('ips');
                if (ips != null) {
                    result = ips.optString(0);
                }
            }
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }
}

getIpUrl方法是传入原url和host以及host对应的ip,进行host替换ip操作,而getIPByHost方法则是根据host获得ip地址,这个过程只是很简单的在子线程中同步的去拿数据,其实这里有一层HttpDNS的库的存在,如果你想把这一层做出一个库来使用,应该要考虑很多东西,包含缓存的处理,等等,你可以参考新浪微博的开源库 HTTPDNSLib 的实现。

然后实现一个HttpDNSInterceptor拦截器去进行替换操作,拿到原始url和host,首先根据host查询ip,得到ip,会对这个ip进行一次判断,如果为null,也就是请求解析失败,包括各种原因,我们不对host进行替换;否则,也就是请求解析成功的情况,调用之前替换url的方法对url进行替换操作,替换完成后开始发起替换后的请求。代码实现如下

public class HttpDNSInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originRequest = chain.request();
        HttpUrl httpUrl = originRequest.url();

        String url = httpUrl.toString();
        String host = httpUrl.host();
        Log.e('HttpDNS', 'origin url:' + url);
        Log.e('HttpDNS', 'origin host:' + host);

        String hostIP = HttpDNSUtil.getIPByHost(host);

        Request.Builder builder = originRequest.newBuilder();
        if (hostIP != null) {
            builder.url(HttpDNSUtil.getIpUrl(url, host, hostIP));
            builder.header('host', hostIP);
            Log.e('HttpDNS', 'the host has replaced with ip ' + hostIP);
        } else {
            Log.e('HttpDNS', 'can't get the ip , can't replace the host');
        }

        Request newRequest = builder.build();
        Log.e('HttpDNS', 'newUrl:' + newRequest.url());
        Response newResponse = chain.proceed(newRequest);
        return newResponse;
    }
}

最后的一步,便是将这个拦截器设置到我们的请求中去。

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.interceptors().add(new HttpDNSInterceptor());
OkHttpClient okHttpClient = builder.build();

找一个支持ip访问的服务器测试下具体效果,看看和域名请求有没有差别,没有差别就成功了

Request.Builder requestBuilder = new Request.Builder();
requestBuilder.url('http://your.domain/path3/path4/path3?param1=value1');
okHttpClient.newCall(requestBuilder.build()).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        String result = response.body().string();
        Log.e('MainActivity', result);
    }
});

如果不出意外,上面的访问会被替换为http://ip/path3/path4/path3?param1=value1进行访问,其中ip为your.domain对应的ip地址。

总之,使用OkHttp作为网络层,要支持HttpDNS是件很简单的事,完全不用修改现有的网络访问代码,直接加一个拦截器,便可透明的支持HttpDNS。使用HttpDNS有利有弊,需要权衡后使用,没必要给自己添加毫无必要的麻烦。

延伸阅读:

  • 专题推荐

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规