偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Java中HttpURLConnection 與 PoLA 法則

開(kāi)發(fā) 后端
如果你也是開(kāi)發(fā)者的話,你很可能已經(jīng)知道PoLA法則(Principle of Lease Astonishment)。那么,看看這篇文章講述的充滿奇幻色彩的調(diào)試經(jīng)歷,來(lái)見(jiàn)識(shí)一下PoLA是如何與HttpURLConnection發(fā)生了關(guān)聯(lián)。

如果你和我一樣也是開(kāi)發(fā)者的話,你很可能已經(jīng)聽(tīng)說(shuō)過(guò)“PoLA”原則,或者叫作“產(chǎn)生最少意外”原則。意思非常簡(jiǎn)單,就是不要讓你的用戶感到驚訝。 或者更明確一些,就像本文這種情況,不要讓另外一個(gè)開(kāi)發(fā)者感到驚訝。不幸的是,我上個(gè)星期就遇到了大大超出我意外的事情,我們有個(gè)服務(wù)的客戶調(diào)用端總是發(fā) 出一些垃圾的請(qǐng)求。

你說(shuō)垃圾請(qǐng)求嗎?是的,就像這樣,我們完全不清楚這些請(qǐng)求是從哪里來(lái)的。又是這樣一個(gè)時(shí)刻,經(jīng)理們毫無(wú)頭緒,抱頭亂竄,驚呼“我們肯定是被黑客攻擊了”,或者 ”有人把防火墻給關(guān)掉了??!”

無(wú)論如何,先說(shuō)點(diǎn)背景情況吧,我們的項(xiàng)目里有自動(dòng)記錄活動(dòng)日志的功能,當(dāng)某些情況下,比如一個(gè)進(jìn)程啟動(dòng)的時(shí)候就會(huì)進(jìn)行記錄。這包括我們那出問(wèn)題的網(wǎng) 絡(luò)服務(wù)客戶端和服務(wù)端,因?yàn)樗鼈儍烧叨紝儆谙到y(tǒng)的一部分。在某些時(shí)候,我們注意到,服務(wù)端的響應(yīng)還沒(méi)有發(fā)出的時(shí)候,另外一個(gè)來(lái)自同樣客戶端的請(qǐng)求又發(fā)了過(guò) 來(lái)。這個(gè)真是出乎意料的,因?yàn)榭蛻舳舜a是單線程的,也沒(méi)有其他的客戶端摻和進(jìn)來(lái)。審查代碼、測(cè)試之后,結(jié)論是我們的客戶端不可能在第一個(gè)請(qǐng)求還沒(méi)結(jié)束的 時(shí)候再同時(shí)發(fā)出另外一個(gè)。

經(jīng)過(guò)一整天的調(diào)試和研究日志發(fā)現(xiàn),事實(shí)上,在服務(wù)端處理還未結(jié)束的時(shí)候客戶端其實(shí)已經(jīng)斷開(kāi)連接了。所以,這些請(qǐng)求終究并不是同時(shí)發(fā)生的,但是為什么我們花了一整天的時(shí)間才發(fā)現(xiàn)呢?這跟我們玩了一整天的星球大戰(zhàn)有啥區(qū)別?

好吧,其實(shí)也不是。我們發(fā)現(xiàn)了罪魁禍?zhǔn)?,服?wù)端的容器軟件HTTP的讀超時(shí)設(shè)置被調(diào)得太低了。服務(wù)端的日志顯示的確生成了響應(yīng),但是客戶端卻在此之 前已經(jīng)斷開(kāi)了,因?yàn)榉?wù)器端發(fā)生了讀超時(shí)。這些在服務(wù)器端當(dāng)然沒(méi)有日志記錄,因?yàn)檫@種行為是更低一層協(xié)議決定的(HTTP棧),而不是服務(wù)端的應(yīng)用代碼。

是的,沒(méi)錯(cuò),我聽(tīng)明白了,但是客戶端的日志該怎么解釋?客戶端是不是應(yīng)該拋出一個(gè)“ReadTimeoutException”異常,或者類似的玩 意,然后可以寫(xiě)到日志里?然而,沒(méi)錯(cuò),事實(shí)上,并沒(méi)有。就像現(xiàn)在發(fā)現(xiàn)的一樣,真正的意外來(lái)自HttpURLConnection類的內(nèi)部(更確切地說(shuō),是 默認(rèn)的Oracle的官方實(shí)現(xiàn)sun.net.www.protocol.http.HttpURLConnection)。

