Commit 7e6c7ee2 by Ryan Baxter Committed by GitHub

Merge pull request #1680 from ryanjbaxter/zuul-retry

Add Retry Logic To Zuul Using Spring Retry. Fixes #1295
parents ee953bef f1fb32cf
...@@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; ...@@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -126,9 +127,10 @@ public class RibbonClientConfiguration { ...@@ -126,9 +127,10 @@ public class RibbonClientConfiguration {
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient( public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient(
IClientConfig config, ServerIntrospector serverIntrospector, IClientConfig config, ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer, RetryHandler retryHandler) { ILoadBalancer loadBalancer, RetryHandler retryHandler,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient( RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(
config, serverIntrospector); config, serverIntrospector, loadBalancedRetryPolicyFactory);
client.setLoadBalancer(loadBalancer); client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler); client.setRetryHandler(retryHandler);
Monitors.registerObject("Client_" + this.name, client); Monitors.registerObject("Client_" + this.name, client);
...@@ -147,9 +149,9 @@ public class RibbonClientConfiguration { ...@@ -147,9 +149,9 @@ public class RibbonClientConfiguration {
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class) @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
public OkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config, public OkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config,
ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer,
RetryHandler retryHandler) { RetryHandler retryHandler, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(config, OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(config,
serverIntrospector); serverIntrospector, loadBalancedRetryPolicyFactory);
client.setLoadBalancer(loadBalancer); client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler); client.setRetryHandler(retryHandler);
Monitors.registerObject("Client_" + this.name, client); Monitors.registerObject("Client_" + this.name, client);
......
...@@ -23,12 +23,15 @@ import org.apache.http.client.HttpClient; ...@@ -23,12 +23,15 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.HttpClientBuilder; 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.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 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.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.ILoadBalancer;
...@@ -38,11 +41,11 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps ...@@ -38,11 +41,11 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps
/** /**
* @author Christian Lohmann * @author Christian Lohmann
* @author Ryan Baxter
*/ */
//TODO: rename (ie new class that extends this in Dalston) to ApacheHttpLoadBalancingClient //TODO: rename (ie new class that extends this in Dalston) to ApacheHttpLoadBalancingClient
public class RibbonLoadBalancingHttpClient public class RibbonLoadBalancingHttpClient
extends extends RetryableLoadBalancingClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse, HttpClient> {
AbstractLoadBalancingClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse, HttpClient> {
@Deprecated @Deprecated
public RibbonLoadBalancingHttpClient() { public RibbonLoadBalancingHttpClient() {
...@@ -62,6 +65,11 @@ public class RibbonLoadBalancingHttpClient ...@@ -62,6 +65,11 @@ public class RibbonLoadBalancingHttpClient
super(delegate, config, serverIntrospector); super(delegate, config, serverIntrospector);
} }
public RibbonLoadBalancingHttpClient(IClientConfig iClientConfig, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
super(iClientConfig, serverIntrospector, loadBalancedRetryPolicyFactory);
}
protected HttpClient createDelegate(IClientConfig config) { protected HttpClient createDelegate(IClientConfig config) {
return HttpClientBuilder.create() return HttpClientBuilder.create()
// already defaults to 0 in builder, so resetting to 0 won't hurt // already defaults to 0 in builder, so resetting to 0 won't hurt
...@@ -74,7 +82,7 @@ public class RibbonLoadBalancingHttpClient ...@@ -74,7 +82,7 @@ public class RibbonLoadBalancingHttpClient
} }
@Override @Override
public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request, public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request,
final IClientConfig configOverride) throws Exception { final IClientConfig configOverride) throws Exception {
final RequestConfig.Builder builder = RequestConfig.custom(); final RequestConfig.Builder builder = RequestConfig.custom();
IClientConfig config = configOverride != null ? configOverride : this.config; IClientConfig config = configOverride != null ? configOverride : this.config;
...@@ -86,27 +94,37 @@ public class RibbonLoadBalancingHttpClient ...@@ -86,27 +94,37 @@ public class RibbonLoadBalancingHttpClient
CommonClientConfigKey.FollowRedirects, this.followRedirects)); CommonClientConfigKey.FollowRedirects, this.followRedirects));
final RequestConfig requestConfig = builder.build(); 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)) { if (isSecure(configOverride)) {
final URI secureUri = UriComponentsBuilder.fromUri(request.getUri()) final URI secureUri = UriComponentsBuilder.fromUri(newRequest.getUri())
.scheme("https").build().toUri(); .scheme("https").build().toUri();
request = request.withNewUri(secureUri); newRequest = newRequest.withNewUri(secureUri);
} }
HttpUriRequest httpUriRequest = newRequest.toRequest(requestConfig);
final HttpUriRequest httpUriRequest = request.toRequest(requestConfig); final HttpResponse httpResponse = RibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest);
final HttpResponse httpResponse = this.delegate.execute(httpUriRequest);
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI()); return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
} }
});
}
@Override @Override
public URI reconstructURIWithServer(Server server, URI original) { public URI reconstructURIWithServer(Server server, URI original) {
URI uri = updateToHttpsIfNeeded(original, this.config, this.serverIntrospector, server); URI uri = updateToHttpsIfNeeded(original, this.config, this.serverIntrospector, server);
return super.reconstructURIWithServer(server, uri); return super.reconstructURIWithServer(server, uri);
} }
@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
}
} }
...@@ -19,8 +19,13 @@ package org.springframework.cloud.netflix.ribbon.okhttp; ...@@ -19,8 +19,13 @@ package org.springframework.cloud.netflix.ribbon.okhttp;
import java.net.URI; import java.net.URI;
import java.util.concurrent.TimeUnit; 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.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 org.springframework.web.util.UriComponentsBuilder;
import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.CommonClientConfigKey;
...@@ -36,9 +41,10 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps ...@@ -36,9 +41,10 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttps
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Ryan Baxter
*/ */
public class OkHttpLoadBalancingClient public class OkHttpLoadBalancingClient
extends AbstractLoadBalancingClient<OkHttpRibbonRequest, OkHttpRibbonResponse, OkHttpClient> { extends RetryableLoadBalancingClient<OkHttpRibbonRequest, OkHttpRibbonResponse, OkHttpClient> {
@Deprecated @Deprecated
public OkHttpLoadBalancingClient() { public OkHttpLoadBalancingClient() {
...@@ -55,6 +61,12 @@ public class OkHttpLoadBalancingClient ...@@ -55,6 +61,12 @@ public class OkHttpLoadBalancingClient
super(config, serverIntrospector); super(config, serverIntrospector);
} }
public OkHttpLoadBalancingClient(IClientConfig config,
ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
super(config, serverIntrospector, loadBalancedRetryPolicyFactory);
}
public OkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, public OkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config,
ServerIntrospector serverIntrospector) { ServerIntrospector serverIntrospector) {
super(delegate, config, serverIntrospector); super(delegate, config, serverIntrospector);
...@@ -66,21 +78,36 @@ public class OkHttpLoadBalancingClient ...@@ -66,21 +78,36 @@ public class OkHttpLoadBalancingClient
} }
@Override @Override
public OkHttpRibbonResponse execute(OkHttpRibbonRequest ribbonRequest, public OkHttpRibbonResponse execute(final OkHttpRibbonRequest ribbonRequest,
final IClientConfig configOverride) throws Exception { final IClientConfig configOverride) throws Exception {
boolean secure = isSecure(configOverride); return this.executeWithRetry(ribbonRequest, new RetryCallback() {
@Override
if (secure) { public OkHttpRibbonResponse doWithRetry(RetryContext context) throws Exception {
final URI secureUri = UriComponentsBuilder.fromUri(ribbonRequest.getUri()) //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(); .scheme("https").build().toUri();
ribbonRequest = ribbonRequest.withNewUri(secureUri); newRequest = newRequest.withNewUri(secureUri);
} }
OkHttpClient httpClient = getOkHttpClient(configOverride, secure); OkHttpClient httpClient = getOkHttpClient(configOverride, secure);
final Request request = ribbonRequest.toRequest(); final Request request = newRequest.toRequest();
Response response = httpClient.newCall(request).execute(); Response response = httpClient.newCall(request).execute();
return new OkHttpRibbonResponse(response, ribbonRequest.getUri()); return new OkHttpRibbonResponse(response, newRequest.getUri());
}
});
} }
OkHttpClient getOkHttpClient(IClientConfig configOverride, boolean secure) { OkHttpClient getOkHttpClient(IClientConfig configOverride, boolean secure) {
......
...@@ -18,18 +18,29 @@ ...@@ -18,18 +18,29 @@
package org.springframework.cloud.netflix.ribbon.support; package org.springframework.cloud.netflix.ribbon.support;
import com.netflix.client.ClientRequest; import com.netflix.client.ClientRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
import org.springframework.util.MultiValueMap;
import java.net.URI; import java.net.URI;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Ryan Baxter
*/ */
public abstract class ContextAwareRequest extends ClientRequest { public abstract class ContextAwareRequest extends ClientRequest implements HttpRequest {
protected final RibbonCommandContext context; protected final RibbonCommandContext context;
private HttpHeaders httpHeaders;
public ContextAwareRequest(RibbonCommandContext context) { public ContextAwareRequest(RibbonCommandContext context) {
this.context = context; this.context = context;
MultiValueMap<String, String> headers = context.getHeaders();
this.httpHeaders = new HttpHeaders();
for(String key : headers.keySet()) {
this.httpHeaders.put(key, headers.get(key));
}
this.uri = context.uri(); this.uri = context.uri();
this.isRetriable = context.getRetryable(); this.isRetriable = context.getRetryable();
} }
...@@ -38,6 +49,21 @@ public abstract class ContextAwareRequest extends ClientRequest { ...@@ -38,6 +49,21 @@ public abstract class ContextAwareRequest extends ClientRequest {
return context; return context;
} }
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(context.getMethod());
}
@Override
public URI getURI() {
return this.getUri();
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
protected RibbonCommandContext newContext(URI uri) { protected RibbonCommandContext newContext(URI uri) {
RibbonCommandContext commandContext = new RibbonCommandContext(this.context.getServiceId(), RibbonCommandContext commandContext = new RibbonCommandContext(this.context.getServiceId(),
this.context.getMethod(), uri.toString(), this.context.getRetryable(), this.context.getMethod(), uri.toString(), this.context.getRetryable(),
......
/*
*
* Copyright 2013-2016 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.support;
import java.io.IOException;
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.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.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import com.netflix.client.IResponse;
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.
* @author Ryan Baxter
*/
public abstract class RetryableLoadBalancingClient<S extends ContextAwareRequest, T extends IResponse, D>
extends AbstractLoadBalancingClient<S, T, D> implements ServiceInstanceChooser {
protected LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory =
new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
@Deprecated
public RetryableLoadBalancingClient() {
super();
}
@Deprecated
public RetryableLoadBalancingClient(final ILoadBalancer lb) {
super(lb);
}
public RetryableLoadBalancingClient(IClientConfig config, ServerIntrospector serverIntrospector) {
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<T, IOException> 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(S 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);
}
}
}
/*
*
* * Copyright 2013-2016 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.support;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
/**
* @author Ryan Baxter
*/
public class ContextAwareRequestTest {
private RibbonCommandContext context;
private ContextAwareRequest request;
@Before
public void setUp() throws Exception {
context = mock(RibbonCommandContext.class);
doReturn("GET").when(context).getMethod();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.put("header1", Collections.<String>emptyList());
headers.put("header2", Arrays.asList("value1", "value2"));
headers.put("header3", Arrays.asList("value1"));
doReturn(headers).when(context).getHeaders();
doReturn(new URI("http://foo")).when(context).uri();
doReturn("foo").when(context).getServiceId();
doReturn(new LinkedMultiValueMap<>()).when(context).getParams();
request = new TestContextAwareRequest(context);
}
@After
public void tearDown() throws Exception {
context = null;
request = null;
}
@Test
public void getContext() throws Exception {
assertEquals(context, request.getContext());
}
@Test
public void getMethod() throws Exception {
assertEquals(HttpMethod.GET, request.getMethod());
}
@Test
public void getURI() throws Exception {
assertEquals(new URI("http://foo"), request.getURI());
RibbonCommandContext badUriContext = mock(RibbonCommandContext.class);
doReturn(new LinkedMultiValueMap()).when(badUriContext).getHeaders();
doReturn("foobar").when(badUriContext).getUri();
ContextAwareRequest badUriRequest = new TestContextAwareRequest(badUriContext);
assertNull(badUriRequest.getURI());
}
@Test
public void getHeaders() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.put("header1", Collections.<String>emptyList());
headers.put("header2", Arrays.asList("value1", "value2"));
headers.put("header3", Arrays.asList("value1"));
assertEquals(headers, request.getHeaders());
}
static class TestContextAwareRequest extends ContextAwareRequest {
public TestContextAwareRequest(RibbonCommandContext context) {
super(context);
}
}
}
\ No newline at end of file
/*
*
* * Copyright 2013-2016 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.zuul.filters.route.apache;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonRetryIntegrationTestBase;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Ryan Baxter
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = RibbonRetryIntegrationTestBase.RetryableTestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = {
"zuul.retryable: false", /* Disable retry by default, have each route enable it */
"hystrix.command.default.execution.timeout.enabled: false", /* Disable hystrix so its timeout doesnt get in the way */
"ribbon.ReadTimeout: 1000", /* Make sure ribbon will timeout before the thread is done sleeping */
"zuul.routes.retryable: /retryable/**",
"zuul.routes.retryable.retryable: true",
"retryable.ribbon.OkToRetryOnAllOperations: true",
"retryable.ribbon.MaxAutoRetries: 1",
"retryable.ribbon.MaxAutoRetriesNextServer: 1",
"zuul.routes.getretryable: /getretryable/**",
"zuul.routes.getretryable.retryable: true",
"getretryable.ribbon.MaxAutoRetries: 1",
"getretryable.ribbon.MaxAutoRetriesNextServer: 1",
"zuul.routes.disableretry: /disableretry/**",
"zuul.routes.disableretry.retryable: false", /* This will override the global */
"disableretry.ribbon.MaxAutoRetries: 1",
"disableretry.ribbon.MaxAutoRetriesNextServer: 1",
"zuul.routes.globalretrydisabled: /globalretrydisabled/**",
"globalretrydisabled.ribbon.MaxAutoRetries: 1",
"globalretrydisabled.ribbon.MaxAutoRetriesNextServer: 1"
})
@DirtiesContext
public class HttpClientRibbonRetryIntegrationTests extends RibbonRetryIntegrationTestBase {
}
/*
*
* * Copyright 2013-2016 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.zuul.filters.route.okhttp;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonRetryIntegrationTestBase;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Ryan Baxter
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = RibbonRetryIntegrationTestBase.RetryableTestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = {
"zuul.retryable: false", /* Disable retry by default, have each route enable it */
"ribbon.okhttp.enabled: true",
"hystrix.command.default.execution.timeout.enabled: false", /* Disable hystrix so its timeout doesnt get in the way */
"ribbon.ReadTimeout: 1000", /* Make sure ribbon will timeout before the thread is done sleeping */
"zuul.routes.retryable: /retryable/**",
"zuul.routes.retryable.retryable: true",
"retryable.ribbon.OkToRetryOnAllOperations: true",
"retryable.ribbon.MaxAutoRetries: 1",
"retryable.ribbon.MaxAutoRetriesNextServer: 1",
"zuul.routes.getretryable: /getretryable/**",
"zuul.routes.getretryable.retryable: true",
"getretryable.ribbon.MaxAutoRetries: 1",
"getretryable.ribbon.MaxAutoRetriesNextServer: 1",
"zuul.routes.disableretry: /disableretry/**",
"zuul.routes.disableretry.retryable: false", /* This will override the global */
"disableretry.ribbon.MaxAutoRetries: 1",
"disableretry.ribbon.MaxAutoRetriesNextServer: 1",
"zuul.routes.globalretrydisabled: /globalretrydisabled/**",
"globalretrydisabled.ribbon.MaxAutoRetries: 1",
"globalretrydisabled.ribbon.MaxAutoRetriesNextServer: 1"
})
@DirtiesContext
public class OkHttpRibbonRetryIntegrationTests extends RibbonRetryIntegrationTestBase {
}
...@@ -52,7 +52,6 @@ public abstract class RibbonCommandFallbackTests { ...@@ -52,7 +52,6 @@ public abstract class RibbonCommandFallbackTests {
ResponseEntity<String> result = new TestRestTemplate().exchange( ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET, "http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class); new HttpEntity<>((Void) null), String.class);
System.out.println("no fallback body: " + result.getBody());
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
} }
} }
/*
*
* * Copyright 2013-2016 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.zuul.filters.route.support;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import static org.junit.Assert.assertEquals;
/**
* @author Ryan Baxter
*/
public abstract class RibbonRetryIntegrationTestBase {
@Value("${local.server.port}")
protected int port;
@Before
public void setup() {
String uri = "/resetError";
new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
}
@Test
public void retryable() {
String uri = "/retryable/everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void postRetryOK() {
String uri = "/retryable/posteveryothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.POST,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void getRetryable() {
String uri = "/getretryable/everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void postNotRetryable() {
String uri = "/getretryable/posteveryothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.POST,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
}
@Test
public void disbaleRetry() {
String uri = "/disableretry/everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
}
@Test
public void globalRetryDisabled() {
String uri = "/globalretrydisabled/everyothererror";
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + uri, HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
}
// Don't use @SpringBootApplication because we don't want to component scan
@Configuration
@EnableAutoConfiguration
@RestController
@EnableZuulProxy
@RibbonClients({
@RibbonClient(name = "retryable", configuration = RibbonClientConfiguration.class),
@RibbonClient(name = "disableretry", configuration = RibbonClientConfiguration.class),
@RibbonClient(name = "globalretrydisabled", configuration = RibbonClientConfiguration.class),
@RibbonClient(name = "getretryable", configuration = RibbonClientConfiguration.class)})
public static class RetryableTestConfig {
private boolean error = true;
@RequestMapping("/resetError")
public void resetError() {
error = true;
}
@RequestMapping("/everyothererror")
public ResponseEntity<String> timeout() {
boolean shouldError = error;
error = !error;
try {
if(shouldError) {
Thread.sleep(80000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ResponseEntity<String>("no error", HttpStatus.OK);
}
@RequestMapping(path = "/posteveryothererror", method = RequestMethod.POST)
public ResponseEntity<String> postTimeout() {
return timeout();
}
}
@Configuration
public static class RibbonClientConfiguration {
@Value("${local.server.port}")
private int port;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
}
}
...@@ -375,7 +375,6 @@ public abstract class ZuulProxyTestBase { ...@@ -375,7 +375,6 @@ public abstract class ZuulProxyTestBase {
e.printStackTrace(); e.printStackTrace();
} }
return "slow"; return "slow";
} }
@Bean @Bean
......
...@@ -123,5 +123,15 @@ ...@@ -123,5 +123,15 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment