Moving new code over from netflix

This commit is contained in:
Ryan Baxter
2018-02-20 14:27:38 -05:00
parent 8138dbd792
commit 26d8e6450b
12 changed files with 236 additions and 26 deletions

View File

@@ -16,12 +16,14 @@
package org.springframework.cloud.openfeign.encoding;
import feign.Client;
import feign.Feign;
import feign.httpclient.ApacheHttpClient;
import okhttp3.OkHttpClient;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
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.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
@@ -37,8 +39,11 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(FeignClientEncodingProperties.class)
@ConditionalOnClass(Feign.class)
@ConditionalOnBean(ApacheHttpClient.class)
@ConditionalOnBean(Client.class)
@ConditionalOnProperty(value = "feign.compression.response.enabled", matchIfMissing = false)
//The OK HTTP client uses "transparent" compression.
//If the accept-encoding header is present it disable transparent compression
@ConditionalOnMissingBean(OkHttpClient.class)
@AutoConfigureAfter(FeignAutoConfiguration.class)
public class FeignAcceptGzipEncodingAutoConfiguration {

View File

@@ -16,12 +16,14 @@
package org.springframework.cloud.openfeign.encoding;
import feign.Client;
import feign.Feign;
import feign.httpclient.ApacheHttpClient;
import okhttp3.OkHttpClient;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
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.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
@@ -37,7 +39,10 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(FeignClientEncodingProperties.class)
@ConditionalOnClass(Feign.class)
@ConditionalOnBean(ApacheHttpClient.class)
@ConditionalOnBean(Client.class)
//The OK HTTP client uses "transparent" compression.
//If the content-encoding header is present it disable transparent compression
@ConditionalOnMissingBean(OkHttpClient.class)
@ConditionalOnProperty(value = "feign.compression.request.enabled", matchIfMissing = false)
@AutoConfigureAfter(FeignAutoConfiguration.class)
public class FeignContentGzipEncodingAutoConfiguration {

View File

@@ -28,6 +28,7 @@ import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -78,17 +79,31 @@ class HttpClientFeignLoadBalancedConfiguration {
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "true")
public CloseableHttpClient customHttpClient(HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement().useSystemProperties();
this.httpClient = createClient(builder, httpClientConnectionManager, httpClientProperties);
return this.httpClient;
}
@Bean
@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "false", matchIfMissing = true)
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
this.httpClient = createClient(httpClientFactory.createBuilder(), httpClientConnectionManager, httpClientProperties);
return this.httpClient;
}
private CloseableHttpClient createClient(HttpClientBuilder builder, HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build();
this.httpClient = httpClientFactory.createBuilder().
setDefaultRequestConfig(defaultRequestConfig).
CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig).
setConnectionManager(httpClientConnectionManager).build();
return this.httpClient;
return httpClient;
}
@PreDestroy

View File

@@ -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.RibbonProperties;
@@ -40,6 +41,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.IClientConfig;
@@ -123,22 +125,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);
}
});
}

View File

@@ -0,0 +1,45 @@
/*
* 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.openfeign.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;
}
}

View File

@@ -17,6 +17,8 @@
package org.springframework.cloud.openfeign.support;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
@@ -101,7 +103,7 @@ public class SpringDecoder implements Decoder {
@Override
public HttpHeaders getHeaders() {
return FeignUtils.getHttpHeaders(this.response.headers());
return getHttpHeaders(this.response.headers());
}
}

View File

@@ -38,6 +38,9 @@ import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
/**
* @author Spencer Gibb
*/
@@ -94,7 +97,7 @@ public class SpringEncoder implements Encoder {
request.headers(null);
// converters can modify headers, so update the request
// with the modified headers
request.headers(FeignUtils.getHeaders(outputMessage.getHeaders()));
request.headers(getHeaders(outputMessage.getHeaders()));
// do not use charset for binary data
if (messageConverter instanceof ByteArrayHttpMessageConverter) {
@@ -121,7 +124,7 @@ public class SpringEncoder implements Encoder {
private final HttpHeaders httpHeaders;
private FeignOutputMessage(RequestTemplate request) {
httpHeaders = FeignUtils.getHttpHeaders(request.headers());
httpHeaders = getHttpHeaders(request.headers());
}
@Override

View File

@@ -17,12 +17,12 @@
package org.springframework.cloud.openfeign.beans.extra;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.beans.FeignClientTests;
import org.springframework.cloud.openfeign.beans.FeignClientTests.Hello;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value = "otherapp", qualifier = "uniquequalifier")
public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
FeignClientTests.Hello getHello();
Hello getHello();
}

View File

@@ -28,6 +28,8 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.RibbonRequest;
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.RibbonResponse;
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
@@ -93,7 +95,7 @@ public class FeignLoadBalancerTests {
this.inspector);
Request request = new RequestTemplate().method("GET").append("http://foo/")
.request();
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request,
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request,
new URI(request.url()));
Response response = Response.create(200, "Test",
@@ -101,7 +103,7 @@ public class FeignLoadBalancerTests {
when(this.delegate.execute(any(Request.class), any(Options.class)))
.thenReturn(response);
FeignLoadBalancer.RibbonResponse resp = this.feignLoadBalancer.execute(ribbonRequest, null);
RibbonResponse resp = this.feignLoadBalancer.execute(ribbonRequest, null);
assertThat(resp.getRequestedURI(), is(new URI("http://foo/")));
}
@@ -158,7 +160,7 @@ public class FeignLoadBalancerTests {
assertThat(request.url(),is(url));
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate,request,new URI(request.url()));
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate,request,new URI(request.url()));
Request cloneRequest = ribbonRequest.toRequest();

View File

@@ -38,7 +38,6 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**

View File

@@ -21,12 +21,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;
@@ -52,6 +57,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;
@@ -67,6 +73,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;

View File

@@ -0,0 +1,60 @@
/*
* 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.openfeign.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()));
}
}