服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - SpringCloud Feign使用ApacheHttpClient代替默认client方式

SpringCloud Feign使用ApacheHttpClient代替默认client方式

2022-09-05 13:43过河的小卒子 Java教程

这篇文章主要介绍了SpringCloud Feign使用ApacheHttpClient代替默认client方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

使用ApacheHttpClient代替默认client

ApacheHttpClient和默认实现的比较

  • Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection。
  • ApacheHttpClient实现了连接池,同时它封装了访问http的请求头,参数,内容体,响应等等,使客户端发送 HTTP 请求变得容易。

ApacheHttpClient 使用

maven 依赖

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.7</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
        <version>10.1.0</version>
    </dependency>

配置文件的修改

?
1
2
3
feign:
  httpclient:
    enabled: true

创建ApacheHttpClient客户端

?
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import javax.net.ssl.SSLContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.springframework.util.ResourceUtils;
import feign.httpclient.ApacheHttpClient;
@Slf4j
public class FeignClientBuilder {
  private boolean enabled;
  private String keyPassword;
  private String keyStore;
  private String keyStorePassword;
  private String trustStore;
  private String trustStorePassword;
  private int maxConnTotal = 2048;
  private int maxConnPerRoute = 512;
  public FeignClientBuilder(boolean enabled, String keyPassword, String keyStore, String keyStorePassword, String trustStore, String trustStorePassword, int maxConnTotal, int maxConnPerRoute) {
    this.enabled = enabled;
    this.keyPassword = keyPassword;
    this.keyStore = keyStore;
    this.keyStorePassword = keyStorePassword;
    this.trustStore = trustStore;
    this.trustStorePassword = trustStorePassword;
    /**
     * maxConnTotal是同时间正在使用的最多的连接数
     */
    this.maxConnTotal = maxConnTotal;
    /**
     * maxConnPerRoute是针对一个域名同时间正在使用的最多的连接数
     */
    this.maxConnPerRoute = maxConnPerRoute;
  }
  public ApacheHttpClient apacheHttpClient() {
    CloseableHttpClient defaultHttpClient = HttpClients.custom()
            .setMaxConnTotal(maxConnTotal)
            .setMaxConnPerRoute(maxConnPerRoute)
            .build();
    ApacheHttpClient defaultApacheHttpClient = new ApacheHttpClient(defaultHttpClient);
    if (!enabled) {
      return defaultApacheHttpClient;
    }
    SSLContextBuilder sslContextBuilder = SSLContexts.custom();
    // 如果 服务端启用了 TLS 客户端验证,则需要指定 keyStore
    if (keyStore == null || keyStore.isEmpty()) {
      return new ApacheHttpClient();
    } else {
      try {
        sslContextBuilder
                .loadKeyMaterial(
                        ResourceUtils.getFile(keyStore),
                        keyStorePassword.toCharArray(),
                        keyPassword.toCharArray());
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    // 如果 https 使用自签名证书,则需要指定 trustStore
    if (trustStore == null || trustStore.isEmpty()) {
    } else {
      try {
        sslContextBuilder
//        .loadTrustMaterial(TrustAllStrategy.INSTANCE)
                .loadTrustMaterial(
                        ResourceUtils.getFile(trustStore),
                        trustStorePassword.toCharArray()
                );
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    try {
      SSLContext sslContext = sslContextBuilder.build();
      SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
              sslContext,
              SSLConnectionSocketFactory.getDefaultHostnameVerifier());
      CloseableHttpClient httpClient = HttpClients.custom()
              .setMaxConnTotal(maxConnTotal)
              .setMaxConnPerRoute(maxConnPerRoute)
              .setSSLSocketFactory(sslsf)
              .build();
      ApacheHttpClient apacheHttpClient = new ApacheHttpClient(httpClient);
      log.info("feign Client load with ssl.");
      return apacheHttpClient;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return defaultApacheHttpClient;
  }
  public static FeignClientBuilderBuilder builder() {
    return new FeignClientBuilderBuilder();
  }
  public static class FeignClientBuilderBuilder {
    private boolean enabled;
    private String keyPassword;
    private String keyStore;
    private String keyStorePassword;
    private String trustStore;
    private String trustStorePassword;
    private int maxConnTotal = 2048;
    private int maxConnPerRoute = 512;
    public FeignClientBuilderBuilder enabled(boolean enabled) {
      this.enabled = enabled;
      return this;
    }
    public FeignClientBuilderBuilder keyPassword(String keyPassword) {
      this.keyPassword = keyPassword;
      return this;
    }
    public FeignClientBuilderBuilder keyStore(String keyStore) {
      this.keyStore = keyStore;
      return this;
    }
    public FeignClientBuilderBuilder keyStorePassword(String keyStorePassword) {
      this.keyStorePassword = keyStorePassword;
      return this;
    }
    public FeignClientBuilderBuilder trustStore(String trustStore) {
      this.trustStore = trustStore;
      return this;
    }
    public FeignClientBuilderBuilder trustStorePassword(String trustStorePassword) {
      this.trustStorePassword = trustStorePassword;
      return this;
    }
    public FeignClientBuilderBuilder maxConnTotal(int maxConnTotal) {
      this.maxConnTotal = maxConnTotal;
      return this;
    }
    public FeignClientBuilderBuilder maxConnPerRoute(int maxConnPerRoute) {
      this.maxConnPerRoute = maxConnPerRoute;
      return this;
    }
    public FeignClientBuilder build() {
      return new FeignClientBuilder(
              this.enabled,
              this.keyPassword,
              this.keyStore,
              this.keyStorePassword,
              this.trustStore,
              this.trustStorePassword,
              this.maxConnTotal,
              this.maxConnPerRoute
      );
    }
  }
}

使用时可以直接使用builder来创建ApacheHttpClient。

apache的HttpClient默认重试机制

maven

?
1
2
3
4
5
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>

异常重试log

2017-01-31 19:31:39.057  INFO 3873 --- [askScheduler-13] o.apache.http.impl.execchain.RetryExec   : I/O exception (org.apache.http.NoHttpResponseException) caught when processing request to {}->http://192.168.99.100:8080: The target server failed to respond
2017-01-31 19:31:39.058  INFO 3873 --- [askScheduler-13] o.apache.http.impl.execchain.RetryExec   : Retrying request to {}->http://192.168.99.100:8080

RetryExec

org/apache/http/impl/execchain/RetryExec.java

?
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
/**
 * Request executor in the request execution chain that is responsible
 * for making a decision whether a request failed due to an I/O error
 * should be re-executed.
 * <p>
 * Further responsibilities such as communication with the opposite
 * endpoint is delegated to the next executor in the request execution
 * chain.
 * </p>
 *
 * @since 4.3
 */
@Immutable
public class RetryExec implements ClientExecChain {
    private final Log log = LogFactory.getLog(getClass());
    private final ClientExecChain requestExecutor;
    private final HttpRequestRetryHandler retryHandler;
    public RetryExec(
            final ClientExecChain requestExecutor,
            final HttpRequestRetryHandler retryHandler) {
        Args.notNull(requestExecutor, "HTTP request executor");
        Args.notNull(retryHandler, "HTTP request retry handler");
        this.requestExecutor = requestExecutor;
        this.retryHandler = retryHandler;
    }
    @Override
    public CloseableHttpResponse execute(
            final HttpRoute route,
            final HttpRequestWrapper request,
            final HttpClientContext context,
            final HttpExecutionAware execAware) throws IOException, HttpException {
        Args.notNull(route, "HTTP route");
        Args.notNull(request, "HTTP request");
        Args.notNull(context, "HTTP context");
        final Header[] origheaders = request.getAllHeaders();
        for (int execCount = 1;; execCount++) {
            try {
                return this.requestExecutor.execute(route, request, context, execAware);
            } catch (final IOException ex) {
                if (execAware != null && execAware.isAborted()) {
                    this.log.debug("Request has been aborted");
                    throw ex;
                }
                if (retryHandler.retryRequest(ex, execCount, context)) {
                    if (this.log.isInfoEnabled()) {
                        this.log.info("I/O exception ("+ ex.getClass().getName() +
                                ") caught when processing request to "
                                + route +
                                ": "
                                + ex.getMessage());
                    }
                    if (this.log.isDebugEnabled()) {
                        this.log.debug(ex.getMessage(), ex);
                    }
                    if (!RequestEntityProxy.isRepeatable(request)) {
                        this.log.debug("Cannot retry non-repeatable request");
                        throw new NonRepeatableRequestException("Cannot retry request " +
                                "with a non-repeatable request entity", ex);
                    }
                    request.setHeaders(origheaders);
                    if (this.log.isInfoEnabled()) {
                        this.log.info("Retrying request to " + route);
                    }
                } else {
                    if (ex instanceof NoHttpResponseException) {
                        final NoHttpResponseException updatedex = new NoHttpResponseException(
                                route.getTargetHost().toHostString() + " failed to respond");
                        updatedex.setStackTrace(ex.getStackTrace());
                        throw updatedex;
                    } else {
                        throw ex;
                    }
                }
            }
        }
    }
}

DefaultHttpRequestRetryHandler

org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java

?
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
 * The default {@link HttpRequestRetryHandler} used by request executors.
 *
 * @since 4.0
 */
@Immutable
public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {
    public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();
    /** the number of times a method will be retried */
    private final int retryCount;
    /** Whether or not methods that have successfully sent their request will be retried */
    private final boolean requestSentRetryEnabled;
    private final Set<Class<? extends IOException>> nonRetriableClasses;
    /**
     * Create the request retry handler using the specified IOException classes
     *
     * @param retryCount how many times to retry; 0 means no retries
     * @param requestSentRetryEnabled true if it's OK to retry requests that have been sent
     * @param clazzes the IOException types that should not be retried
     * @since 4.3
     */
    protected DefaultHttpRequestRetryHandler(
            final int retryCount,
            final boolean requestSentRetryEnabled,
            final Collection<Class<? extends IOException>> clazzes) {
        super();
        this.retryCount = retryCount;
        this.requestSentRetryEnabled = requestSentRetryEnabled;
        this.nonRetriableClasses = new HashSet<Class<? extends IOException>>();
        for (final Class<? extends IOException> clazz: clazzes) {
            this.nonRetriableClasses.add(clazz);
        }
    }
    /**
     * Create the request retry handler using the following list of
     * non-retriable IOException classes: <br>
     * <ul>
     * <li>InterruptedIOException</li>
     * <li>UnknownHostException</li>
     * <li>ConnectException</li>
     * <li>SSLException</li>
     * </ul>
     * @param retryCount how many times to retry; 0 means no retries
     * @param requestSentRetryEnabled true if it's OK to retry non-idempotent requests that have been sent
     */
    @SuppressWarnings("unchecked")
    public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) {
        this(retryCount, requestSentRetryEnabled, Arrays.asList(
                InterruptedIOException.class,
                UnknownHostException.class,
                ConnectException.class,
                SSLException.class));
    }
    /**
     * Create the request retry handler with a retry count of 3, requestSentRetryEnabled false
     * and using the following list of non-retriable IOException classes: <br>
     * <ul>
     * <li>InterruptedIOException</li>
     * <li>UnknownHostException</li>
     * <li>ConnectException</li>
     * <li>SSLException</li>
     * </ul>
     */
    public DefaultHttpRequestRetryHandler() {
        this(3, false);
    }
    /**
     * Used {@code retryCount} and {@code requestSentRetryEnabled} to determine
     * if the given method should be retried.
     */
    @Override
    public boolean retryRequest(
            final IOException exception,
            final int executionCount,
            final HttpContext context) {
        Args.notNull(exception, "Exception parameter");
        Args.notNull(context, "HTTP context");
        if (executionCount > this.retryCount) {
            // Do not retry if over max retry count
            return false;
        }
        if (this.nonRetriableClasses.contains(exception.getClass())) {
            return false;
        } else {
            for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {
                if (rejectException.isInstance(exception)) {
                    return false;
                }
            }
        }
        final HttpClientContext clientContext = HttpClientContext.adapt(context);
        final HttpRequest request = clientContext.getRequest();
        if(requestIsAborted(request)){
            return false;
        }
        if (handleAsIdempotent(request)) {
            // Retry if the request is considered idempotent
            return true;
        }
        if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) {
            // Retry if the request has not been sent fully or
            // if it's OK to retry methods that have been sent
            return true;
        }
        // otherwise do not retry
        return false;
    }
    /**
     * @return {@code true} if this handler will retry methods that have
     * successfully sent their request, {@code false} otherwise
     */
    public boolean isRequestSentRetryEnabled() {
        return requestSentRetryEnabled;
    }
    /**
     * @return the maximum number of times a method will be retried
     */
    public int getRetryCount() {
        return retryCount;
    }
    /**
     * @since 4.2
     */
    protected boolean handleAsIdempotent(final HttpRequest request) {
        return !(request instanceof HttpEntityEnclosingRequest);
    }
    /**
     * @since 4.2
     *
     * @deprecated (4.3)
     */
    @Deprecated
    protected boolean requestIsAborted(final HttpRequest request) {
        HttpRequest req = request;
        if (request instanceof RequestWrapper) { // does not forward request to original
            req = ((RequestWrapper) request).getOriginal();
        }
        return (req instanceof HttpUriRequest && ((HttpUriRequest)req).isAborted());
    }
}

默认重试3次,三次都失败则抛出NoHttpResponseException或其他异常

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/TP89757/article/details/108881301

延伸 · 阅读

精彩推荐
  • Java教程浅谈Spring 重定向指南

    浅谈Spring 重定向指南

    本篇文章主要介绍了浅谈Spring 重定向指南,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    yunfound11402021-01-24
  • Java教程JVM的垃圾回收机制真是通俗易懂

    JVM的垃圾回收机制真是通俗易懂

    这篇文章主要为大家详细介绍了JVM的垃圾回收机制,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你...

    “来都让一让”6272022-08-04
  • Java教程Springboot整合PageOffice 实现word在线编辑保存功能

    Springboot整合PageOffice 实现word在线编辑保存功能

    这篇文章主要介绍了Springboot整合PageOffice 实现word在线编辑保存,本文以Samples5 为示例文件结合示例代码给大家详细介绍,需要的朋友可以参考下...

    悲雨叹风8272021-11-14
  • Java教程SpringBoot事务使用及回滚实现代码详解

    SpringBoot事务使用及回滚实现代码详解

    这篇文章主要介绍了SpringBoot事务使用及回滚实现代码详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友...

    风中飘码6452020-08-03
  • Java教程java实现excel和txt文件互转

    java实现excel和txt文件互转

    本篇文章主要介绍了java实现excel和txt文件互转的相关知识。具有很好的参考价值。下面跟着小编一起来看下吧...

    arocky5852020-09-20
  • Java教程关于java中Map的九大问题分析

    关于java中Map的九大问题分析

    这篇文章主要为大家详细分析了关于java中Map的九大问题,感兴趣的小伙伴们可以参考一下 ...

    java教程网3152020-06-03
  • Java教程Java中正则表达式的使用和详解(下)

    Java中正则表达式的使用和详解(下)

    这篇文章主要介绍了Java正则表达式的使用和详解(下)的相关资料,包括常用正则表达式和正则表达式语法,非常不错,具有参考借鉴价值,需要的的朋友参...

    落叶的博客2402020-09-20
  • Java教程Java数据结构顺序表用法详解

    Java数据结构顺序表用法详解

    顺序表是计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑...

    执梗11722022-02-23