jdk 1.6+
問題描述:應用系統中調用三方jar包提供的http方法,傳了連接超時時間1s,讀取超時時間5s,自測時啟動一個服務端debug住,讓客戶端自動超時,結果發現30min都沒有超時,線程一直在等待響應結果。查看三方jar包源碼超時時間的設置采用了設置系統屬性sun.net.client.defaultConnectTimeout,sun.net.client.defaultReadTimeout的方式。
排查步驟:
1.由於平常設置超時時間都是使用URLConnection.setConnectTimeout(),URLConnection.setReadTimeout()方法,初次用到這種設置系統屬性的方式,莫得說,百度走起。
2.jdk1.5以前URLConnection沒有設置超時的方法,所以采用
System.setProperty("sun.net.client.defaultConnectTimeout", "1000"),
System.setProperty("sun.net.client.defaultReadTimeout", "5000")
jdk1.5+以后,新增了URLConnection.setConnectTimeout(),URLConnection.setReadTimeout()方法,推薦使用
3.那么理論上來說設置系統屬性的方式應該也是有效的,自己寫個demo測試發現確實讀取超時時間有效果,那我就納悶了,為啥應用中調用卻無效呢,jdk版本都是一致的。
4.百度了好幾頁,沒有看到說System.setProperty的方式無效的,(吐槽一下,百度出來的大部分答案都是copy的,千篇一律,看的我都吐了🤮),由於沒法翻牆,只好使用必應搜索,終於看到一些不一樣的答案(英文不好,都是復制,然后翻譯看的😓)。
5.sun.net.client.defaultConnectTimeout,sun.net.client.defaultReadTimeout這兩個參數在第一次連接時設置,並且會被緩存,在程序不關的情況下,再次訪問,還是以第一次的結果為准,就算你代碼中重新調用System.setProperty("sun.net.client.defaultReadTimeout", "5000"),值變了,但讀取超時時間並不會改變。
驗證步驟:
public class HttpTest {
public static void main(String[] args) {
System.out.println("調用doPost1方法");
doPost1();
try {
for(int i = 0; i < 3; i++) {
System.out.println("等待" + (3-i) + "s后再調用第二次");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
}
System.out.println("調用doPost2方法");
// doPost2方法模擬cfca提供jar包中HttpClientUtil#getConnection()設置超時
doPost2();
}
public static void doPost1() {
StringBuffer replyMsg = new StringBuffer();
int statusCode = 999;
PrintWriter out = null;
BufferedReader in = null;
HttpURLConnection urlConnection = null;
try {
URL urlobj = new URL("http://127.0.0.1:8081");
urlConnection = (HttpURLConnection) urlobj.openConnection();
urlConnection.setRequestMethod("POST"); // POST提交數據
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("Connection", "close");
urlConnection.setConnectTimeout(1000);
urlConnection.setReadTimeout(3000);
urlConnection.connect();
out = new PrintWriter(new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8"));
out.write("hello world");
out.flush();
out.close();
statusCode = urlConnection.getResponseCode();
if (200 == statusCode) {
String tLine;
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
while ((tLine = in.readLine()) != null) {
replyMsg.append(tLine);
}
}
urlConnection.disconnect();
} catch (Exception e) {
System.out.println("POST-1超時");
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
public static void doPost2() {
StringBuffer replyMsg = new StringBuffer();
int statusCode = 999;
PrintWriter out = null;
BufferedReader in = null;
HttpURLConnection urlConnection = null;
try {
URL urlobj = new URL("http://127.0.0.1:8081");
urlConnection = (HttpURLConnection) urlobj.openConnection();
urlConnection.setRequestMethod("POST"); // POST提交數據
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("Connection", "close");
//urlConnection.setConnectTimeout(3000);
//urlConnection.setReadTimeout(5000);
System.setProperty("sun.net.client.defaultConnectTimeout", "3000");
System.setProperty("sun.net.client.defaultReadTimeout", "5000");
urlConnection.connect();
out = new PrintWriter(new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8"));
out.write("hello world");
out.flush();
out.close();
statusCode = urlConnection.getResponseCode();
if (200 == statusCode) {
String tLine;
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
while ((tLine = in.readLine()) != null) {
replyMsg.append(tLine);
}
}
urlConnection.disconnect();
} catch (Exception e) {
System.out.println("調用doPost2方法超時");
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
}
doPost1()使用set方式設置超時時間,doPost2()使用System.setProperty()方式設置超時時間
1.連接超時不好測,本次測的都是讀取超時。用springboot寫個簡易的服務端,debug啟動,在接收到請求后打斷點。
2.先調用doPost1(),超時后會拋出異常信息ReadTimeout,再調用doPost2(),發現程序到此就卡住了,一直等待,此處驗證了排查步驟第5點
3.單獨調用doPost2(),或者先調用doPost2(),再調用doPost1(),結果都會在設置的時間后拋出異常信息ReadTimeout,說明System.setProperty的方式也是有效的。此處驗證排查步驟第2,3點
4.應用啟動腳本直接加上-Dsun.net.client.defaultReadTimeout=5000,調用http請求超過5s未響應,會拋出異常信息ReadTimeout
可能結果:應用在啟動時可能先進行了一次連接,導致后續在調用http請求時System.setProperty設置的參數沒起作用。(整個應用框架是其它大牛寫的,啟動應用時具體都干了什么還不是很清楚,這個結論可能不是很准確,有待后續驗證了。)
如果還有其它情況,或者哪點有誤,請各位大佬指正!