HttpClient使用总结

1、使用方法

使用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


  
  
  
  
  1. try {
  2. // 建立一个默认的HttpClient
  3. HttpClient httpclient = new DefaultHttpClient();
  4. // 建立一个GET请求
  5. HttpGet request = new HttpGet( "www.google.com");
  6. // 发送GET请求,并将响应内容转换成字符串
  7. String response = httpclient.execute(request, new BasicResponseHandler());
  8. Log.v( "response text", response);
  9. } catch (ClientProtocolException e) {
  10. e.printStackTrace();
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. }

2、多线程的HttpClient

在实际项目中,咱们极可能在多处须要进行HTTP通讯,这时候咱们不须要为每一个请求都建立一个新的HttpClient。如今咱们的应用程序使用同一个HttpClient来管理全部的Http请求,一旦出现并发请求,那么必定会出现多线程的问题。这就好像咱们的浏览器只有一个标签页却有多个用户,A要上google,B要上baidu,这时浏览器就会忙不过来了。幸运的是,HttpClient提供了建立线程安全对象的API

java

  
  
  
  
  1. public class CustomerHttpClient {
  2. private static final String CHARSET = HTTP.UTF_8;
  3. /**
  4. * 最大链接数
  5. */
  6. public final static int MAX_TOTAL_CONNECTIONS = 800;
  7. /**
  8. * 获取链接的最大等待时间
  9. */
  10. public final static int WAIT_TIMEOUT = 60000;
  11. /**
  12. * 每一个路由最大链接数
  13. */
  14. public final static int MAX_ROUTE_CONNECTIONS = 400;
  15. /**
  16. * 链接超时时间
  17. */
  18. public final static int CONNECT_TIMEOUT = 10000;
  19. /**
  20. * 读取超时时间
  21. */
  22. public final static int READ_TIMEOUT = 10000;
  23. private static HttpClient customerHttpClient;
  24. private CustomerHttpClient() {
  25. }
  26. public static synchronized HttpClient getHttpClient() {
  27. if ( null == customerHttpClient) {
  28. HttpParams params = new BasicHttpParams();
  29. // 设置一些基本参数
  30. HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
  31. HttpProtocolParams.setContentCharset(params,
  32. CHARSET);
  33. HttpProtocolParams.setUseExpectContinue(params, true);
  34. HttpProtocolParams
  35. .setUserAgent(
  36. params,
  37. "Mozilla/5.0(Linux;U;Android 2.2.1;en-us;Nexus One Build.FRG83) "
  38. + "AppleWebKit/553.1(KHTML,like Gecko) Version/4.0 Mobile Safari/533.1");
  39. // 超时设置
  40. /* 从链接池中取链接的超时时间 */
  41. ConnManagerParams.setTimeout(params, WAIT_TIMEOUT);
  42. /* 链接超时 */
  43. HttpConnectionParams.setConnectionTimeout(params, CONNECT_TIMEOUT);
  44. /* 请求超时 */
  45. HttpConnectionParams.setSoTimeout(params, READ_TIMEOUT);
  46. // 设置最大链接数
  47. ConnManagerParams.setMaxTotalConnections(params, MAX_TOTAL_CONNECTIONS);
  48. // 设置每一个路由最大链接数
  49. ConnPerRouteBean connPerRoute = new ConnPerRouteBean(MAX_ROUTE_CONNECTIONS);
  50. ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
  51. // 设置咱们的HttpClient支持HTTP和HTTPS两种模式
  52. SchemeRegistry schReg = new SchemeRegistry();
  53. schReg.register( new Scheme( "http", PlainSocketFactory
  54. .getSocketFactory(), 80));
  55. schReg.register( new Scheme( "https", SSLSocketFactory
  56. .getSocketFactory(), 443));
  57. // 使用线程安全的链接管理来建立HttpClient
  58. ClientConnectionManager conMgr = new ThreadSafeClientConnManager(
  59. params, schReg);
  60. customerHttpClient = new DefaultHttpClient(conMgr, params);
  61. }
  62. return customerHttpClient;
  63. }
  64. }

