Merge branch 'master' of https://github.com/spring-cloud/spring-cloud-netflix
This commit is contained in:
@@ -976,6 +976,7 @@ Spring Cloud Netflix _does not_ provide the following beans by default for feign
|
||||
* `ErrorDecoder`
|
||||
* `Request.Options`
|
||||
* `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:
|
||||
|
||||
@@ -1768,6 +1769,56 @@ zuul:
|
||||
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
|
||||
|
||||
|
||||
@@ -17,14 +17,17 @@
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import feign.hystrix.HystrixFeign;
|
||||
import org.springframework.util.Assert;
|
||||
import feign.hystrix.SetterFactory;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Erik Kringen
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
class HystrixTargeter implements Targeter {
|
||||
@@ -36,6 +39,11 @@ class HystrixTargeter implements Targeter {
|
||||
return feign.target(target);
|
||||
}
|
||||
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();
|
||||
if (fallback != void.class) {
|
||||
return targetWithFallback(factory.getName(), context, target, builder, fallback);
|
||||
@@ -95,4 +103,9 @@ class HystrixTargeter implements Targeter {
|
||||
}
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor;
|
||||
import org.springframework.cloud.netflix.feign.annotation.PathVariableParameterProcessor;
|
||||
import org.springframework.cloud.netflix.feign.annotation.RequestHeaderParameterProcessor;
|
||||
@@ -324,9 +323,7 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
// has a parameter name
|
||||
return parameterNames != null && parameterNames.length > parameterIndex
|
||||
// has a type
|
||||
&& parameterTypes != null && parameterTypes.length > parameterIndex
|
||||
// and it is a simple property
|
||||
&& BeanUtils.isSimpleProperty(parameterTypes[parameterIndex].getClass());
|
||||
&& parameterTypes != null && parameterTypes.length > parameterIndex;
|
||||
}
|
||||
|
||||
private class SimpleAnnotatedParameterContext
|
||||
|
||||
@@ -71,7 +71,7 @@ public class RibbonUtils {
|
||||
public static URI updateToHttpsIfNeeded(URI uri, IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
Server server) {
|
||||
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");
|
||||
if (uri.getRawQuery() != null) {
|
||||
// When building the URI, UriComponentsBuilder verify the allowed characters and does not
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters.post;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -184,28 +185,33 @@ public class SendResponseFilter extends ZuulFilter {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
Object zuulResponse = RequestContext.getCurrentContext()
|
||||
.get("zuulResponse");
|
||||
if (zuulResponse instanceof Closeable) {
|
||||
((Closeable) zuulResponse).close();
|
||||
}
|
||||
outStream.flush();
|
||||
// The container will close the stream for us
|
||||
}
|
||||
catch (IOException ex) {
|
||||
log.warn("Error while sending response to client: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResponse(InputStream zin, OutputStream out) throws Exception {
|
||||
try {
|
||||
byte[] bytes = buffers.get();
|
||||
int bytesRead = -1;
|
||||
while ((bytesRead = zin.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
catch(IOException ioe) {
|
||||
log.warn("Error while sending response to client: "+ioe.getMessage());
|
||||
byte[] bytes = buffers.get();
|
||||
int bytesRead = -1;
|
||||
while ((bytesRead = zin.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.InvalidMediaTypeException;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -52,10 +53,16 @@ import static org.springframework.cloud.netflix.zuul.filters.support.FilterConst
|
||||
*/
|
||||
public class FormBodyWrapperFilter extends ZuulFilter {
|
||||
|
||||
private FormHttpMessageConverter formHttpMessageConverter;
|
||||
private Field requestField;
|
||||
private Field servletRequestField;
|
||||
|
||||
public FormBodyWrapperFilter() {
|
||||
this(new AllEncompassingFormHttpMessageConverter());
|
||||
}
|
||||
|
||||
public FormBodyWrapperFilter(FormHttpMessageConverter formHttpMessageConverter) {
|
||||
this.formHttpMessageConverter = formHttpMessageConverter;
|
||||
this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,
|
||||
"req", HttpServletRequest.class);
|
||||
this.servletRequestField = ReflectionUtils.findField(ServletRequestWrapper.class,
|
||||
@@ -139,8 +146,6 @@ public class FormBodyWrapperFilter extends ZuulFilter {
|
||||
|
||||
private int contentLength;
|
||||
|
||||
private AllEncompassingFormHttpMessageConverter converter = new AllEncompassingFormHttpMessageConverter();
|
||||
|
||||
public FormBodyRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
this.request = request;
|
||||
@@ -184,7 +189,7 @@ public class FormBodyWrapperFilter extends ZuulFilter {
|
||||
|
||||
this.contentType = MediaType.valueOf(this.request.getContentType());
|
||||
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
|
||||
this.contentType = data.getHeaders().getContentType();
|
||||
this.contentData = data.getInput();
|
||||
|
||||
@@ -218,6 +218,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
|
||||
|
||||
protected void setResponse(ClientHttpResponse resp)
|
||||
throws ClientException, IOException {
|
||||
RequestContext.getCurrentContext().set("zuulResponse", resp);
|
||||
this.helper.setResponse(resp.getStatusCode().value(),
|
||||
resp.getBody() == null ? null : resp.getBody(), resp.getHeaders());
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.ProtocolException;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.RedirectStrategy;
|
||||
import org.apache.http.client.config.CookieSpecs;
|
||||
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.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
@@ -192,8 +192,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
this.helper.addIgnoredHeaders();
|
||||
|
||||
try {
|
||||
HttpResponse response = forward(this.httpClient, verb, uri, request, headers,
|
||||
params, requestEntity);
|
||||
CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
|
||||
headers, params, requestEntity);
|
||||
setResponse(response);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -278,8 +278,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
}).build();
|
||||
}
|
||||
|
||||
private HttpResponse forward(HttpClient httpclient, String verb, String uri,
|
||||
HttpServletRequest request, MultiValueMap<String, String> headers,
|
||||
private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
|
||||
String uri, HttpServletRequest request, MultiValueMap<String, String> headers,
|
||||
MultiValueMap<String, String> params, InputStream requestEntity)
|
||||
throws Exception {
|
||||
Map<String, Object> info = this.helper.debug(verb, uri, headers, params,
|
||||
@@ -301,7 +301,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
try {
|
||||
log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " "
|
||||
+ httpHost.getSchemeName());
|
||||
HttpResponse zuulResponse = forwardRequest(httpclient, httpHost, httpRequest);
|
||||
CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost,
|
||||
httpRequest);
|
||||
this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
|
||||
revertHeaders(zuulResponse.getAllHeaders()));
|
||||
return zuulResponse;
|
||||
@@ -379,8 +380,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
return list.toArray(new BasicHeader[0]);
|
||||
}
|
||||
|
||||
private HttpResponse forwardRequest(HttpClient httpclient, HttpHost httpHost,
|
||||
HttpRequest httpRequest) throws IOException {
|
||||
private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient,
|
||||
HttpHost httpHost, HttpRequest httpRequest) throws IOException {
|
||||
return httpclient.execute(httpHost, httpRequest);
|
||||
}
|
||||
|
||||
@@ -407,6 +408,7 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
}
|
||||
|
||||
private void setResponse(HttpResponse response) throws IOException {
|
||||
RequestContext.getCurrentContext().set("zuulResponse", response);
|
||||
this.helper.setResponse(response.getStatusLine().getStatusCode(),
|
||||
response.getEntity() == null ? null : response.getEntity().getContent(),
|
||||
revertHeaders(response.getAllHeaders()));
|
||||
|
||||
@@ -30,15 +30,25 @@ import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider
|
||||
public abstract class AbstractRibbonCommandFactory implements RibbonCommandFactory {
|
||||
|
||||
private Map<String, ZuulFallbackProvider> fallbackProviderCache;
|
||||
private ZuulFallbackProvider defaultFallbackProvider = null;
|
||||
|
||||
public AbstractRibbonCommandFactory(Set<ZuulFallbackProvider> fallbackProviders){
|
||||
this.fallbackProviderCache = new HashMap<>();
|
||||
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) {
|
||||
return fallbackProviderCache.get(route);
|
||||
ZuulFallbackProvider provider = fallbackProviderCache.get(route);
|
||||
if(provider == null) {
|
||||
provider = defaultFallbackProvider;
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +297,19 @@ public class SpringMvcContractTests {
|
||||
assertNotNull(data.indexToExpander().get(0));
|
||||
}
|
||||
|
||||
@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 {
|
||||
Method method = TestTemplate_MapParams.class.getDeclaredMethod("getTest",
|
||||
@@ -457,6 +470,11 @@ public class SpringMvcContractTests {
|
||||
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 {
|
||||
@RequestMapping(value = "/test", method = RequestMethod.GET)
|
||||
ResponseEntity<TestObject> getTest(@RequestParam Map<String, String> params);
|
||||
|
||||
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
@@ -68,14 +69,19 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
import com.netflix.hystrix.HystrixCommandGroupKey;
|
||||
import com.netflix.hystrix.HystrixCommandKey;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import feign.Target;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import feign.hystrix.SetterFactory;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -85,6 +91,7 @@ import rx.Single;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Jakub Narloch
|
||||
* @author Erik Kringen
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = FeignClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
|
||||
@@ -124,6 +131,9 @@ public class FeignClientTests {
|
||||
@Qualifier("localapp3FeignClient")
|
||||
HystrixClient namedHystrixClient;
|
||||
|
||||
@Autowired
|
||||
HystrixSetterFactoryClient hystrixSetterFactoryClient;
|
||||
|
||||
protected enum Arg {
|
||||
A, B;
|
||||
|
||||
@@ -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
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableFeignClients(clients = { TestClientServiceId.class, TestClient.class,
|
||||
DecodingTestClient.class, HystrixClient.class, HystrixClientWithFallBackFactory.class },
|
||||
defaultConfiguration = TestDefaultFeignConfig.class)
|
||||
DecodingTestClient.class, HystrixClient.class, HystrixClientWithFallBackFactory.class,
|
||||
HystrixSetterFactoryClient.class},
|
||||
defaultConfiguration = TestDefaultFeignConfig.class)
|
||||
@RibbonClients({
|
||||
@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp1", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp2", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp3", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp4", configuration = LocalRibbonClientConfiguration.class)
|
||||
@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp1", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp2", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp3", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp4", configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp5", configuration = LocalRibbonClientConfiguration.class)
|
||||
})
|
||||
protected static class Application {
|
||||
|
||||
@@ -525,12 +564,16 @@ public class FeignClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHystrixCommand() {
|
||||
public void testHystrixCommand() throws NoSuchMethodException {
|
||||
HystrixCommand<List<Hello>> command = this.testClient.getHellosHystrix();
|
||||
assertNotNull("command was null", command);
|
||||
assertEquals(
|
||||
"Hystrix command group name should match the name of the feign client",
|
||||
"localapp", command.getCommandGroup().name());
|
||||
"Hystrix command group name should match the name of the feign client",
|
||||
"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();
|
||||
assertNotNull("hellos was null", hellos);
|
||||
assertEquals("hellos didn't match", hellos, getHelloList());
|
||||
@@ -658,6 +701,25 @@ public class FeignClientTests {
|
||||
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
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
|
||||
@@ -110,6 +110,14 @@ public class RibbonUtilsTests {
|
||||
"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) {
|
||||
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
|
||||
config.setProperty(CommonClientConfigKey.IsSecure, value);
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters.post;
|
||||
|
||||
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.HttpServletResponse;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -34,9 +39,16 @@ import com.netflix.zuul.constants.ZuulConstants;
|
||||
import com.netflix.zuul.context.Debug;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -96,6 +108,34 @@ public class SendResponseFilterTests {
|
||||
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 {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
SendResponseFilter filter = createFilter(content, characterEncoding, response, streamContent);
|
||||
|
||||
@@ -33,7 +33,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = HttpClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = RANDOM_PORT,
|
||||
@SpringBootTest(classes = RibbonCommandFallbackTests.TestConfig.class, webEnvironment = RANDOM_PORT,
|
||||
properties = {
|
||||
"zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
|
||||
"ribbon.ReadTimeout: 1"})
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext;
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@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/**",
|
||||
"ribbon.ReadTimeout: 1"})
|
||||
@DirtiesContext
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.netflix.zuul.context.RequestContext;
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@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/**",
|
||||
"ribbon.ReadTimeout: 1"})
|
||||
@DirtiesContext
|
||||
|
||||
@@ -18,13 +18,35 @@
|
||||
|
||||
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.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.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.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -47,11 +69,90 @@ public abstract class RibbonCommandFallbackTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noFallback() {
|
||||
public void defaultFallback() {
|
||||
String uri = "/another/twolevel/slow";
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + uri, HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user