淺談httpclient

做java的對httpclient應該是不會陌生的吧,應該算比較常見的包了,這兩天項目中有同學遇到了httpclient的問題,從團隊同學解決問題的思路、對這塊知識的瞭解程度上看,發現很多人都停留在用的層面。其實,對這塊我也是一知半解。有一知半解的地方就可以做出,所以淺談一下,一方面是自己對這塊理解更深,另外一方面發現很久沒總結,沒寫文章了,要堅持下去。

httpclient算是神器了,當年大學時候就喜歡用這玩意,給自己的博客刷個訪問量,比如:當年的網易博客,不以uid爲維度計數,只以頁面count計數,當年用的還是VC(因爲VC寫出來的exe,直接雙擊就可以運行了,逼格高,話說大學寫代碼的動力不就是裝逼麼),用VC自己socket+http協議封裝了一個類似c版的httpclient。還做過很多,比如當年創業時候從人人網拉流量,申請一堆大波妹賬號,然後遍歷留言,出現驗證碼就請求回來做驗證碼破解(之前有文章講過),總之用這類似的玩意做了挺多。


牛皮吹完了,迴歸正題,用過java的httpclient的應該都大致瞭解這玩意有倆jar包,並且都是apache的,尼瑪好多困惑有麼有,是不是每次用這玩意就1.從老項目中拷貝一個前人已經寫好的httpclientUtil.java然後自己就用了,能通就對了!2.百度httpclient,拷貝一個例子,寫個測試用例訪問下百度,返回成功就哦了。但是好像大家都不清楚這倆httpclient之間啥區別。

httpclient:org.apache.httpcomponents » httpclient

commons-httpclient:commons-httpclient » commons-httpclient

apache提供了以上兩個jar,都有httpclient,他們之前的關係是這樣的。

1. 你可以認爲他們都是Httpclient,commons-httpclient是3.x以前版本,httpclient是4.x以後的版本

2. commons-httpclient後續是不會在做維護了,apache給的建議也是用新的httpclient

3. 從功能上來講,httpclient比commons-httpclient的功能要強大很多,比如:常見的HTTPS的支持、代理等。。。


其實我也是有點不理解的,因爲從編碼的易用性上來講commons-httpclient貌似對研發者更友好,httpclient雖然功能很強大,但是使用方法上不是太友好

如下示例:

commons-httpclient

get示例

public static String get(String url, Map<String, String> queryParams) {
        if (StringUtils.isBlank(url)) {
            return null;
        }
        HttpClient httpclient = new HttpClient();
        GetMethod getMethod = new GetMethod(url);
        //組裝參數
        if (queryParams != null && !queryParams.isEmpty()) {
            NameValuePair[] pairs = new NameValuePair[queryParams.size()];
            int i = 0;
            for (Map.Entry<String, String> entry : queryParams.entrySet()) {
                pairs[i] = new NameValuePair(entry.getKey(), entry.getValue());
                i++;
            }
            getMethod.setQueryString(pairs);
        }

        String resultString = null;
        try {
            int statusCode = httpclient.executeMethod(getMethod);
            if (statusCode == HttpStatus.SC_OK) {
                //獲取請求響應內容
                resultString = getMethod.getResponseBodyAsString();
            } else {
                throw new EmcooperateException("網絡訪問異常,錯誤狀態:" + statusCode);
            }
        } catch (HttpException e) {
            throw new EmcooperateException("網絡訪問異常", e);
        } catch (IOException e) {
            throw new EmcooperateException("網絡訪問異常", e);
        }
        return resultString;
    }

post示例

