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