Commit 384c9b97 by Ryan Baxter
parents 981fa5db c417a9a5
...@@ -976,6 +976,7 @@ Spring Cloud Netflix _does not_ provide the following beans by default for feign ...@@ -976,6 +976,7 @@ Spring Cloud Netflix _does not_ provide the following beans by default for feign
* `ErrorDecoder` * `ErrorDecoder`
* `Request.Options` * `Request.Options`
* `Collection<RequestInterceptor>` * `Collection<RequestInterceptor>`
* `SetterFactory`
Creating a bean of one of those type and placing it in a `@FeignClient` configuration (such as `FooConfiguration` above) allows you to override each one of the beans described. Example: Creating a bean of one of those type and placing it in a `@FeignClient` configuration (such as `FooConfiguration` above) allows you to override each one of the beans described. Example:
...@@ -1768,6 +1769,56 @@ zuul: ...@@ -1768,6 +1769,56 @@ zuul:
customers: /customers/** customers: /customers/**
---- ----
If you would like to provide a default fallback for all routes than you can create a bean of
type `ZuulFallbackProvider` and have the `getRoute` method return `*` or `null`.
[source,java]
----
class MyFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
----
[[zuul-developer-guide]] [[zuul-developer-guide]]
=== Zuul Developer Guide === Zuul Developer Guide
......
...@@ -17,14 +17,17 @@ ...@@ -17,14 +17,17 @@
package org.springframework.cloud.netflix.feign; package org.springframework.cloud.netflix.feign;
import org.springframework.util.Assert;
import feign.Feign; import feign.Feign;
import feign.Target; import feign.Target;
import feign.hystrix.FallbackFactory; import feign.hystrix.FallbackFactory;
import feign.hystrix.HystrixFeign; import feign.hystrix.HystrixFeign;
import org.springframework.util.Assert; import feign.hystrix.SetterFactory;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Erik Kringen
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
class HystrixTargeter implements Targeter { class HystrixTargeter implements Targeter {
...@@ -36,6 +39,11 @@ class HystrixTargeter implements Targeter { ...@@ -36,6 +39,11 @@ class HystrixTargeter implements Targeter {
return feign.target(target); return feign.target(target);
} }
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback(); Class<?> fallback = factory.getFallback();
if (fallback != void.class) { if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback); return targetWithFallback(factory.getName(), context, target, builder, fallback);
...@@ -95,4 +103,9 @@ class HystrixTargeter implements Targeter { ...@@ -95,4 +103,9 @@ class HystrixTargeter implements Targeter {
} }
return (T) fallbackInstance; return (T) fallbackInstance;
} }
private <T> T getOptional(String feignClientName, FeignContext context,
Class<T> beanType) {
return context.getInstance(feignClientName, beanType);
}
} }
...@@ -28,7 +28,6 @@ import java.util.LinkedHashMap; ...@@ -28,7 +28,6 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor; import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.PathVariableParameterProcessor; import org.springframework.cloud.netflix.feign.annotation.PathVariableParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.RequestHeaderParameterProcessor; import org.springframework.cloud.netflix.feign.annotation.RequestHeaderParameterProcessor;
...@@ -324,9 +323,7 @@ public class SpringMvcContract extends Contract.BaseContract ...@@ -324,9 +323,7 @@ public class SpringMvcContract extends Contract.BaseContract
// has a parameter name // has a parameter name
return parameterNames != null && parameterNames.length > parameterIndex return parameterNames != null && parameterNames.length > parameterIndex
// has a type // has a type
&& parameterTypes != null && parameterTypes.length > parameterIndex && parameterTypes != null && parameterTypes.length > parameterIndex;
// and it is a simple property
&& BeanUtils.isSimpleProperty(parameterTypes[parameterIndex].getClass());
} }
private class SimpleAnnotatedParameterContext private class SimpleAnnotatedParameterContext
......
...@@ -71,7 +71,7 @@ public class RibbonUtils { ...@@ -71,7 +71,7 @@ public class RibbonUtils {
public static URI updateToHttpsIfNeeded(URI uri, IClientConfig config, ServerIntrospector serverIntrospector, public static URI updateToHttpsIfNeeded(URI uri, IClientConfig config, ServerIntrospector serverIntrospector,
Server server) { Server server) {
String scheme = uri.getScheme(); String scheme = uri.getScheme();
if (!"https".equals(scheme) && isSecure(config, serverIntrospector, server)) { if (!"".equals(uri.toString()) && !"https".equals(scheme) && isSecure(config, serverIntrospector, server)) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri).scheme("https"); UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri).scheme("https");
if (uri.getRawQuery() != null) { if (uri.getRawQuery() != null) {
// When building the URI, UriComponentsBuilder verify the allowed characters and does not // When building the URI, UriComponentsBuilder verify the allowed characters and does not
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.cloud.netflix.zuul.filters.post; package org.springframework.cloud.netflix.zuul.filters.post;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
...@@ -184,30 +185,35 @@ public class SendResponseFilter extends ZuulFilter { ...@@ -184,30 +185,35 @@ public class SendResponseFilter extends ZuulFilter {
} }
} }
finally { finally {
/**
* Closing the wrapping InputStream itself has no effect on closing the underlying tcp connection since it's a wrapped stream. I guess for http
* keep-alive. When closing the wrapping stream it tries to reach the end of the current request, which is impossible for infinite http streams. So
* instead of closing the InputStream we close the HTTP response.
*
* @author Johannes Edmeier
*/
try { try {
if (is != null) { Object zuulResponse = RequestContext.getCurrentContext()
is.close(); .get("zuulResponse");
if (zuulResponse instanceof Closeable) {
((Closeable) zuulResponse).close();
} }
outStream.flush(); outStream.flush();
// The container will close the stream for us // The container will close the stream for us
} }
catch (IOException ex) { catch (IOException ex) {
log.warn("Error while sending response to client: " + ex.getMessage());
} }
} }
} }
private void writeResponse(InputStream zin, OutputStream out) throws Exception { private void writeResponse(InputStream zin, OutputStream out) throws Exception {
try {
byte[] bytes = buffers.get(); byte[] bytes = buffers.get();
int bytesRead = -1; int bytesRead = -1;
while ((bytesRead = zin.read(bytes)) != -1) { while ((bytesRead = zin.read(bytes)) != -1) {
out.write(bytes, 0, bytesRead); out.write(bytes, 0, bytesRead);
} }
} }
catch(IOException ioe) {
log.warn("Error while sending response to client: "+ioe.getMessage());
}
}
private void addResponseHeaders() { private void addResponseHeaders() {
RequestContext context = RequestContext.getCurrentContext(); RequestContext context = RequestContext.getCurrentContext();
......
...@@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders; ...@@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage; import org.springframework.http.HttpOutputMessage;
import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
...@@ -52,10 +53,16 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst ...@@ -52,10 +53,16 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst
*/ */
public class FormBodyWrapperFilter extends ZuulFilter { public class FormBodyWrapperFilter extends ZuulFilter {
private FormHttpMessageConverter formHttpMessageConverter;
private Field requestField; private Field requestField;
private Field servletRequestField; private Field servletRequestField;
public FormBodyWrapperFilter() { public FormBodyWrapperFilter() {
this(new AllEncompassingFormHttpMessageConverter());
}
public FormBodyWrapperFilter(FormHttpMessageConverter formHttpMessageConverter) {
this.formHttpMessageConverter = formHttpMessageConverter;
this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class, this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,
"req", HttpServletRequest.class); "req", HttpServletRequest.class);
this.servletRequestField = ReflectionUtils.findField(ServletRequestWrapper.class, this.servletRequestField = ReflectionUtils.findField(ServletRequestWrapper.class,
...@@ -139,8 +146,6 @@ public class FormBodyWrapperFilter extends ZuulFilter { ...@@ -139,8 +146,6 @@ public class FormBodyWrapperFilter extends ZuulFilter {
private int contentLength; private int contentLength;
private AllEncompassingFormHttpMessageConverter converter = new AllEncompassingFormHttpMessageConverter();
public FormBodyRequestWrapper(HttpServletRequest request) { public FormBodyRequestWrapper(HttpServletRequest request) {
super(request); super(request);
this.request = request; this.request = request;
...@@ -184,7 +189,7 @@ public class FormBodyWrapperFilter extends ZuulFilter { ...@@ -184,7 +189,7 @@ public class FormBodyWrapperFilter extends ZuulFilter {
this.contentType = MediaType.valueOf(this.request.getContentType()); this.contentType = MediaType.valueOf(this.request.getContentType());
data.getHeaders().setContentType(this.contentType); data.getHeaders().setContentType(this.contentType);
this.converter.write(builder, this.contentType, data); FormBodyWrapperFilter.this.formHttpMessageConverter.write(builder, this.contentType, data);
// copy new content type including multipart boundary // copy new content type including multipart boundary
this.contentType = data.getHeaders().getContentType(); this.contentType = data.getHeaders().getContentType();
this.contentData = data.getInput(); this.contentData = data.getInput();
......
...@@ -218,6 +218,7 @@ public class RibbonRoutingFilter extends ZuulFilter { ...@@ -218,6 +218,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
protected void setResponse(ClientHttpResponse resp) protected void setResponse(ClientHttpResponse resp)
throws ClientException, IOException { throws ClientException, IOException {
RequestContext.getCurrentContext().set("zuulResponse", resp);
this.helper.setResponse(resp.getStatusCode().value(), this.helper.setResponse(resp.getStatusCode().value(),
resp.getBody() == null ? null : resp.getBody(), resp.getHeaders()); resp.getBody() == null ? null : resp.getBody(), resp.getHeaders());
} }
......
...@@ -42,10 +42,10 @@ import org.apache.http.HttpHost; ...@@ -42,10 +42,10 @@ import org.apache.http.HttpHost;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException; import org.apache.http.ProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.RedirectStrategy; import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpPut;
...@@ -192,8 +192,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -192,8 +192,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
this.helper.addIgnoredHeaders(); this.helper.addIgnoredHeaders();
try { try {
HttpResponse response = forward(this.httpClient, verb, uri, request, headers, CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
params, requestEntity); headers, params, requestEntity);
setResponse(response); setResponse(response);
} }
catch (Exception ex) { catch (Exception ex) {
...@@ -278,8 +278,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -278,8 +278,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
}).build(); }).build();
} }
private HttpResponse forward(HttpClient httpclient, String verb, String uri, private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
HttpServletRequest request, MultiValueMap<String, String> headers, String uri, HttpServletRequest request, MultiValueMap<String, String> headers,
MultiValueMap<String, String> params, InputStream requestEntity) MultiValueMap<String, String> params, InputStream requestEntity)
throws Exception { throws Exception {
Map<String, Object> info = this.helper.debug(verb, uri, headers, params, Map<String, Object> info = this.helper.debug(verb, uri, headers, params,
...@@ -301,7 +301,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -301,7 +301,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
try { try {
log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " " log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " "
+ httpHost.getSchemeName()); + httpHost.getSchemeName());
HttpResponse zuulResponse = forwardRequest(httpclient, httpHost, httpRequest); CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost,
httpRequest);
this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(), this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
revertHeaders(zuulResponse.getAllHeaders())); revertHeaders(zuulResponse.getAllHeaders()));
return zuulResponse; return zuulResponse;
...@@ -379,8 +380,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -379,8 +380,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
return list.toArray(new BasicHeader[0]); return list.toArray(new BasicHeader[0]);
} }
private HttpResponse forwardRequest(HttpClient httpclient, HttpHost httpHost, private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient,
HttpRequest httpRequest) throws IOException { HttpHost httpHost, HttpRequest httpRequest) throws IOException {
return httpclient.execute(httpHost, httpRequest); return httpclient.execute(httpHost, httpRequest);
} }
...@@ -407,6 +408,7 @@ public class SimpleHostRoutingFilter extends ZuulFilter { ...@@ -407,6 +408,7 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
} }
private void setResponse(HttpResponse response) throws IOException { private void setResponse(HttpResponse response) throws IOException {
RequestContext.getCurrentContext().set("zuulResponse", response);
this.helper.setResponse(response.getStatusLine().getStatusCode(), this.helper.setResponse(response.getStatusLine().getStatusCode(),
response.getEntity() == null ? null : response.getEntity().getContent(), response.getEntity() == null ? null : response.getEntity().getContent(),
revertHeaders(response.getAllHeaders())); revertHeaders(response.getAllHeaders()));
......
...@@ -30,15 +30,25 @@ import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider ...@@ -30,15 +30,25 @@ import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider
public abstract class AbstractRibbonCommandFactory implements RibbonCommandFactory { public abstract class AbstractRibbonCommandFactory implements RibbonCommandFactory {
private Map<String, ZuulFallbackProvider> fallbackProviderCache; private Map<String, ZuulFallbackProvider> fallbackProviderCache;
private ZuulFallbackProvider defaultFallbackProvider = null;
public AbstractRibbonCommandFactory(Set<ZuulFallbackProvider> fallbackProviders){ public AbstractRibbonCommandFactory(Set<ZuulFallbackProvider> fallbackProviders){
this.fallbackProviderCache = new HashMap<>(); this.fallbackProviderCache = new HashMap<>();
for(ZuulFallbackProvider provider : fallbackProviders) { for(ZuulFallbackProvider provider : fallbackProviders) {
fallbackProviderCache.put(provider.getRoute(), provider); String route = provider.getRoute();
if("*".equals(route) || route == null) {
defaultFallbackProvider = provider;
} else {
fallbackProviderCache.put(route, provider);
}
} }
} }
protected ZuulFallbackProvider getFallbackProvider(String route) { protected ZuulFallbackProvider getFallbackProvider(String route) {
return fallbackProviderCache.get(route); ZuulFallbackProvider provider = fallbackProviderCache.get(route);
if(provider == null) {
provider = defaultFallbackProvider;
}
return provider;
} }
} }
...@@ -298,6 +298,19 @@ public class SpringMvcContractTests { ...@@ -298,6 +298,19 @@ public class SpringMvcContractTests {
} }
@Test @Test
public void testProcessAnnotations_ListParamsWithoutName() throws Exception {
Method method = TestTemplate_ListParamsWithoutName.class.getDeclaredMethod("getTest",
List.class);
MethodMetadata data = this.contract
.parseAndValidateMetadata(method.getDeclaringClass(), method);
assertEquals("/test", data.template().url());
assertEquals("GET", data.template().method());
assertEquals("[{id}]", data.template().queries().get("id").toString());
assertNotNull(data.indexToExpander().get(0));
}
@Test
public void testProcessAnnotations_MapParams() throws Exception { public void testProcessAnnotations_MapParams() throws Exception {
Method method = TestTemplate_MapParams.class.getDeclaredMethod("getTest", Method method = TestTemplate_MapParams.class.getDeclaredMethod("getTest",
Map.class); Map.class);
...@@ -457,6 +470,11 @@ public class SpringMvcContractTests { ...@@ -457,6 +470,11 @@ public class SpringMvcContractTests {
ResponseEntity<TestObject> getTest(@RequestParam("id") List<String> id); ResponseEntity<TestObject> getTest(@RequestParam("id") List<String> id);
} }
public interface TestTemplate_ListParamsWithoutName {
@RequestMapping(value = "/test", method = RequestMethod.GET)
ResponseEntity<TestObject> getTest(@RequestParam List<String> id);
}
public interface TestTemplate_MapParams { public interface TestTemplate_MapParams {
@RequestMapping(value = "/test", method = RequestMethod.GET) @RequestMapping(value = "/test", method = RequestMethod.GET)
ResponseEntity<TestObject> getTest(@RequestParam Map<String, String> params); ResponseEntity<TestObject> getTest(@RequestParam Map<String, String> params);
......
...@@ -25,6 +25,7 @@ import static org.junit.Assert.assertThat; ...@@ -25,6 +25,7 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -68,14 +69,19 @@ import org.springframework.web.bind.annotation.RequestParam; ...@@ -68,14 +69,19 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList; import com.netflix.loadbalancer.ServerList;
import feign.Client; import feign.Client;
import feign.Feign;
import feign.Logger; import feign.Logger;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
import feign.Target;
import feign.hystrix.FallbackFactory; import feign.hystrix.FallbackFactory;
import feign.hystrix.SetterFactory;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
...@@ -85,6 +91,7 @@ import rx.Single; ...@@ -85,6 +91,7 @@ import rx.Single;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Jakub Narloch * @author Jakub Narloch
* @author Erik Kringen
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FeignClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = { @SpringBootTest(classes = FeignClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
...@@ -124,6 +131,9 @@ public class FeignClientTests { ...@@ -124,6 +131,9 @@ public class FeignClientTests {
@Qualifier("localapp3FeignClient") @Qualifier("localapp3FeignClient")
HystrixClient namedHystrixClient; HystrixClient namedHystrixClient;
@Autowired
HystrixSetterFactoryClient hystrixSetterFactoryClient;
protected enum Arg { protected enum Arg {
A, B; A, B;
...@@ -298,18 +308,47 @@ public class FeignClientTests { ...@@ -298,18 +308,47 @@ public class FeignClientTests {
} }
} }
@FeignClient(name = "localapp5", configuration = TestHystrixSetterFactoryClientConfig.class)
protected interface HystrixSetterFactoryClient {
@RequestMapping(method = RequestMethod.GET, path = "/hellos")
HystrixCommand<List<Hello>> getHellosHystrix();
}
public static class TestHystrixSetterFactoryClientConfig {
public static final String SETTER_PREFIX = "SETTER-";
@Bean
public SetterFactory commandKeyIsRequestLineSetterFactory() {
return new SetterFactory() {
@Override public HystrixCommand.Setter create(Target<?> target,
Method method) {
String groupKey = SETTER_PREFIX + target.name();
RequestMapping requestMapping = method
.getAnnotation(RequestMapping.class);
String commandKey =
SETTER_PREFIX + requestMapping.method()[0] + " " + requestMapping
.path()[0];
return HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
}
};
}
}
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@RestController @RestController
@EnableFeignClients(clients = { TestClientServiceId.class, TestClient.class, @EnableFeignClients(clients = { TestClientServiceId.class, TestClient.class,
DecodingTestClient.class, HystrixClient.class, HystrixClientWithFallBackFactory.class }, DecodingTestClient.class, HystrixClient.class, HystrixClientWithFallBackFactory.class,
HystrixSetterFactoryClient.class},
defaultConfiguration = TestDefaultFeignConfig.class) defaultConfiguration = TestDefaultFeignConfig.class)
@RibbonClients({ @RibbonClients({
@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class), @RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp1", configuration = LocalRibbonClientConfiguration.class), @RibbonClient(name = "localapp1", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp2", configuration = LocalRibbonClientConfiguration.class), @RibbonClient(name = "localapp2", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp3", configuration = LocalRibbonClientConfiguration.class), @RibbonClient(name = "localapp3", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp4", configuration = LocalRibbonClientConfiguration.class) @RibbonClient(name = "localapp4", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp5", configuration = LocalRibbonClientConfiguration.class)
}) })
protected static class Application { protected static class Application {
...@@ -525,12 +564,16 @@ public class FeignClientTests { ...@@ -525,12 +564,16 @@ public class FeignClientTests {
} }
@Test @Test
public void testHystrixCommand() { public void testHystrixCommand() throws NoSuchMethodException {
HystrixCommand<List<Hello>> command = this.testClient.getHellosHystrix(); HystrixCommand<List<Hello>> command = this.testClient.getHellosHystrix();
assertNotNull("command was null", command); assertNotNull("command was null", command);
assertEquals( assertEquals(
"Hystrix command group name should match the name of the feign client", "Hystrix command group name should match the name of the feign client",
"localapp", command.getCommandGroup().name()); "localapp", command.getCommandGroup().name());
String configKey = Feign.configKey(TestClient.class,
TestClient.class.getMethod("getHellosHystrix", (Class<?>[]) null));
assertEquals("Hystrix command key name should match the feign config key",
configKey, command.getCommandKey().name());
List<Hello> hellos = command.execute(); List<Hello> hellos = command.execute();
assertNotNull("hellos was null", hellos); assertNotNull("hellos was null", hellos);
assertEquals("hellos didn't match", hellos, getHelloList()); assertEquals("hellos didn't match", hellos, getHelloList());
...@@ -658,6 +701,25 @@ public class FeignClientTests { ...@@ -658,6 +701,25 @@ public class FeignClientTests {
assertNotNull("namedHystrixClient was null", this.namedHystrixClient); assertNotNull("namedHystrixClient was null", this.namedHystrixClient);
} }
@Test
public void testHystrixSetterFactory() {
HystrixCommand<List<Hello>> command = this.hystrixSetterFactoryClient
.getHellosHystrix();
assertNotNull("command was null", command);
String setterPrefix = TestHystrixSetterFactoryClientConfig.SETTER_PREFIX;
assertEquals(
"Hystrix command group name should match the name of the feign client with a prefix of "
+ setterPrefix, setterPrefix + "localapp5",
command.getCommandGroup().name());
assertEquals(
"Hystrix command key name should match the request method (space) request path with a prefix of "
+ setterPrefix, setterPrefix + "GET /hellos",
command.getCommandKey().name());
List<Hello> hellos = command.execute();
assertNotNull("hellos was null", hellos);
assertEquals("hellos didn't match", hellos, getHelloList());
}
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
......
...@@ -110,6 +110,14 @@ public class RibbonUtilsTests { ...@@ -110,6 +110,14 @@ public class RibbonUtilsTests {
"https://foo/%20bar?hello=1%202"))); "https://foo/%20bar?hello=1%202")));
} }
@Test
public void emptyStringUri() throws URISyntaxException {
URI original = new URI("");
URI updated = updateToHttpsIfNeeded(original, SECURE_CONFIG, SECURE_INTROSPECTOR, SERVER);
Assert.assertThat("URI should be the emptry string", updated, is(new URI(
"")));
}
static DefaultClientConfigImpl getConfig(boolean value) { static DefaultClientConfigImpl getConfig(boolean value) {
DefaultClientConfigImpl config = new DefaultClientConfigImpl(); DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.setProperty(CommonClientConfigKey.IsSecure, value); config.setProperty(CommonClientConfigKey.IsSecure, value);
......
...@@ -17,8 +17,13 @@ ...@@ -17,8 +17,13 @@
package org.springframework.cloud.netflix.zuul.filters.post; package org.springframework.cloud.netflix.zuul.filters.post;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
...@@ -34,9 +39,16 @@ import com.netflix.zuul.constants.ZuulConstants; ...@@ -34,9 +39,16 @@ import com.netflix.zuul.constants.ZuulConstants;
import com.netflix.zuul.context.Debug; import com.netflix.zuul.context.Debug;
import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.context.RequestContext;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.X_ZUUL_DEBUG_HEADER; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.X_ZUUL_DEBUG_HEADER;
/** /**
...@@ -96,6 +108,34 @@ public class SendResponseFilterTests { ...@@ -96,6 +108,34 @@ public class SendResponseFilterTests {
assertThat("wrong origin content length", contentLength, equalTo("6")); assertThat("wrong origin content length", contentLength, equalTo("6"));
} }
@Test
public void closeResponseOutpusStreamError() throws Exception {
HttpServletResponse response = mock(HttpServletResponse.class);
RequestContext context = new RequestContext();
context.setRequest(new MockHttpServletRequest());
context.setResponse(response);
context.setResponseDataStream(new ByteArrayInputStream("Hello\n".getBytes("UTF-8")));
Closeable zuulResponse = mock(Closeable.class);
context.set("zuulResponse", zuulResponse);
RequestContext.testSetCurrentContext(context);
SendResponseFilter filter = new SendResponseFilter();
ServletOutputStream zuuloutputstream = mock(ServletOutputStream.class);
doThrow(new IOException("Response to client closed")).when(zuuloutputstream).write(isA(byte[].class), anyInt(), anyInt());
when(response.getOutputStream()).thenReturn(zuuloutputstream);
try {
filter.run();
} catch (UndeclaredThrowableException ex) {
assertThat(ex.getUndeclaredThrowable().getMessage(), is("Response to client closed"));
}
verify(zuulResponse).close();
}
private void runFilter(String characterEncoding, String content, boolean streamContent) throws Exception { private void runFilter(String characterEncoding, String content, boolean streamContent) throws Exception {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
SendResponseFilter filter = createFilter(content, characterEncoding, response, streamContent); SendResponseFilter filter = createFilter(content, characterEncoding, response, streamContent);
......
...@@ -33,7 +33,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen ...@@ -33,7 +33,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = RANDOM_PORT, @SpringBootTest(classes = RibbonCommandFallbackTests.TestConfig.class, webEnvironment = RANDOM_PORT,
properties = { properties = {
"zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
"ribbon.ReadTimeout: 1"}) "ribbon.ReadTimeout: 1"})
......
...@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext; ...@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext;
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = OkHttpRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { @SpringBootTest(classes = RibbonCommandFallbackTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = {
"zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
"ribbon.ReadTimeout: 1"}) "ribbon.ReadTimeout: 1"})
@DirtiesContext @DirtiesContext
......
...@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext; ...@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext;
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = RestClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { @SpringBootTest(classes = RibbonCommandFallbackTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = {
"zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
"ribbon.ReadTimeout: 1"}) "ribbon.ReadTimeout: 1"})
@DirtiesContext @DirtiesContext
......
...@@ -18,13 +18,35 @@ ...@@ -18,13 +18,35 @@
package org.springframework.cloud.netflix.zuul.filters.route.support; package org.springframework.cloud.netflix.zuul.filters.route.support;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Set;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.web.client.TestRestTemplate; 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.SpringClientFactory;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.bind.annotation.RestController;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
...@@ -47,11 +69,90 @@ public abstract class RibbonCommandFallbackTests { ...@@ -47,11 +69,90 @@ public abstract class RibbonCommandFallbackTests {
} }
@Test @Test
public void noFallback() { public void defaultFallback() {
String uri = "/another/twolevel/slow"; String uri = "/another/twolevel/slow";
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);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("default fallback", result.getBody());
}
// Don't use @SpringBootApplication because we don't want to component scan
@Configuration
@EnableAutoConfiguration
@RestController
@EnableZuulProxy
@RibbonClients({
@RibbonClient(name = "simple", configuration = ZuulProxyTestBase.SimpleRibbonClientConfiguration.class),
@RibbonClient(name = "another", configuration = ZuulProxyTestBase.AnotherRibbonClientConfiguration.class)})
public static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication {
@Autowired(required = false)
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
@Bean
public RibbonCommandFactory<?> ribbonCommandFactory(
final SpringClientFactory clientFactory) {
return new HttpClientRibbonCommandFactory(clientFactory, new ZuulProperties(),
zuulFallbackProviders);
}
@Bean
public ZuulProxyTestBase.MyErrorController myErrorController(
ErrorAttributes errorAttributes) {
return new ZuulProxyTestBase.MyErrorController(errorAttributes);
}
@Bean
public ZuulFallbackProvider defaultFallbackProvider() {
return new DefaultFallbackProvider();
}
}
public static class DefaultFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return null;
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("default fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return headers;
}
};
}
} }
} }
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