public static String post(String url, Map<String, String> queryParams) {
        if (StringUtils.isBlank(url)) {
            return null;
        }
        HttpClient httpclient = new HttpClient();
        PostMethod postMethod = new PostMethod(url);
        //組裝參數
        if (queryParams != null && !queryParams.isEmpty()) {
            NameValuePair[] pairs = new NameValuePair[queryParams.size()];
            int i = 0;
            for (Map.Entry<String, String> entry : queryParams.entrySet()) {
                pairs[i] = new NameValuePair(entry.getKey(), entry.getValue());
                i++;
            }
            postMethod.setQueryString(pairs);
        }

        String resultString = null;
        try {
            int statusCode = httpclient.executeMethod(postMethod);
            if (statusCode == HttpStatus.SC_OK) {
                //獲取請求響應內容
                resultString = postMethod.getResponseBodyAsString();
                if (logger.isInfoEnabled()) {
                    logger.info("http返回值 :" + resultString);
                }
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("http返回狀態值 :" + statusCode);
                }
                throw new EmcooperateException("網絡訪問異常,錯誤狀態:" + statusCode);
            }
        } catch (HttpException e) {
            throw new EmcooperateException("網絡訪問異常", e);
        } catch (IOException e) {
            throw new EmcooperateException("網絡訪問異常", e);
        }
        return resultString;
    }

如上:post、get使用都分幾步

1. new httpcliet

2. new xxxMethod

3. 構造參數NameValuePair

4. 執行httpclient.executeMethod(xxxMethod)

使用步驟很明確,就是Httpclient、xxxMethod、NameValuePair幾個關鍵的類,並且post和get採用類似的編碼步驟,基本上看了就懂,並且現在網上大部分的示例都是這麼個用法


