HttpClient工具類

HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,而且它支持 HTTP 協議最新的版本和建議。HttpClient 已經應用在不少的項目中,好比 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient。html

HttpClient 提供的主要的功能,要知道更多詳細的功能能夠參見 HttpClient 的主頁。java

  • 實現了全部 HTTP 的方法(GET,POST,PUT,HEAD 等)
  • 支持自動轉向
  • 支持 HTTPS 協議
  • 支持代理服務器等
import java.io.IOException; 
import java.net.SocketTimeoutException; 
import java.nio.charset.Charset; 
import java.security.cert.CertificateException; 
import java.security.cert.X509Certificate; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Map; 
   
import javax.net.ssl.SSLContext; 
import javax.net.ssl.SSLException; 
import javax.net.ssl.SSLSession; 
import javax.net.ssl.SSLSocket; 
import javax.net.ssl.TrustManager; 
import javax.net.ssl.X509TrustManager; 
   
import org.apache.http.Header; 
import org.apache.http.HttpEntity; 
import org.apache.http.HttpResponse; 
import org.apache.http.NameValuePair; 
import org.apache.http.ParseException; 
import org.apache.http.client.ClientProtocolException; 
import org.apache.http.client.HttpClient; 
import org.apache.http.client.entity.UrlEncodedFormEntity; 
import org.apache.http.client.methods.HttpGet; 
import org.apache.http.client.methods.HttpPost; 
import org.apache.http.conn.ConnectTimeoutException; 
import org.apache.http.conn.scheme.Scheme; 
import org.apache.http.conn.ssl.SSLSocketFactory; 
import org.apache.http.conn.ssl.X509HostnameVerifier; 
import org.apache.http.entity.ContentType; 
import org.apache.http.entity.StringEntity; 
import org.apache.http.impl.client.DefaultHttpClient; 
import org.apache.http.message.BasicNameValuePair; 
import org.apache.http.params.CoreConnectionPNames; 
import org.apache.http.protocol.HTTP; 
import org.apache.http.util.EntityUtils; 
   
/**
 * 封裝了採用HttpClient發送HTTP請求的方法
 * @see 本工具所採用的是HttpComponents-Client-4.2.1
 * @see ===================================================================================================
 * @see 開發HTTPS應用時,時常會遇到兩種狀況
 * @see 一、測試服務器沒有有效的SSL證書,客戶端鏈接時就會拋異常
 * @see    javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
 * @see 二、測試服務器有SSL證書,但可能因爲各類不知名的緣由,它仍是會拋一堆爛碼七糟的異常,諸以下面這兩種
 * @see    javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241>
 * @see    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
 * @see ===================================================================================================
 * @see 這裏使用的是HttpComponents-Client-4.2.1建立的鏈接,因此就要告訴它使用一個不一樣的TrustManager
 * @see 因爲SSL使用的模式是X.509,對於該模式,Java有一個特定的TrustManager,稱爲X509TrustManager
 * @see TrustManager是一個用於檢查給定的證書是否有效的類,因此咱們本身建立一個X509TrustManager實例
 * @see 而在X509TrustManager實例中,若證書無效,那麼TrustManager在它的checkXXX()方法中將拋出CertificateException
 * @see 既然咱們要接受全部的證書,那麼X509TrustManager裏面的方法體中不拋出異常就好了
 * @see 而後建立一個SSLContext並使用X509TrustManager實例來初始化之
 * @see 接着經過SSLContext建立SSLSocketFactory,最後將SSLSocketFactory註冊給HttpClient就能夠了
 * @see ===================================================================================================
 * @version v1.7
 * @history v1.0-->新建<code>sendGetRequest()</code>和<code>sendPostRequest()</code>方法
 * @history v1.1-->新增<code>sendPostSSLRequest()</code>方法,用於發送HTTPS的POST請求
 * @history v1.2-->新增<code>sendPostRequest()</code>方法,用於發送HTTP協議報文體爲任意字符串的POST請求
 * @history v1.3-->新增<code>java.net.HttpURLConnection</code>實現的<code>sendPostRequestByJava()</code>
 * @history v1.4-->全部POST方法中增長鏈接超時限制和讀取超時限制
 * @history v1.5-->重組各方法,並補充自動獲取HTTP響應文本編碼的方式,移除<code>sendPostRequestByJava()</code>
 * @history v1.6-->整理GET和POST請求方法,使之更爲適用
 * @history v1.7-->修正<code>sendPostRequest()</code>請求的CONTENT_TYPE頭信息,並優化各方法參數及內部處理細節
 * @create Feb 1, 2012 3:02:27 PM
 * @update Jul 23, 2013 1:18:35 PM
 * @author 玄玉<http://blog.csdn.net/jadyer>
 */ 
