一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - spring boot封裝HttpClient的示例代碼

spring boot封裝HttpClient的示例代碼

2021-03-23 13:22牛奮lch Java教程

這篇文章主要介紹了spring boot封裝HttpClient的示例代碼,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

最近使用到了HttpClient,看了一下官方文檔:HttpClient implementations are expected to be thread safe. It is recommended that the same instance of this class is reused for multiple request executions,翻譯過來的意思就是:HttpClient的實現是線程安全的,可以重用相同的實例來執行多次請求。遇到這種描述的話,我們就應該想到,需要對HttpClient來進行封裝了。由于是使用的spring boot,所以下面來結合spring boot來封裝HttpClient。

一、Request retry handler(請求重試處理)

為了使自定義異常機制生效,需要實現HttpRequestRetryHandler接口,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * 描述:HttpClient的重試處理機制
 */
@Configuration
public class MyhttpRequestRetryHandler {
 
  @Value("${httpclient.config.retryTime}")// 此處建議采用@ConfigurationProperties(prefix="httpclient.config")方式,方便復用
  private int retryTime;
   
  @Bean
  public HttpRequestRetryHandler httpRequestRetryHandler() {
    // 請求重試
    final int retryTime = this.retryTime;
    return new HttpRequestRetryHandler() {
      public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
        // Do not retry if over max retry count,如果重試次數超過了retryTime,則不再重試請求
        if (executionCount >= retryTime) {
          return false;
        }
        // 服務端斷掉客戶端的連接異常
        if (exception instanceof NoHttpResponseException) {
          return true;
        }
        // time out 超時重試
        if (exception instanceof InterruptedIOException) {
          return true;
        }
        // Unknown host
        if (exception instanceof UnknownHostException) {
          return false;
        }
        // Connection refused
        if (exception instanceof ConnectTimeoutException) {
          return false;
        }
        // SSL handshake exception
        if (exception instanceof SSLException) {
          return false;
        }
        HttpClientContext clientContext = HttpClientContext.adapt(context);
        HttpRequest request = clientContext.getRequest();
        if (!(request instanceof HttpEntityEnclosingRequest)) {
          return true;
        }
        return false;
      }
    };
  }
}

二、Pooling connection manager(連接池管理)

PoolingHttpClientConnectionManager用來管理客戶端的連接池,并且可以為多個線程的請求提供服務,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyPoolingHttpClientConnectionManager {
  /**
   * 連接池最大連接數
   */
  @Value("${httpclient.config.connMaxTotal}")
  private int connMaxTotal = 20;
   
  /**
   *
   */
  @Value("${httpclient.config.maxPerRoute}")
  private int maxPerRoute = 20;
 
    /**
   * 連接存活時間,單位為s
   */
   @Value("${httpclient.config.timeToLive}")
   private int timeToLive = 60;
 
    @Bean
  public PoolingHttpClientConnectionManager poolingClientConnectionManager(){
    PoolingHttpClientConnectionManager poolHttpcConnManager = new PoolingHttpClientConnectionManager(60, TimeUnit.SECONDS);
    // 最大連接數
    poolHttpcConnManager.setMaxTotal(this.connMaxTotal);
    // 路由基數
    poolHttpcConnManager.setDefaultMaxPerRoute(this.maxPerRoute);
    return poolHttpcConnManager;
  }
}

注意:當HttpClient實例不再需要并且即將超出范圍時,重要的是關閉其連接管理器,以確保管理器保持活動的所有連接都被關閉,并釋放由這些連接分配的系統資源

上面PoolingHttpClientConnectionManager類的構造函數如下:

?
1
2
3
4
5
6
7
8
9
10
public PoolingHttpClientConnectionManager(final long timeToLive, final TimeUnit tunit) {
    this(getDefaultRegistry(), null, null ,null, timeToLive, tunit);
  }
 
private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
    return RegistryBuilder.<ConnectionSocketFactory>create()
        .register("http", PlainConnectionSocketFactory.getSocketFactory())
        .register("https", SSLConnectionSocketFactory.getSocketFactory())
        .build();
  }

