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设置的参数没起作用。(整个应用框架是其它大牛写的,启动应用时具体都干了什么还不是很清楚,这个结论可能不是很准确,有待后续验证了。)
如果还有其它情况,或者哪点有误,请各位大佬指正!