好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

查看更多关于SpringCloud Feign使用ApacheHttpClient代替默认client方式的详细内容...

  阅读:16次