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

网友投稿 377 2022-08-22


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

目录使用ApacheHttpClient代替默认clientApacheHttpClient和默认实现的比较ApacheHttpClient使用apache的HttpClient默认重试机制maven异常重试logRetryExecDefaultHttpRequestRetryHandler

使用ApacheHttpClient代替默认client

ApacheHttpClient和默认实现的比较

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

ApacheHttpClient 使用

maven 依赖

org.springframework.cloud

spring-cloud-starter-openfeign

org.apache.httpcomponents

httpclient

4.5.7

io.github.openfeign

feign-httpclient

10.1.0

配置文件的修改

feign:

httpclient:

enabled: true

创建ApacheHttpClient客户端

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 (!NnsIJQOvenabled) {

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 FeignClientBuilderBuildeNnsIJQOvr 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

org.apache.httpcomponents

httpclient

4.5.2

异常重试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 respond2017-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

/**

* 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 http://re-executed.

*

* Further responsibilities such as communication with the opposite

* endpoint is delegated to the next executor in the request execution

* chain.

*

*

* @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

/**

* 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> 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> clazzes) {

super();

this.retryCount = retryCount;

this.requestSentRetryEnabled = requestSentRetryEnabled;

this.nonRetriableClasses = new HashSet>();

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:

*

*

*

*

*

*

* @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:

*

*

*

*

*

*

*/

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或其他异常


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python 爬虫 爬取网易云音乐(python入门教程(非常详细))
下一篇:conda安装报错:PermissionError [Errno 13] Permission denied(conda重新安装)
相关文章