一、超时配置

上面的代码提到了3种超时设置,比较容易搞混,HttpClient的3种超时说明

数据库

  
  
  
  
  1. /* 从链接池中取链接的超时时间 */
  2. ConnManagerParams.setTimeout(params, 1000);
  3. /* 链接超时 */
  4. HttpConnectionParams.setConnectionTimeout(params, 2000);
  5. /* 请求超时 */
  6. HttpConnectionParams.setSoTimeout(params, 4000);

第一行设置ConnectionPoolTimeout:这定义了从ConnectionManager管理的链接池中取出链接的超时时间,此处设置为1秒。
第二行设置ConnectionTimeout:  这定义了经过网络与服务器创建链接的超时时间。Httpclient包中经过一个异步线程去建立与服务器的socket链接,这就是该socket链接的超时时间,此处设置为2秒。
第三行设置SocketTimeout:    这定义了Socket读数据的超时时间,即从服务器获取响应数据须要等待的时间,此处设置为4秒。

以上3种超时分别会抛出ConnectionPoolTimeoutException,ConnectionTimeoutException与SocketTimeoutException。 

二、线程池配置

ThreadSafeClientConnManager默认使用了链接池
apache

  
  
  
  
  1. //设置最大链接数
  2. ConnManagerParams.setMaxTotalConnections(httpParams, 10);
  3. //设置最大路由链接数
  4. ConnPerRouteBean connPerRoute = new ConnPerRouteBean( 10);
  5. ConnManagerParams.setMaxConnectionsPerRoute(httpParams, connPerRoute);

比较特别的是 每一个路由(route)最大链接数。什么是一个route?这里route的概念能够理解为运行环境机器到目标机器的一条线路。举例来讲,咱们使用HttpClient的实现来分别请求 www.baidu.com 的资源和 www.bing.com 的资源那么他就会产生两个route。这里为何要特别提到route最大链接数这个参数呢,由于这个参数的默认值为2,若是不设置这个参数值默认状况下对于同一个目标机器的最大并发链接只有2个!这意味着若是你正在执行一个针对某一台目标机器的抓取任务的时候,哪怕你设置链接池的最大链接数为200,可是实际上仍是只有2个链接在工做,其余剩余的198个链接都在等待,都是为别的目标机器服务的。



三、工具类

有了单例的HttpClient对象,咱们就能够把一些经常使用的发出GET和POST请求的代码也封装起来,写进咱们的工具类中了。POST请求示例:浏览器


  
  
  
  
  1. private static final String TAG = "CustomerHttpClient";
  2. public static String post(String url, NameValuePair... params) {
  3. try {
  4. // 编码参数
  5. List<NameValuePair> formparams = new ArrayList<NameValuePair>(); // 请求参数
  6. for (NameValuePair p : params) {
  7. formparams.add(p);
  8. }
  9. UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams,
  10. HTTP.UTF_8);
  11. // 建立POST请求
  12. HttpPost request = new HttpPost(url);
  13. request.setEntity(entity);
  14. // 发送请求
  15. HttpClient client = getHttpClient();
  16. HttpResponse response = client.execute(request);
  17. if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
  18. throw new RuntimeException( "请求失败");
  19. }
  20. HttpEntity resEntity = response.getEntity();
  21. return (resEntity == null) ? null : EntityUtils.toString(resEntity, CHARSET);
  22. } catch (UnsupportedEncodingException e) {
  23. Log.w(TAG, e.getMessage());
  24. return null;
  25. } catch (ClientProtocolException e) {
  26. Log.w(TAG, e.getMessage());
  27. return null;
  28. } catch (IOException e) {
  29. throw new RuntimeException( "链接失败", e);
  30. }
  31. } 