在PoolingHttpClientConnectionManager的配置中有兩個最大連接數量,分別控制著總的最大連接數量和每個route的最大連接數量。如果沒有顯式設置,默認每個route只允許最多2個connection,總的connection數量不超過20。這個值對于很多并發度高的應用來說是不夠的,必須根據實際的情況設置合適的值,思路和線程池的大小設置方式是類似的,如果所有的連接請求都是到同一個url,那可以把MaxPerRoute的值設置成和MaxTotal一致,這樣就能更高效地復用連接

特別注意:想要復用一個connection就必須要讓它占有的系統資源得到正確釋放,釋放方法如下:

如果是使用outputStream就要保證整個entity都被write out,如果是inputStream,則再最后要記得調用inputStream.close()。或者使用EntityUtils.consume(entity)或EntityUtils.consumeQuietly(entity)來讓entity被完全耗盡(后者不拋異常)來做這一工作。EntityUtils中有個toString方法也很方便的(調用這個方法最后也會自動把inputStream close掉的,但是在實際的測試過程中,會導致連接沒有釋放的現象),不過只有在可以確定收到的entity不是特別大的情況下才能使用。如果沒有讓整個entity被fully consumed,則該連接是不能被復用的,很快就會因為在連接池中取不到可用的連接超時或者阻塞在這里(因為該連接的狀態將會一直是leased的,即正在被使用的狀態)。所以如果想要復用connection,一定一定要記得把entity fully consume掉,只要檢測到stream的eof,是會自動調用ConnectionHolder的releaseConnection方法進行處理的

三、Connection keep alive strategy(保持連接策略)

HTTP規范沒有指定持久連接可能和應該保持存活多久。一些HTTP服務器使用非標準的Keep-Alive標頭來向客戶端通信它們打算在服務器端保持連接的時間段(以秒為單位)。HttpClient可以使用這些信息。如果響應中不存在Keep-Alive頭,HttpClient會假定連接可以無限期地保持活動。然而,一般使用的許多HTTP服務器都配置為在一段不活動狀態之后刪除持久連接,以便節省系統資源,而不會通知客戶端。如果默認策略過于樂觀,則可能需要提供自定義的保持活動策略,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpResponse;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 
/**
 * 描述:連接保持策略
 * @author chhliu
 */
@Configuration
public class MyconnectionKeepAliveStrategy {
   
  @Value("${httpclient.config.keepAliveTime}")
  private int keepAliveTime = 30;
   
  @Bean("connectionKeepAliveStrategy")
  public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
    return 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) {
            }
          }
        }
        return 30 * 1000;
      }
    };
  }
}

注意:長連接并不使用于所有的情況,尤其現在的系統,大都是部署在多臺服務器上,且具有負載均衡的功能,如果我們在訪問的時候,一直保持長連接,一旦那臺服務器掛了,就會影響客戶端,同時也不能充分的利用服務端的負載均衡的特性,反而短連接更有利一些,這些需要根據具體的需求來定,而不是一言概括。

四、HttpClient proxy configuration(代理配置)

用來配置代理,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.apache.http.HttpHost;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 描述:HttpClient代理
 * @author chhliu
 */
@Configuration
public class MyDefaultProxyRoutePlanner {
  // 代理的host地址
  @Value("${httpclient.config.proxyhost}")
  private String proxyHost;
   
  // 代理的端口號
  @Value("${httpclient.config.proxyPort}")
  private int proxyPort = 8080;
   
  @Bean
  public DefaultProxyRoutePlanner defaultProxyRoutePlanner(){
    HttpHost proxy = new HttpHost(this.proxyHost, this.proxyPort);
    return new DefaultProxyRoutePlanner(proxy);
  }
}

HttpClient不僅支持簡單的直連、復雜的路由策略以及代理。HttpRoutePlanner是基于http上下文情況下,客戶端到服務器的路由計算策略,一般沒有代理的話,就不用設置這個東西。這里有一個很關鍵的概念—Route:在HttpClient中,一個Route指 運行環境機器->目標機器host的一條線路,也就是如果目標url的host是同一個,那么它們的route也是一樣的

五、RequestConfig

用來設置請求的各種配置,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.apache.http.client.config.RequestConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 
@Configuration
public class MyRequestConfig {
  @Value("${httpclient.config.connectTimeout}")
  private int connectTimeout = 2000;
   
