給 JDK 報了一個 P4 的 Bug,結果竟然……

點擊上方藍色字體,關注我 ——
html

一個在阿里雲打工的清華學渣!java


圖 by:石頭@北京-望京python

關於做者: 程序猿石頭(ID: tangleithu),現任阿里巴巴技術專家,清華學渣,前大疆後端 Leader。 歡迎關注,交流和指導!

背景

分享一下以前踩的一個坑,背景是這樣的:nginx

咱們的項目依賴於一個外部服務,該外部服務提供 REST 接口供我方調用,這是很常見的一個場景。本地和測試環境測試都沒有問題,一切就緒上了生產後,程序調用接口就老是網絡不通。程序員

須要說明的是本地、測試環境、生產環境經過不一樣的域名訪問該外部服務。生產程序調用不通,神奇的是在生產環境經過 curl 等命令卻可以正常調用對方接口。web

What??

這 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 等嘗試)去請求。centos

  • 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-8

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

後記

以爲本號分享的文章有價值,記得添加星標哦。周更很累,不要白 piao,須要來點正反饋,安排個 「一鍵三連」(點贊、在看、分享)如何?😝 這將是我持續輸出優質文章的最強動力。


推 薦 閱 讀

快快加入咱們——「阿里雲-ECS/神龍計算平臺」 招人啦
面試官:會玩牌吧?給我講講洗牌算法和它的應用場景吧!

面了 7 輪 Google,最終仍是逃不脫被掛的命運

從一道面試題談談一線大廠碼農應該具有的基本能力

分析了得到家庭搖號新能源指標的數據後,我發現了一個祕密



程序猿石頭 


程序猿石頭(ID: tangleithu),現任阿里巴巴技術專家,清華學渣,前大疆後端 Leader。用不一樣的視角分享高質量技術文章,以每篇文章都讓人有收穫爲目的,歡迎關注,交流和指導!掃碼回覆關鍵字 「1024」 獲取程序員大廠面試指南


附本文連接:

[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

本文分享自微信公衆號 - 程序猿石頭(tangleithu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索