HttpURLConnection的变态事: Keep-Alive
JDK自带的HttpURLConnection默认启动Keep-Alive, 使用后的HttpURLConnection会放入池里重用. 相关描述:
What does the current JDK do for Keep-Alive?
The JDK supports both HTTP/1.1 and HTTP/1.0 persistent connections.
When the application finishes reading the response body or when the application calls close() on the InputStream returned by URLConnection.getInputStream(), the JDK's HTTP protocol handler will try to clean up the connection and if successful, put the connection into a connection cache for reuse by future HTTP requests.
The support for HTTP keep-Alive is done transparently. However, it can be controlled by system properties http.keepAlive, and http.maxConnections, as well as by HTTP/1.1 specified request and response headers.
The system properties that control the behavior of Keep-Alive are:
http.keepAlive=<boolean>
default: true
Indicates if keep alive (persistent) connections should be supported.
http.maxConnections=<int>
default: 5
Indicates the maximum number of connections per destination to be kept alive at any given time
HTTP header that influences connection persistence is:
Connection: close
If the "Connection" header is specified with the value "close" in either the request or the response header fields, it indicates that the connection should not be considered 'persistent' after the current request/response is complete.
The current implementation doesn't buffer the response body. Which means that the application has to finish reading the response body or call close() to abandon the rest of the response body, in order for that connection to be reused. Furthermore, current implementation will not try block-reading when cleaning up the connection, meaning if the whole response body is not available, the connection will not be reused.
如果服务端不支持Keep-Alive, 这种默认行为就会带来很多蛋疼的事, 举个例子:
mogilefs服务端默认没有启用Keep-Alive. 使用HttpURLConnection上传多个文件时, 由于Keep-Alive这种重用特性,导致for循环中第一个文件后的其实PUT请求卡死! 直到Socket Request Timeout! 这种行为实在很变态!
解决办法在上面描述已经提及, 经测试, 完成可行:
(1)System.setProperty("http.keepAlive", String.valueOf(false));
(2)conn.setRequestProperty("Connection", "close");
根据个人兴趣选择其一即可.
以下是mogilefs上传多个文件的实现(其实就是HttpURLConnection的PUT请求):
public static String upload(String uri, int size, InputStream content) throws DfsClientException, MalformedURLException, IOException {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(uri).openConnection();
System.out.println(conn.hashCode());
conn.setFixedLengthStreamingMode(size);
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setConnectTimeout(1000);
conn.setReadTimeout(2000);
conn.setRequestMethod("PUT");
//System.setProperty("http.keepAlive", String.valueOf(false));
conn.setRequestProperty("Connection", "close");
conn.connect();
System.out.println(conn.usingProxy());
OutputStream out = conn.getOutputStream();
for (int bt = 0; (bt = content.read()) != -1;) {
out.write(bt);
}
out.close();
int code = conn.getResponseCode();
StringBuilder sb = new StringBuilder();
InputStream in = conn.getErrorStream();
if (in == null) {
in = conn.getInputStream();
}
if (in != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
for (String line = null; (line = reader.readLine()) != null;) {
sb.append(line);
}
reader.close();
}
System.out.format("RESP[%d] %s", code, sb);
System.out.println(uri);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return uri.replace("/upload", "/download");
}