  @Value("${httpclient.config.connectRequestTimeout}")
  private int connectRequestTimeout = 2000;
   
  @Value("${httpclient.config.socketTimeout}")
  private int socketTimeout = 2000;
  @Bean
  public RequestConfig config(){
    return RequestConfig.custom()
        .setConnectionRequestTimeout(this.connectRequestTimeout)
        .setConnectTimeout(this.connectTimeout)
        .setSocketTimeout(this.socketTimeout)
        .build();
  }
}

RequestConfig是對request的一些配置。里面比較重要的有三個超時時間,默認的情況下這三個超時時間都為0(如果不設置request的Config,會在execute的過程中使用HttpClientParamConfig的getRequestConfig中用默認參數進行設置),這也就意味著無限等待,很容易導致所有的請求阻塞在這個地方無限期等待。這三個超時時間為:

a、connectionRequestTimeout—從連接池中取連接的超時時間

這個時間定義的是從ConnectionManager管理的連接池中取出連接的超時時間, 如果連接池中沒有可用的連接,則request會被阻塞,最長等待connectionRequestTimeout的時間,如果還沒有被服務,則拋出ConnectionPoolTimeoutException異常,不繼續等待。

b、connectTimeout—連接超時時間

這個時間定義了通過網絡與服務器建立連接的超時時間,也就是取得了連接池中的某個連接之后到接通目標url的連接等待時間。發生超時,會拋出ConnectionTimeoutException異常。

c、socketTimeout—請求超時時間

這個時間定義了socket讀數據的超時時間,也就是連接到服務器之后到從服務器獲取響應數據需要等待的時間,或者說是連接上一個url之后到獲取response的返回等待時間。發生超時,會拋出SocketTimeoutException異常。

六、實例化HttpClient

通過實現FactoryBean來實例化HttpClient,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 
/**
 * 描述:HttpClient客戶端封裝
 */
@Service("httpClientManagerFactoryBen")
public class HttpClientManagerFactoryBen implements FactoryBean<CloseableHttpClient>, InitializingBean, DisposableBean {
 
  /**
   * FactoryBean生成的目標對象
   */
  private CloseableHttpClient client;
   
  @Autowired
  private ConnectionKeepAliveStrategy connectionKeepAliveStrategy;
   
  @Autowired
  private HttpRequestRetryHandler httpRequestRetryHandler;
   
  @Autowired
  private DefaultProxyRoutePlanner proxyRoutePlanner;
   
  @Autowired
  private PoolingHttpClientConnectionManager poolHttpcConnManager;
   
  @Autowired
  private RequestConfig config;
   
    // 銷毀上下文時,銷毀HttpClient實例
  @Override
  public void destroy() throws Exception {
         /*
      * 調用httpClient.close()會先shut down connection manager,然后再釋放該HttpClient所占用的所有資源,
      * 關閉所有在使用或者空閑的connection包括底層socket。由于這里把它所使用的connection manager關閉了,
      * 所以在下次還要進行http請求的時候,要重新new一個connection manager來build一個HttpClient,
      * 也就是在需要關閉和新建Client的情況下,connection manager不能是單例的.
      */
        if(null != this.client){
      this.client.close();
      }
  }
 
  @Override// 初始化實例
  public void afterPropertiesSet() throws Exception {
         /*
     * 建議此處使用HttpClients.custom的方式來創建HttpClientBuilder,而不要使用HttpClientBuilder.create()方法來創建HttpClientBuilder
     * 從官方文檔可以得出,HttpClientBuilder是非線程安全的,但是HttpClients確實Immutable的,immutable 對象不僅能夠保證對象的狀態不被改變,
     * 而且還可以不使用鎖機制就能被其他線程共享
     */
         this.client = HttpClients.custom().setConnectionManager(poolHttpcConnManager)
        .setRetryHandler(httpRequestRetryHandler)
        .setKeepAliveStrategy(connectionKeepAliveStrategy)
        .setRoutePlanner(proxyRoutePlanner)
        .setDefaultRequestConfig(config)
        .build();
  }
 
    // 返回實例的類型
  @Override
  public CloseableHttpClient getObject() throws Exception {
    return this.client;
  }
 
  @Override
  public Class<?> getObjectType() {
    return (this.client == null ? CloseableHttpClient.class : this.client.getClass());
  }
 
