給JDK提交了一個bug, 然而...

這實際上是去年就踩的一個坑了, 以前又踩到一個相似的, 因而想起在這裏來分享一下. 背景是這樣的:html

咱們的項目依賴於一個外部服務, 該外部服務提供 REST 接口供我方調用, 本地測試和測試環境都沒有問題, 可是一上生產環境就發現網絡不通. (本地測試/測試環境, 生產環境網絡經過不通的域名訪問該外部服務), 且在生產環境經過 curl 等命令可以正常調用對方接口. 最終排查緣由出如今域名上, 在生產環境中經過 java 的 httpclient (該第三方包依賴java.net.URI) 調用未發出請求. 該域名形如 http://test_1.tanglei.namejava

下面來重現一下該案例.python

server 端準備nginx

這裏用 nginx 模擬了一下 上文提到的 REST 服務, 假設調用正常返回 "Hello, World\n", nginx 配置以下:git

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";
    }
}

clientapi

curl 命令網絡

curl 請求
給JDK提交了一個bug, 然而...app

請忽略上面的兩個重複的header(nginx 默認有一個header, 上面的配置又加了一個), 能夠點擊這裏查看效果 http://test_1.tanglei.name/testurl. (對,我解析了這個域名)dom

python requestscurl

python 也是調用OK
給JDK提交了一個bug, 然而...

java

咱們來看一下經過 Java 調用.
給JDK提交了一個bug, 然而...

上面的這個方法 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

換 java.net.URI 試試? (這裏不展開講URL和URI的區別聯繫了, 能夠簡單的認爲URL是URI的一個子集, 詳細的可參考 URI、URL 和 URN, wiki URI)
直接經過java.net.URI構造, 再調用 URI.toURL 獲得URL,調用一樣正常。關鍵的來了

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 嗎?

從官網上還真找到了關於包含下劃線做爲hostname的bug提交ticket, 戳這裏 JDK-8132508 : Bug JDK-8029354 reproduces with underscore in hostname , 而後發現該 「bug」 reporter 的狀況貌似跟個人差很少,只不過引爆bug的點不同.

該 「bug」 reviewer 最後以 「Not an Issue」 關閉,給出的理由是

RFC 952 disallows _ underscores in hostnames. So, this is not a bug.

確實, rfc952 明確說了域名只能由 字母 (A-Z), 數字(0-9), 減號 (-), 和 點 (.) 組成。

那 OK 吧, 既然明確規定了 hostname 不能包含下劃線, 爲啥 java.net.URL 確容許呢? 形成 java.net.URI 和 java.net.URL 在處理 hostname 時的標準不一致, 且自己 java.net.URI 在構造的時候也帶了 「有色」眼鏡, 經過靜態方法 java.net.URI.create(String) 或者經過帶1個參數的構造方法 java.net.URI(String) 都能成功構造出 URI 的實例,經過帶4個參數的構造方法就不能構造了. (同一個url字符串).

要知道, 在 coding 過程當中,儘早反饋異常信息更有利於軟件開發持續迭代的過程. 咱們在開發過程當中也應該遵循這一點原則。

JDK(java.net.URL) 中的 「bug」 ?

我記得去年我就到JDK官網提交了一個 bug, 大意是說 java.net.URI 和 java.net.URL 在處理hostname的時候標準不一致, 容易使開發人員埋藏一些潛在的bug. 不過當初提交以後就沒有反應了。 (爲啥沒有收到相應的郵件通知 report 狀態? 也bug了?)

直到前兩天, 又把該問題提交到 stackoverflow.

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

過了1天才發現原來我去年提交的bug有更新狀態了. bug 詳細信息見 JDK-8170265 : underscore is allowed in java.net.URL while not in java.net.URI, (openjdk JDK-8170265 更詳細)。 然而該 bug 狀態也以 「Not an Issue」 了結.
不過其中一個reviewer仍是認可了這個問題, 說的是 java.net.URL 遵循的是 RFC 2396 規範, 確實不容許含有下劃線的hostname,java.net.URI 作到了, 而 java.net.URL 沒有作到。

As per RFC 2396:
「Hostnames take the form described in Section 3 of [RFC1034] and
Section 2.1 of [RFC1123]: a sequence of domain labels separated by
「.」, each domain label starting and ending with an alphanumeric
character and possibly also containing 「-「 characters. The rightmost
domain label of a fully qualified domain name will never start with a
digit, thus syntactically distinguishing domain names from IPv4
addresses, and may be followed by a single 「.」 if it is necessary to
distinguish between the complete domain name and any local domain.
To actually be 「Uniform」 as a resource locator, a URL hostname should
be a fully qualified domain name. In practice, however, the host
component may be a local domain literal.」

URI class is following the above, but URL class doesn’t seem to follow the same rules.

To reproduce the issue , run the attached test case.
Following is the output on various JDK versions:
JDK 8 - Fail
JDK 8u112 - Fail
JDK 8u122-ea - Fail
JDK 9-ea + 141 - Fail

重點來了, 而後, 被上一級 reviewer 直接個斃了. 緣由是 java.net.URL 構造方法中,api文檔中說了原本也不會作驗證即 No validation of the inputs is performed by this constructor. 在線 api doc 戳這裏 (能夠點鏈接,進去搜索關鍵字 「No validation」)

The constructors of URL class (e.g., http://download.java.net/java/jdk9/docs/api/java/net/URL.html#URL-java.lang.String-java.lang.String-java.lang.String-) specifically mention about the validation:

「No validation of the inputs is performed by this constructor.」

So not throwing an exception isn’t an issue here.
給JDK提交了一個bug, 然而...

其實就算 「No validation of the inputs is performed by this constructor.」 是合理的, 裏面也只有3個構造函數有這樣的說明,按照這樣的邏輯是否是說另外的構造函數有驗證呢….. (示例中的默認的構造函數都沒有說呀)

這裏有java.net.URL 的源碼, 看興趣的同窗能夠看看.

恩,以上就是結論了。
不過,反正我本身感受目前Java API 關於這裏的設計不太合理, 歡迎你們討論。(也對SO上某答案表示贊同, 哈哈)

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".

搞個投票, 說說你的意見?

點擊原文連接能夠看到本文所附代碼.

相關文章
相關標籤/搜索