昨天同事遇到一个问题,在Android设备上用HttpUrlConnection下载文件的时候抛出了UnknownHostException异常,这个异常本身并不少见,相信很多开发者遇到过,一眼望过去so easy(我开始也是这么觉得),但是并非那么简单,因为电脑或手机浏览器都可以请求成功,并且同样的代码在电脑上运行没有问题。
分析问题
Firebug跟踪”http://fget.iviny.com:10010/c/91″这个链接的请求,发现链接会做一次302跳转,但我们的代码已经处理了url重定向,所以问题的根源不在这里。
代码直接对重定向地址进行测试,这个时候就抛出了UnknownHostException异常,实际上是跳转之后的地址域名无法解析,为了排除网络环境的原因,分别在WIFI,移动网络下测试都得到同样的结果。
1 2 3 4 5 6 7 8 9 |
11-10 11:40:55.696: W/System.err(10997): java.net.UnknownHostException: host == null 11-10 11:40:55.697: W/System.err(10997): at com.android.okhttp.HostResolver$1.getAllByName(HostResolver.java:28) 11-10 11:40:55.697: W/System.err(10997): at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:232) 11-10 11:40:55.697: W/System.err(10997): at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:124) 11-10 11:40:55.697: W/System.err(10997): at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:272) 11-10 11:40:55.697: W/System.err(10997): at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:211) 11-10 11:40:55.697: W/System.err(10997): at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:373) 11-10 11:40:55.697: W/System.err(10997): at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:323) 11-10 11:40:55.697: W/System.err(10997): at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:491) |
Ping 测试
然后通过ping命令去测试这个域名发现,它是cname类型的域名解析,指向了另外一个域名。
结论
于是我大胆猜测Android默认的DNS解析没有处理cname这种类型(或者有,而我不知道),于是我分别用HttpUrlConnection、DefaultHttpClient、AndroidHttpClient三种HTTP的实现去测试。得到的结果和预测一样,还是UnknownHostException。 那么如何解决呢?
手动解析域名
目前想到的办法就是,自己做域名解析,采用一个开源的dns解析库xbill,当发生UnknownHostException异常的时候手动解析,用解析之后的域名替换老域名。
1 2 3 4 5 6 7 8 9 10 11 12 |
private String getCnameForHost(final String host) throws TextParseException { final Lookup lookup = new Lookup(host, Type.CNAME); lookup.run(); if (lookup.getAnswers().length == 0) { return null; } String target = ((CNAMERecord) lookup.getAnswers()[0]).getTarget() .toString(); return target.substring(0, target.length() - 1); } |
总结
然后对整个案例的现场回顾一下,首先是url连接有302重定向(这里需要手动接管url跳转,可以参考我的另外一篇博客),然后是域名的cname,由于公司采用亚马逊的云服务,这个文件是放在亚马逊的cdn上,而cdn就需要依赖cname,整个场景串联起来就产生了这个非典型的UnknownHostException。其实这种解决方式并不完美,因为是依赖异常来处理正常的业务逻辑,所以希望谁有更好的方式一起讨论一下。