Unverified Commit e6943b8f by Ryan Baxter Committed by GitHub

Return content of last request when retrying failed requests (#2685)

parent ba87fef3
......@@ -22,13 +22,14 @@ import feign.Request;
import feign.Response;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.cloud.client.loadbalancer.RibbonRecoveryCallback;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
......@@ -39,6 +40,7 @@ import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.StreamUtils;
import com.netflix.client.DefaultLoadBalancerRetryHandler;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.config.CommonClientConfigKey;
......@@ -124,22 +126,29 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser
Request feignRequest = null;
//on retries the policy will choose the server and set it in the context
//extract the server and update the request being made
if(retryContext instanceof LoadBalancedRetryContext) {
ServiceInstance service = ((LoadBalancedRetryContext)retryContext).getServiceInstance();
if(service != null) {
feignRequest = ((RibbonRequest)request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();
if (retryContext instanceof LoadBalancedRetryContext) {
ServiceInstance service = ((LoadBalancedRetryContext) retryContext).getServiceInstance();
if (service != null) {
feignRequest = ((RibbonRequest) request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();
}
}
if(feignRequest == null) {
if (feignRequest == null) {
feignRequest = request.toRequest();
}
Response response = request.client().execute(feignRequest, options);
if(retryPolicy.retryableStatusCode(response.status())) {
if (retryPolicy.retryableStatusCode(response.status())) {
byte[] byteArray = StreamUtils.copyToByteArray(response.body().asInputStream());
response.close();
throw new RetryableStatusCodeException(RetryableFeignLoadBalancer.this.getClientName(), response.status());
throw new RibbonResponseStatusCodeException(RetryableFeignLoadBalancer.this.clientName, response,
byteArray, request.getUri());
}
return new RibbonResponse(request.getUri(), response);
}
}, new RibbonRecoveryCallback<RibbonResponse, Response>() {
@Override
protected RibbonResponse createResponse(Response response, URI uri) {
return new RibbonResponse(uri, response);
}
});
}
......
/*
* Copyright 2013-2018 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.feign.ribbon;
import feign.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.util.StreamUtils;
/**
* A {@link RetryableStatusCodeException} for {@link Response}s
* @author Ryan Baxter
*/
public class RibbonResponseStatusCodeException extends RetryableStatusCodeException {
private Response response;
public RibbonResponseStatusCodeException(String serviceId, Response response, byte[] body, URI uri) {
super(serviceId, response.status(), response, uri);
this.response = Response.builder().body(new ByteArrayInputStream(body), body.length)
.headers(response.headers()).reason(response.reason())
.status(response.status()).request(response.request()).build();
}
@Override
public Response getResponse() {
return response;
}
}
/*
* Copyright 2013-2018 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.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
/**
* A {@link RetryableStatusCodeException} for {@link HttpResponse}s
* @author Ryan Baxter
*/
public class HttpClientStatusCodeException extends RetryableStatusCodeException {
private BasicHttpResponse response;
public HttpClientStatusCodeException(String serviceId, HttpResponse response, HttpEntity entity, URI uri) throws IOException {
super(serviceId, response.getStatusLine().getStatusCode(), response, uri);
this.response = new BasicHttpResponse(response.getStatusLine());
this.response.setLocale(response.getLocale());
this.response.setStatusCode(response.getStatusLine().getStatusCode());
this.response.setReasonPhrase(response.getStatusLine().getReasonPhrase());
this.response.setHeaders(response.getAllHeaders());
EntityUtils.updateEntity(this.response, entity);
}
@Override
public HttpResponse getResponse() {
return this.response;
}
}
/*
* Copyright 2013-2018 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.ByteArrayInputStream;
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.util.EntityUtils;
/**
* Provides basic utilities for {@link org.apache.http.client.HttpClient}
* @author Ryan Baxter
*/
public class HttpClientUtils {
/**
* Creates an new {@link HttpEntity} by copying the {@link HttpEntity} from the {@link HttpResponse}.
* This method will close the response after copying the entity.
* @param response The response to create the {@link HttpEntity} from
* @return A new {@link HttpEntity}
* @throws IOException thrown if there is a problem closing the response.
*/
public static HttpEntity createEntity(HttpResponse response) throws IOException {
ByteArrayInputStream is = new ByteArrayInputStream(
EntityUtils.toByteArray(response.getEntity()));
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContent(is);
entity.setContentLength(response.getEntity().getContentLength());
if(CloseableHttpResponse.class.isInstance(response)) {
((CloseableHttpResponse)response).close();
}
return entity;
}
}
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 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.
......@@ -20,7 +20,6 @@ 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.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.cloud.client.ServiceInstance;
......@@ -29,13 +28,14 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.cloud.client.loadbalancer.RibbonRecoveryCallback;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.support.ContextAwareRequest;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
......@@ -116,9 +116,9 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
final RequestConfig requestConfig = builder.build();
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
RetryCallback retryCallback = new RetryCallback() {
RetryCallback<RibbonApacheHttpResponse, IOException> retryCallback = new RetryCallback<RibbonApacheHttpResponse, IOException>() {
@Override
public RibbonApacheHttpResponse doWithRetry(RetryContext context) throws Exception {
public RibbonApacheHttpResponse doWithRetry(RetryContext context) throws IOException {
//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;
......@@ -137,16 +137,19 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
HttpUriRequest httpUriRequest = newRequest.toRequest(requestConfig);
final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest);
if(retryPolicy.retryableStatusCode(httpResponse.getStatusLine().getStatusCode())) {
if(CloseableHttpResponse.class.isInstance(httpResponse)) {
((CloseableHttpResponse)httpResponse).close();
}
throw new RetryableStatusCodeException(RetryableRibbonLoadBalancingHttpClient.this.clientName,
httpResponse.getStatusLine().getStatusCode());
throw new HttpClientStatusCodeException(RetryableRibbonLoadBalancingHttpClient.this.clientName,
httpResponse, HttpClientUtils.createEntity(httpResponse), httpUriRequest.getURI());
}
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
}
};
return this.executeWithRetry(request, retryPolicy, retryCallback);
RibbonRecoveryCallback<RibbonApacheHttpResponse, HttpResponse> recoveryCallback = new RibbonRecoveryCallback<RibbonApacheHttpResponse, HttpResponse>() {
@Override
protected RibbonApacheHttpResponse createResponse(HttpResponse response, URI uri) {
return new RibbonApacheHttpResponse(response, uri);
}
};
return this.executeWithRetry(request, retryPolicy, retryCallback, recoveryCallback);
}
@Override
......@@ -159,7 +162,9 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
}
private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, LoadBalancedRetryPolicy retryPolicy, RetryCallback<RibbonApacheHttpResponse, IOException> callback) throws Exception {
private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, LoadBalancedRetryPolicy retryPolicy,
RetryCallback<RibbonApacheHttpResponse, IOException> callback,
RecoveryCallback<RibbonApacheHttpResponse> recoveryCallback) throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
boolean retryable = isRequestRetryable(request);
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()
......@@ -170,7 +175,7 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
if (retryListeners != null && retryListeners.length != 0) {
retryTemplate.setListeners(retryListeners);
}
return retryTemplate.execute(callback);
return retryTemplate.execute(callback, recoveryCallback);
}
@Override
......
/*
* Copyright 2013-2018 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 okhttp3.Response;
import okhttp3.ResponseBody;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
/**
* An {@link RetryableStatusCodeException} that captures a {@link Response}
* @author Ryan Baxter
*/
public class OkHttpStatusCodeException extends RetryableStatusCodeException {
private Response response;
public OkHttpStatusCodeException(String serviceId, Response response, ResponseBody responseBody, URI uri) {
super(serviceId, response.code(), response, uri);
this.response = new Response.Builder().code(response.code()).message(response.message()).protocol(response.protocol())
.request(response.request()).headers(response.headers()).handshake(response.handshake())
.cacheResponse(response.cacheResponse()).networkResponse(response.networkResponse())
.priorResponse(response.priorResponse()).sentRequestAtMillis(response.sentRequestAtMillis())
.body(responseBody).build();
}
@Override
public Response getResponse() {
return response;
}
}
......@@ -18,6 +18,7 @@ package org.springframework.cloud.netflix.ribbon.okhttp;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.net.URI;
import org.apache.commons.lang.BooleanUtils;
......@@ -27,13 +28,14 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.cloud.client.loadbalancer.RibbonRecoveryCallback;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.support.ContextAwareRequest;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
......@@ -99,7 +101,8 @@ public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClien
}
private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request, LoadBalancedRetryPolicy retryPolicy,
RetryCallback<OkHttpRibbonResponse, Exception> callback) throws Exception {
RetryCallback<OkHttpRibbonResponse, Exception> callback,
RecoveryCallback<OkHttpRibbonResponse> recoveryCallback) throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
......@@ -110,7 +113,7 @@ public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClien
boolean retryable = isRequestRetryable(request);
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()
: new RetryPolicy(request, retryPolicy, this, this.getClientName()));
return retryTemplate.execute(callback);
return retryTemplate.execute(callback, recoveryCallback);
}
@Override
......@@ -143,13 +146,21 @@ public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClien
final Request request = newRequest.toRequest();
Response response = httpClient.newCall(request).execute();
if(retryPolicy.retryableStatusCode(response.code())) {
ResponseBody responseBody = response.peekBody(Integer.MAX_VALUE);
response.close();
throw new RetryableStatusCodeException(RetryableOkHttpLoadBalancingClient.this.clientName, response.code());
throw new OkHttpStatusCodeException(RetryableOkHttpLoadBalancingClient.this.clientName,
response, responseBody, newRequest.getURI());
}
return new OkHttpRibbonResponse(response, newRequest.getUri());
}
};
return this.executeWithRetry(ribbonRequest, retryPolicy, retryCallback);
return this.executeWithRetry(ribbonRequest, retryPolicy, retryCallback, new RibbonRecoveryCallback<OkHttpRibbonResponse, Response>(){
@Override
protected OkHttpRibbonResponse createResponse(Response response, URI uri) {
return new OkHttpRibbonResponse(response, uri);
}
});
}
@Override
......
......@@ -22,12 +22,17 @@ import feign.Client;
import feign.Request;
import feign.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
......@@ -53,6 +58,7 @@ import org.springframework.retry.backoff.BackOffContext;
import org.springframework.retry.backoff.BackOffInterruptedException;
import org.springframework.retry.backoff.BackOffPolicy;
import com.netflix.client.DefaultLoadBalancerRetryHandler;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig;
......@@ -68,6 +74,7 @@ import static com.netflix.client.config.DefaultClientConfigImpl.DEFAULT_MAX_AUTO
import static com.netflix.client.config.DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
......@@ -381,6 +388,64 @@ public class RetryableFeignLoadBalancerTests {
assertEquals(1, backOffPolicyFactory.getCount());
}
@Test
public void executeRetryFail() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
lbContext.setRetryHandler(new DefaultLoadBalancerRetryHandler(1, 0, true));
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
IClientConfig config = mock(IClientConfig.class);
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
doReturn(0).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(true).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(defaultConnectTimeout).when(config).get(eq(CommonClientConfigKey.ConnectTimeout));
doReturn(defaultReadTimeout).when(config).get(eq(CommonClientConfigKey.ReadTimeout));
doReturn("404").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://foo", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://foo"));
Response fourOFourResponse = Response.builder().status(404).headers(new HashMap<String, Collection<String>>())
.body(new Response.Body() { //set content into response
@Override
public Integer length() {
return "test".getBytes().length;
}
@Override
public boolean isRepeatable() {
return true;
}
@Override
public InputStream asInputStream() throws IOException {
return new ByteArrayInputStream("test".getBytes());
}
@Override
public Reader asReader() throws IOException {
return new InputStreamReader(asInputStream(), "UTF-8");
}
@Override
public void close() throws IOException {
}
}).build();
doReturn(fourOFourResponse).when(client).execute(any(Request.class), any(Request.Options.class));
MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory();
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory, backOffPolicyFactory);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
assertEquals(1, backOffPolicyFactory.getCount());
InputStream inputStream = ribbonResponse.toResponse().body().asInputStream();
byte[] buf = new byte[100];
int read = inputStream.read(buf);
Assert.assertThat(new String(buf, 0, read), is("test"));
}
class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy {
private int count = 0;
......
/*
* Copyright 2013-2018 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.feign.ribbon;
import feign.Request;
import feign.Response;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.util.StreamUtils;
import static org.junit.Assert.assertEquals;
/**
* @author Ryan Baxter
*/
@RunWith(MockitoJUnitRunner.class)
public class RibbonResponseStatusCodeExceptionTest {
@Test
public void getResponse() throws Exception {
Map<String, Collection<String>> headers = new HashMap<String, Collection<String>>();
List<String> fooValues = new ArrayList<String>();
fooValues.add("bar");
headers.put("foo", fooValues);
Request request = Request.create("GET", "http://service.com",
new HashMap<String, Collection<String>>(), new byte[]{}, Charset.defaultCharset());
byte[] body = "foo".getBytes();
ByteArrayInputStream is = new ByteArrayInputStream(body);
Response response = Response.builder().status(200).reason("Success").request(request).body(is, body.length).headers(headers).build();
RibbonResponseStatusCodeException ex = new RibbonResponseStatusCodeException("service", response, body,
new URI(request.url()));
assertEquals(200, ex.getResponse().status());
assertEquals(request, ex.getResponse().request());
assertEquals("Success", ex.getResponse().reason());
assertEquals("foo", StreamUtils.copyToString(ex.getResponse().body().asInputStream(), Charset.defaultCharset()));
}
}
\ No newline at end of file
/*
* Copyright 2013-2018 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.ByteArrayInputStream;
import java.net.URI;
import java.util.Locale;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Ryan Baxter
*/
@RunWith(MockitoJUnitRunner.class)
public class HttpClientStatusCodeExceptionTest {
@Test
public void getResponse() throws Exception {
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
doReturn(new Locale("en")).when(response).getLocale();
Header foo = new BasicHeader("foo", "bar");
Header[] headers = new Header[]{foo};
doReturn(headers).when(response).getAllHeaders();
StatusLine statusLine = new BasicStatusLine(new ProtocolVersion("http", 1, 1),
200, "Success");
doReturn(statusLine).when(response).getStatusLine();
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContent(new ByteArrayInputStream("foo".getBytes()));
entity.setContentLength(3);
doReturn(entity).when(response).getEntity();
HttpEntity copiedEntity = HttpClientUtils.createEntity(response);
HttpClientStatusCodeException ex = new HttpClientStatusCodeException("service", response, copiedEntity,
new URI("http://service.com"));
assertEquals("en", ex.getResponse().getLocale().toString());
assertArrayEquals(headers, ex.getResponse().getAllHeaders());
assertEquals("Success", ex.getResponse().getStatusLine().getReasonPhrase());
assertEquals(200, ex.getResponse().getStatusLine().getStatusCode());
assertEquals("http", ex.getResponse().getStatusLine().getProtocolVersion().getProtocol());
assertEquals(1, ex.getResponse().getStatusLine().getProtocolVersion().getMajor());
assertEquals(1, ex.getResponse().getStatusLine().getProtocolVersion().getMinor());
assertEquals("foo", EntityUtils.toString(ex.getResponse().getEntity()));
verify(response, times(1)).close();
}
}
\ No newline at end of file
......@@ -18,14 +18,17 @@ package org.springframework.cloud.netflix.ribbon.apache;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import org.apache.http.HttpResponse;
import java.util.Locale;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.junit.After;
......@@ -33,6 +36,8 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
......@@ -76,6 +81,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
......@@ -199,7 +205,6 @@ public class RibbonLoadBalancingHttpClientTests {
public void testNeverRetry() throws Exception {
ServerIntrospector introspector = mock(ServerIntrospector.class);
CloseableHttpClient delegate = mock(CloseableHttpClient.class);
HttpResponse response = mock(HttpResponse.class);
doThrow(new IOException("boom")).when(delegate).execute(any(HttpUriRequest.class));
DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl();
clientConfig.setClientName("foo");
......@@ -214,6 +219,53 @@ public class RibbonLoadBalancingHttpClientTests {
}
}
@Test
public void testRetryFail() throws Exception {
int retriesNextServer = 0;
int retriesSameServer = 1;
boolean retryable = true;
boolean retryOnAllOps = false;
String serviceName = "foo";
String host = serviceName;
int port = 80;
HttpMethod method = HttpMethod.GET;
URI uri = new URI("http://" + host + ":" + port);
CloseableHttpClient delegate = mock(CloseableHttpClient.class);
StatusLine fourOFourStatusLine = mock(StatusLine.class);
CloseableHttpResponse fourOFourResponse = mock(CloseableHttpResponse.class);
Locale locale = new Locale("en");
doReturn(locale).when(fourOFourResponse).getLocale();
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
HttpEntity entity = mock(HttpEntity.class);
doReturn(new ByteArrayInputStream("test".getBytes())).when(entity).getContent();
return entity;
}
}).when(fourOFourResponse).getEntity();
doReturn(404).when(fourOFourStatusLine).getStatusCode();
doReturn(fourOFourStatusLine).when(fourOFourResponse).getStatusLine();
doReturn(locale).when(fourOFourResponse).getLocale();
doReturn(fourOFourResponse).when(delegate).execute(any(HttpUriRequest.class));
ILoadBalancer lb = mock(ILoadBalancer.class);
MyBackOffPolicyFactory myBackOffPolicyFactory = new MyBackOffPolicyFactory();
RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps,
serviceName, host, port, delegate, lb, "404", myBackOffPolicyFactory);
RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class);
doReturn(uri).when(request).getURI();
doReturn(method).when(request).getMethod();
doReturn(request).when(request).withNewUri(any(URI.class));
HttpUriRequest uriRequest = mock(HttpUriRequest.class);
doReturn(uri).when(uriRequest).getURI();
doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class));
RibbonApacheHttpResponse returnedResponse = client.execute(request, null);
verify(delegate, times(2)).execute(any(HttpUriRequest.class));
byte[] buf = new byte[100];
InputStream inputStream = returnedResponse.getInputStream();
int read = inputStream.read(buf);
assertThat(new String(buf, 0, read), is("test"));
}
private RetryableRibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer,
boolean retryable, boolean retryOnAllOps,
String serviceName, String host, int port,
......@@ -245,7 +297,7 @@ public class RibbonLoadBalancingHttpClientTests {
doReturn(clientConfig).when(clientFactory).getClientConfig(eq(serviceName));
LoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(delegate, clientConfig,
introspector, factory, loadBalancedBackOffPolicyFactory);
introspector, factory, loadBalancedBackOffPolicyFactory, new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory());
client.setLoadBalancer(lb);
ReflectionTestUtils.setField(client, "delegate", delegate);
return client;
......@@ -340,7 +392,7 @@ public class RibbonLoadBalancingHttpClientTests {
HttpUriRequest uriRequest = mock(HttpUriRequest.class);
doReturn(uri).when(uriRequest).getURI();
doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class));
RibbonApacheHttpResponse returnedResponse = client.execute(request, null);
client.execute(request, null);
verify(delegate, times(3)).execute(any(HttpUriRequest.class));
verify(lb, times(2)).chooseServer(eq(serviceName));
assertEquals(2, myBackOffPolicyFactory.getCount());
......@@ -393,7 +445,7 @@ public class RibbonLoadBalancingHttpClientTests {
ServerIntrospector introspector = mock(ServerIntrospector.class);
RibbonCommandContext context = new RibbonCommandContext(serviceName, method.toString(), uri.toString(), false,
new LinkedMultiValueMap<String, String>(), new LinkedMultiValueMap<String, String>(),
new ByteArrayInputStream(new String("bar").getBytes()),
new ByteArrayInputStream("bar".getBytes()),
new ArrayList<RibbonRequestCustomizer>());
RibbonApacheHttpRequest request = new RibbonApacheHttpRequest(context);
CloseableHttpClient delegate = mock(CloseableHttpClient.class);
......@@ -404,7 +456,7 @@ public class RibbonLoadBalancingHttpClientTests {
doReturn(response).
when(delegate).execute(any(HttpUriRequest.class));
RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(delegate, clientConfig, introspector);
RibbonApacheHttpResponse returnedResponse = client.execute(request, null);
client.execute(request, null);
verify(response, times(0)).close();
verify(delegate, times(1)).execute(argThat(new ArgumentMatcher<HttpUriRequest>() {
@Override
......@@ -506,10 +558,17 @@ public class RibbonLoadBalancingHttpClientTests {
URI uri = new URI("http://" + host + ":" + port);
CloseableHttpClient delegate = mock(CloseableHttpClient.class);
final CloseableHttpResponse response = mock(CloseableHttpResponse.class);
Locale locale = new Locale("en");
doReturn(locale).when(response).getLocale();
StatusLine statusLine = mock(StatusLine.class);
doReturn(200).when(statusLine).getStatusCode();
doReturn(statusLine).when(response).getStatusLine();
final CloseableHttpResponse fourOFourResponse = mock(CloseableHttpResponse.class);
doReturn(locale).when(fourOFourResponse).getLocale();
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContentLength(5);
entity.setContent(new ByteArrayInputStream("error".getBytes()));
doReturn(entity).when(fourOFourResponse).getEntity();
StatusLine fourOFourStatusLine = mock(StatusLine.class);
doReturn(404).when(fourOFourStatusLine).getStatusCode();
doReturn(fourOFourStatusLine).when(fourOFourResponse).getStatusLine();
......@@ -525,7 +584,7 @@ public class RibbonLoadBalancingHttpClientTests {
HttpUriRequest uriRequest = mock(HttpUriRequest.class);
doReturn(uri).when(uriRequest).getURI();
doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class));
RibbonApacheHttpResponse returnedResponse = client.execute(request, null);
client.execute(request, null);
verify(fourOFourResponse, times(1)).close();
verify(delegate, times(2)).execute(any(HttpUriRequest.class));
verify(lb, times(1)).chooseServer(eq(serviceName));
......@@ -777,7 +836,7 @@ public class RibbonLoadBalancingHttpClientTests {
count++;
}
public int getCount() {
int getCount() {
return count;
}
......@@ -811,7 +870,7 @@ public class RibbonLoadBalancingHttpClientTests {
}};
}
public int getOnError() {
int getOnError() {
return onError;
}
}
......
/*
* Copyright 2013-2018 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 okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.net.URI;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
/**
* @author Ryan Baxter
*/
@RunWith(MockitoJUnitRunner.class)
public class OkHttpStatusCodeExceptionTest {
@Test
public void getResponse() throws Exception {
Headers headers = new Headers.Builder().add("foo", "bar").build();
Response response = new Response.Builder().code(200).headers(headers).code(200).message("Success")
.body(ResponseBody.create(MediaType.parse("text/plain"), "foo")).protocol(Protocol.HTTP_1_1)
.request(new Request.Builder().url("http://service.com").build()).build();
ResponseBody body = response.peekBody(Integer.MAX_VALUE);
OkHttpStatusCodeException ex = new OkHttpStatusCodeException("service", response, body, new URI("http://service.com"));
assertEquals(headers, ex.getResponse().headers());
assertEquals(200, ex.getResponse().code());
assertEquals("Success", ex.getResponse().message());
assertEquals("foo", ex.getResponse().body().string());
assertEquals(Protocol.HTTP_1_1, ex.getResponse().protocol());
}
}
\ No newline at end of file
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