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();
}
}