1 package com.cucpay.tradeportal.util; 2 3 import java.io.IOException; 4 import java.net.SocketTimeoutException; 5 import java.nio.charset.Charset; 6 import java.security.cert.CertificateException; 7 import java.security.cert.X509Certificate; 8 import java.util.ArrayList; 9 import java.util.List; 10 import java.util.Map; 11 12 import javax.net.ssl.SSLContext; 13 import javax.net.ssl.SSLException; 14 import javax.net.ssl.SSLSession; 15 import javax.net.ssl.SSLSocket; 16 import javax.net.ssl.TrustManager; 17 import javax.net.ssl.X509TrustManager; 18 19 import org.apache.http.HttpEntity; 20 import org.apache.http.HttpResponse; 21 import org.apache.http.NameValuePair; 22 import org.apache.http.ParseException; 23 import org.apache.http.client.ClientProtocolException; 24 import org.apache.http.client.HttpClient; 25 import org.apache.http.client.entity.UrlEncodedFormEntity; 26 import org.apache.http.client.methods.HttpGet; 27 import org.apache.http.client.methods.HttpPost; 28 import org.apache.http.client.utils.URLEncodedUtils; 29 import org.apache.http.conn.ConnectTimeoutException; 30 import org.apache.http.conn.scheme.Scheme; 31 import org.apache.http.conn.ssl.SSLSocketFactory; 32 import org.apache.http.conn.ssl.X509HostnameVerifier; 33 import org.apache.http.entity.ContentType; 34 import org.apache.http.entity.StringEntity; 35 import org.apache.http.impl.client.DefaultHttpClient; 36 import org.apache.http.message.BasicNameValuePair; 37 import org.apache.http.params.CoreConnectionPNames; 38 import org.apache.http.util.EntityUtils; 39 40 /** 41 * 封装了采用HttpClient发送HTTP请求的方法 42 * @see 本工具所采用的是最新的HttpComponents-Client-4.2.1 43 * @see =================================================================================================== 44 * @see 开发HTTPS应用时,时常会遇到两种情况 45 * @see 1、测试服务器没有有效的SSL证书,客户端连接时就会抛异常 46 * @see javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated 47 * @see 2、测试服务器有SSL证书,但可能由于各种不知名的原因,它还是会抛一堆烂码七糟的异常,诸如下面这几种 48 * @see javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241> 49 * @see javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 50 * @see sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 51 * @see =================================================================================================== 52 * @see 这里使用的是HttpComponents-Client-4.1.2创建的连接,所以就要告诉它使用一个不同的TrustManager 53 * @see 由于SSL使用的模式是X.509,对于该模式,Java有一个特定的TrustManager,称为X509TrustManager 54 * @see TrustManager是一个用于检查给定的证书是否有效的类,所以我们自己创建一个X509TrustManager实例 55 * @see 而在X509TrustManager实例中,若证书无效,那么TrustManager在它的checkXXX()方法中将抛出CertificateException 56 * @see 既然我们要接受所有的证书,那么X509TrustManager里面的方法体中不抛出异常就行了 57 * @see 然后创建一个SSLContext并使用X509TrustManager实例来初始化之 58 * @see 接着通过SSLContext创建SSLSocketFactory,最后将SSLSocketFactory注册给HttpClient就可以了 59 * @see =================================================================================================== 60 * @version v1.6 61 * @history v1.0-->新建<code>sendGetRequest()</code>和<code>sendPostRequest()</code>方法 62 * @history v1.1-->新增<code>sendPostSSLRequest()</code>方法,用于发送HTTPS的POST请求 63 * @history v1.2-->新增<code>sendPostRequest()</code>方法,用于发送HTTP协议报文体为任意字符串的POST请求 64 * @history v1.3-->新增<code>java.net.HttpURLConnection</code>实现的<code>sendPostRequestByJava()</code> 65 * @history v1.4-->所有POST方法中增加连接超时限制和读取超时限制 66 * @history v1.5-->重组各方法,并补充自动获取HTTP响应文本编码的方式,移除<code>sendPostRequestByJava()</code> 67 * @history v1.6-->整理GET和POST请求方法,使之更为适用 68 * @create Feb 1, 2012 3:02:27 PM 69 * @update Apr 15, 2013 10:44:35 AM 70 */ 71 public class HttpClientUtil { 72 private HttpClientUtil(){} 73 74 /** 75 * 发送HTTP_GET请求 76 * @see 1)该方法会自动关闭连接,释放资源 77 * @see 2)该方法会自动获取到响应消息头中[Content-Type:text/html; charset=GBK]的charset值作为响应报文解码字符集 78 * @see 若响应消息头中无Content-Type属性,则默认使用HttpClient内部默认的ISO-8859-1 79 * @param requestURL 请求地址(含参数) 80 * @return 远程主机响应正文 81 */ 82 public static String sendGetRequest(String reqURL){ 83 long respLen = 0; //响应长度 84 String respContent = null; //响应内容 85 Charset respCharset = null; //响应编码 86 HttpClient httpClient = new DefaultHttpClient(); //创建默认的httpClient实例 87 HttpGet httpGet = new HttpGet(reqURL); //创建org.apache.http.client.methods.HttpGet 88 try{ 89 HttpResponse response = httpClient.execute(httpGet); //执行GET请求 90 HttpEntity entity = response.getEntity(); //获取响应实体 91 if(null != entity){ 92 //使用EntityUtils.getContentCharSet(entity)也可以获取响应编码,但从4.1.3开始不建议使用这种方式 93 respCharset = ContentType.getOrDefault(entity).getCharset(); 94 respLen = entity.getContentLength(); 95 respContent = EntityUtils.toString(entity, respCharset); 96 //Consume response content 97 EntityUtils.consume(entity); 98 } 99 System.out.println("-----------------------------------------------------------------------"); 100 System.out.println("请求地址: " + httpGet.getURI()); 101 System.out.println("响应状态: " + response.getStatusLine()); 102 System.out.println("响应长度: " + respLen); 103 System.out.println("响应编码: " + respCharset); 104 System.out.println("响应内容: " + respContent); 105 System.out.println("-----------------------------------------------------------------------"); 106 }catch(ClientProtocolException e){ 107 //该异常通常是协议错误导致:比如构造HttpGet对象时传入协议不对(将'http'写成'htp')or响应内容不符合HTTP协议要求等 108 LogUtil.getLogger().error("协议异常,堆栈信息如下", e); 109 }catch(ParseException e){ 110 LogUtil.getLogger().error(e.getMessage(), e); 111 }catch(IOException e){ 112 //该异常通常是网络原因引起的,如HTTP服务器未启动等 113 LogUtil.getLogger().error("网络异常,堆栈信息如下", e); 114 }finally{ 115 //关闭连接,释放资源 116 httpClient.getConnectionManager().shutdown(); 117 } 118 return respContent; 119 } 120 121 122 /** 123 * 发送HTTP_POST请求 124 * @see 1)该方法的优点在于-->允许自定义任何格式和内容的HTTP请求报文体,只要你熟悉HTTP/1.1协议 125 * @see 2)该方法会自动关闭连接,释放资源 126 * @see 3)方法内设置了连接和读取超时时间,单位为毫秒,超时后方法会自动返回空字符串 127 * @see 4)请求参数含中文等特殊字符时,可在传入前自行<code>URLEncoder.encode(string,encodeCharset)</code>,再指定encodeCharset为null 128 * @see 5)亦可指定encodeCharset参数值,由本方法代为编码 129 * @see 6)该方法在解码响应报文时所采用的编码,为响应消息头中[Content-Type:text/html; charset=GBK]的charset值 130 * @see 若响应消息头中未指定Content-Type属性,则默认使用HttpClient内部默认的ISO-8859-1 131 * @param reqURL 请求地址 132 * @param reqData 请求参数,若有多个参数则应拼接为param11=value11&22=value22&33=value33的形式 133 * @param encodeCharset 编码字符集,编码请求数据时用之,其为null表示请求参数已编码完毕,不需要二次编码 134 * @return 远程主机响应正文 135 */ 136 public static String sendPostRequest(String reqURL, String reqData, String encodeCharset){ 137 String reseContent = ""; 138 HttpClient httpClient = new DefaultHttpClient(); 139 httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000); 140 httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 2000); 141 HttpPost httpPost = new HttpPost(reqURL); 142 try{ 143 if(null != reqData){ 144 if(reqData.contains("&")){ 145 List<NameValuePair> formParams = new ArrayList<NameValuePair>(); 146 for(String str : reqData.split("&")){ 147 formParams.add(new BasicNameValuePair(str.substring(0,str.indexOf("=")), str.substring(str.indexOf("=")+1))); 148 } 149 httpPost.setEntity(new StringEntity(URLEncodedUtils.format(formParams, encodeCharset))); 150 }else{ 151 httpPost.setEntity(new StringEntity(reqData, encodeCharset)); 152 } 153 } 154 HttpResponse response = httpClient.execute(httpPost); 155 HttpEntity entity = response.getEntity(); 156 if (null != entity) { 157 reseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset()); 158 EntityUtils.consume(entity); 159 } 160 } catch (ConnectTimeoutException cte){ 161 LogUtil.getLogger().error("与[" + reqURL + "]连接超时,自动返回空字符串"); 162 } catch (SocketTimeoutException ste){ 163 LogUtil.getLogger().error("与[" + reqURL + "]读取超时,自动返回空字符串"); 164 }catch(Exception e){ 165 LogUtil.getLogger().error("与[" + reqURL + "]通信异常,堆栈信息如下", e); 166 }finally{ 167 httpClient.getConnectionManager().shutdown(); 168 } 169 return reseContent; 170 } 171 172 173 /** 174 * 发送HTTP_POST_SSL请求 175 * @see 1)该方法会自动关闭连接,释放资源 176 * @see 2)该方法亦可处理普通的HTTP_POST请求 177 * @see 3)方法内设置了连接和读取超时时间,单位为毫秒,超时后方法会自动返回空字符串 178 * @see 4)请求参数含中文等特殊字符时,可在传入前自行<code>URLEncoder.encode(string,encodeCharset)</code>,再指定encodeCharset为null 179 * @see 5)亦可指定encodeCharset参数值,由本方法代为编码 180 * @see 6)该方法在解码响应报文时所采用的编码,为响应消息头中[Content-Type:text/html; charset=GBK]的charset值 181 * @see 若响应消息头中未指定Content-Type属性,则默认使用HttpClient内部默认的ISO-8859-1 182 * @see 7)若reqURL的HTTPS端口不是默认的443,那么只需要在reqURL中指明其实际的HTTPS端口即可,而无需修改本方法内部指定的443 183 * @see 此时,方法默认会取指明的HTTPS端口进行连接..如reqURL=https://123.125.97.66:8085/pay/则实际请求即对方HTTPS_8085端口 184 * @param reqURL 请求地址 185 * @param params 请求参数 186 * @param encodeCharset 编码字符集,编码请求数据时用之,其为null表示请求参数已编码完毕,不需要二次编码 187 * @return 远程主机响应正文 188 */ 189 public static String sendPostSSLRequest(String reqURL, Map<String, String> params, String encodeCharset){ 190 String responseContent = ""; 191 HttpClient httpClient = new DefaultHttpClient(); 192 //设置代理服务器 193 //httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, new HttpHost("10.0.0.4", 8080)); 194 httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000); 195 httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 2000); 196 //创建TrustManager 197 //用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated 198 X509TrustManager trustManager = new X509TrustManager(){ 199 @Override 200 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} 201 @Override 202 public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} 203 @Override 204 public X509Certificate[] getAcceptedIssuers() {return null;} 205 }; 206 //创建HostnameVerifier 207 //用于解决javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241> 208 X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier(){ 209 @Override 210 public void verify(String host, SSLSocket ssl) throws IOException {} 211 @Override 212 public void verify(String host, X509Certificate cert) throws SSLException {} 213 @Override 214 public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {} 215 @Override 216 public boolean verify(String arg0, SSLSession arg1) {return true;} 217 }; 218 try { 219 //TLS1.0与SSL3.0基本上没有太大的差别,可粗略理解为TLS是SSL的继承者,但它们使用的是相同的SSLContext 220 SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS); 221 //使用TrustManager来初始化该上下文,TrustManager只是被SSL的Socket所使用 222 sslContext.init(null, new TrustManager[]{trustManager}, null); 223 //创建SSLSocketFactory 224 SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier); 225 //通过SchemeRegistry将SSLSocketFactory注册到HttpClient上 226 httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory)); 227 //创建HttpPost 228 HttpPost httpPost = new HttpPost(reqURL); 229 //httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=UTF-8"); 230 //构建POST请求的表单参数 231 if(null != params){ 232 List<NameValuePair> formParams = new ArrayList<NameValuePair>(); 233 for(Map.Entry<String,String> entry : params.entrySet()){ 234 formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); 235 } 236 httpPost.setEntity(new UrlEncodedFormEntity(formParams, encodeCharset)); 237 } 238 HttpResponse response = httpClient.execute(httpPost); 239 HttpEntity entity = response.getEntity(); 240 if (null != entity) { 241 // //打印HTTP响应报文的第一行,即状态行 242 // System.out.println(response.getStatusLine()); 243 // //打印HTTP响应头信息 244 // for(Header header : response.getAllHeaders()){ 245 // System.out.println(header.toString()); 246 // } 247 responseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset()); 248 EntityUtils.consume(entity); 249 } 250 } catch (ConnectTimeoutException cte){ 251 //Should catch ConnectTimeoutException, and don`t catch org.apache.http.conn.HttpHostConnectException 252 LogUtil.getLogger().error("与[" + reqURL + "]连接超时,自动返回空字符串"); 253 } catch (SocketTimeoutException ste){ 254 LogUtil.getLogger().error("与[" + reqURL + "]读取超时,自动返回空字符串"); 255 } catch (Exception e) { 256 LogUtil.getLogger().error("与[" + reqURL + "]通信异常,堆栈信息为", e); 257 } finally { 258 httpClient.getConnectionManager().shutdown(); 259 } 260 return responseContent; 261 } 262 }