public class HttpClientUtil { 
    private HttpClientUtil(){} 
       
    /**
     * 發送HTTP_GET請求
     * @see 1)該方法會自動關閉鏈接,釋放資源
     * @see 2)方法內設置了鏈接和讀取超時時間,單位爲毫秒,超時或發生其它異常時方法會自動返回"通訊失敗"字符串
     * @see 3)請求參數含中文時,經測試可直接傳入中文,HttpClient會自動編碼發給Server,應用時應根據實際效果決定傳入前是否轉碼
     * @see 4)該方法會自動獲取到響應消息頭中[Content-Type:text/html; charset=GBK]的charset值做爲響應報文的解碼字符集
     * @see   若響應消息頭中無Content-Type屬性,則會使用HttpClient內部默認的ISO-8859-1做爲響應報文的解碼字符集
     * @param requestURL 請求地址(含參數)
     * @return 遠程主機響應正文
     */ 
    public static String sendGetRequest(String reqURL){ 
        String respContent = "通訊失敗"; //響應內容 
        HttpClient httpClient = new DefaultHttpClient(); //建立默認的httpClient實例 
        //設置代理服務器 
        //httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, new HttpHost("10.0.0.4", 8080)); 
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000); //鏈接超時10s 
        httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 20000);         //讀取超時20s 
        HttpGet httpGet = new HttpGet(reqURL); //建立org.apache.http.client.methods.HttpGet 
        try{ 
            HttpResponse response = httpClient.execute(httpGet); //執行GET請求 
            HttpEntity entity = response.getEntity();            //獲取響應實體 
            if(null != entity){ 
                //respCharset=EntityUtils.getContentCharSet(entity)也能夠獲取響應編碼,但從4.1.3開始不建議使用這種方式 
                Charset respCharset = ContentType.getOrDefault(entity).getCharset(); 
                respContent = EntityUtils.toString(entity, respCharset); 
                //Consume response content 
                EntityUtils.consume(entity); 
            } 
            System.out.println("-------------------------------------------------------------------------------------------"); 
            StringBuilder respHeaderDatas = new StringBuilder(); 
            for(Header header : response.getAllHeaders()){ 
                respHeaderDatas.append(header.toString()).append("\r\n"); 
            } 
            String respStatusLine = response.getStatusLine().toString(); //HTTP應答狀態行信息 
            String respHeaderMsg = respHeaderDatas.toString().trim();    //HTTP應答報文頭信息 
            String respBodyMsg = respContent;                            //HTTP應答報文體信息 
            System.out.println("HTTP應答完整報文=[" + respStatusLine + "\r\n" + respHeaderMsg + "\r\n\r\n" + respBodyMsg + "]"); 
            System.out.println("-------------------------------------------------------------------------------------------"); 
        } catch (ConnectTimeoutException cte){ 
            //Should catch ConnectTimeoutException, and don`t catch org.apache.http.conn.HttpHostConnectException 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時鏈接超時,堆棧軌跡以下", cte); 
        } catch (SocketTimeoutException ste){ 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時讀取超時,堆棧軌跡以下", ste); 
        }catch(ClientProtocolException cpe){ 
            //該異常一般是協議錯誤致使:好比構造HttpGet對象時傳入協議不對(將'http'寫成'htp')or響應內容不符合HTTP協議要求等 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時協議異常,堆棧軌跡以下", cpe); 
        }catch(ParseException pe){ 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時解析異常,堆棧軌跡以下", pe); 
        }catch(IOException ioe){ 
            //該異常一般是網絡緣由引發的,如HTTP服務器未啓動等 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時網絡異常,堆棧軌跡以下", ioe); 
        }catch (Exception e){ 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時偶遇異常,堆棧軌跡以下", e); 
        }finally{ 
            //關閉鏈接,釋放資源 
            httpClient.getConnectionManager().shutdown(); 
        } 
        return respContent; 
    } 
       
       
    /**
     * 發送HTTP_POST請求
     * @see 1)該方法容許自定義任何格式和內容的HTTP請求報文體
     * @see 2)該方法會自動關閉鏈接,釋放資源
     * @see 3)方法內設置了鏈接和讀取超時時間,單位爲毫秒,超時或發生其它異常時方法會自動返回"通訊失敗"字符串
     * @see 4)請求參數含中文等特殊字符時,可直接傳入本方法,並指明其編碼字符集encodeCharset參數,方法內部會自動對其轉碼
     * @see 5)該方法在解碼響應報文時所採用的編碼,取自響應消息頭中的[Content-Type:text/html; charset=GBK]的charset值
     * @see   若響應消息頭中未指定Content-Type屬性,則會使用HttpClient內部默認的ISO-8859-1
     * @param reqURL        請求地址
     * @param reqData       請求參數,如有多個參數則應拼接爲param11=value11&22=value22&33=value33的形式
     * @param encodeCharset 編碼字符集,編碼請求數據時用之,此參數爲必填項(不能爲""或null)
     * @return 遠程主機響應正文
     */ 
    public static String sendPostRequest(String reqURL, String reqData, String encodeCharset){ 
        String reseContent = "通訊失敗"; 
        HttpClient httpClient = new DefaultHttpClient(); 
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000); 
        httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 20000); 
        HttpPost httpPost = new HttpPost(reqURL); 
        //因爲下面使用的是new StringEntity(....),因此默認發出去的請求報文頭中CONTENT_TYPE值爲text/plain; charset=ISO-8859-1 
        //這就有可能會致使服務端接收不到POST過去的參數,好比運行在Tomcat6.0.36中的Servlet,因此咱們手工指定CONTENT_TYPE頭消息 
        httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=" + encodeCharset); 
        try{ 
            httpPost.setEntity(new StringEntity(reqData==null?"":reqData, encodeCharset)); 
            HttpResponse response = httpClient.execute(httpPost); 
            HttpEntity entity = response.getEntity(); 
            if (null != entity) { 
                reseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset()); 
                EntityUtils.consume(entity); 
            } 
        } catch (ConnectTimeoutException cte){ 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時鏈接超時,堆棧軌跡以下", cte); 
        } catch (SocketTimeoutException ste){ 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時讀取超時,堆棧軌跡以下", ste); 
        }catch(Exception e){ 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時偶遇異常,堆棧軌跡以下", e); 
        }finally{ 
            httpClient.getConnectionManager().shutdown(); 
        } 
        return reseContent; 
    } 
       
       
    /**
     * 發送HTTP_POST_SSL請求
     * @see 1)該方法會自動關閉鏈接,釋放資源
     * @see 2)該方法亦可處理普通的HTTP_POST請求
     * @see 3)當處理HTTP_POST_SSL請求時,默認請求的是對方443端口,除非reqURL參數中指明瞭SSL端口
     * @see 4)方法內設置了鏈接和讀取超時時間,單位爲毫秒,超時或發生其它異常時方法會自動返回"通訊失敗"字符串
     * @see 5)請求參數含中文等特殊字符時,可直接傳入本方法,並指明其編碼字符集encodeCharset參數,方法內部會自動對其轉碼
     * @see 6)方法內部會自動註冊443做爲SSL端口,若實際使用中reqURL指定的SSL端口非443,可自行嘗試更改方法內部註冊的SSL端口
     * @see 7)該方法在解碼響應報文時所採用的編碼,取自響應消息頭中的[Content-Type:text/html; charset=GBK]的charset值
     * @see   若響應消息頭中未指定Content-Type屬性,則會使用HttpClient內部默認的ISO-8859-1
     * @param reqURL        請求地址
     * @param params        請求參數
     * @param encodeCharset 編碼字符集,編碼請求數據時用之,當其爲null時,則取HttpClient內部默認的ISO-8859-1編碼請求參數
     * @return 遠程主機響應正文
     */ 
    public static String sendPostSSLRequest(String reqURL, Map<String, String> params, String encodeCharset){ 
        String responseContent = "通訊失敗"; 
        HttpClient httpClient = new DefaultHttpClient(); 
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000); 
        httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 20000); 
        //建立TrustManager() 
        //用於解決javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated 
        X509TrustManager trustManager = new X509TrustManager(){ 
            @Override 
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} 
            @Override 
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} 
            @Override 
            public X509Certificate[] getAcceptedIssuers() {return null;} 
        }; 
        //建立HostnameVerifier 
        //用於解決javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241> 
        X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier(){ 
            @Override 
            public void verify(String host, SSLSocket ssl) throws IOException {} 
            @Override 
            public void verify(String host, X509Certificate cert) throws SSLException {} 
            @Override 
            public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {} 
            @Override 
            public boolean verify(String arg0, SSLSession arg1) {return true;} 
        }; 
        try { 
            //TLS1.0與SSL3.0基本上沒有太大的差異,可粗略理解爲TLS是SSL的繼承者,但它們使用的是相同的SSLContext 
            SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS); 
            //使用TrustManager來初始化該上下文,TrustManager只是被SSL的Socket所使用 
            sslContext.init(null, new TrustManager[]{trustManager}, null); 
            //建立SSLSocketFactory 
            SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier); 
            //經過SchemeRegistry將SSLSocketFactory註冊到HttpClient上 
            httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory)); 
            //建立HttpPost 
            HttpPost httpPost = new HttpPost(reqURL); 
            //因爲下面使用的是new UrlEncodedFormEntity(....),因此這裏不須要手工指定CONTENT_TYPE爲application/x-www-form-urlencoded 
            //由於在查看了HttpClient的源碼後發現,UrlEncodedFormEntity所採用的默認CONTENT_TYPE就是application/x-www-form-urlencoded 
            //httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=" + encodeCharset); 
            //構建POST請求的表單參數 
            if(null != params){ 
                List<NameValuePair> formParams = new ArrayList<NameValuePair>(); 
                for(Map.Entry<String,String> entry : params.entrySet()){ 
                    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); 
                } 
                httpPost.setEntity(new UrlEncodedFormEntity(formParams, encodeCharset)); 
            } 
            HttpResponse response = httpClient.execute(httpPost); 
            HttpEntity entity = response.getEntity(); 
            if (null != entity) { 
                responseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset()); 
                EntityUtils.consume(entity); 
            } 
        } catch (ConnectTimeoutException cte){ 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時鏈接超時,堆棧軌跡以下", cte); 
        } catch (SocketTimeoutException ste){ 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時讀取超時,堆棧軌跡以下", ste); 
        } catch (Exception e) { 
            LogUtil.getLogger().error("請求通訊[" + reqURL + "]時偶遇異常,堆棧軌跡以下", e); 
        } finally { 
            httpClient.getConnectionManager().shutdown(); 
        } 
        return responseContent; 
    } 
}

下面是測試代碼apache

 

public static void main(String[] args) { 
    Map<String, String> params = new HashMap<String, String>(); 
    params.put("merNo", "301100100001630"); 
    params.put("signType", "MD5"); 
    params.put("merBindAgrNo", "00003018007000006450000013866742"); 
    params.put("interfaceVersion", "1.0.0.0"); 
    params.put("amount", "1000"); 
    params.put("orderDate", "20120823"); 
    params.put("orderNo", "UDP1208230917531231111"); 
    params.put("merReqTime", "20120823091802"); 
    params.put("goodsDesc", "爲號碼交費充值元"); 
    params.put("goodsName", "中國聯通交費充值"); 
    params.put("userIdeMark", "3"); 
    params.put("bankAgrMode", "9"); 
    params.put("signMsg", "3ced24a118461043901d47815e6905a9"); 
    System.out.println(HttpClientUtil.sendPostSSLRequest("https://123.125.97.66:8085/pay/servlet/CreditPayReqServlte", params, "UTF-8")); 
}
相關文章
相關標籤/搜索