spring boot封装HttpClient

最近使用到了HttpClient,看了一下官方文档:HttpClient实现是线程安全的。建议将这个类的实例重用于多个请求执行,翻译过来的意思就是:HttpClient的实现是线程安全的,可以重用相同的实例来执行多次请求。遇到这种描述的话,我们就应该想到,需要对HttpClient来进行封装了。由于是使用的spring boot,所以下面来结合spring boot来封装HttpClient。

一,请求重试处理

为了使自定义异常机制生效,实现需要HttpRequestRetryHandler接口,代码如下:

[java]  查看纯 文本  
  1. import  java.io.IOException;  
  2. import  java.io.InterruptedIOException;  
  3. import  java.net.UnknownHostException;  
  4.   
  5. import  javax.net.ssl.SSLException;  
  6. import  javax.net.ssl.SSLHandshakeException;  
  7.   
  8. import  org.apache.http.HttpEntityEnclosingRequest;  
  9. import  org.apache.http.HttpRequest;  
  10. import  org.apache.http.NoHttpResponseException;  
  11. import  org.apache.http.client.HttpRequestRetryHandler;  
  12. import  org.apache.http.client.protocol.HttpClientContext;  
  13. import  org.apache.http.conn.ConnectTimeoutException;  
  14. import  org.apache.http.protocol.HttpContext;  
  15. import  org.springframework.beans.factory.annotation.Value;  
  16. import  org.springframework.context.annotation.Bean;  
  17. import  org.springframework.context.annotation.Configuration;  
  18.   
  19. / ** 
  20.  *描述:HttpClient的重试处理机制 
  21.  * /  
  22. @组态  
  23. 公共 MyhttpRequestRetryHandler {   
  24.   
  25.     @Value “$ {httpclient.config.retryTime}” //此处建议采用@ConfigurationProperties(prefix =“httpclient.config”)方式,方便复用  
  26.     private int  retryTime;   
  27.       
  28.     @豆  
  29.     public  HttpRequestRetryHandler httpRequestRetryHandler(){  
  30.         //请求重试  
  31.         final int  retryTime =  this .retryTime;   
  32.         返回新的 HttpRequestRetryHandler(){   
  33.             public boolean  retryRequest(IOException exception,  int  executionCount,HttpContext context){   
  34.                 //如果超过最大重试计数,请不要重试,如​​果重试次数超过了retryTime,则不再重试请求  
  35.                 if  (executionCount> = retryTime){  
  36.                     返回假的;   
  37.                 }  
  38.                 //服务端断掉客户端的连接异常  
  39.                 if  (exception  instanceof  NoHttpResponseException){  
  40.                     返回;   
  41.                 }  
  42.                 // time out超时重试  
  43.                 if  (异常  instanceof  InterruptedIOException){  
  44.                     返回;   
  45.                 }  
  46.                 // 未知主机  
  47.                 if  (exception  instanceof  UnknownHostException){  
  48.                     返回假的;   
  49.                 }  
  50.                 // 拒绝连接  
  51.                 if  (异常  instanceof  ConnectTimeoutException){  
  52.                     返回假的;   
  53.                 }  
  54.                 // SSL握手异常  
  55.                 if  (exception  instanceof  SSLException){  
  56.                     返回假的;   
  57.                 }  
  58.                 HttpClientContext clientContext = HttpClientContext.adapt(context);  
  59.                 HttpRequest request = clientContext.getRequest();  
  60.                 if  (!(request  instanceof  HttpEntityEnclosingRequest)){  
  61.                     返回;   
  62.                 }  
  63.                 返回假的;   
  64.             }  
  65.         };  
  66.     }  
  67. }  
二连池池管理

PoolingHttpClientConnectionManager用来管理客户端的连接池,并且可以为多个线程的请求提供,代码如下:

