HttpClient真的是一個神奇的東西,版本之間差異過大,V4.3之前,V4.3以後V4.5以前,V4.5以後都幾本是不兼容。所以JAVA在這一塊的開發真的是十分頭疼。
這位仁兄在深深的被折磨後很好的幫我們總結了HTTP不同版本超時。
然而,本人在HttpClient 4.3.6版本下,試了無數次。。還是走不通。雖然已經把RequestConfig加到配置裏面了,而且斷點跟蹤也看到配置已配好,也加在httpPost對象裏面。但是,它就是不生效,我也很無奈。
所以,改用HttpUrlConnection進行發起POST請求。發現可以,代碼如下。
- 其中,data是一個普通的POJO,可以轉成json然後再轉成String,作爲請求實體。
- 一定要養好設置編碼的習慣,不管是請求內容還是返回的數據流。
- 請求結束後一定要把可以關的都關閉。
- 一部分代碼是爲了忽略SSL證書錯誤才寫的。如果沒有這個需求,去掉此段代碼。
HttpsURLConnection httpUrlCoNN = null;
OutputStream outputStrem = null;
OutputStreamWriter outWrite = null;
InputStream inputStream = null;
InputStreamReader inputReader = null;
BufferedReader bufferReader = null;
String tmpLineStr = null;
StringBuffer resultBuffer = new StringBuffer();
try{
trustAllHttpCertifications();
HostnameVerifier verify = new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
// TODO Auto-generated method stub
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(verify);
String dataParam = JSON.toJSONString(data);
URL url = new URL(Url);
URLConnection urlConn = url.openConnection();
httpUrlCoNN = (HttpsURLConnection) urlConn;
httpUrlCoNN.setDoInput(true);
httpUrlCoNN.setDoOutput(true);
//超時
int timeoutMilSec = 60000;
httpUrlCoNN.setConnectTimeout(timeoutMilSec);
httpUrlCoNN.setReadTimeout(timeoutMilSec);
httpUrlCoNN.setUseCaches(false);
httpUrlCoNN.setRequestMethod("POST");
httpUrlCoNN.setRequestProperty("Connection", "Keep-Alive");
httpUrlCoNN.setRequestProperty("Accept-Charset", "UTF-8");
httpUrlCoNN.setRequestProperty("Content-Type", "application/json;charsert=utf-8");
httpUrlCoNN.setRequestProperty("Content-Length", String.valueOf(dataParam.length()));
outputStrem = httpUrlCoNN.getOutputStream();
outWrite = new OutputStreamWriter(outputStrem);
outWrite.write(dataParam.toString());
outWrite.flush();
if(httpUrlCoNN.getResponseCode() >= 300){
//返回錯
log.info("httpResultCode = {}",httpUrlCoNN.getResponseCode());
return null;
}
//接收響應流
inputStream = httpUrlCoNN.getInputStream();
inputReader = new InputStreamReader(inputStream,"UTF-8");
bufferReader = new BufferedReader(inputReader);
while((tmpLineStr = bufferReader.readLine())!= null){
resultBuffer.append(tmpLineStr);
}
}catch(Exception e){
log.info("return = {}",e.getMessage());
}finally{
if(null != outWrite){
outWrite.close();
}
if(null != outputStrem){
outputStrem.close();
}
if(null != bufferReader){
bufferReader.close();
}
if(null != inputReader){
inputReader.close();
}
if(null != inputStream){
inputStream.close();
}
if(null != httpUrlCoNN){
httpUrlCoNN.disconnect();
}
}
這部分代碼是爲了忽略SSL證書錯誤才寫的。如果沒有這個需求,去掉此段代碼。
private void trustAllHttpCertifications(){
TrustManager[] trustAllCerts = new TrustManager[1];
TrustManager tm = new miTM();
trustAllCerts[0] = tm;
SSLContext sc = null;
try {
sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, null);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyManagementException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
}
class miTM implements TrustManager,X509TrustManager{
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
public boolean isServerTrusted(X509Certificate[] certs){
return true;
}
public boolean isClientTrusted(X509Certificate[] certs){
return true;
}
}
1)如果新建線程,然後在線程裏發起http請求,一定要做超時控制。要不然,先不說會一直存在http連接,佔用帶寬,如果線程先於http請求斷開,會導致報出SocketException。而且此時,這個報錯是不可以通過IOException捕獲(我也知道SocketException是繼承了IOException,但是無奈人家就在這種情況下不會捕獲)。你只能使用catch (Throwable e){}來做處理。但是,此時,就算捕獲到,但是該線程在線程池裏也不能重新運行了。
2)這是經驗,可以運用到其他地方:因爲HttpClient相對於HttpUrlConnection而言,只是做了一層封裝。如果不同的版本都不能使配置生效的時候,可以直接使用封裝之前的自己來構造。這樣子可以避免版本的差異性。
3)HTTPClient對於超時是會自動發起3次自動重連。如果你的http請求是在線程裏發起的。那就需要注意一下這一部分的消耗了。可以手動在代碼裏把重發關閉。黃色部分。(老夫雖然用下面那代碼控制了自動重發,但是無奈,沒有把超時控制住,所以只能棄了這一版。)
HttpPost httpPost = new HttpPost(Url);
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException arg0, int arg1, HttpContext arg2) {
// TODO Auto-generated method stub
return false;
}
};
int miSec = 60000;//超時時間
RequestConfig requestConf = RequestConfig.custom().setConnectTimeout(miSec).setConnectionRequestTimeout(miSec)
.setSocketTimeout(miSec).build();
CloseableHttpClient client = HttpClients.custom().setRetryHandler(myRetryHandler).build();
StringEntity entity = new StringEntity(JSON.toJSONString(data), "utf-8");// 解決中文亂碼問題
entity.setContentEncoding("UTF-8");
//FIXME
entity.setContentType(MediaType.APPLICATION_JSON_VALUE);
entity.setContentEncoding("UTF-8");
httpPost.setEntity(entity);
httpPost.setConfig(requestConf);
httpPost.addHeader("Accept", MediaType.APPLICATION_JSON.toString());
HttpResponse resp = null;
try {
resp = client.execute(httpPost);
result = JSON.parseObject(resp.getEntity().getContent(),JSONObject.class);
} catch (Throwable e) {
//e.printStackTrace();
log.info(e.getMessage());
return null;
}finally {
httpPost.releaseConnection();
if(null != client){
try {
client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}