接下來看看httpclient的使用:(http://hc.apache.org/httpcomponents-client-4.5.x/quickstart.html)

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response1 = httpclient.execute(httpGet);

try {
    System.out.println(response1.getStatusLine());
    HttpEntity entity1 = response1.getEntity();
    EntityUtils.consume(entity1);
} finally {
    response1.close();
}


HttpPost httpPost = new HttpPost(url);
List <NameValuePair> nvps = new ArrayList <NameValuePair>();
nvps.add(new BasicNameValuePair("username", "vip"));
nvps.add(new BasicNameValuePair("password", "secret"));
httpPost.setEntity(new UrlEncodedFormEntity(nvps));

CloseableHttpResponse response2 = httpclient.execute(httpPost);

try {
    HttpEntity entity2 = response2.getEntity();
    EntityUtils.consume(entity2);
} finally {
    response2.close();
}


可以看到httpget並沒有設置參數的地方,那麼參數是如何設置呢?原來參數默認是在url中包裝好的。爲了能快速的包裝url,httpcliet提供了URIBuilder來實現參數的封裝

URIBuilder builder = new URIBuilder();
            
URI uri=builder.setScheme("http")
               .setHost("www.google.com")
               .setPath("/search")
               .setParameter("q", "httpclient")
               .setParameter("btnG", "Google Search")
               .setParameter("aq", "f")
               .setParameter("oq", "").build();
HttpGet httpget = new HttpGet(uri);

最後通過setEntity來做數據的傳遞,這裏有各種類型的數據支持 參見:http://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/fundamentals.html#d5e43    (1.7.1章節)


從上可以看出,httpclient4x開始基本上很少做通用的封裝了,更多的是類似http協議的編程實現方式,帶來的優勢是可以保證各種http協議的支持,缺點就是不理解http協議(head、cookie、response)的同學會覺得用起來很麻煩,畢竟沒有像commons-httpclient那麼套路了,但是功能確實強大了很多,隨便操作。


最後,commons-httpclient做普通的http都沒太大問題,但是在做https的訪問時候可能就會遇到ssl的簽名驗證相關問題,這個是因爲jdk的

X509TrustManagerImpl會坐checkServerTrusted的證書驗證於是就會報錯。那麼如何能通過httpclient實現https的訪問呢。參考下面示例

public class HttpClientUtil2 {
    /** logger */
    private final static Logger logger = LoggerFactory.getLogger(HttpClientUtil2.class);

    public  static void ignoreHttps(HttpClient httpClient) {//設置httpclient自己的x509驗證,忽略證書
        
        // 創建TrustManager
        X509TrustManager xtm = new X509TrustManager() {
          public void checkClientTrusted(X509Certificate[] chain,
              String authType) throws CertificateException {
              System.out.println("1");
          }
          
          public void checkServerTrusted(X509Certificate[] chain,
              String authType) throws CertificateException {
              System.out.println("2");

          }
          
          public X509Certificate[] getAcceptedIssuers() {
              System.out.println("3");

            return new X509Certificate[] {};
          }
        };
        try {
          SSLContext ctx = SSLContext.getInstance("SSL");
          
          // 使用TrustManager來初始化該上下文,TrustManager只是被SSL的Socket所使用
          ctx.init(null, new TrustManager[] { xtm }, null);
          
          SSLSocketFactory sf = new SSLSocketFactory(
              ctx,
              SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
          Scheme sch = new Scheme("https", 443, sf);
          httpClient.getConnectionManager().getSchemeRegistry().register(sch);

        } catch (Exception e) {
        } finally {
          // 關閉連接,釋放資源
//              httpClient.getConnectionManager().shutdown(); 
        }
      }
    /**
     * http get請求 
     * @param url 請求的url
     * @param queryParams 請求參數
     * @return 響應文本
     */
    public static String get(String url, Map<String, String> queryParams) {
        if (StringUtils.isBlank(url)) {
            return null;
        }
        HttpClient httpclient = new DefaultHttpClient();      
        //ignoreHttps(httpclient);  //這裏默認不忽略,那麼訪問12306就會報錯
        String params="?";
        //組裝參數
        if (queryParams != null && !queryParams.isEmpty()) {
            List <NameValuePair> nvps = new ArrayList <NameValuePair>();
            for (Map.Entry<String, String> entry : queryParams.entrySet()) {
                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                params=params+entry.getKey()+"="+ entry.getValue()+"&";
            }
        }
        HttpGet getMethod = new HttpGet(url+params);
       


        String resultString = null;
        try {
            
            
            HttpResponse statusCode = httpclient.execute(getMethod);
            if (statusCode.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                //獲取請求響應內容
                resultString = EntityUtils.toString(statusCode.getEntity());
            } else {
                throw new EmcooperateException("網絡訪問異常,錯誤狀態:" + statusCode);
            }
        } catch (Exception e) {
            throw new EmcooperateException("網絡訪問異常", e);
        } 
        return resultString;
    }

    /**
     * http post請求 
     * @param url 請求的url
     * @param queryParams 請求參數
     * @return 響應文本
     */
    public static String post(String url, Map<String, String> queryParams) {
        if (StringUtils.isBlank(url)) {
            return null;
        }
        HttpClient httpclient = new DefaultHttpClient();
        buildHttps(httpclient);

        HttpPost postMethod = new HttpPost(url);
        
        String resultString = null;
        try {
          //組裝參數
            if (queryParams != null && !queryParams.isEmpty()) {
                List <NameValuePair> nvps = new ArrayList <NameValuePair>();
                for (Map.Entry<String, String> entry : queryParams.entrySet()) {
                    nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                postMethod.setEntity(new UrlEncodedFormEntity(nvps));
            }

            HttpResponse statusCode = httpclient.execute(postMethod);
            if (statusCode.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                //獲取請求響應內容
                resultString = EntityUtils.toString(statusCode.getEntity());
                if (logger.isInfoEnabled()) {
                    logger.info("http返回值 :" + resultString);
                }
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("http返回狀態值 :" + statusCode);
                }
                throw new EmcooperateException("網絡訪問異常,錯誤狀態:" + statusCode);
            }
        } catch (Exception e) {
            throw new EmcooperateException("網絡訪問異常", e);
        } 
        return resultString;
    }
    
    public static void main(String args[]) { 
       Map<String, String> queryParams =new HashMap<String,String>();
       System.out.println(HttpClientUtil2.get("https://www.12306.cn/mormhweb/", queryParams));
        
    } 
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章