[java]  查看纯 文本  
  1. import  org.apache.http.config.Registry;  
  2. import  org.apache.http.config.RegistryBuilder;  
  3. import  org.apache.http.conn.socket.ConnectionSocketFactory;  
  4. import  org.apache.http.conn.socket.LayeredConnectionSocketFactory;  
  5. import  org.apache.http.conn.socket.PlainConnectionSocketFactory;  
  6. import  org.apache.http.conn.ssl.SSLConnectionSocketFactory;  
  7. import  org.apache.http.impl.conn.PoolingHttpClientConnectionManager;  
  8. import  org.springframework.beans.factory.annotation.Value;  
  9. import  org.springframework.context.annotation.Bean;  
  10. import  org.springframework.context.annotation.Configuration;  
  11.   
  12. @组态  
  13. public class  MyPoolingHttpClientConnectionManager {   
  14.     / ** 
  15.      *连接池最大连接数 
  16.      * /  
  17.     @Value “$ {httpclient.config.connMaxTotal}” )  
  18.     private int  connMaxTotal =  20 ;   
  19.       
  20.     / ** 
  21.      *  
  22.      * /  
  23.     @Value “$ {httpclient.config.maxPerRoute}” )  
  24.     private int  maxPerRoute =  20 ;   
  25.   
  26.         / ** 
  27.      *连接存活时间,单位为s 
  28.      * /  
  29.      @Value “$ {httpclient.config.timeToLive}” )  
  30.      private int  timeToLive =  60 ;   
  31.   
  32.        @豆  
  33.     public  poolingHttpClientConnectionManager poolingClientConnectionManager(){  
  34.         PoolingHttpClientConnectionManager poolHttpcConnManager =  new  PoolingHttpClientConnectionManager(60 ,TimeUnit.SECONDS);  
  35.         //最大连接数  
  36.         poolHttpcConnManager.setMaxTotal(this .connMaxTotal);  
  37.         //路由基数  
  38.         poolHttpcConnManager.setDefaultMaxPerRoute(.maxPerRoute);  
  39.         返回 poolHttpcConnManager;  
  40.     }  
  41. }  

注意:当HttpClient的实例不再需要并且即将超出范围时,重要的是关闭其连接管理器,以确保管理器保持活动的所有连接都被关闭,并释放由这些连接分配的系统资源

上面PoolingHttpClientConnectionManager类的构造函数如下:

[java]  查看纯 文本  
  1. public  PoolingHttpClientConnectionManager(final long  timeToLive,  final  TimeUnit tunit){   
  2.         this (getDefaultRegistry(),  null ,  null  ,null ,timeToLive,tunit);  
  3.     }  
  4.   
  5. private static  Registry <ConnectionSocketFactory> getDefaultRegistry(){   
  6.         return  RegistryBuilder。<ConnectionSocketFactory> create()  
  7.                 .register(“http” ,PlainConnectionSocketFactory.getSocketFactory())  
  8.                 .register(“https” ,SSLConnectionSocketFactory.getSocketFactory())  
  9.                 。建立();  
  10.     }  

        在PoolingHttpClientConnectionManager的配置中有两个最大连接数量,分别控制着总的最大连接数量和每个路线的最大连接数量。如果没有显式设置,默认每个路径只允许最多2个连接,总的连接数量不超过20这个值对于很多并发度高的应用来说是不够的,必须根据实际的情况设置合适的值,思路和线程池的大小设置方式是类似的,如果所有的连接请求都是到同一个网址,那可以把MaxPerRoute的值设置成和MaxTotal一致,这样就能更高效地复用连接

特别注意:想要复用一个连接就必须要让它占有的系统资源得到正确释放,释放方法如下:

        如果是使用outputStream就要保证整个实体都被写出,如果是inputStream,则再最后要记得调用inputStream.close()。或者使用EntityUtils.consume(entity)或EntityUtils.consumeQuietly(entity)来让实体被完全耗尽(后者不抛异常)来做这一工作.EntityUtils中有个toString方法也很方便的(调用这个方法最后也会自动把inputStream close掉的,但是在实际的测试过程中,会导致连接没有释放的现象),不过只有在可以确定收到的实体不是特别大的情况下才能使用如果没有让整个实体被完全消费,则该连接是不能被复用的,很快就会因为在连接所以如果想要复用连接,一定一定要记得把实体完全消耗掉,只要检测到流的EOF,是会自动调用ConnectionHolder的releaseConnection方法 行处理的

