点击上方 好好学java ,选择 星标 公众号

重磅资讯、干货,第一时间送达

今日推荐:腾讯推出高性能 RPC 开发框架

个人原创100W+访问量博客:点击前往,查看更多

作者:程序猿石头/码农唐磊(本文来自作者投稿)

背景

分享一下之前踩的一个坑,背景是这样的:

我们的项目依赖于一个外部服务,该外部服务提供 REST 接口供我方调用,这是很常见的一个场景。本地和测试环境测试都没有问题,一切就绪。上了生产后,程序调用接口就总是网络不通。

需要说明的是本地、测试环境、生产环境通过不同的域名访问该外部服务。生产程序调用不通,神奇的是在生产环境通过 curl 等命令却能够正常调用对方接口。

这 TM 就神奇了,唯一不同的就是发起 HTTP 请求的客户端了,估计就是 HTTP 客户端有问题了?通过最后排查发现,居然发现了一枚 “JDK 的 bug”,然后石头就提交到了 JDK 的官网……

下面我们就来重现一下这个问题。

Server 端准备

这里用 Nginx 模拟了一下 上文提到的 REST 服务,假设调用正常返回 "Hello, World\n",Nginx 配置如下:

server {listen    80;server_name test_1.tanglei.name;location /testurl {add_header Content-Type 'text/plain; charset=utf-8';return 200 "Hello, World\n";}
}

不同的 Client 请求

下面用不同的 HTTP Client (分别用命令行 curl、Python 的 requests 包和 Java 的 URL 等尝试)去请求。

  • curl 请求,正常。

[root@VM_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; charset=utf-8Hello, World
[root@VM_77_245_centos vhost]#
  • Python requests 正常。

>>> import requests
>>> r = requests.get("http://test_1.tanglei.name/testurl")
>>> r.text
u'Hello, 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 规范,确实不允许含有下划线的 hostname。java.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 reviewer's 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 didn't 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 can't happen.

So ... this inconsistency is a "feature".

附本文链接

[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_id=8132508

[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_id=8170265

[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?answertab=active#tab-to

最后,再附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,笔者这几年及春招的总结,github 1.4k star,拿去不谢!下载方式1. 首先扫描下方二维码
2. 后台回复「Java面试」即可获取

给JDK报了一个P4的Bug,结果居然……相关推荐

  1. 给 JDK 报了一个 P4 的 Bug,结果居然……

    背景 分享一下之前踩的一个坑,背景是这样的: 我们的项目依赖于一个外部服务,该外部服务提供 REST 接口供我方调用,这是很常见的一个场景.本地和测试环境测试都没有问题,一切就绪上了生产后,程序调用接 ...

  2. jdk说明文档_给JDK报了一个P4的Bug,结果居然……

    (给ImportNew加星标,提高Java技能) 作者:程序猿石头/码农唐磊(本文来自作者投稿) 背景 分享一下之前踩的一个坑,背景是这样的: 我们的项目依赖于一个外部服务,该外部服务提供 REST ...

  3. 一个 P4 的 Bug,就难倒了 JDK 吗 ?

    作者 | 码农唐磊 来源 | 程序猿石头(ID:tangleithu) 背景 分享一下之前踩的一个坑,背景是这样的: 我们的项目依赖于一个外部服务,该外部服务提供 REST 接口供我方调用,这是很常见 ...

  4. 一个神奇的bug:OOM?优雅终止线程?系统内存占用较高?

    摘要:该项目是DAYU平台的数据开发(DLF),数据开发中一个重要的功能就是ETL(数据清洗).ETL由源端到目的端,中间的业务逻辑一般由用户自己编写的SQL模板实现,velocity是其中涉及的一种 ...

  5. android 4.4 keyfactory.getinstance 报错_Android实际开发bug大总结

    点击上方蓝字关注 ? 来源:杨充https://juejin.im/post/5d4ae16051882575595c4197 目录介绍 1.1 java.lang.UnsatisfiedLinkEr ...

  6. FIREDAC(DELPHI10 or 10.1)提交数据给ORACLE数据库的一个不是BUG的BUG

    发现FIREDAC(DELPHI10 or 10.1)提交数据给ORACLE数据库的一个不是BUG的BUG,提交的表名大小写是敏感的. 只要有一个表名字母的大小写不匹配,ORACLE就会认为是一个不认 ...

  7. 一个GDIPlus的Bug -- OutofMemory异常

    今天发现 framework2.0中的一个GDIPlus的Bug: 在Form的OnPaint事件里面写如下代码: private void Form1_Paint(object sender, Pa ...

  8. AI一分钟|阿里云解释故障原因:触发了一个未知代码Bug;清华蝉联ISC18超算竞赛总冠军...

     ▌阿里云发说明解释昨日故障原因:触发了一个未知代码Bug 今日凌晨,阿里云官方微博针对昨日出现的大规模故障问题作出官方回应.声明称,在运维上的一个操作失误,导致一些客户访问阿里云官网控制台和使用 ...

  9. Struts2环境下Tomcat启动异常:Exception starting filter struts2,报了一个java.lang.ClassNotFoundException

    Struts2环境下Tomcat启动异常:Exception starting filter struts2,报了一个java.lang.ClassNotFoundException 参考文章: (1 ...

最新文章

  1. java B2B2C Springboot电子商务平台源码-SSO单点登录之OAuth2.0登录认证
  2. 在项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?
  3. 通过zabbix_sender实现批量传递key值
  4. RabbitMQ 高可用之镜像队列
  5. 如何在 Mac 上查找路由器 IP 地址?
  6. 易建科技eKing Cloud招聘,快到碗里来~
  7. 计算机中缺少qt5quick.dll,qt5quick.dll
  8. MATLAB机器学习决策树网格法优化参数学习笔记
  9. VMware ESXi 7.0 Update 3c SLIC 2.6 Unlocker (2022 U3 Refresh)
  10. Vue 自定义指令里面获取Vue实例 实现v-copy与i18n结合。
  11. 2、★☆STM32的智能浇水补光系统√☆★
  12. 加一 — Python
  13. 浅析STM32H7 FDCAN(二)
  14. [zt]给你的Mp4大换血,精选Touch里3年收集的900多首歌,经典不忍去的最新近流行的,与你共享~~...
  15. 2012北京航空航天大学考研机试真题
  16. 全国高校食堂排行榜TOP10?!
  17. C语言编程练习——循环结构(一)
  18. 傅里叶分析(基础介绍)
  19. linux系统如何下载报文,基于Linux系统的报文转发方法和装置专利_专利查询 - 天眼查...
  20. python期中考试及答案_PYTHON期中考试试卷

热门文章

  1. Object C学习笔记12-集合
  2. hdu 1081To The Max
  3. ST17H26开发小总结
  4. Fedora 11 上安装subversion 和apache服务器
  5. Keras学习代码—github官网examples
  6. leetcode-简单题-题序:1+7
  7. [BUUCTF]Reverse——[网鼎杯 2020 青龙组]jocker
  8. 攻防世界Reverse第十题getit
  9. 05-cache相关的系统寄存器
  10. optee3.12.0 qemu_v8的环境搭建篇(ubuntu20.04)