使用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;
-
}
-
}
-
})