三,连接保持策略(保持连接策略)

        HTTP规范没有指定持久连接可能和应该保持存活多久。一些HTTP服务器使用非标准的Keep-Alive标头来向客户端通信它们打算在服务器端保持连接的时间段(以秒为单位)HttpClient可以使用这些信息。如果响应中不存在保持活动头,HttpClient的会假定连接可以无限期地保持活动。然而,一般使用的许多HTTP服务器都配置为在一段不活动状态之后删除持久连接,以便节省系统资源,而不会通知客户端。如果默认策略过于乐观,则可能需要提供自定义的保持活动策略,代码如下:

[java]  查看纯 文本  
  1. import  org.apache.http.HeaderElement;  
  2. import  org.apache.http.HeaderElementIterator;  
  3. import  org.apache.http.HttpResponse;  
  4. import  org.apache.http.conn.ConnectionKeepAliveStrategy;  
  5. import  org.apache.http.message.BasicHeaderElementIterator;  
  6. import  org.apache.http.protocol.HTTP;  
  7. import  org.apache.http.protocol.HttpContext;  
  8. import  org.springframework.beans.factory.annotation.Value;  
  9. import  org.springframework.context.annotation.Bean;  
  10. import  org.springframework.context.annotation.Configuration;  
  11.   
  12. / ** 
  13.  *描述:连接保持策略 
  14.  * @author chhliu 
  15.  * /  
  16. @组态  
  17. 公共 MyconnectionKeepAliveStrategy {   
  18.       
  19.     @Value “$ {httpclient.config.keepAliveTime}” )  
  20.     private int  keepAliveTime =  30 ;   
  21.       
  22.     @Bean “connectionKeepAliveStrategy” )  
  23.     public  ConnectionKeepAliveStrategy connectionKeepAliveStrategy(){  
  24.         返回新的 ConnectionKeepAliveStrategy(){   
  25.   
  26.             public long  getKeepAliveDuration(HttpResponse response,HttpContext context){   
  27.                 // Honor'keep-alive'标题  
  28.                 HeaderElementIterator it =  new  BasicHeaderElementIterator(  
  29.                         response.headerIterator(HTTP.CONN_KEEP_ALIVE));  
  30.                 while  (it.hasNext()){  
  31.                     HeaderElement he = it.nextElement();  
  32.                     String param = he.getName();  
  33.                     String value = he.getValue();  
  34.                     if  (value!=  null  && param.equalsIgnoreCase(“timeout” )){  
  35.                         尝试 {  
  36.                             返回 Long.parseLong(value)*  1000 ;  
  37.                         }  catch  (NumberFormatException ignore){  
  38.                         }  
  39.                     }  
  40.                 }  
  41.                 返回30  *  1000 ;   
  42.             }  
  43.         };  
  44.     }  
  45. }  

注意:长连接并不使用于所有的情况,尤其现在的系统,大都是部署在多台服务器上,且具有负载均衡的功能,如果我们在访问的时候,一直保持长连接,一旦那台服务器挂了,就会影响客户端,同时也不能充分的利用服务端的负载均衡的特性,反而短连接更有利一些,这些需要根据具体的需求来定,而不是一言概括。

四,HttpClient代理配置(代理配置)

用来配置代理,代码如下:

