培训如何优化网站,苏州设计网站建设,普陀网站制作有哪些,重庆做网站推广公司图 by#xff1a;石头北京-望京关于作者#xff1a;程序猿石头(ID: tangleithu)#xff0c;现任阿里巴巴技术专家#xff0c;清华学渣#xff0c;前大疆后端 Leader。背景分享一下之前踩的一个坑#xff0c;背景是这样的#xff1a;我们的项目依赖于一个外部服务#x… 图 by石头北京-望京关于作者程序猿石头(ID: tangleithu)现任阿里巴巴技术专家清华学渣前大疆后端 Leader。背景分享一下之前踩的一个坑背景是这样的我们的项目依赖于一个外部服务该外部服务提供 REST 接口供我方调用这是很常见的一个场景。本地和测试环境测试都没有问题一切就绪上了生产后程序调用接口就总是网络不通。需要说明的是本地、测试环境、生产环境通过不同的域名访问该外部服务。生产程序调用不通神奇的是在生产环境通过 curl 等命令却能够正常调用对方接口。What??这 TM 就神奇了唯一不同的就是发起 HTTP 请求的客户端了估计就是 http客户端有问题了通过最后排查发现居然发现了一枚 “JDK 的 bug”然后石头就提交到了 JDK 的官网……下面我们就来重现一下这个问题。server 端准备这里用 Nginx 模拟了一下 上文提到的 REST 服务假设调用正常返回 Hello, World\nNginx 配置如下server {listen 80;server_name test_1.tanglei.name;location /testurl {add_header Content-Type text/plain; charsetutf-8;return 200 Hello, World\n;}
}
不同的 client 请求下面用不同的 Http client 分别用命令行curlpython的 requests包和 Java 的 URL 等尝试去请求。curl 请求正常。[rootVM_77_245_centos vhost]# curl -i http://test_1.tanglei.name/testurl
HTTP/1.1 200 OK
Server: nginx
Content-Length: 13
Connection: keep-alive
Content-Type: text/plain; charsetutf-8Hello, World
[rootVM_77_245_centos vhost]#
python requests 正常。 import requestsr requests.get(http://test_1.tanglei.name/testurl)r.text
uHello, World\n
Java 的 java.net.URLConnection 同样正常。static String getContent(java.net.URL url) throws Exception {java.net.URLConnection conn url.openConnection();java.io.InputStreamReader in new java.io.InputStreamReader(conn.getInputStream(), utf-8);java.io.BufferedReader reader new java.io.BufferedReader(in); StringBuilder sb new StringBuilder();int c -1;while ((c reader.read()) ! -1) {sb.append((char)c);}reader.close();in.close();String response sb.toString();return response;
}
上面的这个方法 String getContent(java.net.URL url) 传入一个构造好的 java.net.URL 然后 get 请求并以 String 方式返回 response。String srcUrl http://test_1.tanglei.name/testurl;
java.net.URL url new java.net.URL(srcUrl);
System.out.println(\nurl result:\n getContent(url)); // OK
上面的语句输出正常结果如下url result:
Hello, World
这就尼玛神奇了吧。看看我们程序中用的 httpclient 的实现结果发现是有用 java.net.URI心想这不至于吧用 URI 就不行了么。换 java.net.URI 试试? 这里不展开讲URL和URI的区别联系了可以简单的认为URL是URI的一个子集详细的可参考 URI、URL 和 URN[1], wiki URI[2]直接通过java.net.URI构造再调用 URI.toURL 得到 URL调用同样正常。关键的来了httpclient 源码中用的构造函数是另外一个URI(String scheme, String host, String path, String fragment)
Constructs a hierarchical URI from the given components.
我用这个方法构造URI会构造失败new java.net.URI(uri.getScheme(), uri.getHost(), uri.getPath(), null) error: protocol http host null
new java.net.URI(url.getProtocol(), url.getHost(), url.getPath(), null) error: Illegal character in hostname at index 11: http://test_1.tanglei.name/testurl
所以问题发现了我们的项目中依赖的第三方 httpclient包底层用到了 java.net.URI恰好在 java.net.URI 中是不允许以下划线(_)作为 hostname 字段的。即 uri.getHost() 和 uri.toURL().getHost() 居然能不相等。这是 JDK 的 Bug 吧有理由怀疑这是 JDK 的 Bug 吧从官网上还真找到了关于包含下划线作为hostname的bug提交issue戳这里 JDK-8132508 : Bug JDK-8029354 reproduces with underscore in hostname[3]然后发现该 bug reporter 的情况貌似跟我的差不多只不过引爆bug的点不一样。该 bug reviewer 最后以 Not an Issue 关闭给出的理由是RFC 952 disallows _ underscores in hostnames. So, this is not a bug.确实rfc952[4] 明确明确说了域名只能由 字母 (A-Z)、 数字(0-9)、 减号 (-) 和 点 (.) 组成。那 OK 吧既然明确规定了 hostname 不能包含下划线为啥 java.net.URL 确允许呢造成 java.net.URI 和 java.net.URL 在处理 hostname 时的标准不一致且本身 java.net.URI 在构造的时候也带了 有色眼镜同一个url字符串 通过静态方法 java.net.URI.create(String) 或者通过带1个参数的构造方法 java.net.URI(String) 都能成功构造出 URI 的实例但通过带4个参数的构造方法就不能构造了。要知道在 coding 过程中尽早反馈异常信息更有利于软件开发持续迭代的过程。我们在开发过程中也应该遵循这一点原则。于是我就去 JDK 官网提交了一个 bug大意是说 java.net.URI 和 java.net.URL 在处理hostname的时候标准不一致容易使开发人员埋藏一些潜在的bug同时也还把这个问题反馈到 stackoverflow[5] 了I am wondering, if hostname with underscore is not valid, why the result is differrent between java.net.URI and java.net.URL? Is it a bug or a feature? Here is the example.java.net.URL url new java.net.URL(http://test_1.tanglei.name); System.out.println(url.getHost()); //test_1.tanglei.name java.net.URI uri new java.net.URI(http://test_1.tanglei.name); System.out.println(uri.getHost()); //null这个 JDK bug issue 详细信息见 JDK-8170265 : underscore is allowed in java.net.URL while not in java.net.URI[6]openjdk JDK-8170265[7]经过初步 Review被认为是一个 P4 的 Bug说的是 java.net.URL 遵循的是 RFC 2396 规范确实不允许含有下划线的 hostnamejava.net.URI 做到了 而 java.net.URL 没有做到。重点来了然后却被另外一个 Reviewer 直接个毙了。给出的原因是 java.net.URL 构造方法中API 文档中说了本来也不会做验证即 No validation of the inputs is performed by this constructor. 在线 api doc 戳这里[8] (可以点连接进去搜索关键字 No validation)当初没有收到及时反馈就没有来得及怼回去。其实就算 No validation of the inputs is performed by this constructor. 是合理的里面也只有3个构造函数有这样的说明按照这样的逻辑是不是说另外的构造函数有验证呢..... (示例中的默认的构造函数都没有说呀)这里有java.net.URL 的源码[9]看兴趣的同学可以看看。恩以上就是结论了。不过反正我自己感觉目前 Java API 关于这里的设计不太合理欢迎大家讨论。我在SO提问的这个回答[10]比较有意思哈哈。The review is somewhat terse, but the reviewers point is the URL constructor is behaving in accordance with its specification. Since the specification explicitly states that no validation is performed, this is not a bug in the code. This is indisputable.What he didnt spell out is that fixing this inconsistency (by changing the URL class specification) would break lots of peoples 20 year old code Java code. That would be a really bad idea. It cant happen.So ... this inconsistency is a feature.后记觉得本号分享的文章有价值记得添加星标哦。周更很累不要白 piao需要来点正反馈安排个 “一键三连”点赞、在看、分享如何???? 这将是我持续输出优质文章的最强动力。附本文链接[1] URI、URL 和 URN: https://www.ibm.com/developerworks/cn/xml/x-urlni.html[2]wiki URI: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier[3]JDK-8132508 : Bug JDK-8029354 reproduces with underscore in hostname: http://bugs.java.com/bugdatabase/view_bug.do?bug_id8132508[4]rfc952: https://tools.ietf.org/html/rfc952[5]stackoverflow: https://stackoverflow.com/questions/44226003/conflicts-between-java-net-url-and-java-net-uri-when-dealing-with-hostname-conta[6]JDK-8170265 : underscore is allowed in java.net.URL while not in java.net.URI: http://bugs.java.com/bugdatabase/view_bug.do?bug_id8170265[7]openjdk JDK-8170265: https://bugs.openjdk.java.net/browse/JDK-8170265[8]在线 api doc 戳这里: https://docs.oracle.com/javase/8/docs/api/java/net/URL.html[9]这里有java.net.URL 的源码: http://www.docjar.com/html/api/java/net/URL.java.html[10]我在SO提问的这个回答: https://stackoverflow.com/questions/44226003/conflicts-between-java-net-url-and-java-net-uri-when-dealing-with-hostname-conta?answertabactive#tab-to
往期推荐
Java新特性数据类型可以扔掉了动图演示手撸堆栈的两种实现方法JDK15正式发布新增功能预览关注下方二维码收获更多干货