四、线程池技术

4.1 长链接和短链接

所谓长链接是指客户端与服务器端一旦创建链接之后,能够进行屡次数据传输而不需从新创建链接,而短链接则每次数据传输都须要客户端和服务器端创建一次链接。

长链接的优点在于省去了每次数据传输链接创建的时间开销,可以大幅度提升数据传输的速度,对于P2P应用十分适合。
短链接每次数据传输都须要创建链接,咱们知道HTTP协议的传输层协议是TCP协议,TCP链接的创建和释放分别须要进行3次握手和4次握手,频繁的创建链接即增长了时间开销,同时频繁的建立和销毁Socket一样是对服务器端资源的浪费。

对于诸如Web网站之类的B2C应用,并发请求量大,每个用户又不需频繁的操做的场景下,维护大量的长链接对服务器无疑是一个巨大的考验。而此时,短链接可能更加适用。
而对于须要频繁发送HTTP请求的应用,须要在客户端使用HTTP长链接。

安全

4.二、链接池

链接池管理的对象是长链接。链接池技术做为建立和管理链接的缓冲池技术,目前已普遍用于诸如数据库链接等长链接的维护和管理中,可以有效减小系统的响应时间,节省服务器资源开销。其优点主要有两个:其一是减小建立链接的资源开销,其二是资源的访问控制。

       
HTTP链接是无状态的,这样很容易给咱们形成HTTP链接是短链接的错觉,实际上HTTP1.1默认便是持久链接,HTTP1.0也能够经过在请求头中设置Connection:keep-alive使得链接为长链接。

服务器

4.三、HttpConnection

没有链接池的概念,多少次请求就会创建多少个IO,在访问量巨大的状况下服务器的IO可能会耗尽。

网络

4.四、HttpClient3

也有链接池的东西在里头,使用MultiThreadedHttpConnectionManager,大体过程以下:

多线程

  
  
  
  
  1. MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
  2. HttpClient client = new HttpClient(connectionManager);... // 在某个线程中。
  3. GetMethod get = new GetMethod( "http://jakarta.apache.org/");
  4. try {
  5. client.executeMethod(get); // print response to stdout
  6. System.out.println(get.getResponseBodyAsStream());
  7. } finally {
  8. // be sure the connection is released back to the connection
  9. managerget.releaseConnection();
  10. }



能够看出来,它的方式与jdbc链接池的使用方式相近,比较不爽的就是须要手动调用releaseConnection去释放链接。对每个HttpClient.executeMethod须有一个method.releaseConnection()与之匹配。

4.五、HttpClient4

HTTP Client4.0的ThreadSafeClientConnManager实现了HTTP链接的池化管理,其管理链接的基本单位是Route(路由),每一个路由上都会维护必定数量的HTTP链接。这里的Route的概念能够理解为客户端机器到目标机器的一条线路,例如使用HttpClient的实现来分别请求 www.163.com 的资源和 www.sina.com 的资源就会产生两个route。缺省条件下对于每一个Route,HttpClient仅维护2个链接,总数不超过20个链接,显然对于大多数应用来说,都是不够用的,能够经过设置HTTP参数进行调整。

并发

  
  
  
  
  1. HttpParams params = new BasicHttpParams();
  2. //将每一个路由的最大链接数增长到200
  3. ConnManagerParams.setMaxTotalConnections(params, 200);
  4. // 将每一个路由的默认链接数设置为20
  5. ConnPerRouteBean connPerRoute = new ConnPerRouteBean( 20);
  6. // 设置某一个IP的最大链接数
  7. HttpHost localhost = new HttpHost( "locahost", 80);
  8. connPerRoute.setMaxForRoute( new HttpRoute(localhost), 50);
  9. ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
  10. SchemeRegistry schemeRegistry = new SchemeRegistry();
  11. schemeRegistry.register(
  12. new Scheme( "http", PlainSocketFactory.getSocketFactory(), 80));
  13. schemeRegistry.register(
  14. new Scheme( "https", SSLSocketFactory.getSocketFactory(), 443));
  15. ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
  16. HttpClient httpClient = new DefaultHttpClient(cm, params);


     