[java]  查看纯 文本  
  1. import  org.apache.http.HttpHost;  
  2. import  org.apache.http.impl.conn.DefaultProxyRoutePlanner;  
  3. import  org.springframework.beans.factory.annotation.Value;  
  4. import  org.springframework.context.annotation.Bean;  
  5. import  org.springframework.context.annotation.Configuration;  
  6.   
  7. / ** 
  8.  *描述:HttpClient代理 
  9.  * @author chhliu 
  10.  * /  
  11. @组态  
  12. 公共 MyDefaultProxyRoutePlanner {   
  13.     //代理的主机地址  
  14.     @Value “$ {httpclient.config.proxyhost}” )  
  15.     private  String proxyHost;  
  16.       
  17.     //代理的端口号  
  18.     @Value “$ {httpclient.config.proxyPort}” )  
  19.     private int  proxyPort =  8080 ;   
  20.       
  21.     @豆  
  22.     public  DefaultProxyRoutePlanner defaultProxyRoutePlanner(){  
  23.         HttpHost proxy =  new  HttpHost(这个.proxyHost,  这个.proxyPort);  
  24.         返回新的 DefaultProxyRoutePlanner(代理);   
  25.     }  
  26. }  

        HttpClient的不仅支持简单的直连,复杂的路由策略以及代理.HttpRoutePlanner是基于HTTP上下文情况下,客户端到服务器的路由计算策略,一般没有代理的话,就不用设置这个东西。这里有一个很关键的概念 - 路线指示运行环境机器

五,RequestConfig

用来设置请求的各种配置,代码如下:

[java]  查看纯 文本  
  1. import  org.apache.http.client.config.RequestConfig;  
  2. import  org.springframework.beans.factory.annotation.Value;  
  3. import  org.springframework.context.annotation.Bean;  
  4. import  org.springframework.context.annotation.Configuration;  
  5.   
  6. @组态  
  7. 公共 MyRequestConfig {   
  8.     @Value “$ {httpclient.config.connectTimeout}” )  
  9.     private int  connectTimeout =  2000 ;   
  10.       
  11.     @Value “$ {httpclient.config.connectRequestTimeout}” )  
  12.     private int  connectRequestTimeout =  2000 ;   
  13.       
  14.     @Value “$ {httpclient.config.socketTimeout}” )  
  15.     private int  socketTimeout =  2000 ;   
  16.     @豆  
  17.     public  RequestConfig config(){  
  18.         返回 RequestConfig.custom()  
  19.                 .setConnectionRequestTimeout(this .connectRequestTimeout)  
  20.                 .setConnectTimeout(this .connectTimeout)  
  21.                 .setSocketTimeout(this .socketTimeout)  
  22.                 。建立();  
  23.     }  
  24. }  

        RequestConfig是对请求的一些配置。里面比较重要的有三个超时时间,默认的情况下这三个超时时间都为0(如果不设置请求的配置,会在执行的过程中使用HttpClientParamConfig的getRequestConfig中用默认参数进行设置),这也就意味着无限等待,很容易导致所有的请求阻塞在这个地方无限期等待这三个超时时间为:
        a,connectionRequestTimeout-从连接池中取连接的超时时间
        这个时间定义的是从ConnectionManager管理的连接池中取出连接的超时时间,如果连接池中没有可用的连接,则请求被阻塞,最长等待connectionRequestTimeout的时间,如果还没有被服务,则抛出ConnectionPoolTimeoutException异常,不继续等待
        b 
        连接等待时间定时 时,会抛出ConnectionTimeoutException异常
        c,socketTimeout-请求超时时间
        这个时间定义了socket读数据的超时时间,也就是连接到服务器之后到从服务器获取响应数据需要等待的时间,或者说是连接上一个url之后到获取响应的返回等待时间。发生超时,会抛出SocketTimeoutException异常。

六,实例化的HttpClient

通过实现FactoryBean的来实例化HttpClient的,代码如下:

