HttpClient使用总结

Stella981
• 阅读 488

一、使用方法

使用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. 释放连接。无论执行方法是否成功,都必须释放连接


  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. }

二、多线程的HttpClient

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

  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. }

1、超时配置

上面的代码提到了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。 

2、线程池配置

ThreadSafeClientConnManager默认使用了连接池

  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个连接都在等待,都是为别的目标机器服务的。



3、工具类

有了单例的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、线程池技术

4.1 长连接和短连接

所谓长连接是指客户端与服务器端一旦建立连接以后,可以进行多次数据传输而不需重新建立连接,而短连接则每次数据传输都需要客户端和服务器端建立一次连接。

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

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

4.2、连接池

连接池管理的对象是长连接。连接池技术作为创建和管理连接的缓冲池技术,目前已广泛用于诸如数据库连接等长连接的维护和管理中,能够有效减少系统的响应时间,节省服务器资源开销。其优势主要有两个:其一是减少创建连接的资源开销,其二是资源的访问控制。

       
HTTP连接是无状态的,这样很容易给我们造成HTTP连接是短连接的错觉,实际上HTTP1.1默认即是持久连接,HTTP1.0也可以通过在请求头中设置Connection:keep-alive使得连接为长连接。

4.3、HttpConnection

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

4.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.5、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.6、过期长连接

连接的有效性检测是所有连接池都面临的一个通用问题,大部分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. })



点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
AndroidStudio封装SDK的那些事
<divclass"markdown\_views"<!flowchart箭头图标勿删<svgxmlns"http://www.w3.org/2000/svg"style"display:none;"<pathstrokelinecap"round"d"M5,00,2.55,5z"id"raphael
Wesley13 Wesley13
2年前
Activiti 工作流入门指南
<divclass"htmledit\_views"id"content\_views"<h1<aname"t0"</a概览</h1<p如我们的介绍部分所述,Activiti目前分为两大类:</p<ul<li<p<ahref"https://activiti.gitbook.io/activiti7deve
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这