    // 構建的實例為單例
  @Override
  public boolean isSingleton() {
    return true;
  }
 
}

七、增加配置文件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 代理的host
httpclient.config.proxyhost=xxx.xx.xx.xx
# 代理端口
httpclient.config.proxyPort=8080
# 連接超時或異常重試次數
httpclient.config.retryTime=3
# 長連接保持時間,單位為s
httpclient.config.keepAliveTime=30
# 連接池最大連接數
httpclient.config.connMaxTotal=20
httpclient.config.maxPerRoute=20
# 連接超時時間,單位ms
httpclient.config.connectTimeout=2000
# 請求超時時間
httpclient.config.connectRequestTimeout=2000
# sock超時時間
httpclient.config.socketTimeout=2000
# 連接存活時間,單位s
httpclient.config.timeToLive=60

八、測試

測試代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import javax.annotation.Resource;
 
import org.apache.http.Consts;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpClientManagerFactoryBenTest {
    // 注入HttpClient實例
    @Resource(name = "httpClientManagerFactoryBen")
  private CloseableHttpClient client;
   
  @Test
  public void test() throws ClientProtocolException, IOException, InterruptedException{
    ExecutorService service = Executors.newFixedThreadPool(2);
    for(int i=0; i<10; i++){
      service.submit(new Runnable() {
         
        @Override
        public void run() {
          System.out.println("the current thread is:"+Thread.currentThread().getName());
                    HttpEntity entity = null;
                    try {
            HttpGet get = new HttpGet("https://localhost:8080/testjson");
            // 通過httpclient的execute提交 請求 ,并用CloseableHttpResponse接受返回信息
            CloseableHttpResponse response = client.execute(get);
            System.out.println("client object:"+client);
                        entity = response.getEntity();
                        System.out.println("============"+EntityUtils.toString(entity, Consts.UTF_8)+"=============");
                        EntityUtils.consumeQuietly(entity);// 釋放連接
                    } catch (ClientProtocolException e) {
            e.printStackTrace();
          } catch (ParseException e) {
            e.printStackTrace();
          } catch (IOException e) {
            e.printStackTrace();
          } finally{
                        if(null != entity){// 釋放連接
                EntityUtils.consumeQuietly(entity);
               }
                    }
                }
      });
    }
    Thread.sleep(60000);
  }
}

通過上面的幾個步驟,就基本上完成了對HttpClient的封裝,如果需要更細致的話,可以按照上面的思路,逐步完善,將HttpClient封裝成HttpClientTemplate,因為CloseableHttpClient內部使用了回調機制,和JdbcTemplate,或者是RedisTemplate類似,直到可以以spring boot starter的方式提供服務。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://blog.csdn.net/liuchuanhong1/article/details/68194036

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产欧美日韩一区二区三区在线 | 亚洲国产在 | 欧美一级片免费看 | 午夜伦伦电影理论片大片 | 国产精品国产色综合色 | 四虎网址在线 | 久久九九久精品国产尤物 | 翁熄性放纵交换300章 | 成年女人毛片免费观看中文w | 99免费看| 视频一区在线观看 | 国产拍拍视频一二三四区 | 我的奶头被客人吸的又肿又红 | 亚洲色图欧美图片 | 亚洲天天做夜夜做天天欢 | 国产一区二区视频免费 | 国产日韩精品一区二区在线观看 | 午夜福利体验免费体验区 | 青草久久精品亚洲综合专区 | 日本连裤袜xxxxx在线视频 | 国产精品不卡 | 亚洲精品卡1卡二卡3卡四卡 | 国精视频一区二区视频 | 日本中文字幕在线视频 | 亚洲2017天堂色无码 | 青草视频久久 | 法国女佣系列在线播放 | 国产综合色在线视频区色吧图片 | 极品蜜桃臀美女啪啪 | 双性肉文h | 男人资源站 | 欧美精品亚洲精品日韩专区va | 成人精品一区二区三区 | 精品一区二区三区色花堂 | 精品国产一区二区三区久久影院 | 亚洲精品一线二线三线 | 蝴蝶传媒免费安装 | 我和么公的秘密小说免费 | 欧美bbb人妖 | bt天堂午夜国产精品 | 亚洲天堂中文 |