diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 110fb6a9..0cd34756 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -1479,6 +1479,20 @@ The default HTTP client used by zuul is now backed by the Apache HTTP Client ins deprecated Ribbon `RestClient`. To use `RestClient` or to use the `okhttp3.OkHttpClient` set `ribbon.restclient.enabled=true` or `ribbon.okhttp.enabled=true` respectively. +==== Retrying Failed Requests + +When using the Apache Http Client or the OK HTTP Client you can enable them to automatically +retry failed requests by adding https://github.com/spring-projects/spring-retry[Spring Retry] +to your application's classpath. When Zuul uses Ribbon, Zuul will honor some of the Ribbon +configuration values related to retrying failed requests. The properties you can use are +`client.ribbon.MaxAutoRetries`, `client.ribbon.MaxAutoRetriesNextServer`, and +`client.ribbon.OkToRetryOnAllOperations`. See the https://github.com/Netflix/ribbon/wiki/Getting-Started#the-properties-file-sample-clientproperties[Ribbon documentation] +for a description of what there properties do. + +You can turn off Zuul's retry functionality by setting `zuul.retryable` to `false`. You +can also disable retry functionality on route by route basis by setting +`zuul.routes.routename.retryable` to `false`. + === Cookies and Sensitive Headers It's OK to share headers between services in the same system, but you diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java index 4998363e..a0772078 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java @@ -28,9 +28,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration; @@ -38,11 +38,9 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFact import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; -import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.RestTemplate; @@ -90,6 +88,12 @@ public class RibbonAutoConfiguration { return new RibbonLoadBalancedRetryPolicyFactory(clientFactory); } + @Bean + @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate") + public LoadBalancedRetryPolicyFactory neverRetryPolicyFactory() { + return new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); + } + @Bean @ConditionalOnMissingBean public PropertiesFactory propertiesFactory() { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java index 62cc05a4..402b6f06 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java @@ -26,11 +26,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; +import org.springframework.cloud.netflix.ribbon.okhttp.RetryableOkHttpLoadBalancingClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @@ -125,11 +128,26 @@ public class RibbonClientConfiguration { @Bean @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) + @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate") public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient( + IClientConfig config, ServerIntrospector serverIntrospector, + ILoadBalancer loadBalancer, RetryHandler retryHandler) { + RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient( + config, serverIntrospector); + client.setLoadBalancer(loadBalancer); + client.setRetryHandler(retryHandler); + Monitors.registerObject("Client_" + this.name, client); + return client; + } + + @Bean + @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) + @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") + public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient( IClientConfig config, ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, RetryHandler retryHandler, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { - RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient( + RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient( config, serverIntrospector, loadBalancedRetryPolicyFactory); client.setLoadBalancer(loadBalancer); client.setRetryHandler(retryHandler); @@ -145,18 +163,37 @@ public class RibbonClientConfiguration { @Value("${ribbon.client.name}") private String name = "client"; + + @Bean @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) - public OkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config, - ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, - RetryHandler retryHandler, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { - OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(config, + @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") + public RetryableOkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config, + ServerIntrospector serverIntrospector, + ILoadBalancer loadBalancer, + RetryHandler retryHandler, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(config, serverIntrospector, loadBalancedRetryPolicyFactory); client.setLoadBalancer(loadBalancer); client.setRetryHandler(retryHandler); Monitors.registerObject("Client_" + this.name, client); return client; } + + @Bean + @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) + @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate") + public OkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient(IClientConfig config, + ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, + RetryHandler retryHandler) { + OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(config, + serverIntrospector); + client.setLoadBalancer(loadBalancer); + client.setRetryHandler(retryHandler); + Monitors.registerObject("Client_" + this.name, client); + return client; + } } @Configuration diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java new file mode 100644 index 00000000..92b080b4 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java @@ -0,0 +1,123 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.netflix.ribbon.apache; + +import java.io.IOException; +import java.net.URI; +import org.apache.commons.lang.BooleanUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpUriRequest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; +import org.springframework.cloud.netflix.feign.ribbon.FeignRetryPolicy; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; +import org.springframework.http.HttpRequest; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.policy.NeverRetryPolicy; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import com.netflix.client.RequestSpecificRetryHandler; +import com.netflix.client.RetryHandler; +import com.netflix.client.config.CommonClientConfigKey; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.Server; + +/** + * An Apache HTTP client which leverages Spring Retry to retry failed requests. + * @author Ryan Baxter + */ +public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingHttpClient implements ServiceInstanceChooser { + private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = + new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); + public RetryableRibbonLoadBalancingHttpClient(IClientConfig config, ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + super(config, serverIntrospector); + this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; + } + + @Override + public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception { + final RequestConfig.Builder builder = RequestConfig.custom(); + IClientConfig config = configOverride != null ? configOverride : this.config; + builder.setConnectTimeout(config.get( + CommonClientConfigKey.ConnectTimeout, this.connectTimeout)); + builder.setSocketTimeout(config.get( + CommonClientConfigKey.ReadTimeout, this.readTimeout)); + builder.setRedirectsEnabled(config.get( + CommonClientConfigKey.FollowRedirects, this.followRedirects)); + + final RequestConfig requestConfig = builder.build(); + return this.executeWithRetry(request, new RetryCallback() { + @Override + public RibbonApacheHttpResponse doWithRetry(RetryContext context) throws Exception { + //on retries the policy will choose the server and set it in the context + //extract the server and update the request being made + RibbonApacheHttpRequest newRequest = request; + if(context instanceof LoadBalancedRetryContext) { + ServiceInstance service = ((LoadBalancedRetryContext)context).getServiceInstance(); + if(service != null) { + //Reconstruct the request URI using the host and port set in the retry context + newRequest = newRequest.withNewUri(new URI(service.getUri().getScheme(), + newRequest.getURI().getUserInfo(), service.getHost(), service.getPort(), + newRequest.getURI().getPath(), newRequest.getURI().getQuery(), + newRequest.getURI().getFragment())); + } + } + if (isSecure(configOverride)) { + final URI secureUri = UriComponentsBuilder.fromUri(newRequest.getUri()) + .scheme("https").build().toUri(); + newRequest = newRequest.withNewUri(secureUri); + } + HttpUriRequest httpUriRequest = newRequest.toRequest(requestConfig); + final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest); + return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI()); + } + }); + } + + private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, RetryCallback callback) throws Exception { + LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this); + RetryTemplate retryTemplate = new RetryTemplate(); + boolean retryable = request.getContext() == null ? true : + BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true); + retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy() + : new RetryPolicy(request, retryPolicy, this, this.getClientName())); + return retryTemplate.execute(callback); + } + + @Override + public ServiceInstance choose(String serviceId) { + Server server = this.getLoadBalancer().chooseServer(serviceId); + return new RibbonLoadBalancerClient.RibbonServer(serviceId, + server); + } + + @Override + public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) { + return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null); + } + + static class RetryPolicy extends FeignRetryPolicy { + public RetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy, ServiceInstanceChooser serviceInstanceChooser, String serviceName) { + super(request, policy, serviceInstanceChooser, serviceName); + } + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java index 786d19d1..73ad0e8e 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java @@ -27,14 +27,8 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.HttpClientBuilder; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient; -import org.springframework.cloud.netflix.ribbon.support.RetryableLoadBalancingClient; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; @@ -46,8 +40,8 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps * @author Ryan Baxter */ //TODO: rename (ie new class that extends this in Dalston) to ApacheHttpLoadBalancingClient -public class RibbonLoadBalancingHttpClient - extends RetryableLoadBalancingClient { +public class RibbonLoadBalancingHttpClient extends + AbstractLoadBalancingClient { @Deprecated public RibbonLoadBalancingHttpClient() { @@ -67,11 +61,6 @@ public class RibbonLoadBalancingHttpClient super(delegate, config, serverIntrospector); } - public RibbonLoadBalancingHttpClient(IClientConfig iClientConfig, ServerIntrospector serverIntrospector, - LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { - super(iClientConfig, serverIntrospector, loadBalancedRetryPolicyFactory); - } - protected HttpClient createDelegate(IClientConfig config) { return HttpClientBuilder.create() // already defaults to 0 in builder, so resetting to 0 won't hurt @@ -84,7 +73,7 @@ public class RibbonLoadBalancingHttpClient } @Override - public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, + public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception { final RequestConfig.Builder builder = RequestConfig.custom(); IClientConfig config = configOverride != null ? configOverride : this.config; @@ -96,32 +85,14 @@ public class RibbonLoadBalancingHttpClient CommonClientConfigKey.FollowRedirects, this.followRedirects)); final RequestConfig requestConfig = builder.build(); - return this.executeWithRetry(request, new RetryCallback() { - @Override - public RibbonApacheHttpResponse doWithRetry(RetryContext context) throws Exception { - //on retries the policy will choose the server and set it in the context - //extract the server and update the request being made - RibbonApacheHttpRequest newRequest = request; - if(context instanceof LoadBalancedRetryContext) { - ServiceInstance service = ((LoadBalancedRetryContext)context).getServiceInstance(); - if(service != null) { - //Reconstruct the request URI using the host and port set in the retry context - newRequest = newRequest.withNewUri(new URI(service.getUri().getScheme(), - newRequest.getURI().getUserInfo(), service.getHost(), service.getPort(), - newRequest.getURI().getPath(), newRequest.getURI().getQuery(), - newRequest.getURI().getFragment())); - } - } - if (isSecure(configOverride)) { - final URI secureUri = UriComponentsBuilder.fromUri(newRequest.getUri()) - .scheme("https").build().toUri(); - newRequest = newRequest.withNewUri(secureUri); - } - HttpUriRequest httpUriRequest = newRequest.toRequest(requestConfig); - final HttpResponse httpResponse = RibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest); - return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI()); - } - }); + if (isSecure(configOverride)) { + final URI secureUri = UriComponentsBuilder.fromUri(request.getUri()) + .scheme("https").build().toUri(); + request = request.withNewUri(secureUri); + } + final HttpUriRequest httpUriRequest = request.toRequest(requestConfig); + final HttpResponse httpResponse = this.delegate.execute(httpUriRequest); + return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI()); } @Override @@ -129,4 +100,9 @@ public class RibbonLoadBalancingHttpClient URI uri = updateToHttpsIfNeeded(original, this.config, this.serverIntrospector, server); return super.reconstructURIWithServer(server, uri); } + + @Override + public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) { + return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null); + } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpLoadBalancingClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpLoadBalancingClient.java index 1179e149..70d7eeb3 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpLoadBalancingClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/OkHttpLoadBalancingClient.java @@ -19,13 +19,8 @@ package org.springframework.cloud.netflix.ribbon.okhttp; import java.net.URI; import java.util.concurrent.TimeUnit; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; -import org.springframework.cloud.netflix.ribbon.support.RetryableLoadBalancingClient; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; +import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient; import org.springframework.web.util.UriComponentsBuilder; import com.netflix.client.config.CommonClientConfigKey; @@ -44,7 +39,7 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps * @author Ryan Baxter */ public class OkHttpLoadBalancingClient - extends RetryableLoadBalancingClient { + extends AbstractLoadBalancingClient { @Deprecated public OkHttpLoadBalancingClient() { @@ -61,12 +56,6 @@ public class OkHttpLoadBalancingClient super(config, serverIntrospector); } - public OkHttpLoadBalancingClient(IClientConfig config, - ServerIntrospector serverIntrospector, - LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { - super(config, serverIntrospector, loadBalancedRetryPolicyFactory); - } - public OkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector) { super(delegate, config, serverIntrospector); @@ -78,36 +67,19 @@ public class OkHttpLoadBalancingClient } @Override - public OkHttpRibbonResponse execute(final OkHttpRibbonRequest ribbonRequest, + public OkHttpRibbonResponse execute(OkHttpRibbonRequest ribbonRequest, final IClientConfig configOverride) throws Exception { - return this.executeWithRetry(ribbonRequest, new RetryCallback() { - @Override - public OkHttpRibbonResponse doWithRetry(RetryContext context) throws Exception { - //on retries the policy will choose the server and set it in the context - //extract the server and update the request being made - OkHttpRibbonRequest newRequest = ribbonRequest; - if(context instanceof LoadBalancedRetryContext) { - ServiceInstance service = ((LoadBalancedRetryContext)context).getServiceInstance(); - if(service != null) { - //Reconstruct the request URI using the host and port set in the retry context - newRequest = newRequest.withNewUri(new URI(service.getUri().getScheme(), - newRequest.getURI().getUserInfo(), service.getHost(), service.getPort(), - newRequest.getURI().getPath(), newRequest.getURI().getQuery(), - newRequest.getURI().getFragment())); - } - } - if (isSecure(configOverride)) { - final URI secureUri = UriComponentsBuilder.fromUri(newRequest.getUri()) - .scheme("https").build().toUri(); - newRequest = newRequest.withNewUri(secureUri); - } - OkHttpClient httpClient = getOkHttpClient(configOverride, secure); + boolean secure = isSecure(configOverride); + if (secure) { + final URI secureUri = UriComponentsBuilder.fromUri(ribbonRequest.getUri()) + .scheme("https").build().toUri(); + ribbonRequest = ribbonRequest.withNewUri(secureUri); + } - final Request request = newRequest.toRequest(); - Response response = httpClient.newCall(request).execute(); - return new OkHttpRibbonResponse(response, newRequest.getUri()); - } - }); + OkHttpClient httpClient = getOkHttpClient(configOverride, secure); + final Request request = ribbonRequest.toRequest(); + Response response = httpClient.newCall(request).execute(); + return new OkHttpRibbonResponse(response, ribbonRequest.getUri()); } OkHttpClient getOkHttpClient(IClientConfig configOverride, boolean secure) { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/RetryableLoadBalancingClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/RetryableOkHttpLoadBalancingClient.java similarity index 52% rename from spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/RetryableLoadBalancingClient.java rename to spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/RetryableOkHttpLoadBalancingClient.java index 5916dfd6..e5ae121d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/support/RetryableLoadBalancingClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/okhttp/RetryableOkHttpLoadBalancingClient.java @@ -1,6 +1,5 @@ /* - * - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +12,18 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ +package org.springframework.cloud.netflix.ribbon.okhttp; -package org.springframework.cloud.netflix.ribbon.support; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import java.io.IOException; +import java.net.URI; import org.apache.commons.lang.BooleanUtils; import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; @@ -30,57 +32,32 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.http.HttpRequest; import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.support.RetryTemplate; -import com.netflix.client.IResponse; +import org.springframework.web.util.UriComponentsBuilder; import com.netflix.client.RequestSpecificRetryHandler; import com.netflix.client.RetryHandler; import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; /** - * A load balancing client which uses Spring Retry to retry failed requests. + * An OK HTTP client which leverages Spring Retry to retry failed request. * @author Ryan Baxter */ -public abstract class RetryableLoadBalancingClient - extends AbstractLoadBalancingClient implements ServiceInstanceChooser { +public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClient implements ServiceInstanceChooser { - protected LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = - new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); + private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory; - @Deprecated - public RetryableLoadBalancingClient() { - super(); - } - - @Deprecated - public RetryableLoadBalancingClient(final ILoadBalancer lb) { - super(lb); - } - - public RetryableLoadBalancingClient(IClientConfig config, ServerIntrospector serverIntrospector) { + public RetryableOkHttpLoadBalancingClient(IClientConfig config, ServerIntrospector serverIntrospector, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { super(config, serverIntrospector); - } - - public RetryableLoadBalancingClient(D delegate, IClientConfig config, ServerIntrospector serverIntrospector) { - super(delegate, config, serverIntrospector); - } - - public RetryableLoadBalancingClient(IClientConfig iClientConfig, ServerIntrospector serverIntrospector, - LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { - this(iClientConfig, serverIntrospector); this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; } - /** - * Executes a {@link S} using Spring Retry. - * @param request The request to execute. - * @param callback The retry callback to use. - * @return The response. - * @throws Exception Thrown if there is an error making the request and a retry cannot be completed successfully. - */ - protected T executeWithRetry(S request, RetryCallback callback) throws Exception { + private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request, + RetryCallback callback) + throws Exception { LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this); RetryTemplate retryTemplate = new RetryTemplate(); boolean retryable = request.getContext() == null ? true : @@ -90,6 +67,39 @@ public abstract class RetryableLoadBalancingClient factories = context.getBeansOfType(LoadBalancedRetryPolicyFactory.class); - assertThat(factories.values(), hasSize(0)); + assertThat(factories.values(), hasSize(1)); + assertThat(factories.values().toArray()[0], instanceOf(LoadBalancedRetryPolicyFactory.NeverRetryFactory.class)); + Map clients = context.getBeansOfType(RibbonLoadBalancingHttpClient.class); + assertThat(clients.values(), hasSize(1)); + assertThat(clients.values().toArray()[0], instanceOf(RibbonLoadBalancingHttpClient.class)); } } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java index 539b94bc..66a0e7a3 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java @@ -24,6 +24,8 @@ import org.junit.runner.RunWith; import org.springframework.beans.BeansException; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient; +import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.test.context.ContextConfiguration; @@ -37,7 +39,7 @@ import static org.hamcrest.collection.IsCollectionWithSize.hasSize; * @author Ryan Baxter */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {RibbonAutoConfiguration.class, LoadBalancerAutoConfiguration.class}) +@ContextConfiguration(classes = {RibbonAutoConfiguration.class, RibbonClientConfiguration.class, LoadBalancerAutoConfiguration.class}) public class SpringRetryEnabledTests implements ApplicationContextAware { private ApplicationContext context; @@ -47,6 +49,9 @@ public class SpringRetryEnabledTests implements ApplicationContextAware { Map factories = context.getBeansOfType(LoadBalancedRetryPolicyFactory.class); assertThat(factories.values(), hasSize(1)); assertThat(factories.values().toArray()[0], instanceOf(RibbonLoadBalancedRetryPolicyFactory.class)); + Map clients = context.getBeansOfType(RibbonLoadBalancingHttpClient.class); + assertThat(clients.values(), hasSize(1)); + assertThat(clients.values().toArray()[0], instanceOf(RetryableRibbonLoadBalancingHttpClient.class)); } @Override diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java index 44022423..fa3b04a6 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java @@ -113,8 +113,8 @@ public class RibbonLoadBalancingHttpClientTests { SpringClientFactory factory = new SpringClientFactory(); factory.setApplicationContext(new AnnotationConfigApplicationContext( RibbonAutoConfiguration.class, Connections.class)); - RibbonLoadBalancingHttpClient client = factory.getClient("service", - RibbonLoadBalancingHttpClient.class); + RetryableRibbonLoadBalancingHttpClient client = factory.getClient("service", + RetryableRibbonLoadBalancingHttpClient.class); HttpClient delegate = client.getDelegate(); PoolingHttpClientConnectionManager connManager = (PoolingHttpClientConnectionManager) ReflectionTestUtils.getField(delegate, "connManager"); @@ -182,7 +182,7 @@ public class RibbonLoadBalancingHttpClientTests { } } - private RibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer, + private RetryableRibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer, boolean retryable, boolean retryOnAllOps, String serviceName, String host, int port, HttpClient delegate, ILoadBalancer lb) throws Exception { @@ -198,7 +198,7 @@ public class RibbonLoadBalancingHttpClientTests { SpringClientFactory clientFactory = mock(SpringClientFactory.class); doReturn(context).when(clientFactory).getLoadBalancerContext(eq(serviceName)); LoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory); - RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(clientConfig, introspector, factory); + RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(clientConfig, introspector, factory); client.setLoadBalancer(lb); ReflectionTestUtils.setField(client, "delegate", delegate); return client; @@ -219,7 +219,7 @@ public class RibbonLoadBalancingHttpClientTests { final HttpResponse response = mock(HttpResponse.class); doThrow(new IOException("boom")).doReturn(response).when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); - RibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, + RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); @@ -249,7 +249,7 @@ public class RibbonLoadBalancingHttpClientTests { doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); - RibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, + RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); @@ -279,7 +279,7 @@ public class RibbonLoadBalancingHttpClientTests { doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); - RibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, + RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(method).when(request).getMethod(); @@ -308,7 +308,7 @@ public class RibbonLoadBalancingHttpClientTests { doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); - RibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, + RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(method).when(request).getMethod(); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/okhttp/SpringRetryDisableOkHttpClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/okhttp/SpringRetryDisableOkHttpClientTests.java new file mode 100644 index 00000000..ec5a4b9d --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/okhttp/SpringRetryDisableOkHttpClientTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.netflix.ribbon.okhttp; + +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.ClassPathExclusions; +import org.springframework.cloud.FilteredClassPathRunner; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; + +/** + * @author Ryan Baxter + */ +@RunWith(FilteredClassPathRunner.class) +@ClassPathExclusions({"spring-retry-*.jar", "spring-boot-starter-aop-*.jar"}) +public class SpringRetryDisableOkHttpClientTests { + + private ConfigurableApplicationContext context; + + @Before + public void setUp() { + context = new SpringApplicationBuilder().web(false).properties("ribbon.okhttp.enabled=true") + .sources(RibbonAutoConfiguration.class,LoadBalancerAutoConfiguration.class, RibbonClientConfiguration.class).run(); + + } + + @After + public void tearDown() { + if(context != null) { + context.close(); + } + } + + @Test + public void testLoadBalancedRetryFactoryBean() throws Exception { + Map factories = context.getBeansOfType(LoadBalancedRetryPolicyFactory.class); + assertThat(factories.values(), hasSize(1)); + assertThat(factories.values().toArray()[0], instanceOf(LoadBalancedRetryPolicyFactory.NeverRetryFactory.class)); + Map clients = context.getBeansOfType(OkHttpLoadBalancingClient.class); + assertThat(clients.values(), hasSize(1)); + assertThat(clients.values().toArray()[0], instanceOf(OkHttpLoadBalancingClient.class)); + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/okhttp/SpringRetryEnabledOkHttpClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/okhttp/SpringRetryEnabledOkHttpClientTests.java new file mode 100644 index 00000000..c8b3dcab --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/okhttp/SpringRetryEnabledOkHttpClientTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.netflix.ribbon.okhttp; + +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(value = {"ribbon.okhttp.enabled: true"}) +@ContextConfiguration(classes = {RibbonAutoConfiguration.class, RibbonClientConfiguration.class, LoadBalancerAutoConfiguration.class}) +public class SpringRetryEnabledOkHttpClientTests implements ApplicationContextAware { + + private ApplicationContext context; + + @Test + public void testLoadBalancedRetryFactoryBean() throws Exception { + Map factories = context.getBeansOfType(LoadBalancedRetryPolicyFactory.class); + assertThat(factories.values(), hasSize(1)); + assertThat(factories.values().toArray()[0], instanceOf(RibbonLoadBalancedRetryPolicyFactory.class)); + Map clients = context.getBeansOfType(OkHttpLoadBalancingClient.class); + assertThat(clients.values(), hasSize(1)); + assertThat(clients.values().toArray()[0], instanceOf(RetryableOkHttpLoadBalancingClient.class)); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } +} diff --git a/spring-cloud-starter-ribbon/pom.xml b/spring-cloud-starter-ribbon/pom.xml index 890e913f..0960f000 100644 --- a/spring-cloud-starter-ribbon/pom.xml +++ b/spring-cloud-starter-ribbon/pom.xml @@ -20,14 +20,6 @@ ${basedir}/../.. - - org.springframework.boot - spring-boot-starter-aop - - - org.springframework.retry - spring-retry - org.springframework.cloud spring-cloud-starter