[java]  查看纯 文本  
  1. import  org.apache.http.client.HttpRequestRetryHandler;  
  2. import  org.apache.http.client.config.RequestConfig;  
  3. import  org.apache.http.conn.ConnectionKeepAliveStrategy;  
  4. import  org.apache.http.impl.client.CloseableHttpClient;  
  5. import  org.apache.http.impl.client.HttpClients;  
  6. import  org.apache.http.impl.conn.DefaultProxyRoutePlanner;  
  7. import  org.apache.http.impl.conn.PoolingHttpClientConnectionManager;  
  8. import  org.springframework.beans.factory.DisposableBean;  
  9. import  org.springframework.beans.factory.FactoryBean;  
  10. import  org.springframework.beans.factory.InitializingBean;  
  11. import  org.springframework.beans.factory.annotation.Autowired;  
  12. import  org.springframework.stereotype.Service;  
  13.   
  14. / ** 
  15.  *描述:HttpClient客户端封装 
  16.  * /  
  17. @Service “httpClientManagerFactoryBen” )  
  18. public class  HttpClientManagerFactoryBen  实现 FactoryBean <CloseableHttpClient>,InitializingBean,DisposableBean {   
  19.   
  20.     / ** 
  21.      * FactoryBean生成的目标对象 
  22.      * /  
  23.     私人 CloseableHttpClient客户端;  
  24.       
  25.     @Autowired  
  26.     私人 ConnectionKeepAliveStrategy connectionKeepAliveStrategy;  
  27.       
  28.     @Autowired  
  29.     私人 HttpRequestRetryHandler httpRequestRetryHandler;  
  30.       
  31.     @Autowired  
  32.     私人 DefaultProxyRoutePlanner proxyRoutePlanner;  
  33.       
  34.     @Autowired  
  35.     private  PoolingHttpClientConnectionManager poolHttpcConnManager;  
  36.       
  37.     @Autowired  
  38.     private  RequestConfig config;  
  39.       
  40.         //销毁上下文时,销毁HttpClient实例  
  41.     @覆盖  
  42.     public void  destroy()  throws  Exception {   
  43.                  / * 
  44.            *调用httpClient.close()会先关闭连接管理器,然后再释放该HttpClient所占用的所有资源, 
  45.            *关闭所有在使用或者空闲的连接包括底层插座。由于这里把它所使用的连接管理器关闭了, 
  46.            *所以在下次还要进行http请求的时候,要重新新一个连接管理器来构建一个HttpClient, 
  47.            *也就是在需要关闭和新建客户的情况下,连接管理器不能是单例的。 
  48.            * /  
  49.                 if null  !=  this .client){  
  50.             这个.client.close();  
  51.            }  
  52.     }  
  53.   
  54.     @Override //初始化实例  
  55.     public void  afterPropertiesSet()  抛出 异常{   
  56.                  / * 
  57.          *建议此处使用HttpClient.custom的方式来创建HttpClientBuilder,而不要使用HttpClientBuilder.create()方法来创建HttpClientBuilder 
  58.          *从官方文档可以得出,HttpClientBuilder是非线程安全的,但是HttpClients确实Immutable的,immutable对象不仅能够保证对象的状态不被改变, 
  59.          *而且还可以不使用锁机制就能被其他线程共享 
  60.          * /  
  61.                   这个.client = HttpClients.custom()。setConnectionManager(poolHttpcConnManager)  
  62.                 .setRetryHandler(httpRequestRetryHandler)  
  63.                 .setKeepAliveStrategy(connectionKeepAliveStrategy)  
  64.                 .setRoutePlanner(proxyRoutePlanner)  
  65.                 .setDefaultRequestConfig(配置)  
  66.                 。建立();  
  67.     }  
  68.   
  69.         //返回实例的类型  
  70.     @覆盖  
  71.     public  CloseableHttpClient getObject()  throws  Exception {  
  72.         返回.client;   
  73.     }  
  74.   
  75.     @覆盖  
  76.     public  class <?> getObjectType(){  
  77.          (.client ==   CloseableHttpClient? :  .client.getClass());  
  78.     }  
  79.   
  80.         //构建的实例为单例  
  81.     @覆盖  
  82.     public boolean  isSingleton(){   
  83.         返回;   
  84.     }  
  85.   
  86. }  
七,增加配置文件