你以前是否知道HttpURLConnection的默認(rèn)實(shí)現(xiàn)有個(gè)在某些情形下自動(dòng)重試的特性?好吧,我之前就不知道。當(dāng)時(shí)的情況是,客戶端的確觸 發(fā)了超時(shí)異常,但是卻被HttpURLConnection給捕捉了,而它自己決定重新嘗試一次。這就意味著,你調(diào)用了 HttpURLConnection的read()方法,它阻塞了,你正在等待,看起來(lái)就好像是在等待第一次請(qǐng)求的響應(yīng)一樣。但是在 HttpURLConnection內(nèi)部,它作了不止一次嘗試,因此創(chuàng)建了不止一個(gè)socket連接。這就解釋了為什么第二次及以后的請(qǐng)求永遠(yuǎn)在日志里找 不到,因?yàn)檫@些第二次之后的請(qǐng)求是HttpURLConnection內(nèi)部發(fā)起的。

讓我們上一些代碼重現(xiàn)一下。

import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.concurrent.Executors;
import com.sun.net.httpserver.HttpServer;
/**
 * Created by koen on 30/01/16.
 */
public class TestMe {
 public static void main(String[] args) throws Exception {
  startHttpd();
  HttpURLConnection httpURLConnection = (HttpURLConnection) new URL("http://localhost:8080/").openConnection();
  if (!(httpURLConnection instanceof sun.net.www.protocol.http.HttpURLConnection)) {
   throw new IllegalStateException("Well it should really be sun.net.www.protocol.http.HttpURLConnection. "
     + "Check if no library registered it's impl using URL.setURLStreamHandlerFactory()");
  }
  httpURLConnection.setRequestMethod("POST");
  httpURLConnection.connect();
  System.out.println("Reading from stream...");
  httpURLConnection.getInputStream().read();
  System.out.println("Done");
 }
 public static void startHttpd() throws Exception {
  InetSocketAddress addr = new InetSocketAddress(8080);
  HttpServer server = HttpServer.create(addr, 0);
  server.createContext("/", httpExchange -> {
   System.out.println("------> Httpd got request. Request method was:" + httpExchange.getRequestMethod() + " Throwing timeout exception");
   if (true) {
    throw new SocketTimeoutException();
   }
  });
  server.setExecutor(Executors.newCachedThreadPool());
  server.start();
  System.out.println("Open for business.");
 }
}

運(yùn)行之,將會(huì)得到類似下面的輸出。

Open for business.
Reading from stream...
------> Httpd got request. Request method was:POST Throwing timeout exception
------> Httpd got request. Request method was:POST Throwing timeout exception
Exception in thread "main" java.net.SocketException: Unexpected end of file from server
 at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:792)
 ...

注意,我們的監(jiān)聽(tīng)服務(wù)被調(diào)用了兩次,但是我們只發(fā)了一個(gè)請(qǐng)求。如果我們加上-Dsun.net.http.retryPost=false這個(gè)屬性再運(yùn)行一次的話,我們會(huì)得到下面的輸出:

------> Httpd got request. Request method was:POST Throwing timeout exception
Exception in thread "main" java.net.SocketException: Unexpected end of file from server
 at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:792)
 ...

好,先把這事放一邊,我想問(wèn)的是,到底是誰(shuí)搞出這么個(gè)設(shè)計(jì)來(lái),既沒(méi)文檔描述又沒(méi)有可配置選項(xiàng)?為啥我做了十五年的Java開(kāi)發(fā),卻對(duì)此一無(wú)所知?更要命的是,為什么它要對(duì)一個(gè)構(gòu)造異常的POST請(qǐng)求進(jìn)行重試呢?這是對(duì)PoLA赤裸裸的違背!

現(xiàn)在你可能已經(jīng)猜到了,這是一個(gè)BUG(鏈接:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6382788)。 當(dāng)然了,說(shuō)是BUG并不是指的它的重試機(jī)制,而是指它為什么對(duì)異常POST請(qǐng)求也會(huì)進(jìn)行重試。按照HTTP RFC的規(guī)范,POST請(qǐng)求并非冪等,因此多次提交POST會(huì)帶來(lái)服務(wù)器端數(shù)據(jù)的改變。但是別擔(dān)心,Bill早就把這個(gè)BUG修改好了。Bill的解決方 法是加了一個(gè)開(kāi)關(guān)。Bill了解向后兼容原則。Bill認(rèn)為最好的方法是添加一個(gè)默認(rèn)開(kāi)啟的開(kāi)關(guān),這樣可以保證這個(gè)BUG的向后兼容。Bill笑了。 Bill已經(jīng)能夠看見(jiàn)全球無(wú)數(shù)的Java開(kāi)發(fā)者掉進(jìn)這個(gè)大坑時(shí)驚愕的面孔。但是,你們都別學(xué)Bill好嗎?