能够配置的HTTP参数有:
1)  http.conn-manager.timeout 当某一线程向链接池请求分配线程时,若是链接池已经没有能够分配的链接时,该线程将会被阻塞,直至http.conn-manager.timeout超时,抛出ConnectionPoolTimeoutException。
2)  http.conn-manager.max-per-route 每一个路由的最大链接数;
3)  http.conn-manager.max-total 总的链接数;

4.六、过时长链接

链接的有效性检测是全部链接池都面临的一个通用问题,大部分HTTP服务器为了控制资源开销,并不会永久的维护一个长链接,而是一段时间就会关闭该链接。放回链接池的链接,若是在服务器端已经关闭,客户端是没法检测到这个状态变化而及时的关闭Socket的。这就形成了线程从链接池中获取的链接不必定是有效的。这个问题的一个解决方法就是在每次请求以前检查该链接是否已经存在了过长时间,可能已过时。可是这个方法会使得每次请求都增长额外的开销。HTTP Client4.0的ThreadSafeClientConnManager 提供了closeExpiredConnections()方法和closeIdleConnections()方法来解决该问题。前一个方法是清除链接池中全部过时的链接,至于链接何时过时能够设置,设置方法将在下面提到,然后一个方法则是关闭必定时间空闲的链接,可使用一个单独的线程完成这个工做。

  
  
  
  
  1. public static class IdleConnectionMonitorThread extends Thread {
  2. private final ClientConnectionManager connMgr;
  3. private volatile boolean shutdown;
  4. public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
  5. super();
  6. this.connMgr = connMgr;
  7. }
  8. @Override
  9. public void run() {
  10. try {
  11. while (!shutdown) {
  12. synchronized ( this) {
  13. wait( 5000);
  14. // 关闭过时的链接
  15. connMgr.closeExpiredConnections();
  16. // 关闭空闲时间超过30秒的链接
  17. connMgr.closeIdleConnections( 30, TimeUnit.SECONDS);
  18. }
  19. }
  20. } catch (InterruptedException ex) {
  21. // terminate
  22. }
  23. }
  24. public void shutdown() {
  25. shutdown = true;
  26. synchronized ( this) {
  27. notifyAll();
  28. }


刚才提到,客户端能够设置链接的过时时间,能够经过HttpClient的setKeepAliveStrategy方法设置链接的过时时间,这样就能够配合closeExpiredConnections()方法解决链接池中链接失效的。

  
  
  
  
  1. DefaultHttpClient httpclient = new DefaultHttpClient();
  2. httpclient.setKeepAliveStrategy( new ConnectionKeepAliveStrategy() {
  3. public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
  4. // Honor 'keep-alive' header
  5. HeaderElementIterator it = new BasicHeaderElementIterator(
  6. response.headerIterator(HTTP.CONN_KEEP_ALIVE));
  7. while (it.hasNext()) {
  8. HeaderElement he = it.nextElement();
  9. String param = he.getName();
  10. String value = he.getValue();
  11. if (value != null && param.equalsIgnoreCase( "timeout")) {
  12. try {
  13. return Long.parseLong(value) * 1000;
  14. } catch(NumberFormatException ignore) {
  15. }
  16. }
  17. }
  18. HttpHost target = (HttpHost) context.getAttribute(
  19. ExecutionContext.HTTP_TARGET_HOST);
  20. if ( "www.163.com".equalsIgnoreCase(target.getHostName())) {
  21. // 对于163这个路由的链接,保持5秒
  22. return 5 * 1000;
  23. } else {
  24. // 其余路由保持30秒
  25. return 30 * 1000;
  26. }
  27. }
  28. })
相关文章
相关标签/搜索