[java]  查看纯 文本  
  1. #代理的主机  
  2. httpclient.config.proxyhost = xxx.xx.xx.xx  
  3. #代理端口  
  4. httpclient.config.proxyPort = 8080  
  5. #连接超时或异常重试次数  
  6. httpclient.config.retryTime = 3  
  7. #长连接保持时间,单位为s  
  8. httpclient.config.keepAliveTime = 30  
  9. #连接池最大连接数  
  10. httpclient.config.connMaxTotal = 20  
  11. httpclient.config.maxPerRoute = 20  
  12. #连接超时时间,单位ms  
  13. httpclient.config.connectTimeout = 2000  
  14. #请求超时时间  
  15. httpclient.config.connectRequestTimeout = 2000  
  16. #sock超时时间  
  17. httpclient.config.socketTimeout = 2000  
  18. #连接存活时间,单位  
  19. httpclient.config.timeToLive = 60  
八,测试

测试代码如下:

[java]  查看纯 文本  
  1. import  java.io.IOException;  
  2. import  java.util.concurrent.ExecutorService;  
  3. import  java.util.concurrent.Executors;  
  4.   
  5. import  javax.annotation.Resource;  
  6.   
  7. import  org.apache.http.Consts;  
  8. import  org.apache.http.ParseException;  
  9. import  org.apache.http.client.ClientProtocolException;  
  10. import  org.apache.http.client.methods.CloseableHttpResponse;  
  11. import  org.apache.http.client.methods.HttpGet;  
  12. import  org.apache.http.impl.client.CloseableHttpClient;  
  13. import  org.apache.http.util.EntityUtils;  
  14. import  org.junit.Test;  
  15. import  org.junit.runner.RunWith;  
  16. import  org.springframework.boot.test.context.SpringBootTest;  
  17. import  org.springframework.test.context.junit4.SpringRunner;  
  18.   
  19. @RunWith (SpringRunner。)  
  20. @SpringBootTest  
  21. 公共 HttpClientManagerFactoryBenTest {   
  22.         //注入HttpClient实例  
  23.        @Resource (name =  “httpClientManagerFactoryBen” )  
  24.     私人 CloseableHttpClient客户端;  
  25.       
  26.     @测试  
  27.     public void  test()  throws  ClientProtocolException,IOException,InterruptedException {   
  28.         ExecutorService service = Executors.newFixedThreadPool();  
  29.         for int  i = ; i < 10 ; i ++){  
  30.             service.submit(new  Runnable(){  
  31.                   
  32.                 @覆盖  
  33.                 public void  run(){   
  34.                     System.out.println(“当前线程是:” + Thread.currentThread()。getName());  
  35.                                         HttpEntity entity =  null ;  
  36.                                        尝试 {  
  37.                         HttpGet get =  new  HttpGet(“https:// localhost:8080 / testjson” );  
  38.                         //通过httpclient的执行提交请求,并用CloseableHttpResponse接受返回信息  
  39.                         CloseableHttpResponse response = client.execute(get);  
  40.                         System.out.println(“client object:” + client);  
  41.                                                 entity = response.getEntity();  
  42.                                                System.out.println(“============” + EntityUtils.toString(entity,Consts.UTF_8)+“=============” );  
  43.                                                 EntityUtils.consumeQuietly(实体); //释放连接  
  44.                                        }  catch  (ClientProtocolException e){  
  45.                         e.printStackTrace();  
  46.                     }  catch  (ParseException e){  
  47.                         e.printStackTrace();  
  48.                     }  catch  (IOException e){  
  49.                         e.printStackTrace();  
  50.                     }  finally {  
  51.                                                 if null  != entity){ //释放连接  
  52.                                EntityUtils.consumeQuietly(实体);  
  53.                               }  
  54.                                         }  
  55.                                 }  
  56.             });  
  57.         }  
  58.         Thread.sleep(60000 );  
  59.     }  
  60. }  
通过上面的几个步骤,就基本上完成了对HttpClient的的封装,如果需要更细致的话,可以按照上面的思路,逐步完善,将HttpClient的封装成HttpClientTemplate,因为CloseableHttpClient内部使用了回调机制,和JdbcTemplate的,或者是RedisTemplate类似,直到可以以spring boot starter的方式提供服务。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值