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