使用HttpClient發送請求、接收響應很簡單,通常須要以下幾步便可。
1. 建立HttpClient對象。
2. 建立請求方法的實例,並指定請求URL。若是須要發送GET請求,建立HttpGet對象;若是須要發送POST請求,建立HttpPost對象。
3. 若是須要發送請求參數,可調用HttpGet、HttpPost共同的setParams(HetpParams params)方法來添加請求參數;對於HttpPost對象而言,也可調用setEntity(HttpEntity entity)方法來設置請求參數。
4. 調用HttpClient對象的execute(HttpUriRequest request)發送請求,該方法返回一個HttpResponse。
5. 調用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可獲取服務器的響應頭;調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包裝了服務器的響應內容。程序可經過該對象獲取服務器的響應內容。
6. 釋放鏈接。不管執行方法是否成功,都必須釋放鏈接html
-
try {
-
// 建立一個默認的HttpClient
-
HttpClient httpclient =
new DefaultHttpClient();
-
// 建立一個GET請求
-
HttpGet request =
new HttpGet(
"www.google.com");
-
// 發送GET請求,並將響應內容轉換成字符串
-
String response = httpclient.execute(request,
new BasicResponseHandler());
-
Log.v(
"response text", response);
-
}
catch (ClientProtocolException e) {
-
e.printStackTrace();
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
在實際項目中,咱們極可能在多處須要進行HTTP通訊,這時候咱們不須要爲每一個請求都建立一個新的HttpClient。如今咱們的應用程序使用同一個HttpClient來管理全部的Http請求,一旦出現併發請求,那麼必定會出現多線程的問題。這就好像咱們的瀏覽器只有一個標籤頁卻有多個用戶,A要上google,B要上baidu,這時瀏覽器就會忙不過來了。幸運的是,HttpClient提供了建立線程安全對象的API
java
-
public
class CustomerHttpClient {
-
private
static
final String CHARSET = HTTP.UTF_8;
-
/**
-
* 最大鏈接數
-
*/
-
public
final
static
int MAX_TOTAL_CONNECTIONS =
800;
-
/**
-
* 獲取鏈接的最大等待時間
-
*/
-
public
final
static
int WAIT_TIMEOUT =
60000;
-
/**
-
* 每一個路由最大鏈接數
-
*/
-
public
final
static
int MAX_ROUTE_CONNECTIONS =
400;
-
/**
-
* 鏈接超時時間
-
*/
-
public
final
static
int CONNECT_TIMEOUT =
10000;
-
/**
-
* 讀取超時時間
-
*/
-
public
final
static
int READ_TIMEOUT =
10000;
-
-
-
private
static HttpClient customerHttpClient;
-
-
private CustomerHttpClient() {
-
}
-
-
public static synchronized HttpClient getHttpClient() {
-
if (
null == customerHttpClient) {
-
HttpParams params =
new BasicHttpParams();
-
// 設置一些基本參數
-
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
-
HttpProtocolParams.setContentCharset(params,
-
CHARSET);
-
HttpProtocolParams.setUseExpectContinue(params,
true);
-
HttpProtocolParams
-
.setUserAgent(
-
params,
-
"Mozilla/5.0(Linux;U;Android 2.2.1;en-us;Nexus One Build.FRG83) "
-
+
"AppleWebKit/553.1(KHTML,like Gecko) Version/4.0 Mobile Safari/533.1");
-
// 超時設置
-
/* 從鏈接池中取鏈接的超時時間 */
-
ConnManagerParams.setTimeout(params, WAIT_TIMEOUT);
-
/* 鏈接超時 */
-
HttpConnectionParams.setConnectionTimeout(params, CONNECT_TIMEOUT);
-
/* 請求超時 */
-
HttpConnectionParams.setSoTimeout(params, READ_TIMEOUT);
-
-
-
// 設置最大鏈接數
-
ConnManagerParams.setMaxTotalConnections(params, MAX_TOTAL_CONNECTIONS);
-
// 設置每一個路由最大鏈接數
-
ConnPerRouteBean connPerRoute =
new ConnPerRouteBean(MAX_ROUTE_CONNECTIONS);
-
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
-
-
// 設置咱們的HttpClient支持HTTP和HTTPS兩種模式
-
SchemeRegistry schReg =
new SchemeRegistry();
-
schReg.register(
new Scheme(
"http", PlainSocketFactory
-
.getSocketFactory(),
80));
-
schReg.register(
new Scheme(
"https", SSLSocketFactory
-
.getSocketFactory(),
443));
-
-
// 使用線程安全的鏈接管理來建立HttpClient
-
ClientConnectionManager conMgr =
new ThreadSafeClientConnManager(
-
params, schReg);
-
customerHttpClient =
new DefaultHttpClient(conMgr, params);
-
}
-
return customerHttpClient;
-
}
-
}
上面的代碼提到了3種超時設置,比較容易搞混,HttpClient的3種超時說明
數據庫
-
/* 從鏈接池中取鏈接的超時時間 */
-
ConnManagerParams.setTimeout(params,
1000);
-
/* 鏈接超時 */
-
HttpConnectionParams.setConnectionTimeout(params,
2000);
-
/* 請求超時 */
-
HttpConnectionParams.setSoTimeout(params,
4000);
ThreadSafeClientConnManager默認使用了鏈接池
apache
-
//設置最大鏈接數
-
ConnManagerParams.setMaxTotalConnections(httpParams,
10);
-
//設置最大路由鏈接數
-
ConnPerRouteBean connPerRoute =
new ConnPerRouteBean(
10);
-
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, connPerRoute);
有了單例的HttpClient對象,咱們就能夠把一些經常使用的發出GET和POST請求的代碼也封裝起來,寫進咱們的工具類中了。POST請求示例:瀏覽器
-
private
static
final String TAG =
"CustomerHttpClient";
-
-
public static String post(String url, NameValuePair... params) {
-
try {
-
// 編碼參數
-
List<NameValuePair> formparams =
new ArrayList<NameValuePair>();
// 請求參數
-
for (NameValuePair p : params) {
-
formparams.add(p);
-
}
-
UrlEncodedFormEntity entity =
new UrlEncodedFormEntity(formparams,
-
HTTP.UTF_8);
-
// 建立POST請求
-
HttpPost request =
new HttpPost(url);
-
request.setEntity(entity);
-
// 發送請求
-
HttpClient client = getHttpClient();
-
HttpResponse response = client.execute(request);
-
if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
-
throw
new RuntimeException(
"請求失敗");
-
}
-
HttpEntity resEntity = response.getEntity();
-
return (resEntity ==
null) ?
null : EntityUtils.toString(resEntity, CHARSET);
-
}
catch (UnsupportedEncodingException e) {
-
Log.w(TAG, e.getMessage());
-
return
null;
-
}
catch (ClientProtocolException e) {
-
Log.w(TAG, e.getMessage());
-
return
null;
-
}
catch (IOException e) {
-
throw
new RuntimeException(
"鏈接失敗", e);
-
}
-
-
}
所謂長鏈接是指客戶端與服務器端一旦創建鏈接之後,能夠進行屢次數據傳輸而不需從新創建鏈接,而短鏈接則每次數據傳輸都須要客戶端和服務器端創建一次鏈接。
長鏈接的優點在於省去了每次數據傳輸鏈接創建的時間開銷,可以大幅度提升數據傳輸的速度,對於P2P應用十分適合。
短鏈接每次數據傳輸都須要創建鏈接,咱們知道HTTP協議的傳輸層協議是TCP協議,TCP鏈接的創建和釋放分別須要進行3次握手和4次握手,頻繁的創建鏈接即增長了時間開銷,同時頻繁的建立和銷燬Socket一樣是對服務器端資源的浪費。
對於諸如Web網站之類的B2C應用,併發請求量大,每個用戶又不需頻繁的操做的場景下,維護大量的長鏈接對服務器無疑是一個巨大的考驗。而此時,短鏈接可能更加適用。
而對於須要頻繁發送HTTP請求的應用,須要在客戶端使用HTTP長鏈接。
安全
鏈接池管理的對象是長鏈接。鏈接池技術做爲建立和管理鏈接的緩衝池技術,目前已普遍用於諸如數據庫鏈接等長鏈接的維護和管理中,可以有效減小系統的響應時間,節省服務器資源開銷。其優點主要有兩個:其一是減小建立鏈接的資源開銷,其二是資源的訪問控制。
HTTP鏈接是無狀態的,這樣很容易給咱們形成HTTP鏈接是短鏈接的錯覺,實際上HTTP1.1默認便是持久鏈接,HTTP1.0也能夠經過在請求頭中設置Connection:keep-alive使得鏈接爲長鏈接。
服務器
沒有鏈接池的概念,多少次請求就會創建多少個IO,在訪問量巨大的狀況下服務器的IO可能會耗盡。
網絡
也有鏈接池的東西在裏頭,使用MultiThreadedHttpConnectionManager,大體過程以下:
多線程
-
MultiThreadedHttpConnectionManager connectionManager =
new MultiThreadedHttpConnectionManager();
-
HttpClient client =
new HttpClient(connectionManager);...
// 在某個線程中。
-
GetMethod get =
new GetMethod(
"http://jakarta.apache.org/");
-
try {
-
client.executeMethod(get);
// print response to stdout
-
System.out.println(get.getResponseBodyAsStream());
-
}
finally {
-
// be sure the connection is released back to the connection
-
managerget.releaseConnection();
-
}
HTTP Client4.0的ThreadSafeClientConnManager實現了HTTP鏈接的池化管理,其管理鏈接的基本單位是Route(路由),每一個路由上都會維護必定數量的HTTP鏈接。這裏的Route的概念能夠理解爲客戶端機器到目標機器的一條線路,例如使用HttpClient的實現來分別請求 www.163.com 的資源和 www.sina.com 的資源就會產生兩個route。缺省條件下對於每一個Route,HttpClient僅維護2個鏈接,總數不超過20個鏈接,顯然對於大多數應用來說,都是不夠用的,能夠經過設置HTTP參數進行調整。
併發
-
HttpParams params =
new BasicHttpParams();
-
//將每一個路由的最大鏈接數增長到200
-
ConnManagerParams.setMaxTotalConnections(params,
200);
-
// 將每一個路由的默認鏈接數設置爲20
-
ConnPerRouteBean connPerRoute =
new ConnPerRouteBean(
20);
-
// 設置某一個IP的最大鏈接數
-
HttpHost localhost =
new HttpHost(
"locahost",
80);
-
connPerRoute.setMaxForRoute(
new HttpRoute(localhost),
50);
-
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
-
SchemeRegistry schemeRegistry =
new SchemeRegistry();
-
schemeRegistry.register(
-
new Scheme(
"http", PlainSocketFactory.getSocketFactory(),
80));
-
schemeRegistry.register(
-
new Scheme(
"https", SSLSocketFactory.getSocketFactory(),
443));
-
ClientConnectionManager cm =
new ThreadSafeClientConnManager(params, schemeRegistry);
-
HttpClient httpClient =
new DefaultHttpClient(cm, params);
鏈接的有效性檢測是全部鏈接池都面臨的一個通用問題,大部分HTTP服務器爲了控制資源開銷,並不會永久的維護一個長鏈接,而是一段時間就會關閉該鏈接。放回鏈接池的鏈接,若是在服務器端已經關閉,客戶端是沒法檢測到這個狀態變化而及時的關閉Socket的。這就形成了線程從鏈接池中獲取的鏈接不必定是有效的。這個問題的一個解決方法就是在每次請求以前檢查該鏈接是否已經存在了過長時間,可能已過時。可是這個方法會使得每次請求都增長額外的開銷。HTTP Client4.0的ThreadSafeClientConnManager 提供了closeExpiredConnections()方法和closeIdleConnections()方法來解決該問題。前一個方法是清除鏈接池中全部過時的鏈接,至於鏈接何時過時能夠設置,設置方法將在下面提到,然後一個方法則是關閉必定時間空閒的鏈接,可使用一個單獨的線程完成這個工做。
-
public
static
class IdleConnectionMonitorThread extends Thread {
-
private
final ClientConnectionManager connMgr;
-
private
volatile
boolean shutdown;
-
public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
-
super();
-
this.connMgr = connMgr;
-
}
-
@Override
-
public void run() {
-
try {
-
while (!shutdown) {
-
synchronized (
this) {
-
wait(
5000);
-
// 關閉過時的鏈接
-
connMgr.closeExpiredConnections();
-
// 關閉空閒時間超過30秒的鏈接
-
connMgr.closeIdleConnections(
30, TimeUnit.SECONDS);
-
}
-
}
-
}
catch (InterruptedException ex) {
-
// terminate
-
}
-
}
-
public void shutdown() {
-
shutdown =
true;
-
synchronized (
this) {
-
notifyAll();
-
}
-
DefaultHttpClient httpclient =
new DefaultHttpClient();
-
httpclient.setKeepAliveStrategy(
new ConnectionKeepAliveStrategy() {
-
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
-
// Honor 'keep-alive' header
-
HeaderElementIterator it =
new BasicHeaderElementIterator(
-
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
-
while (it.hasNext()) {
-
HeaderElement he = it.nextElement();
-
String param = he.getName();
-
String value = he.getValue();
-
if (value !=
null && param.equalsIgnoreCase(
"timeout")) {
-
try {
-
return Long.parseLong(value) *
1000;
-
}
catch(NumberFormatException ignore) {
-
}
-
}
-
}
-
HttpHost target = (HttpHost) context.getAttribute(
-
ExecutionContext.HTTP_TARGET_HOST);
-
if (
"www.163.com".equalsIgnoreCase(target.getHostName())) {
-
// 對於163這個路由的鏈接,保持5秒
-
return
5 *
1000;
-
}
else {
-
// 其餘路由保持30秒
-
return
30 *
1000;
-
}
-
}
-
})