經(jīng)過(guò)好幾天激動(dòng)人心的調(diào)試,最后問(wèn)題解決的方式卻略顯輕巧,僅僅指定了一個(gè)屬性就搞定了。無(wú)論如何,這個(gè)設(shè)計(jì)真是著實(shí)讓我很意外,因此我還專門(mén)寫(xiě)了這篇文章來(lái)講述,并且,你也看到了這篇文章。

為了完整起見(jiàn),再提醒一下,如果你讓這段代碼在容器環(huán)境里執(zhí)行的話,結(jié)果可能會(huì)不同。你的容器或者你的代碼所依賴的庫(kù)有可能會(huì)替換掉Oracle默 認(rèn)的內(nèi)部實(shí)現(xiàn),請(qǐng)參考URL.setURLStreamHandlerFactory()?,F(xiàn)在你可能會(huì)問(wèn),那個(gè)家伙當(dāng)時(shí)為什么要使用 HttpURLConnection呢?他難道是坐著演講巡游車(chē)上班嗎(原文Wooden Soapbox,由來(lái)參見(jiàn)https://en.wikipedia.org/wiki/Soapbox)?他難道是用剪子來(lái)割草嗎?建議他傳遞信息的時(shí) 候最好還是使用烽火吧!當(dāng)然了,你這么想我也不能責(zé)怪你。我們出問(wèn)題的代碼有點(diǎn)特別,使用的是SAAJ中的SOAPConnectionFactory, 而SOAPConnectionFactory內(nèi)部又默認(rèn)使用了HttpURLConnection,如果沒(méi)有其他代碼來(lái)注冊(cè)其他的實(shí)現(xiàn)類的話,使用的當(dāng) 然就是默認(rèn)的Oracle實(shí)現(xiàn)嘍~

如果你使用其他更專業(yè)的web服務(wù)實(shí)現(xiàn)的時(shí)候(如Spring WS, CXF, JAX-WS實(shí)現(xiàn)等等),他們很可能使用了諸如Apache HTTP Client的組件。當(dāng)然了,如果你自己的代碼需要發(fā)起HTTP連接的話,你也可以使用它。沒(méi)錯(cuò),我還是推薦你使用Apache Commons HttpClient,雖然這貨修改API的頻率比普通時(shí)尚達(dá)人換鞋的頻率都還要高。好了,我的牢騷完了。

譯文鏈接:http://www.codeceo.com/article/java-httpurlconnection-pola.html
英文原文:HttpURLConnection vs. the Principle of Least Astonishment

責(zé)任編輯:王雪燕 來(lái)源: 碼農(nóng)網(wǎng)
相關(guān)推薦

2014-08-13 10:20:59

HttpURLConn

2014-08-15 13:11:03

HttpURLConn

2016-12-15 08:28:34

HttpURLConn上傳文件

2016-02-15 09:49:21

2024-05-09 08:30:57

OkHttpHTTP客戶端

2010-01-25 11:09:58

Android Htt

2025-05-22 08:25:00

C++開(kāi)發(fā)資源管理

2025-05-07 03:00:00

數(shù)據(jù)中臺(tái)大數(shù)據(jù)

2019-09-09 15:28:04

數(shù)據(jù)科學(xué)帕累托法則工具

2015-04-14 11:01:08

大數(shù)據(jù)速度與激情用車(chē)法則

2011-05-06 10:49:13

網(wǎng)頁(yè)設(shè)計(jì)

2015-10-13 09:37:37

開(kāi)源法則

2012-04-25 23:53:08

APP

2012-07-24 12:47:37

軟件設(shè)計(jì)架構(gòu)設(shè)計(jì)

2010-05-11 10:27:49

企業(yè)培訓(xùn)

2025-04-27 08:23:38

Kotlin協(xié)程管理

2024-05-23 10:58:49

2010-01-26 10:02:51

Android But

2010-10-26 12:30:21

網(wǎng)絡(luò)管理

2019-12-05 14:19:20

設(shè)計(jì)用戶搜索
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)