diff --git a/docs/pom.xml b/docs/pom.xml index 750a06999..baa750767 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT spring-cloud-netflix-docs pom diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 539611160..129b47dd3 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -970,6 +970,45 @@ This replaces the `SpringMvcContract` with `feign.Contract.Default` and adds a ` Default configurations can be specified in the `@EnableFeignClients` attribute `defaultConfiguration` in a similar manner as described above. The difference is that this configuration will apply to _all_ feign clients. +=== Creating Feign Clients Manually + +In some cases it might be necessary to customize your Feign Clients in a way that is not +possible using the methods above. In this case you can create Clients using the +https://github.com/OpenFeign/feign/#basics[Feign Builder API]. Below is an example +which creates two Feign Clients with the same interface but configures each one with +a separate request interceptor. + +[source,java,indent=0] +---- +@Import(FeignClientsConfiguration.class) +class FooController { + + private FooClient fooClient; + + private FooClient adminClient; + + @Autowired + public FooController( + ResponseEntityDecoder decoder, SpringEncoder encoder, Client client) { + this.fooClient = Feign.builder().client(client) + .encoder(encoder) + .decoder(decoder) + .requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) + .target(FooClient.class, "http://PROD-SVC"); + this.adminClient = Feign.builder().client(client) + .encoder(encoder) + .decoder(decoder) + .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) + .target(FooClient.class, "http://PROD-SVC"); + } +} +---- + +NOTE: In the above example `FeignClientsConfiguration.class` is the default configuration +provided by Spring Cloud Netflix. + +NOTE: `PROD-SVC` is the name of the service the Clients will be making requests to. + [[spring-cloud-feign-hystrix]] === Feign Hystrix Support @@ -1012,6 +1051,30 @@ static class HystrixClientFallback implements HystrixClient { } ---- +If one needs access to the cause that made the fallback trigger, one can use the `fallbackFactory` attribute inside `@FeignClient`. + +[source,java,indent=0] +---- +@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class) +protected interface HystrixClient { + @RequestMapping(method = RequestMethod.GET, value = "/hello") + Hello iFailSometimes(); +} + +@Component +static class HystrixClientFallbackFactory implements FallbackFactory { + @Override + public HystrixClient create(Throwable cause) { + return new HystrixClientWithFallBackFactory() { + @Override + public Hello iFailSometimes() { + return new Hello("fallback; reason was: " + cause.getMessage()); + } + }; + } +} +---- + WARNING: There is a limitation with the implementation of fallbacks in Feign and how Hystrix fallbacks work. Fallbacks are currently not supported for methods that return `com.netflix.hystrix.HystrixCommand` and `rx.Observable`. [[spring-cloud-feign-inheritance]] @@ -1173,7 +1236,8 @@ To enable it, annotate a Spring Boot main class with service. By convention, a service with the ID "users", will receive requests from the proxy located at `/users` (with the prefix stripped). The proxy uses Ribbon to locate an instance to forward to -via discovery, and all requests are executed in a hystrix command, so +via discovery, and all requests are executed in a +<>, so failures will show up in Hystrix metrics, and once the circuit is open the proxy will not try to contact the service. @@ -1363,7 +1427,7 @@ path rendering the `users` path unreachable. === Zuul Http Client The default HTTP client used by zuul is now backed by the Apache HTTP Client instead of the -deprecated Ribbon `RestClient. To use `RestClient` or to use the `okhttp3.OkHttpClient` set +deprecated Ribbon `RestClient`. To use `RestClient` or to use the `okhttp3.OkHttpClient` set `ribbon.restclient.enabled=true` or `ribbon.okhttp.enabled=true` respectively. === Cookies and Sensitive Headers @@ -1541,6 +1605,70 @@ possible filters that are enabled. If you want to disable one, simply set `org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter` set `zuul.SendResponseFilter.post.disable=true`. +[[hystrix-fallbacks-for-routes]] +=== Providing Hystrix Fallbacks For Routes + +When a circuit for a given route in Zuul is tripped you can provide a fallback response +by creating a bean of type `ZuulFallbackProvider`. Within this bean you need to specify +the route ID the fallback is for and provide a `ClientHttpResponse` to return +as a fallback. Here is a very simple `ZuulFallbackProvider` implementation. + +[source,java] +---- +class MyFallbackProvider implements ZuulFallbackProvider { + @Override + public String getRoute() { + return "customers"; + } + + @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; + } + }; + } +} +---- + +And here is what the route configuration would look like. + +[source,yaml] +---- +zuul: + routes: + customers: /customers/** +---- + === Polyglot support with Sidecar Do you have non-jvm languages you want to take advantage of Eureka, Ribbon and @@ -1734,6 +1862,18 @@ If Spring AOP is enabled and `org.aspectj:aspectjweaver` is present on your runt 3. URI, sanitized for Atlas 4. Client name +WARNING: Avoid using hardcoded url parameters within `RestTemplate`. When targeting dynamic endpoints use URL variables. This will avoid potential "GC Overhead Limit Reached" issues where `ServoMonitorCache` treats each url as a unique key. + +[source,java,indent=0] +---- +// recommended +String orderid = "1"; +restTemplate.getForObject("http://testeurekabrixtonclient/orders/{orderid}", String.class, orderid) + +// avoid +restTemplate.getForObject("http://testeurekabrixtonclient/orders/1", String.class) +---- + [[netflix-metrics-spectator]] === Metrics Collection: Spectator diff --git a/pom.xml b/pom.xml index b0994d49a..ca7de3596 100644 --- a/pom.xml +++ b/pom.xml @@ -3,14 +3,14 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT pom Spring Cloud Netflix Spring Cloud Netflix org.springframework.cloud spring-cloud-build - 1.2.1.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT @@ -24,8 +24,8 @@ ${basedir} 4.0.27.Final 2.7.3 - 1.1.4.BUILD-SNAPSHOT - 1.2.1.BUILD-SNAPSHOT + 1.1.6.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT Brooklyn.BUILD-SNAPSHOT @@ -79,6 +79,13 @@ pom import + + org.springframework.cloud + spring-cloud-commons + test-jar + test + ${spring-cloud-commons.version} + org.springframework.cloud spring-cloud-config-dependencies diff --git a/spring-cloud-netflix-core/pom.xml b/spring-cloud-netflix-core/pom.xml index 8f6985cc3..681bde90d 100644 --- a/spring-cloud-netflix-core/pom.xml +++ b/spring-cloud-netflix-core/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-core @@ -44,6 +44,16 @@ spring-boot-starter-web true + + org.springframework.boot + spring-boot-starter-aop + true + + + org.springframework.retry + spring-retry + true + org.springframework.cloud spring-cloud-commons @@ -183,6 +193,12 @@ spring-boot-starter-test test + + org.springframework.cloud + spring-cloud-commons + test-jar + test + org.aspectj aspectjweaver diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/DefaultFeignLoggerFactory.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/DefaultFeignLoggerFactory.java new file mode 100644 index 000000000..d7ca25d18 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/DefaultFeignLoggerFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign; + +import feign.Logger; +import feign.slf4j.Slf4jLogger; + +/** + * @author Venil Noronha + */ +public class DefaultFeignLoggerFactory implements FeignLoggerFactory { + + private Logger logger; + + public DefaultFeignLoggerFactory(Logger logger) { + this.logger = logger; + } + + @Override + public Logger create(Class type) { + return this.logger != null ? this.logger : new Slf4jLogger(type); + } + +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java index 8c2e361f6..1dc139a16 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java @@ -90,6 +90,16 @@ public @interface FeignClient { */ Class fallback() default void.class; + /** + * Define a fallback factory for the specified Feign client interface. The fallback + * factory must produce instances of fallback classes that implement the interface + * annotated by {@link FeignClient}. The fallback factory must be a valid spring + * bean. + * + * @see feign.hystrix.FallbackFactory for details. + */ + Class fallbackFactory() default void.class; + /** * Path prefix to be used by all method-level mappings. Can be used with or without * @RibbonClient. diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java index fa345d88c..bf21845f3 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientFactoryBean.java @@ -39,7 +39,6 @@ import feign.Target.HardCodedTarget; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; -import feign.slf4j.Slf4jLogger; import lombok.Data; import lombok.EqualsAndHashCode; @@ -51,9 +50,9 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = false) class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware { - - @Autowired - private Targeter targeter; + /*********************************** + * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some lifecycle race condition. + ***********************************/ private Class type; @@ -69,6 +68,8 @@ class FeignClientFactoryBean implements FactoryBean, InitializingBean, private Class fallback = void.class; + private Class fallbackFactory = void.class; + @Override public void afterPropertiesSet() throws Exception { Assert.hasText(this.name, "Name must be set"); @@ -81,11 +82,8 @@ class FeignClientFactoryBean implements FactoryBean, InitializingBean, } protected Feign.Builder feign(FeignContext context) { - Logger logger = getOptional(context, Logger.class); - - if (logger == null) { - logger = new Slf4jLogger(this.type); - } + FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); + Logger logger = loggerFactory.create(this.type); // @formatter:off Feign.Builder builder = get(context, Feign.Builder.class) @@ -144,6 +142,7 @@ class FeignClientFactoryBean implements FactoryBean, InitializingBean, Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); + Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, target); } @@ -181,6 +180,7 @@ class FeignClientFactoryBean implements FactoryBean, InitializingBean, } builder.client(client); } + Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsConfiguration.java index 41fca2327..2237c7905 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsConfiguration.java @@ -23,6 +23,9 @@ import feign.Retryer; import feign.codec.Decoder; import feign.codec.Encoder; import feign.hystrix.HystrixFeign; +import java.util.ArrayList; +import java.util.List; + import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -42,9 +45,18 @@ import org.springframework.format.support.FormattingConversionService; import java.util.ArrayList; import java.util.List; +import com.netflix.hystrix.HystrixCommand; + +import feign.Contract; +import feign.Feign; +import feign.Logger; +import feign.codec.Decoder; +import feign.codec.Encoder; +import feign.hystrix.HystrixFeign; /** * @author Dave Syer + * @author Venil Noronha */ @Configuration public class FeignClientsConfiguration { @@ -58,6 +70,9 @@ public class FeignClientsConfiguration { @Autowired(required = false) private List feignFormatterRegistrars = new ArrayList<>(); + @Autowired(required = false) + private Logger logger; + @Bean @ConditionalOnMissingBean public Decoder feignDecoder() { @@ -104,4 +119,10 @@ public class FeignClientsConfiguration { return Feign.builder().retryer(Retryer.NEVER_RETRY); } + @Bean + @ConditionalOnMissingBean(FeignLoggerFactory.class) + public FeignLoggerFactory feignLoggerFactory() { + return new DefaultFeignLoggerFactory(logger); + } + } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java index 9020c6e75..53c34ef55 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java @@ -179,6 +179,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); + definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = name + "FeignClient"; diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignLoggerFactory.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignLoggerFactory.java new file mode 100644 index 000000000..9440fdf0d --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignLoggerFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign; + +import feign.Logger; + +/** + * Allows an application to use a custom Feign {@link Logger}. + * + * @author Venil Noronha + */ +public interface FeignLoggerFactory { + + /** + * Factory method to provide a {@link Logger} for a given {@link Class}. + * + * @param type the {@link Class} for which a {@link Logger} instance is to be created + * @return a {@link Logger} instance + */ + public Logger create(Class type); + +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/HystrixTargeter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/HystrixTargeter.java index 5b9ed8d84..66106c702 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/HystrixTargeter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/HystrixTargeter.java @@ -19,6 +19,9 @@ package org.springframework.cloud.netflix.feign; import feign.Feign; import feign.Target; +import feign.hystrix.FallbackFactory; +import feign.hystrix.HystrixFeign; +import org.springframework.util.Assert; /** * @author Spencer Gibb @@ -29,26 +32,67 @@ class HystrixTargeter implements Targeter { @Override public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget target) { - if (factory.getFallback() == void.class - || !(feign instanceof feign.hystrix.HystrixFeign.Builder)) { + if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } + feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; + Class fallback = factory.getFallback(); + if (fallback != void.class) { + return targetWithFallback(factory.getName(), context, target, builder, fallback); + } + Class fallbackFactory = factory.getFallbackFactory(); + if (fallbackFactory != void.class) { + return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory); + } - Object fallbackInstance = context.getInstance(factory.getName(), factory.getFallback()); + return feign.target(target); + } + + private T targetWithFallbackFactory(String feignClientName, FeignContext context, + Target.HardCodedTarget target, + HystrixFeign.Builder builder, + Class fallbackFactoryClass) { + FallbackFactory fallbackFactory = (FallbackFactory) + getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class); + /* We take a sample fallback from the fallback factory to check if it returns a fallback + that is compatible with the annotated feign interface. */ + Object exampleFallback = fallbackFactory.create(new RuntimeException()); + Assert.notNull(exampleFallback, + String.format( + "Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!", + feignClientName)); + if (!target.type().isAssignableFrom(exampleFallback.getClass())) { + throw new IllegalStateException( + String.format( + "Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'", + feignClientName, exampleFallback.getClass(), target.type())); + } + return builder.target(target, fallbackFactory); + } + + + private T targetWithFallback(String feignClientName, FeignContext context, + Target.HardCodedTarget target, + HystrixFeign.Builder builder, Class fallback) { + T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type()); + return builder.target(target, fallbackInstance); + } + + private T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context, + Class beanType, Class targetType) { + Object fallbackInstance = context.getInstance(feignClientName, beanType); if (fallbackInstance == null) { throw new IllegalStateException(String.format( - "No fallback instance of type %s found for feign client %s", - factory.getFallback(), factory.getName())); + "No " + fallbackMechanism + " instance of type %s found for feign client %s", + beanType, feignClientName)); } - if (!target.type().isAssignableFrom(factory.getFallback())) { + if (!targetType.isAssignableFrom(beanType)) { throw new IllegalStateException( String.format( - "Incompatible fallback instance. Fallback of type %s is not assignable to %s for feign client %s", - factory.getFallback(), target.type(), factory.getName())); + "Incompatible " + fallbackMechanism + " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", + beanType, targetType, feignClientName)); } - - feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; - return builder.target(target, (T) fallbackInstance); + return (T) fallbackInstance; } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignLoadBalancer.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignLoadBalancer.java index c1988efc9..5c2ebd0e2 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignLoadBalancer.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignLoadBalancer.java @@ -16,6 +16,34 @@ package org.springframework.cloud.netflix.feign.ribbon; +import feign.Client; +import feign.Request; +import feign.Response; +import feign.Util; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.policy.NeverRetryPolicy; +import org.springframework.retry.support.RetryTemplate; import com.netflix.client.AbstractLoadBalancerAwareClient; import com.netflix.client.ClientException; import com.netflix.client.ClientRequest; @@ -26,40 +54,12 @@ import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; -import feign.Client; -import feign.Request; -import feign.Response; -import feign.Util; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy; -import org.springframework.cloud.client.loadbalancer.LoadBalanceChooser; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; -import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; -import org.springframework.cloud.netflix.ribbon.ServerIntrospector; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpRequest; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.policy.NeverRetryPolicy; -import org.springframework.retry.support.RetryTemplate; - -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded; public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient implements - LoadBalanceChooser { + ServiceInstanceChooser { private final int connectTimeout; private final int readTimeout; diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java index cc12ecd81..55f48ecbd 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +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; @@ -36,9 +37,11 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFact import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; +import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.RestTemplate; import com.netflix.client.IClient; @@ -80,6 +83,7 @@ public class RibbonAutoConfiguration { } @Bean + @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory(SpringClientFactory clientFactory) { return new RibbonLoadBalancedRetryPolicyFactory(clientFactory); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancedRetryPolicyFactory.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancedRetryPolicyFactory.java index 65f843540..563071c35 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancedRetryPolicyFactory.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancedRetryPolicyFactory.java @@ -16,10 +16,10 @@ package org.springframework.cloud.netflix.ribbon; import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.LoadBalanceChooser; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; import org.springframework.http.HttpMethod; /** @@ -34,7 +34,7 @@ public class RibbonLoadBalancedRetryPolicyFactory implements LoadBalancedRetryPo } @Override - public LoadBalancedRetryPolicy create(final String serviceId, final LoadBalanceChooser loadBalanceChooser) { + public LoadBalancedRetryPolicy create(final String serviceId, final ServiceInstanceChooser loadBalanceChooser) { final RibbonLoadBalancerContext lbContext = this.clientFactory .getLoadBalancerContext(serviceId); return new LoadBalancedRetryPolicy() { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java index 784d3c9df..000e75574 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClient.java @@ -16,28 +16,26 @@ package org.springframework.cloud.netflix.ribbon; -import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.Map; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; -import org.springframework.web.util.UriComponentsBuilder; - -import java.io.IOException; -import java.net.URI; -import java.util.Collections; -import java.util.Map; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; /** * @author Spencer Gibb * @author Dave Syer * @author Ryan Baxter */ -public class RibbonLoadBalancerClient implements LoadBalancerClient{ +public class RibbonLoadBalancerClient implements LoadBalancerClient { private SpringClientFactory clientFactory; @@ -52,11 +50,10 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient{ RibbonLoadBalancerContext context = this.clientFactory .getLoadBalancerContext(serviceId); Server server = new Server(instance.getHost(), instance.getPort()); - boolean secure = isSecure(server, serviceId); - URI uri = original; - if (secure) { - uri = UriComponentsBuilder.fromUri(uri).scheme("https").build().toUri(); - } + IClientConfig clientConfig = clientFactory.getClientConfig(serviceId); + ServerIntrospector serverIntrospector = serverIntrospector(serviceId); + URI uri = RibbonUtils.updateToHttpsIfNeeded(original, clientConfig, + serverIntrospector, server); return context.reconstructURIWithServer(server, uri); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RibbonCommandFactoryConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RibbonCommandFactoryConfiguration.java new file mode 100644 index 000000000..957416032 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/RibbonCommandFactoryConfiguration.java @@ -0,0 +1,154 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.zuul; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collections; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; +import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory; +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.cloud.netflix.zuul.filters.route.okhttp.OkHttpRibbonCommandFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +/** + * @author Dave Syer + * + */ +public class RibbonCommandFactoryConfiguration { + + @Configuration + @ConditionalOnRibbonRestClient + protected static class RestClientRibbonConfiguration { + + @Autowired(required = false) + private Set zuulFallbackProviders = Collections.emptySet(); + + @Bean + @ConditionalOnMissingBean + public RibbonCommandFactory ribbonCommandFactory( + SpringClientFactory clientFactory, ZuulProperties zuulProperties) { + return new RestClientRibbonCommandFactory(clientFactory, zuulProperties, + zuulFallbackProviders); + } + } + + @Configuration + @ConditionalOnRibbonOkHttpClient + @ConditionalOnClass(name = "okhttp3.OkHttpClient") + protected static class OkHttpRibbonConfiguration { + + @Autowired(required = false) + private Set zuulFallbackProviders = Collections.emptySet(); + + @Bean + @ConditionalOnMissingBean + public RibbonCommandFactory ribbonCommandFactory( + SpringClientFactory clientFactory, ZuulProperties zuulProperties) { + return new OkHttpRibbonCommandFactory(clientFactory, zuulProperties, + zuulFallbackProviders); + } + } + + @Configuration + @ConditionalOnRibbonHttpClient + protected static class HttpClientRibbonConfiguration { + + @Autowired(required = false) + private Set zuulFallbackProviders = Collections.emptySet(); + + @Bean + @ConditionalOnMissingBean + public RibbonCommandFactory ribbonCommandFactory( + SpringClientFactory clientFactory, ZuulProperties zuulProperties) { + return new HttpClientRibbonCommandFactory(clientFactory, zuulProperties, zuulFallbackProviders); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Conditional(OnRibbonHttpClientCondition.class) + @interface ConditionalOnRibbonHttpClient { } + + private static class OnRibbonHttpClientCondition extends AnyNestedCondition { + public OnRibbonHttpClientCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @Deprecated //remove in Edgware" + @ConditionalOnProperty(name = "zuul.ribbon.httpclient.enabled", matchIfMissing = true) + static class ZuulProperty {} + + @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true) + static class RibbonProperty {} + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Conditional(OnRibbonOkHttpClientCondition.class) + @interface ConditionalOnRibbonOkHttpClient { } + + private static class OnRibbonOkHttpClientCondition extends AnyNestedCondition { + public OnRibbonOkHttpClientCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @Deprecated //remove in Edgware" + @ConditionalOnProperty("zuul.ribbon.okhttp.enabled") + static class ZuulProperty {} + + @ConditionalOnProperty("ribbon.okhttp.enabled") + static class RibbonProperty {} + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Conditional(OnRibbonRestClientCondition.class) + @interface ConditionalOnRibbonRestClient { } + + private static class OnRibbonRestClientCondition extends AnyNestedCondition { + public OnRibbonRestClientCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @Deprecated //remove in Edgware" + @ConditionalOnProperty("zuul.ribbon.restclient.enabled") + static class ZuulProperty {} + + @ConditionalOnProperty("ribbon.restclient.enabled") + static class RibbonProperty {} + } + +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java index 8777cd928..40ea7a9cd 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyConfiguration.java @@ -16,29 +16,21 @@ package org.springframework.cloud.netflix.zuul; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.Collections; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.trace.TraceRepository; -import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.event.HeartbeatEvent; import org.springframework.cloud.client.discovery.event.HeartbeatMonitor; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent; -import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.cloud.netflix.zuul.filters.RouteLocator; @@ -48,26 +40,27 @@ import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientR import org.springframework.cloud.netflix.zuul.filters.discovery.ServiceRouteMapper; import org.springframework.cloud.netflix.zuul.filters.discovery.SimpleServiceRouteMapper; import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter; -import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter; import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter; -import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory; -import org.springframework.cloud.netflix.zuul.filters.route.okhttp.OkHttpRibbonCommandFactory; import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; /** * @author Spencer Gibb * @author Dave Syer */ @Configuration +@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class, + RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class, + RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class }) public class ZuulProxyConfiguration extends ZuulConfiguration { + @SuppressWarnings("rawtypes") @Autowired(required = false) private List requestCustomizers = Collections.emptyList(); @@ -86,124 +79,27 @@ public class ZuulProxyConfiguration extends ZuulConfiguration { @Override @ConditionalOnMissingBean(RouteLocator.class) public DiscoveryClientRouteLocator routeLocator() { - return new DiscoveryClientRouteLocator(this.server.getServletPrefix(), - this.discovery, this.zuulProperties, this.serviceRouteMapper); - } - - @Configuration - @ConditionalOnRibbonHttpClient - protected static class HttpClientRibbonConfiguration { - - @Bean - @ConditionalOnMissingBean - public RibbonCommandFactory ribbonCommandFactory( - SpringClientFactory clientFactory, ZuulProperties zuulProperties) { - return new HttpClientRibbonCommandFactory(clientFactory, zuulProperties); - } - } - - @Configuration - @ConditionalOnRibbonRestClient - protected static class RestClientRibbonConfiguration { - - @Bean - @ConditionalOnMissingBean - public RibbonCommandFactory ribbonCommandFactory( - SpringClientFactory clientFactory, ZuulProperties zuulProperties) { - return new RestClientRibbonCommandFactory(clientFactory, zuulProperties); - } - } - - @Configuration - @ConditionalOnRibbonOkHttpClient - @ConditionalOnClass(name = "okhttp3.OkHttpClient") - protected static class OkHttpRibbonConfiguration { - - @Bean - @ConditionalOnMissingBean - public RibbonCommandFactory ribbonCommandFactory( - SpringClientFactory clientFactory, ZuulProperties zuulProperties) { - return new OkHttpRibbonCommandFactory(clientFactory, zuulProperties); - } - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @Conditional(OnRibbonHttpClientCondition.class) - @interface ConditionalOnRibbonHttpClient { } - - private static class OnRibbonHttpClientCondition extends AnyNestedCondition { - public OnRibbonHttpClientCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @Deprecated //remove in Edgware" - @ConditionalOnProperty(name = "zuul.ribbon.httpclient.enabled", matchIfMissing = true) - static class ZuulProperty {} - - @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true) - static class RibbonProperty {} - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @Conditional(OnRibbonOkHttpClientCondition.class) - @interface ConditionalOnRibbonOkHttpClient { } - - private static class OnRibbonOkHttpClientCondition extends AnyNestedCondition { - public OnRibbonOkHttpClientCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @Deprecated //remove in Edgware" - @ConditionalOnProperty("zuul.ribbon.okhttp.enabled") - static class ZuulProperty {} - - @ConditionalOnProperty("ribbon.okhttp.enabled") - static class RibbonProperty {} - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @Conditional(OnRibbonRestClientCondition.class) - @interface ConditionalOnRibbonRestClient { } - - private static class OnRibbonRestClientCondition extends AnyNestedCondition { - public OnRibbonRestClientCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @Deprecated //remove in Edgware" - @ConditionalOnProperty("zuul.ribbon.restclient.enabled") - static class ZuulProperty {} - - @ConditionalOnProperty("ribbon.restclient.enabled") - static class RibbonProperty {} + return new DiscoveryClientRouteLocator(this.server.getServletPrefix(), this.discovery, this.zuulProperties, + this.serviceRouteMapper); } // pre filters @Bean - public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, - ProxyRequestHelper proxyRequestHelper) { - return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), - this.zuulProperties, proxyRequestHelper); + public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { + return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties, + proxyRequestHelper); } // route filters @Bean public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory ribbonCommandFactory) { - RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, - this.requestCustomizers); + RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers); return filter; } @Bean - public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, - ZuulProperties zuulProperties) { + public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) { return new SimpleHostRoutingFilter(helper, zuulProperties); } @@ -256,8 +152,7 @@ public class ZuulProxyConfiguration extends ZuulConfiguration { } } - private static class ZuulDiscoveryRefreshListener - implements ApplicationListener { + private static class ZuulDiscoveryRefreshListener implements ApplicationListener { private HeartbeatMonitor monitor = new HeartbeatMonitor(); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java index 0f99a99e6..85bdb5997 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java @@ -249,6 +249,12 @@ public class ProxyRequestHelper { MultiValueMap headers) { } + /** + * Get url encoded query string. Pay special attention to single parameters with no values + * and parameter names with colon (:) from use of UriTemplate. + * @param params Un-encoded request parameters + * @return + */ public String getQueryString(MultiValueMap params) { if (params.isEmpty()) { return ""; @@ -260,10 +266,21 @@ public class ProxyRequestHelper { for (String value : params.get(param)) { query.append("&"); query.append(param); - if (!"".equals(value)) { - singles.put(param + i, value); + if (!"".equals(value)) { // don't add =, if original is ?wsdl, output is not ?wsdl= + String key = param; + // if form feed is already part of param name double + // since form feed is used as the colon replacement below + if (key.contains("\f")) { + key = (key.replaceAll("\f", "\f\f")); + } + // colon is special to UriTemplate + if (key.contains(":")) { + key = key.replaceAll(":", "\f"); + } + key = key + i; + singles.put(key, value); query.append("={"); - query.append(param + i); + query.append(key); query.append("}"); } i++; diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java index a19dd3daa..bbffa0212 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java @@ -100,7 +100,7 @@ public class ZuulProperties { private Set ignoredHeaders = new LinkedHashSet<>(); /** - * SECURITY_HEADERS are added to ignored headers if spring security is on the classpath and ignoreSecurityHeaders = true + * Flag to say that SECURITY_HEADERS are added to ignored headers if spring security is on the classpath. * By setting ignoreSecurityHeaders to false we can switch off this default behaviour. This should be used together with * disabling the default spring security headers * see https://docs.spring.io/spring-security/site/docs/current/reference/html/headers.html#default-security-headers diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/FormBodyWrapperFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/FormBodyWrapperFilter.java index 226817629..500ee5a23 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/FormBodyWrapperFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/FormBodyWrapperFilter.java @@ -22,16 +22,14 @@ import java.io.OutputStream; import java.lang.reflect.Field; import java.util.HashSet; import java.util.List; -import java.util.Map.Entry; import java.util.Set; +import java.util.Map.Entry; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.Part; -import org.apache.commons.lang3.StringUtils; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpEntity; @@ -44,6 +42,7 @@ import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.servlet.DispatcherServlet; @@ -65,7 +64,7 @@ public class FormBodyWrapperFilter extends ZuulFilter { this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class, "req", HttpServletRequest.class); this.servletRequestField = ReflectionUtils.findField(ServletRequestWrapper.class, - "request", ServletRequest.class); + "request", ServletRequest.class); Assert.notNull(this.requestField, "HttpServletRequestWrapper.req field not found"); Assert.notNull(this.servletRequestField, @@ -121,7 +120,7 @@ public class FormBodyWrapperFilter extends ZuulFilter { .getField(this.requestField, request); wrapper = new FormBodyRequestWrapper(wrapped); ReflectionUtils.setField(this.requestField, request, wrapper); - if(request instanceof ServletRequestWrapper) { + if (request instanceof ServletRequestWrapper) { ReflectionUtils.setField(this.servletRequestField, request, wrapper); } } @@ -170,7 +169,7 @@ public class FormBodyWrapperFilter extends ZuulFilter { } return this.contentLength; } - + @Override public long getContentLengthLong() { return getContentLength(); @@ -233,7 +232,7 @@ public class FormBodyWrapperFilter extends ZuulFilter { Set result = new HashSet<>(); String query = this.request.getQueryString(); if (query != null) { - for (String value : StringUtils.split(query, "&")) { + for (String value : StringUtils.tokenizeToStringArray(query, "&")) { if (value.contains("=")) { value = value.substring(0, value.indexOf("=")); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java index 6db078a9a..1f65218fa 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java @@ -50,12 +50,11 @@ public class PreDecorationFilter extends ZuulFilter { private ProxyRequestHelper proxyRequestHelper; - public PreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath, - ZuulProperties properties, ProxyRequestHelper proxyRequestHelper) { + public PreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath, ZuulProperties properties, + ProxyRequestHelper proxyRequestHelper) { this.routeLocator = routeLocator; this.properties = properties; - this.urlPathHelper - .setRemoveSemicolonContent(properties.isRemoveSemicolonContent()); + this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent()); this.dispatcherServletPath = dispatcherServletPath; this.proxyRequestHelper = proxyRequestHelper; } @@ -81,8 +80,7 @@ public class PreDecorationFilter extends ZuulFilter { @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); - final String requestURI = this.urlPathHelper - .getPathWithinApplication(ctx.getRequest()); + final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest()); Route route = this.routeLocator.getMatchingRoute(requestURI); if (route != null) { String location = route.getLocation(); @@ -90,12 +88,11 @@ public class PreDecorationFilter extends ZuulFilter { ctx.put("requestURI", route.getPath()); ctx.put("proxy", route.getId()); if (!route.isCustomSensitiveHeaders()) { - this.proxyRequestHelper.addIgnoredHeaders( - this.properties.getSensitiveHeaders().toArray(new String[0])); + this.proxyRequestHelper + .addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0])); } else { - this.proxyRequestHelper.addIgnoredHeaders( - route.getSensitiveHeaders().toArray(new String[0])); + this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0])); } if (route.getRetryable() != null) { @@ -107,8 +104,8 @@ public class PreDecorationFilter extends ZuulFilter { ctx.addOriginResponseHeader("X-Zuul-Service", location); } else if (location.startsWith("forward:")) { - ctx.set("forward.to", StringUtils.cleanPath( - location.substring("forward:".length()) + route.getPath())); + ctx.set("forward.to", + StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath())); ctx.setRouteHost(null); return null; } @@ -119,35 +116,7 @@ public class PreDecorationFilter extends ZuulFilter { ctx.addOriginResponseHeader("X-Zuul-ServiceId", location); } if (this.properties.isAddProxyHeaders()) { - ctx.addZuulRequestHeader("X-Forwarded-Host", toHostHeader(ctx.getRequest())); - ctx.addZuulRequestHeader("X-Forwarded-Port", - String.valueOf(ctx.getRequest().getServerPort())); - ctx.addZuulRequestHeader(ZuulHeaders.X_FORWARDED_PROTO, - ctx.getRequest().getScheme()); - String forwardedPrefix = - ctx.getRequest().getHeader("X-Forwarded-Prefix"); - String contextPath = ctx.getRequest().getContextPath(); - String prefix = StringUtils.hasLength(forwardedPrefix) - ? forwardedPrefix - : (StringUtils.hasLength(contextPath) ? contextPath : null); - if (StringUtils.hasText(route.getPrefix())) { - StringBuilder newPrefixBuilder = new StringBuilder(); - if (prefix != null) { - if (prefix.endsWith("/") - && route.getPrefix().startsWith("/")) { - newPrefixBuilder.append(prefix, 0, - prefix.length() - 1); - } - else { - newPrefixBuilder.append(prefix); - } - } - newPrefixBuilder.append(route.getPrefix()); - prefix = newPrefixBuilder.toString(); - } - if (prefix != null) { - ctx.addZuulRequestHeader("X-Forwarded-Prefix", prefix); - } + addProxyHeaders(ctx, route); String xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For"); String remoteAddr = ctx.getRequest().getRemoteAddr(); if (xforwardedfor == null) { @@ -174,8 +143,7 @@ public class PreDecorationFilter extends ZuulFilter { if (RequestUtils.isZuulServletRequest()) { // remove the Zuul servletPath from the requestUri log.debug("zuulServletPath=" + this.properties.getServletPath()); - fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), - ""); + fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), ""); log.debug("Replaced Zuul servlet path:" + fallBackUri); } else { @@ -194,11 +162,70 @@ public class PreDecorationFilter extends ZuulFilter { return null; } + private void addProxyHeaders(RequestContext ctx, Route route) { + HttpServletRequest request = ctx.getRequest(); + String host = toHostHeader(request); + String port = String.valueOf(request.getServerPort()); + String proto = request.getScheme(); + if (hasHeader(request, "X-Forwarded-Host")) { + host = request.getHeader("X-Forwarded-Host") + "," + host; + if (!hasHeader(request, "X-Forwarded-Port")) { + if (hasHeader(request, "X-Forwarded-Proto")) { + StringBuilder builder = new StringBuilder(); + for (String previous : StringUtils.commaDelimitedListToStringArray(request.getHeader("X-Forwarded-Proto"))) { + if (builder.length()>0) { + builder.append(","); + } + builder.append("https".equals(previous) ? "443" : "80"); + } + builder.append(",").append(port); + port = builder.toString(); + } + } else { + port = request.getHeader("X-Forwarded-Port") + "," + port; + } + proto = request.getHeader("X-Forwarded-Proto") + "," + proto; + } + ctx.addZuulRequestHeader("X-Forwarded-Host", host); + ctx.addZuulRequestHeader("X-Forwarded-Port", port); + ctx.addZuulRequestHeader(ZuulHeaders.X_FORWARDED_PROTO, proto); + addProxyPrefix(ctx, route); + } + + private boolean hasHeader(HttpServletRequest request, String name) { + return StringUtils.hasLength(request.getHeader(name)); + } + + private void addProxyPrefix(RequestContext ctx, Route route) { + String forwardedPrefix = ctx.getRequest().getHeader("X-Forwarded-Prefix"); + String contextPath = ctx.getRequest().getContextPath(); + String prefix = StringUtils.hasLength(forwardedPrefix) ? forwardedPrefix + : (StringUtils.hasLength(contextPath) ? contextPath : null); + if (StringUtils.hasText(route.getPrefix())) { + StringBuilder newPrefixBuilder = new StringBuilder(); + if (prefix != null) { + if (prefix.endsWith("/") && route.getPrefix().startsWith("/")) { + newPrefixBuilder.append(prefix, 0, prefix.length() - 1); + } + else { + newPrefixBuilder.append(prefix); + } + } + newPrefixBuilder.append(route.getPrefix()); + prefix = newPrefixBuilder.toString(); + } + if (prefix != null) { + ctx.addZuulRequestHeader("X-Forwarded-Prefix", prefix); + } + } + private String toHostHeader(HttpServletRequest request) { int port = request.getServerPort(); - if ((port == 80 && "http".equals(request.getScheme())) || (port == 443 && "https".equals(request.getScheme()))) { + if ((port == 80 && "http".equals(request.getScheme())) + || (port == 443 && "https".equals(request.getScheme()))) { return request.getServerName(); - } else { + } + else { return request.getServerName() + ":" + port; } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RestClientRibbonCommand.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RestClientRibbonCommand.java index d8fb01edf..bf913fe4c 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RestClientRibbonCommand.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/RestClientRibbonCommand.java @@ -17,20 +17,18 @@ package org.springframework.cloud.netflix.zuul.filters.route; -import static org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer.Runner.customize; - import java.io.InputStream; import java.net.URI; import java.util.List; - import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand; import org.springframework.util.MultiValueMap; - import com.netflix.client.http.HttpRequest; import com.netflix.client.http.HttpResponse; import com.netflix.niws.client.http.RestClient; +import static org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer.Runner.customize; + /** * Hystrix wrapper around Eureka Ribbon command * @@ -44,6 +42,12 @@ public class RestClientRibbonCommand extends AbstractRibbonCommand { +public class RestClientRibbonCommandFactory extends AbstractRibbonCommandFactory { - private final SpringClientFactory clientFactory; + private SpringClientFactory clientFactory; private ZuulProperties zuulProperties; public RestClientRibbonCommandFactory(SpringClientFactory clientFactory) { - this(clientFactory, new ZuulProperties()); + this(clientFactory, new ZuulProperties(), Collections.emptySet()); } public RestClientRibbonCommandFactory(SpringClientFactory clientFactory, - ZuulProperties zuulProperties) { + ZuulProperties zuulProperties, + Set zuulFallbackProviders) { + super(zuulFallbackProviders); this.clientFactory = clientFactory; this.zuulProperties = zuulProperties; } @@ -45,10 +52,12 @@ public class RestClientRibbonCommandFactory implements RibbonCommandFactory headers, MultiValueMap params) + { + HttpRequest httpRequest; + + switch (verb.toUpperCase()) { + case "POST": + HttpPost httpPost = new HttpPost(uri + this.helper.getQueryString(params)); + httpRequest = httpPost; + httpPost.setEntity(entity); + break; + case "PUT": + HttpPut httpPut = new HttpPut(uri + this.helper.getQueryString(params)); + httpRequest = httpPut; + httpPut.setEntity(entity); + break; + case "PATCH": + HttpPatch httpPatch = new HttpPatch(uri + this.helper.getQueryString(params)); + httpRequest = httpPatch; + httpPatch.setEntity(entity); + break; + case "DELETE": + BasicHttpEntityEnclosingRequest entityRequest = new BasicHttpEntityEnclosingRequest(verb, + uri + this.helper.getQueryString(params)); + httpRequest = entityRequest; + entityRequest.setEntity(entity); + break; + default: + httpRequest = new BasicHttpRequest(verb, + uri + this.helper.getQueryString(params)); + log.debug(uri + this.helper.getQueryString(params)); + } + + httpRequest.setHeaders(convertHeaders(headers)); + return httpRequest; + } + private MultiValueMap revertHeaders(Header[] headers) { MultiValueMap map = new LinkedMultiValueMap(); for (Header header : headers) { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/ZuulFallbackProvider.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/ZuulFallbackProvider.java new file mode 100644 index 000000000..d5383b528 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/ZuulFallbackProvider.java @@ -0,0 +1,40 @@ +/* + * + * * Copyright 2013-2016 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.zuul.filters.route; + +import org.springframework.http.client.ClientHttpResponse; + +/** + * Provides fallback when a failure occurs on a route. + * @author Ryan Baxter + */ +public interface ZuulFallbackProvider { + + /** + * The route this fallback will be used for. + * @return The route the fallback will be used for. + */ + public String getRoute(); + + /** + * Provides a fallback response. + * @return The fallback response. + */ + public ClientHttpResponse fallbackResponse(); +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommand.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommand.java index 5061fb1f8..609460d7e 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommand.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommand.java @@ -22,10 +22,12 @@ import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpResponse; import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; +import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand; /** * @author Spencer Gibb + * @author Ryan Baxter */ public class HttpClientRibbonCommand extends AbstractRibbonCommand { @@ -36,6 +38,14 @@ public class HttpClientRibbonCommand extends AbstractRibbonCommand { +public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory { private final SpringClientFactory clientFactory; private final ZuulProperties zuulProperties; + public HttpClientRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties) { + this(clientFactory, zuulProperties, Collections.emptySet()); + } + + public HttpClientRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties, + Set fallbackProviders) { + super(fallbackProviders); + this.clientFactory = clientFactory; + this.zuulProperties = zuulProperties; + } + @Override public HttpClientRibbonCommand create(final RibbonCommandContext context) { + ZuulFallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId()); final String serviceId = context.getServiceId(); final RibbonLoadBalancingHttpClient client = this.clientFactory.getClient( serviceId, RibbonLoadBalancingHttpClient.class); client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId)); - return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties); + return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties, zuulFallbackProvider); } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommand.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommand.java index f66f36b7a..e4cd1972d 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommand.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommand.java @@ -22,20 +22,30 @@ import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonRequest; import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonResponse; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; +import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand; /** * @author Spencer Gibb + * @author Ryan Baxter */ public class OkHttpRibbonCommand extends AbstractRibbonCommand { public OkHttpRibbonCommand(final String commandKey, - final OkHttpLoadBalancingClient client, - final RibbonCommandContext context, - final ZuulProperties zuulProperties) { + final OkHttpLoadBalancingClient client, + final RibbonCommandContext context, + final ZuulProperties zuulProperties) { super(commandKey, client, context, zuulProperties); } + public OkHttpRibbonCommand(final String commandKey, + final OkHttpLoadBalancingClient client, + final RibbonCommandContext context, + final ZuulProperties zuulProperties, + final ZuulFallbackProvider zuulFallbackProvider) { + super(commandKey, client, context, zuulProperties, zuulFallbackProvider); + } + @Override protected OkHttpRibbonRequest createRequest() throws Exception { return new OkHttpRibbonRequest(this.context); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFactory.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFactory.java index 610014438..67ca4fcc6 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFactory.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFactory.java @@ -16,33 +16,46 @@ package org.springframework.cloud.netflix.zuul.filters.route.okhttp; +import java.util.Collections; +import java.util.Set; + import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; - -import lombok.RequiredArgsConstructor; +import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; +import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommandFactory; /** * @author Spencer Gibb + * @author Ryan Baxter */ -@RequiredArgsConstructor -public class OkHttpRibbonCommandFactory implements - RibbonCommandFactory { +public class OkHttpRibbonCommandFactory extends AbstractRibbonCommandFactory { - private final SpringClientFactory clientFactory; + private SpringClientFactory clientFactory; - private final ZuulProperties zuulProperties; + private ZuulProperties zuulProperties; + + public OkHttpRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties) { + this(clientFactory, zuulProperties, Collections.emptySet()); + } + + public OkHttpRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties, + Set zuulFallbackProviders) { + super(zuulFallbackProviders); + this.clientFactory = clientFactory; + this.zuulProperties = zuulProperties; + } @Override public OkHttpRibbonCommand create(final RibbonCommandContext context) { final String serviceId = context.getServiceId(); + ZuulFallbackProvider fallbackProvider = getFallbackProvider(serviceId); final OkHttpLoadBalancingClient client = this.clientFactory.getClient( serviceId, OkHttpLoadBalancingClient.class); client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId)); - return new OkHttpRibbonCommand(serviceId, client, context, zuulProperties); + return new OkHttpRibbonCommand(serviceId, client, context, zuulProperties, fallbackProvider); } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommand.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommand.java index 64f74f870..f5cb685f4 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommand.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/support/AbstractRibbonCommand.java @@ -21,8 +21,8 @@ import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; +import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; import org.springframework.http.client.ClientHttpResponse; - import com.netflix.client.AbstractLoadBalancerAwareClient; import com.netflix.client.ClientRequest; import com.netflix.client.http.HttpResponse; @@ -44,6 +44,7 @@ public abstract class AbstractRibbonCommand fallbackProviderCache; + + public AbstractRibbonCommandFactory(Set fallbackProviders){ + this.fallbackProviderCache = new HashMap(); + for(ZuulFallbackProvider provider : fallbackProviders) { + fallbackProviderCache.put(provider.getRoute(), provider); + } + } + + protected ZuulFallbackProvider getFallbackProvider(String route) { + return fallbackProviderCache.get(route); + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulController.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulController.java index 8f5d4b1d5..d8fe936d1 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulController.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulController.java @@ -37,9 +37,10 @@ public class ZuulController extends ServletWrappingController { } @Override - protected ModelAndView handleRequestInternal(HttpServletRequest request, - HttpServletResponse response) throws Exception { + public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { try { + // We don't care about the other features of the base class, just want to + // handle the request return super.handleRequestInternal(request, response); } finally { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulHandlerMapping.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulHandlerMapping.java index af81c9ae1..c1e6dd540 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulHandlerMapping.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/web/ZuulHandlerMapping.java @@ -25,6 +25,8 @@ import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.util.PatternMatchUtils; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; import com.netflix.zuul.context.RequestContext; @@ -51,6 +53,16 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { setOrder(-200); } + @Override + protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, + HandlerExecutionChain chain, CorsConfiguration config) { + if (config == null) { + // Allow CORS requests to go to the backend + return chain; + } + return super.getCorsHandlerExecutionChain(request, chain, config); + } + public void setErrorController(ErrorController errorController) { this.errorController = errorController; } @@ -63,10 +75,8 @@ public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { } @Override - protected Object lookupHandler(String urlPath, HttpServletRequest request) - throws Exception { - if (this.errorController != null - && urlPath.equals(this.errorController.getErrorPath())) { + protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { + if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) { return null; } String[] ignored = this.routeLocator.getIgnoredPaths().toArray(new String[0]); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/AdhocTestSuite.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/AdhocTestSuite.java index 2200743e9..47a365ede 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/AdhocTestSuite.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/AdhocTestSuite.java @@ -20,7 +20,11 @@ import org.junit.Ignore; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; -import org.springframework.cloud.netflix.zuul.filters.route.restclient.RestClientRibbonCommandIntegrationTests; +import org.springframework.cloud.netflix.feign.encoding.FeignAcceptEncodingTests; +import org.springframework.cloud.netflix.metrics.servo.ServoMetricReaderTests; +import org.springframework.cloud.netflix.ribbon.RibbonInterceptorTests; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClientTests; +import org.springframework.cloud.netflix.zuul.ZuulProxyConfigurationTests; /** * A test suite for probing weird ordering problems in the tests. @@ -28,10 +32,8 @@ import org.springframework.cloud.netflix.zuul.filters.route.restclient.RestClien * @author Dave Syer */ @RunWith(Suite.class) -@SuiteClasses({ - org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelperTests.class, - RestClientRibbonCommandIntegrationTests.class, - org.springframework.cloud.netflix.zuul.FormZuulProxyApplicationTests.class }) +@SuiteClasses({ RibbonLoadBalancerClientTests.class, RibbonInterceptorTests.class, FeignAcceptEncodingTests.class, + ServoMetricReaderTests.class, ZuulProxyConfigurationTests.class }) @Ignore public class AdhocTestSuite { diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignLoggerFactoryTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignLoggerFactoryTests.java new file mode 100644 index 000000000..c773ab4d8 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignLoggerFactoryTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import feign.Logger; +import feign.slf4j.Slf4jLogger; + +/** + * @author Venil Noronha + */ +public class FeignLoggerFactoryTests { + + @Test + public void testDefaultLogger() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration1.class); + FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class); + assertNotNull(loggerFactory); + Logger logger = loggerFactory.create(Object.class); + assertNotNull(logger); + assertTrue(logger instanceof Slf4jLogger); + context.close(); + } + + @Configuration + @Import(FeignClientsConfiguration.class) + protected static class SampleConfiguration1 { + + } + + @Test + public void testCustomLogger() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration2.class); + FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class); + assertNotNull(loggerFactory); + Logger logger = loggerFactory.create(Object.class); + assertNotNull(logger); + assertTrue(logger instanceof LoggerImpl1); + context.close(); + } + + @Configuration + @Import(FeignClientsConfiguration.class) + protected static class SampleConfiguration2 { + + @Bean + public Logger logger() { + return new LoggerImpl1(); + } + + } + + static class LoggerImpl1 extends Logger { + + @Override + protected void log(String arg0, String arg1, Object... arg2) { + // noop + } + + } + + @Test + public void testCustomLoggerFactory() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration3.class); + FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class); + assertNotNull(loggerFactory); + assertTrue(loggerFactory instanceof LoggerFactoryImpl); + Logger logger = loggerFactory.create(Object.class); + assertNotNull(logger); + assertTrue(logger instanceof LoggerImpl2); + context.close(); + } + + @Configuration + @Import(FeignClientsConfiguration.class) + protected static class SampleConfiguration3 { + + @Bean + public FeignLoggerFactory feignLoggerFactory() { + return new LoggerFactoryImpl(); + } + + } + + static class LoggerFactoryImpl implements FeignLoggerFactory { + + @Override + public Logger create(Class type) { + return new LoggerImpl2(); + } + + } + + static class LoggerImpl2 extends Logger { + + @Override + protected void log(String arg0, String arg1, Object... arg2) { + // noop + } + + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/invalid/FeignClientValidationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/invalid/FeignClientValidationTests.java index 178fc6435..bd7d235fd 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/invalid/FeignClientValidationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/invalid/FeignClientValidationTests.java @@ -16,6 +16,7 @@ package org.springframework.cloud.netflix.feign.invalid; +import feign.hystrix.FallbackFactory; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -42,10 +43,7 @@ public class FeignClientValidationTests { @Test public void testNameAndValue() { this.expected.expectMessage("only one is permitted"); - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - NameAndValueConfiguration.class); - assertNotNull(context.getBean(NameAndValueConfiguration.Client.class)); - context.close(); + new AnnotationConfigApplicationContext(NameAndValueConfiguration.class); } @Configuration @@ -86,10 +84,7 @@ public class FeignClientValidationTests { @Test public void testNotLegalHostname() { this.expected.expectMessage("not legal hostname (foo_bar)"); - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - BadHostnameConfiguration.class); - assertNotNull(context.getBean(BadHostnameConfiguration.Client.class)); - context.close(); + new AnnotationConfigApplicationContext(BadHostnameConfiguration.class); } @Configuration @@ -107,11 +102,12 @@ public class FeignClientValidationTests { @Test public void testMissingFallback() { - this.expected.expectMessage("No fallback instance of type"); + try ( AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - MissingFallbackConfiguration.class); - assertNotNull(context.getBean(MissingFallbackConfiguration.Client.class)); - context.close(); + MissingFallbackConfiguration.class)) { + this.expected.expectMessage("No fallback instance of type"); + assertNotNull(context.getBean(MissingFallbackConfiguration.Client.class)); + } } @Configuration @@ -136,11 +132,11 @@ public class FeignClientValidationTests { @Test public void testWrongFallbackType() { - this.expected.expectMessage("Incompatible fallback instance"); - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - WrongFallbackTypeConfiguration.class); - assertNotNull(context.getBean(WrongFallbackTypeConfiguration.Client.class)); - context.close(); + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + WrongFallbackTypeConfiguration.class)) { + this.expected.expectMessage("Incompatible fallback instance"); + assertNotNull(context.getBean(WrongFallbackTypeConfiguration.Client.class)); + } } @Configuration @@ -163,4 +159,98 @@ public class FeignClientValidationTests { } } + + @Test + public void testMissingFallbackFactory() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + MissingFallbackFactoryConfiguration.class)) { + this.expected.expectMessage("No fallbackFactory instance of type"); + assertNotNull(context.getBean(MissingFallbackFactoryConfiguration.Client.class)); + } + } + + @Configuration + @Import(FeignAutoConfiguration.class) + @EnableFeignClients(clients = MissingFallbackFactoryConfiguration.Client.class) + protected static class MissingFallbackFactoryConfiguration { + + @FeignClient(name = "foobar", url = "http://localhost", fallbackFactory = ClientFallback.class) + interface Client { + @RequestMapping(method = RequestMethod.GET, value = "/") + String get(); + } + + class ClientFallback implements FallbackFactory { + + @Override + public Client create(Throwable cause) { + return null; + } + } + } + + @Test + public void testWrongFallbackFactoryType() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + WrongFallbackFactoryTypeConfiguration.class)) { + this.expected.expectMessage("Incompatible fallbackFactory instance"); + assertNotNull(context.getBean(WrongFallbackFactoryTypeConfiguration.Client.class)); + } + } + + @Configuration + @Import(FeignAutoConfiguration.class) + @EnableFeignClients(clients = WrongFallbackFactoryTypeConfiguration.Client.class) + protected static class WrongFallbackFactoryTypeConfiguration { + + @FeignClient(name = "foobar", url = "http://localhost", fallbackFactory = Dummy.class) + interface Client { + @RequestMapping(method = RequestMethod.GET, value = "/") + String get(); + } + + @Bean + Dummy dummy() { + return new Dummy(); + } + + class Dummy { + } + + } + + @Test + public void testWrongFallbackFactoryGenericType() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + WrongFallbackFactoryGenericTypeConfiguration.class)) { + this.expected.expectMessage("Incompatible fallbackFactory instance"); + assertNotNull(context.getBean(WrongFallbackFactoryGenericTypeConfiguration.Client.class)); + } + } + + @Configuration + @Import(FeignAutoConfiguration.class) + @EnableFeignClients(clients = WrongFallbackFactoryGenericTypeConfiguration.Client.class) + protected static class WrongFallbackFactoryGenericTypeConfiguration { + + @FeignClient(name = "foobar", url = "http://localhost", fallbackFactory = ClientFallback.class) + interface Client { + @RequestMapping(method = RequestMethod.GET, value = "/") + String get(); + } + + @Bean + ClientFallback dummy() { + return new ClientFallback(); + } + + class ClientFallback implements FallbackFactory { + + @Override + public String create(Throwable cause) { + return "tryinToTrickYa"; + } + } + + } } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java index 49f3b8310..366e64545 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java @@ -74,6 +74,7 @@ import feign.Client; import feign.Logger; import feign.RequestInterceptor; import feign.RequestTemplate; +import feign.hystrix.FallbackFactory; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -115,6 +116,9 @@ public class FeignClientTests { @Autowired HystrixClient hystrixClient; + @Autowired + private HystrixClientWithFallBackFactory hystrixClientWithFallBackFactory; + @Autowired @Qualifier("localapp3FeignClient") HystrixClient namedHystrixClient; @@ -237,6 +241,27 @@ public class FeignClientTests { Future failFuture(); } + @FeignClient(name = "localapp4", fallbackFactory = HystrixClientFallbackFactory.class) + protected interface HystrixClientWithFallBackFactory { + + @RequestMapping(method = RequestMethod.GET, path = "/fail") + Hello fail(); + } + + static class HystrixClientFallbackFactory implements FallbackFactory { + + @Override + public HystrixClientWithFallBackFactory create(final Throwable cause) { + return new HystrixClientWithFallBackFactory() { + @Override + public Hello fail() { + assertNotNull("Cause was null", cause); + return new Hello("Hello from the fallback side: " + cause.getMessage()); + } + }; + } + } + static class HystrixClientFallback implements HystrixClient { @Override public Hello fail() { @@ -268,13 +293,15 @@ public class FeignClientTests { @EnableAutoConfiguration @RestController @EnableFeignClients(clients = { TestClientServiceId.class, TestClient.class, - DecodingTestClient.class, - HystrixClient.class }, defaultConfiguration = TestDefaultFeignConfig.class) + DecodingTestClient.class, HystrixClient.class, HystrixClientWithFallBackFactory.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 = "localapp3", configuration = LocalRibbonClientConfiguration.class), + @RibbonClient(name = "localapp4", configuration = LocalRibbonClientConfiguration.class) + }) protected static class Application { // needs to be in parent context to test multiple HystrixClient beans @@ -283,6 +310,11 @@ public class FeignClientTests { return new HystrixClientFallback(); } + @Bean + public HystrixClientFallbackFactory hystrixClientFallbackFactory() { + return new HystrixClientFallbackFactory(); + } + @Bean FeignFormatterRegistrar feignFormatterRegistrar() { return new FeignFormatterRegistrar() { @@ -584,6 +616,15 @@ public class FeignClientTests { assertEquals("message was wrong", "fallbackfuture", hello.getMessage()); } + @Test + public void testHystrixClientWithFallBackFactory() throws Exception { + Hello hello = hystrixClientWithFallBackFactory.fail(); + assertNotNull("hello was null", hello); + assertNotNull("hello#message was null", hello.getMessage()); + assertTrue("hello#message did not contain the cause (status code) of the fallback invocation", + hello.getMessage().contains("500")); + } + @Test public void namedFeignClientWorks() { assertNotNull("namedHystrixClient was null", this.namedHystrixClient); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTests.java index 90bf4d3a3..4a8856342 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTests.java @@ -47,7 +47,7 @@ import static org.junit.Assert.assertTrue; @SpringBootTest(classes = RestTemplateRetryTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = { "spring.application.name=resttemplatetest", "logging.level.com.netflix=DEBUG", "logging.level.org.springframework.cloud.netflix.resttemplate=DEBUG", - "logging.level.com.netflix=DEBUG", "badClients.ribbon.MaxAutoRetries=0", + "logging.level.com.netflix=DEBUG", "badClients.ribbon.MaxAutoRetries=25", "badClients.ribbon.OkToRetryOnAllOperations=true", "ribbon.http.client.enabled" }) @DirtiesContext public class RestTemplateRetryTests { diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonInterceptorTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonInterceptorTests.java index 644a7c37a..aa9bb89ea 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonInterceptorTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonInterceptorTests.java @@ -16,30 +16,26 @@ package org.springframework.cloud.netflix.ribbon; -import com.netflix.loadbalancer.Server; +import java.io.IOException; +import java.net.URI; +import java.net.URL; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; -import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.RibbonServer; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.ReflectionUtils; import org.springframework.web.util.UriComponentsBuilder; - -import java.io.IOException; -import java.net.URI; -import java.net.URL; +import com.netflix.loadbalancer.Server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -69,8 +65,7 @@ public class RibbonInterceptorTests { @Test public void testIntercept() throws Exception { RibbonServer server = new RibbonServer("myservice", new Server("myhost", 8080)); - LoadBalancerInterceptor interceptor = new LoadBalancerInterceptor(new MyClient(server), new RetryTemplate(), - new LoadBalancerRetryProperties(), new LoadBalancedRetryPolicyFactory.NeverRetryFactory()); + LoadBalancerInterceptor interceptor = new LoadBalancerInterceptor(new MyClient(server)); given(this.request.getURI()).willReturn(new URL("http://myservice").toURI()); given(this.execution.execute(isA(HttpRequest.class), isA(byte[].class))) .willReturn(this.response); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClientTests.java index e224a1342..905cc6ac5 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClientTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonLoadBalancerClientTests.java @@ -29,6 +29,7 @@ import org.mockito.MockitoAnnotations; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.RibbonServer; +import org.springframework.web.util.DefaultUriTemplateHandler; import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.IClientConfig; @@ -107,6 +108,31 @@ public class RibbonLoadBalancerClientTests { assertEquals(server.getPort(), uri.getPort()); } + @Test + public void testReconstructSecureUriWithSpecialCharsPath() { + testReconstructUriWithPath("https", "/foo=|"); + } + + @Test + public void testReconstructUnsecureUriWithSpecialCharsPath() { + testReconstructUriWithPath("http", "/foo=|"); + } + + private void testReconstructUriWithPath(String scheme, String path) { + RibbonServer server = getRibbonServer(); + IClientConfig config = mock(IClientConfig.class); + when(config.get(CommonClientConfigKey.IsSecure)).thenReturn(true); + when(clientFactory.getClientConfig(server.getServiceId())).thenReturn(config); + + RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server); + ServiceInstance serviceInstance = client.choose(server.getServiceId()); + + URI expanded = new DefaultUriTemplateHandler() + .expand(scheme + "://" + server.getServiceId() + path); + URI reconstructed = client.reconstructURI(serviceInstance, expanded); + assertEquals(expanded.getPath(), reconstructed.getPath()); + } + @Test @SneakyThrows public void testReconstructUriWithSecureClientConfig() { diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonUtilsTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonUtilsTests.java index 53b1bd8d3..92548e7f3 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonUtilsTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonUtilsTests.java @@ -17,9 +17,6 @@ package org.springframework.cloud.netflix.ribbon; -import static org.hamcrest.Matchers.is; -import static org.springframework.cloud.netflix.ribbon.RibbonUtils.*; - import java.net.URI; import java.net.URISyntaxException; import java.util.Map; @@ -31,6 +28,10 @@ import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.DefaultClientConfigImpl; import com.netflix.loadbalancer.Server; +import static org.hamcrest.Matchers.is; +import static org.springframework.cloud.netflix.ribbon.RibbonUtils.isSecure; +import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded; + /** * @author Spencer Gibb * @author Jacques-Etienne Beaudet diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryDisabledTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryDisabledTests.java new file mode 100644 index 000000000..3317c567d --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryDisabledTests.java @@ -0,0 +1,63 @@ +/* + * + * * Copyright 2013-2016 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.ribbon; + +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.ClassPathExclusions; +import org.springframework.cloud.FilteredClassPathRunner; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +/** + * @author Ryan Baxter + */ +@RunWith(FilteredClassPathRunner.class) +@ClassPathExclusions({"spring-retry-*.jar", "spring-boot-starter-aop-*.jar"}) +public class SpringRetryDisabledTests { + + private ConfigurableApplicationContext context; + + @Before + public void setUp() { + context = new SpringApplicationBuilder().web(false) + .sources(RibbonAutoConfiguration.class,LoadBalancerAutoConfiguration.class).run(); + } + + @After + public void tearDown() { + if(context != null) { + context.close(); + } + } + + @Test + public void testLoadBalancedRetryFactoryBean() throws Exception { + Map factories = context.getBeansOfType(LoadBalancedRetryPolicyFactory.class); + assertThat(factories.values(), hasSize(0)); + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java new file mode 100644 index 000000000..539b94bc4 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java @@ -0,0 +1,56 @@ +/* + * + * * Copyright 2013-2016 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.ribbon; + +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {RibbonAutoConfiguration.class, LoadBalancerAutoConfiguration.class}) +public class SpringRetryEnabledTests implements ApplicationContextAware { + + private ApplicationContext context; + + @Test + public void testLoadBalancedRetryFactoryBean() throws Exception { + Map factories = context.getBeansOfType(LoadBalancedRetryPolicyFactory.class); + assertThat(factories.values(), hasSize(1)); + assertThat(factories.values().toArray()[0], instanceOf(RibbonLoadBalancedRetryPolicyFactory.class)); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ServletPathZuulProxyApplicationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ServletPathZuulProxyApplicationTests.java index 9b103cce7..eb14d9110 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ServletPathZuulProxyApplicationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/ServletPathZuulProxyApplicationTests.java @@ -18,6 +18,9 @@ package org.springframework.cloud.netflix.zuul; import static org.junit.Assert.assertEquals; +import java.net.URI; +import java.net.URISyntaxException; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,13 +36,16 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClientException; import com.netflix.zuul.context.RequestContext; @@ -68,9 +74,38 @@ public class ServletPathZuulProxyApplicationTests { public void getOnSelfViaSimpleHostRoutingFilter() { this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/app/local"); this.endpoint.reset(); + ResponseEntity result = new TestRestTemplate().exchange("http://localhost:" + this.port + "/app/self/1", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("Gotten 1!", result.getBody()); + } + + @Test + public void optionsOnRawEndpoint() throws Exception { + ResponseEntity result = new TestRestTemplate().exchange(RequestEntity + .options(new URI("http://localhost:" + this.port + "/app/local/1")) + .header("Origin", "http://localhost:9000").header("Access-Control-Request-Method", "GET").build(), + String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("http://localhost:9000", result.getHeaders().getFirst("Access-Control-Allow-Origin")); + } + + @Test + public void optionsOnSelf() throws Exception { + this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/app/local"); + this.endpoint.reset(); + ResponseEntity result = new TestRestTemplate().exchange(RequestEntity + .options(new URI("http://localhost:" + this.port + "/app/self/1")) + .header("Origin", "http://localhost:9000").header("Access-Control-Request-Method", "GET").build(), + String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("http://localhost:9000", result.getHeaders().getFirst("Access-Control-Allow-Origin")); + } + + @Test + public void contentOnRawEndpoint() throws Exception { ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/app/self/1", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); + RequestEntity.get(new URI("http://localhost:" + this.port + "/app/local/1")).build(), String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals("Gotten 1!", result.getBody()); } @@ -80,9 +115,8 @@ public class ServletPathZuulProxyApplicationTests { this.routes.addRoute(new ZuulRoute("strip", "/strip/**", "strip", "http://localhost:" + this.port + "/app/local", false, false, null)); this.endpoint.reset(); - ResponseEntity result = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/app/strip", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); + ResponseEntity result = new TestRestTemplate().exchange("http://localhost:" + this.port + "/app/strip", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); assertEquals(HttpStatus.OK, result.getStatusCode()); // Prefix not stripped to it goes to /local/strip assertEquals("Gotten strip!", result.getBody()); @@ -96,6 +130,7 @@ public class ServletPathZuulProxyApplicationTests { static class ServletPathZuulProxyApplication { @RequestMapping(value = "/local/{id}", method = RequestMethod.GET) + @CrossOrigin(origins = "*") public String get(@PathVariable String id) { return "Gotten " + id + "!"; } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelperTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelperTests.java index a3f6f6abb..4d211dc01 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelperTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelperTests.java @@ -259,6 +259,28 @@ public class ProxyRequestHelperTests { assertThat(queryString, is("?wsdl")); } + @Test + public void getQueryStringEncoded() { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("foo", "weird#chars"); + + String queryString = new ProxyRequestHelper().getQueryString(params); + + assertThat(queryString, is("?foo=weird%23chars")); + } + + @Test + public void getQueryParamNameWithColon() { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("foo:bar", "baz"); + params.add("foobar", "bam"); + params.add("foo\fbar", "bat"); // form feed is the colon replacement char + + String queryString = new ProxyRequestHelper().getQueryString(params); + + assertThat(queryString, is("?foo:bar=baz&foobar=bam&foo%0Cbar=bat")); + } + @Test public void buildZuulRequestURIWithUTF8() throws Exception { String encodedURI = "/resource/esp%C3%A9cial-char"; diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java index 940634ff1..b07b43a28 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java @@ -103,6 +103,23 @@ public class PreDecorationFilterTests { assertEquals("localhost:8080", ctx.getZuulRequestHeaders().get("x-forwarded-host")); } + @Test + public void xForwardedHostAppends() throws Exception { + this.properties.setPrefix("/api"); + this.request.setRequestURI("/api/foo/1"); + this.request.setRemoteAddr("5.6.7.8"); + this.request.setServerPort(8080); + this.request.addHeader("X-Forwarded-Host", "example.com"); + this.request.addHeader("X-Forwarded-Proto", "https"); + this.routeLocator.addRoute( + new ZuulRoute("foo", "/foo/**", "foo", null, false, null, null)); + this.filter.run(); + RequestContext ctx = RequestContext.getCurrentContext(); + assertEquals("example.com,localhost:8080", ctx.getZuulRequestHeaders().get("x-forwarded-host")); + assertEquals("443,8080", ctx.getZuulRequestHeaders().get("x-forwarded-port")); + assertEquals("https,http", ctx.getZuulRequestHeaders().get("x-forwarded-proto")); + } + @Test public void hostHeaderSet() throws Exception { this.properties.setPrefix("/api"); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilterTests.java index 7275e36d7..faf5871e0 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilterTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilterTests.java @@ -16,6 +16,11 @@ package org.springframework.cloud.netflix.zuul.filters.route; +import java.io.ByteArrayInputStream; + +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpRequest; +import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.junit.After; import org.junit.Test; @@ -26,6 +31,7 @@ import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.util.LinkedMultiValueMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -80,6 +86,18 @@ public class SimpleHostRoutingFilterTests { assertEquals(20, connMgr.getDefaultMaxPerRoute()); } + @Test + public void deleteRequestBuiltWithBody() { + setupContext(); + InputStreamEntity inputStreamEntity = new InputStreamEntity(new ByteArrayInputStream(new byte[]{1})); + HttpRequest httpRequest = getFilter().buildHttpRequest("DELETE", "uri", inputStreamEntity, + new LinkedMultiValueMap(), new LinkedMultiValueMap()); + + assertTrue(httpRequest instanceof HttpEntityEnclosingRequest); + HttpEntityEnclosingRequest httpEntityEnclosingRequest = (HttpEntityEnclosingRequest) httpRequest; + assertTrue(httpEntityEnclosingRequest.getEntity() != null); + } + private void setupContext() { this.context.register(PropertyPlaceholderAutoConfiguration.class, TestConfiguration.class); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandFallbackTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandFallbackTests.java new file mode 100644 index 000000000..ea30e4e1a --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandFallbackTests.java @@ -0,0 +1,46 @@ +/* + * + * * Copyright 2013-2016 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.zuul.filters.route.apache; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonCommandFallbackTests; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.netflix.zuul.context.RequestContext; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = HttpClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { + "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", + "ribbon.ReadTimeout: 1"}) +@DirtiesContext +public class HttpClientRibbonCommandFallbackTests extends RibbonCommandFallbackTests { + + @Before + public void init() { + RequestContext.testSetCurrentContext(null); + RequestContext.getCurrentContext().unset(); + } + +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandIntegrationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandIntegrationTests.java index b41940e74..6f4201506 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandIntegrationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommandIntegrationTests.java @@ -22,6 +22,9 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.springframework.http.HttpHeaders.SET_COOKIE; +import java.util.Collections; +import java.util.Set; + import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -29,6 +32,7 @@ import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.web.ErrorAttributes; @@ -44,6 +48,7 @@ import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpCl 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.support.ZuulProxyTestBase; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -155,6 +160,9 @@ public class HttpClientRibbonCommandIntegrationTests extends ZuulProxyTestBase { @RibbonClient(name = "singleton", configuration = SingletonRibbonClientConfiguration.class) }) static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication { + @Autowired(required = false) + private Set zuulFallbackProviders = Collections.emptySet(); + @RequestMapping(value = "/local/{id}", method = RequestMethod.PATCH) public String patch(@PathVariable final String id, @RequestBody final String body) { @@ -176,7 +184,8 @@ public class HttpClientRibbonCommandIntegrationTests extends ZuulProxyTestBase { @Bean public RibbonCommandFactory ribbonCommandFactory( final SpringClientFactory clientFactory) { - return new HttpClientRibbonCommandFactory(clientFactory, new ZuulProperties()); + return new HttpClientRibbonCommandFactory(clientFactory, new ZuulProperties(), + zuulFallbackProviders); } @Bean diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFallbackTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFallbackTests.java new file mode 100644 index 000000000..864e5504c --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandFallbackTests.java @@ -0,0 +1,44 @@ +/* + * + * * Copyright 2013-2016 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.zuul.filters.route.okhttp; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonCommandFallbackTests; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.netflix.zuul.context.RequestContext; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = OkHttpRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { + "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", + "ribbon.ReadTimeout: 1"}) +@DirtiesContext +public class OkHttpRibbonCommandFallbackTests extends RibbonCommandFallbackTests { + @Before + public void init() { + RequestContext.testSetCurrentContext(null); + RequestContext.getCurrentContext().unset(); + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandIntegrationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandIntegrationTests.java index 80f068067..bfd5b7ea6 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandIntegrationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/okhttp/OkHttpRibbonCommandIntegrationTests.java @@ -20,9 +20,13 @@ package org.springframework.cloud.netflix.zuul.filters.route.okhttp; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.util.Collections; +import java.util.Set; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.web.ErrorAttributes; import org.springframework.boot.test.context.SpringBootTest; @@ -34,6 +38,7 @@ 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.support.ZuulProxyTestBase; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -100,10 +105,14 @@ public class OkHttpRibbonCommandIntegrationTests extends ZuulProxyTestBase { @RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) }) static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication { + @Autowired(required = false) + private Set zuulFallbackProviders = Collections.emptySet(); + @Bean public RibbonCommandFactory ribbonCommandFactory( final SpringClientFactory clientFactory) { - return new OkHttpRibbonCommandFactory(clientFactory, new ZuulProperties()); + return new OkHttpRibbonCommandFactory(clientFactory, new ZuulProperties(), + zuulFallbackProviders); } @Bean diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandFallbackTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandFallbackTests.java new file mode 100644 index 000000000..ca9784f3f --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandFallbackTests.java @@ -0,0 +1,44 @@ +/* + * + * * Copyright 2013-2016 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.zuul.filters.route.restclient; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonCommandFallbackTests; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.netflix.zuul.context.RequestContext; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = RestClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = { + "zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**", + "ribbon.ReadTimeout: 1"}) +@DirtiesContext +public class RestClientRibbonCommandFallbackTests extends RibbonCommandFallbackTests { + @Before + public void init() { + RequestContext.testSetCurrentContext(null); + RequestContext.getCurrentContext().unset(); + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandIntegrationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandIntegrationTests.java index 826a574b8..790cab60a 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandIntegrationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/restclient/RestClientRibbonCommandIntegrationTests.java @@ -26,10 +26,14 @@ import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Set; import java.util.UUID; import javax.servlet.http.HttpServletRequest; +import lombok.SneakyThrows; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -52,6 +56,7 @@ import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonComm import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; 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.support.NoEncodingFormHttpMessageConverter; import org.springframework.cloud.netflix.zuul.filters.route.support.ZuulProxyTestBase; import org.springframework.context.annotation.Bean; @@ -80,8 +85,6 @@ import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerList; import com.netflix.niws.client.http.RestClient; -import lombok.SneakyThrows; - @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = RestClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = { "zuul.routes.other: /test/**=http://localhost:7777/local", @@ -186,6 +189,17 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase { assertEquals("/query?foo=weird#chars", result.getBody()); } + @Test + public void simpleHostRouteWithColonParamNames() { + this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/"); + this.endpoint.reset(); + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/self/colonquery?foo:bar={foobar0}&foobar={foobar1}", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class, "baz", "bam"); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("/colonquery?foo:bar=baz&foobar=bam", result.getBody()); + } + @Test public void simpleHostRouteWithContentType() { this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/"); @@ -274,6 +288,9 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase { @RibbonClient(name = "another", configuration = ZuulProxyTestBase.AnotherRibbonClientConfiguration.class) }) static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication { + @Autowired(required = false) + private Set fallbackProviders = Collections.emptySet(); + @RequestMapping("/trailing-slash") public String trailingSlash(HttpServletRequest request) { return request.getRequestURI(); @@ -295,10 +312,15 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase { } @RequestMapping("/query") - public String addQuery(HttpServletRequest request, @RequestParam String foo) { + public String query(HttpServletRequest request, @RequestParam String foo) { return request.getRequestURI() + "?foo=" + foo; } + @RequestMapping("/colonquery") + public String colonQuery(HttpServletRequest request, @RequestParam(name = "foo:bar") String foobar0, @RequestParam(name = "foobar") String foobar1) { + return request.getRequestURI() + "?foo:bar=" + foobar0 + "&foobar=" + foobar1; + } + @RequestMapping("/matrix/{name}/{another}") public String matrix(@PathVariable("name") String name, @MatrixVariable(value = "p", pathVar = "name") int p, @@ -311,7 +333,7 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase { @Bean public RibbonCommandFactory ribbonCommandFactory( SpringClientFactory clientFactory) { - return new MyRibbonCommandFactory(clientFactory); + return new MyRibbonCommandFactory(clientFactory, fallbackProviders); } @Bean @@ -334,8 +356,9 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase { private SpringClientFactory clientFactory; - public MyRibbonCommandFactory(SpringClientFactory clientFactory) { - super(clientFactory, new ZuulProperties()); + public MyRibbonCommandFactory(SpringClientFactory clientFactory, + Set fallbackProviders) { + super(clientFactory, new ZuulProperties(), fallbackProviders); this.clientFactory = clientFactory; } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonCommandFallbackTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonCommandFallbackTests.java new file mode 100644 index 000000000..303241e1c --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/RibbonCommandFallbackTests.java @@ -0,0 +1,58 @@ +/* + * + * * Copyright 2013-2016 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.zuul.filters.route.support; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.junit.Assert.assertEquals; + +/** + * @author Ryan Baxter + */ +public abstract class RibbonCommandFallbackTests { + + @Value("${local.server.port}") + protected int port; + + @Test + public void fallback() { + String uri = "/simple/slow"; + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals("fallback", result.getBody()); + } + + @Test + public void noFallback() { + String uri = "/another/twolevel/slow"; + ResponseEntity result = new TestRestTemplate().exchange( + "http://localhost:" + this.port + uri, HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + System.out.println("no fallback body: " + result.getBody()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode()); + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/ZuulProxyTestBase.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/ZuulProxyTestBase.java index 9119c4a38..58bd1806b 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/ZuulProxyTestBase.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/support/ZuulProxyTestBase.java @@ -17,11 +17,9 @@ package org.springframework.cloud.netflix.zuul.filters.route.support; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assume.assumeThat; - +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; @@ -29,9 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; - import javax.servlet.http.HttpServletRequest; - import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -46,13 +42,16 @@ import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; +import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; 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.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; @@ -66,14 +65,19 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerList; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assume.assumeThat; + /** * @author Spencer Gibb + * @author Ryan Baxter */ public abstract class ZuulProxyTestBase { @@ -363,6 +367,22 @@ public abstract class ZuulProxyTestBase { return "Hello space"; } + @RequestMapping("/slow") + public String slow() { + try { + Thread.sleep(80000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return "slow"; + + } + + @Bean + public ZuulFallbackProvider fallbackProvider() { + return new FallbackProvider(); + } + @Bean public ZuulFilter sampleFilter() { return new ZuulFilter() { @@ -393,6 +413,7 @@ public abstract class ZuulProxyTestBase { return 0; } }; + } @Override @@ -401,6 +422,53 @@ public abstract class ZuulProxyTestBase { mapping.setRemoveSemicolonContent(false); return mapping; } + + + } + + public static class FallbackProvider implements ZuulFallbackProvider { + + @Override + public String getRoute() { + return "simple"; + } + + @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("fallback".getBytes()); + } + + @Override + public HttpHeaders getHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_HTML); + return headers; + } + }; + } } @Configuration diff --git a/spring-cloud-netflix-dependencies/pom.xml b/spring-cloud-netflix-dependencies/pom.xml index 99e3cb2dd..c2123fa6b 100644 --- a/spring-cloud-netflix-dependencies/pom.xml +++ b/spring-cloud-netflix-dependencies/pom.xml @@ -5,11 +5,11 @@ spring-cloud-dependencies-parent org.springframework.cloud - 1.2.1.BUILD-SNAPSHOT + 1.2.2.BUILD-SNAPSHOT spring-cloud-netflix-dependencies - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT pom spring-cloud-netflix-dependencies Spring Cloud Netflix Dependencies @@ -17,10 +17,10 @@ 0.7.4 1.4.11 9.3.1 - 1.5.5 + 1.5.6 2.2.0 0.10.1 - 1.2.2 + 1.3.0 1.1.10 1.7 1.0.0 @@ -270,6 +270,10 @@ jackson-dataformat-xml com.fasterxml.jackson.dataformat + + * + com.amazonaws + diff --git a/spring-cloud-netflix-eureka-client/pom.xml b/spring-cloud-netflix-eureka-client/pom.xml index 6d6bdcd9c..e43ee8ec8 100644 --- a/spring-cloud-netflix-eureka-client/pom.xml +++ b/spring-cloud-netflix-eureka-client/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-eureka-client diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java index 9f6e5c26c..d8b6205bb 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java @@ -64,6 +64,7 @@ import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceI * @author Spencer Gibb * @author Jon Schneider * @author Matt Jenkins + * @author Ryan Baxter */ @Configuration @EnableConfigurationProperties @@ -106,14 +107,15 @@ public class EurekaClientAutoConfiguration { @Bean @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT) public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) { - RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(env, "eureka.instance."); EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils); instance.setNonSecurePort(this.nonSecurePort); instance.setInstanceId(getDefaultInstanceId(this.env)); + if (this.managementPort != this.nonSecurePort && this.managementPort != 0) { if (StringUtils.hasText(this.hostname)) { instance.setHostname(this.hostname); } + RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(env, "eureka.instance."); String statusPageUrlPath = relaxedPropertyResolver.getProperty("statusPageUrlPath"); String healthCheckUrlPath = relaxedPropertyResolver.getProperty("healthCheckUrlPath"); if (StringUtils.hasText(statusPageUrlPath)) { diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBean.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBean.java index cf4cc1854..4bf14cd41 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBean.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBean.java @@ -19,10 +19,14 @@ package org.springframework.cloud.netflix.eureka; import java.util.HashMap; import java.util.Map; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.commons.util.InetUtils.HostInfo; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; import com.netflix.appinfo.DataCenterInfo; import com.netflix.appinfo.InstanceInfo.InstanceStatus; @@ -36,10 +40,13 @@ import lombok.Setter; /** * @author Dave Syer * @author Spencer Gibb + * @author Ryan Baxter */ @Data @ConfigurationProperties("eureka.instance") -public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig { +public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware, InitializingBean { + + private static final String UNKNOWN = "unknown"; @Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PRIVATE) @@ -52,8 +59,7 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig { /** * Get the name of the application to be registered with eureka. */ - @Value("${spring.application.name:unknown}") - private String appname = "unknown"; + private String appname = UNKNOWN; /** * Get the name of the application group to be registered with eureka. @@ -119,8 +125,7 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig { * virtual host name.Think of this as similar to the fully qualified domain name, that * the users of your services will need to find this instance. */ - @Value("${spring.application.name:unknown}") - private String virtualHostName; + private String virtualHostName = UNKNOWN; /** * Get the unique Id (within the scope of the appName) of this instance to be @@ -135,8 +140,7 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig { * secure virtual host name.Think of this as similar to the fully qualified domain * name, that the users of your services will need to find this instance. */ - @Value("${spring.application.name:unknown}") - private String secureVirtualHostName; + private String secureVirtualHostName = UNKNOWN; /** * Gets the AWS autoscaling group name associated with this instance. This information @@ -275,6 +279,7 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig { private InstanceStatus initialStatus = InstanceStatus.UP; private String[] defaultAddressResolutionOrder = new String[0]; + private Environment environment; public String getHostname() { return getHostName(false); @@ -322,4 +327,20 @@ public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig { } return this.preferIpAddress ? this.ipAddress : this.hostname; } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Override + public void afterPropertiesSet() throws Exception { + RelaxedPropertyResolver springPropertyResolver = new RelaxedPropertyResolver(this.environment, "spring.application."); + String springAppName = springPropertyResolver.getProperty("name"); + if(StringUtils.hasText(springAppName)) { + setAppname(springAppName); + setVirtualHostName(springAppName); + setSecureVirtualHostName(springAppName); + } + } } diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java index c1a159150..be8145d69 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java @@ -189,6 +189,32 @@ public class EurekaClientAutoConfigurationTests { // Mockito.verify(http).addFilter(Matchers.any(HTTPBasicAuthFilter.class)); } + @Test + public void testDefaultAppName() throws Exception { + setupContext(); + assertEquals("unknown", getInstanceConfig().getAppname()); + assertEquals("unknown", getInstanceConfig().getVirtualHostName()); + assertEquals("unknown", getInstanceConfig().getSecureVirtualHostName()); + } + + @Test + public void testAppName() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, "spring.application.name=mytest"); + setupContext(); + assertEquals("mytest", getInstanceConfig().getAppname()); + assertEquals("mytest", getInstanceConfig().getVirtualHostName()); + assertEquals("mytest", getInstanceConfig().getSecureVirtualHostName()); + } + + @Test + public void testAppNameUpper() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, "SPRING_APPLICATION_NAME=mytestupper"); + setupContext(); + assertEquals("mytestupper", getInstanceConfig().getAppname()); + assertEquals("mytestupper", getInstanceConfig().getVirtualHostName()); + assertEquals("mytestupper", getInstanceConfig().getSecureVirtualHostName()); + } + private void testNonSecurePort(String propName) { addEnvironment(this.context, propName + ":8888"); setupContext(); diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBeanTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBeanTests.java index 0f3f99b62..284388dd5 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBeanTests.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaInstanceConfigBeanTests.java @@ -20,15 +20,18 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.commons.util.InetUtilsProperties; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.test.util.ReflectionTestUtils; - +import org.springframework.util.StringUtils; import com.netflix.appinfo.InstanceInfo.InstanceStatus; import static org.junit.Assert.assertEquals; @@ -38,6 +41,7 @@ import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnviron /** * @author Dave Syer * @author Spencer Gibb + * @author Ryan Baxter */ public class EurekaInstanceConfigBeanTests { @@ -185,6 +189,14 @@ public class EurekaInstanceConfigBeanTests { } + @Test + public void testDefaultAppName() throws Exception { + setupContext(); + assertEquals("default app name is wrong", "unknown", getInstanceConfig().getAppname()); + assertEquals("default virtual hostname is wrong", "unknown", getInstanceConfig().getVirtualHostName()); + assertEquals("default secure virtual hostname is wrong", "unknown", getInstanceConfig().getSecureVirtualHostName()); + } + private void setupContext() { this.context.register(PropertyPlaceholderAutoConfiguration.class, TestConfiguration.class); @@ -198,9 +210,19 @@ public class EurekaInstanceConfigBeanTests { @Configuration @EnableConfigurationProperties protected static class TestConfiguration { + @Autowired + ConfigurableEnvironment env; @Bean public EurekaInstanceConfigBean eurekaInstanceConfigBean() { - return new EurekaInstanceConfigBean(new InetUtils(new InetUtilsProperties())); + EurekaInstanceConfigBean configBean = new EurekaInstanceConfigBean(new InetUtils(new InetUtilsProperties())); + RelaxedPropertyResolver springPropertyResolver = new RelaxedPropertyResolver(env, "spring.application."); + String springAppName = springPropertyResolver.getProperty("name"); + if(StringUtils.hasText(springAppName)) { + configBean.setSecureVirtualHostName(springAppName); + configBean.setVirtualHostName(springAppName); + configBean.setAppname(springAppName); + } + return configBean; } } diff --git a/spring-cloud-netflix-eureka-server/pom.xml b/spring-cloud-netflix-eureka-server/pom.xml index 74b41f126..9ee12b8a4 100644 --- a/spring-cloud-netflix-eureka-server/pom.xml +++ b/spring-cloud-netflix-eureka-server/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-eureka-server diff --git a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/EurekaServerConfiguration.java b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/EurekaServerConfiguration.java index 0e2f81edf..8a19354a0 100644 --- a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/EurekaServerConfiguration.java +++ b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/EurekaServerConfiguration.java @@ -28,7 +28,6 @@ import javax.ws.rs.ext.Provider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -71,7 +70,7 @@ import com.sun.jersey.spi.container.servlet.ServletContainer; @Configuration @Import(EurekaServerInitializerConfiguration.class) @EnableDiscoveryClient -@EnableConfigurationProperties(EurekaDashboardProperties.class) +@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class }) @PropertySource("classpath:/eureka/server.properties") public class EurekaServerConfiguration extends WebMvcConfigurerAdapter { /** @@ -92,17 +91,9 @@ public class EurekaServerConfiguration extends WebMvcConfigurerAdapter { @Autowired private EurekaClient eurekaClient; - /* - * Setting expectedNumberOfRenewsPerMin to non-zero to ensure that even an isolated - * server can adjust its eviction policy to the number of registrations (when it's - * zero, even a successful registration won't reset the rate threshold in - * InstanceRegistry.register()). - */ - @Value("${eureka.server.expectedNumberOfRenewsPerMin:1}") - private int expectedNumberOfRenewsPerMin; + @Autowired + private InstanceRegistryProperties instanceRegistryProperties; - @Value("${eureka.server.defaultOpenForTrafficCount:1}") - private int defaultOpenForTrafficCount; public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson(); @Bean @@ -166,8 +157,9 @@ public class EurekaServerConfiguration extends WebMvcConfigurerAdapter { ServerCodecs serverCodecs) { this.eurekaClient.getApplications(); // force initialization return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, - serverCodecs, this.eurekaClient, this.expectedNumberOfRenewsPerMin, - this.defaultOpenForTrafficCount); + serverCodecs, this.eurekaClient, + this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(), + this.instanceRegistryProperties.getDefaultOpenForTrafficCount()); } @Bean diff --git a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistry.java b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistry.java index 05c5676f7..3faaef7ee 100644 --- a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistry.java +++ b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistry.java @@ -18,6 +18,7 @@ package org.springframework.cloud.netflix.eureka.server; import java.util.List; +import com.netflix.eureka.lease.Lease; import org.springframework.beans.BeansException; import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent; import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent; @@ -35,6 +36,7 @@ import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl; import com.netflix.eureka.resources.ServerCodecs; import lombok.extern.apachecommons.CommonsLog; +import org.springframework.context.ApplicationEvent; /** * @author Spencer Gibb @@ -64,8 +66,8 @@ public class InstanceRegistry extends PeerAwareInstanceRegistryImpl /** * If * {@link PeerAwareInstanceRegistryImpl#openForTraffic(ApplicationInfoManager, int)} - * is called with a zero * argument, it means that leases are not automatically * - * cancelled if the instance * hasn't sent any renewals recently. This happens for a + * is called with a zero argument, it means that leases are not automatically + * cancelled if the instance hasn't sent any renewals recently. This happens for a * standalone server. It seems like a bad default, so we set it to the smallest * non-zero value we can, so that any instances that subsequently register can bump up * the threshold. @@ -78,37 +80,27 @@ public class InstanceRegistry extends PeerAwareInstanceRegistryImpl @Override public void register(InstanceInfo info, int leaseDuration, boolean isReplication) { - if (log.isDebugEnabled()) { - log.debug("register " + info.getAppName() + ", vip " + info.getVIPAddress() - + ", leaseDuration " + leaseDuration + ", isReplication " - + isReplication); - } - // TODO: what to publish from info (whole object?) - this.ctxt.publishEvent(new EurekaInstanceRegisteredEvent(this, info, - leaseDuration, isReplication)); - + handleRegistration(info, leaseDuration, isReplication); super.register(info, leaseDuration, isReplication); } @Override - public boolean cancel(String appName, String serverId, boolean isReplication) { - if (log.isDebugEnabled()) { - log.debug("cancel " + appName + " serverId " + serverId + ", isReplication {}" - + isReplication); - } - this.ctxt.publishEvent( - new EurekaInstanceCanceledEvent(this, appName, serverId, isReplication)); + public void register(final InstanceInfo info, final boolean isReplication) { + handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication); + super.register(info, isReplication); + } + @Override + public boolean cancel(String appName, String serverId, boolean isReplication) { + handleCancelation(appName, serverId, isReplication); return super.cancel(appName, serverId, isReplication); } @Override public boolean renew(final String appName, final String serverId, boolean isReplication) { - if (log.isDebugEnabled()) { - log.debug("renew " + appName + " serverId " + serverId + ", isReplication {}" - + isReplication); - } + log("renew " + appName + " serverId " + serverId + ", isReplication {}" + + isReplication); List applications = getSortedApplications(); for (Application input : applications) { if (input.getName().equals(appName)) { @@ -119,11 +111,49 @@ public class InstanceRegistry extends PeerAwareInstanceRegistryImpl break; } } - this.ctxt.publishEvent(new EurekaInstanceRenewedEvent(this, appName, - serverId, instance, isReplication)); + publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId, + instance, isReplication)); break; } } return super.renew(appName, serverId, isReplication); } + + @Override + protected boolean internalCancel(String appName, String id, boolean isReplication) { + handleCancelation(appName, id, isReplication); + return super.internalCancel(appName, id, isReplication); + } + + private void handleCancelation(String appName, String id, boolean isReplication) { + log("cancel " + appName + ", serverId " + id + ", isReplication " + isReplication); + publishEvent(new EurekaInstanceCanceledEvent(this, appName, id, isReplication)); + } + + private void handleRegistration(InstanceInfo info, int leaseDuration, + boolean isReplication) { + log("register " + info.getAppName() + ", vip " + info.getVIPAddress() + + ", leaseDuration " + leaseDuration + ", isReplication " + + isReplication); + publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, + isReplication)); + } + + private void log(String message) { + if (log.isDebugEnabled()) { + log.debug(message); + } + } + + private void publishEvent(ApplicationEvent applicationEvent) { + this.ctxt.publishEvent(applicationEvent); + } + + private int resolveInstanceLeaseDuration(final InstanceInfo info) { + int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS; + if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) { + leaseDuration = info.getLeaseInfo().getDurationInSecs(); + } + return leaseDuration; + } } diff --git a/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistryProperties.java b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistryProperties.java new file mode 100644 index 000000000..87a0ebaca --- /dev/null +++ b/spring-cloud-netflix-eureka-server/src/main/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistryProperties.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.cloud.netflix.eureka.server; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import static org.springframework.cloud.netflix.eureka.server.InstanceRegistryProperties.PREFIX; + +/** + * @author Spencer Gibb + */ +@ConfigurationProperties(PREFIX) +public class InstanceRegistryProperties { + + public static final String PREFIX = "eureka.instance.registry"; + + + /* Default number of expected renews per minute, defaults to 1. + * Setting expectedNumberOfRenewsPerMin to non-zero to ensure that even an isolated + * server can adjust its eviction policy to the number of registrations (when it's + * zero, even a successful registration won't reset the rate threshold in + * InstanceRegistry.register()). + */ + @Value("${eureka.server.expectedNumberOfRenewsPerMin:1}") // for backwards compatibility + private int expectedNumberOfRenewsPerMin = 1; + + /** Value used in determining when leases are cancelled, default to 1 for standalone. + * Should be set to 0 for peer replicated eurekas */ + @Value("${eureka.server.defaultOpenForTrafficCount:1}") // for backwards compatibility + private int defaultOpenForTrafficCount = 1; + + public int getExpectedNumberOfRenewsPerMin() { + return expectedNumberOfRenewsPerMin; + } + + public void setExpectedNumberOfRenewsPerMin(int expectedNumberOfRenewsPerMin) { + this.expectedNumberOfRenewsPerMin = expectedNumberOfRenewsPerMin; + } + + public int getDefaultOpenForTrafficCount() { + return defaultOpenForTrafficCount; + } + + public void setDefaultOpenForTrafficCount(int defaultOpenForTrafficCount) { + this.defaultOpenForTrafficCount = defaultOpenForTrafficCount; + } +} diff --git a/spring-cloud-netflix-eureka-server/src/test/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistryTest.java b/spring-cloud-netflix-eureka-server/src/test/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistryTest.java new file mode 100644 index 000000000..9689e7795 --- /dev/null +++ b/spring-cloud-netflix-eureka-server/src/test/java/org/springframework/cloud/netflix/eureka/server/InstanceRegistryTest.java @@ -0,0 +1,186 @@ +package org.springframework.cloud.netflix.eureka.server; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import com.netflix.discovery.shared.Application; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.cloud.netflix.eureka.server.InstanceRegistryTest.TestApplication; +import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent; +import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent; +import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.LeaseInfo; +import com.netflix.eureka.registry.PeerAwareInstanceRegistry; + +/** + * @author Bartlomiej Slota + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = TestApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + value = {"spring.application.name=eureka", "logging.level.org.springframework." + + "cloud.netflix.eureka.server.InstanceRegistry=DEBUG"}) +public class InstanceRegistryTest { + + private final List applicationEvents = new LinkedList<>(); + private static final String APP_NAME = "MY-APP-NAME"; + private static final String HOST_NAME = "my-host-name"; + + @SpyBean(PeerAwareInstanceRegistry.class) + private InstanceRegistry instanceRegistry; + + @MockBean + private ApplicationListener + instanceRegisteredEventListenerMock; + + @MockBean + private ApplicationListener + instanceCanceledEventListenerMock; + + @MockBean + private ApplicationListener instanceRenewedEventListener; + + @Before + public void setup() { + applicationEvents.clear(); + Answer applicationListenerAnswer = prepareListenerMockAnswer(); + doAnswer(applicationListenerAnswer).when(instanceRegisteredEventListenerMock) + .onApplicationEvent(isA(EurekaInstanceRegisteredEvent.class)); + doAnswer(applicationListenerAnswer).when(instanceCanceledEventListenerMock) + .onApplicationEvent(isA(EurekaInstanceCanceledEvent.class)); + doAnswer(applicationListenerAnswer).when(instanceRenewedEventListener) + .onApplicationEvent(isA(EurekaInstanceRenewedEvent.class)); + } + + + @Test + public void testRegister() throws Exception { + // creating instance info + final LeaseInfo leaseInfo = getLeaseInfo(); + final InstanceInfo instanceInfo = getInstanceInfo(leaseInfo); + // calling tested method + instanceRegistry.register(instanceInfo, false); + // event of proper type is registered + assertEquals(1, applicationEvents.size()); + assertTrue(applicationEvents.get(0) instanceof EurekaInstanceRegisteredEvent); + // event details are correct + final EurekaInstanceRegisteredEvent registeredEvent = + (EurekaInstanceRegisteredEvent) (applicationEvents.get(0)); + assertEquals(instanceInfo, registeredEvent.getInstanceInfo()); + assertEquals(leaseInfo.getDurationInSecs(), registeredEvent.getLeaseDuration()); + assertEquals(instanceRegistry, registeredEvent.getSource()); + assertFalse(registeredEvent.isReplication()); + } + + @Test + public void testDefaultLeaseDurationRegisterEvent() throws Exception { + // creating instance info + final InstanceInfo instanceInfo = getInstanceInfo(null); + // calling tested method + instanceRegistry.register(instanceInfo, false); + // instance info duration is set to default + final EurekaInstanceRegisteredEvent registeredEvent = + (EurekaInstanceRegisteredEvent) (applicationEvents.get(0)); + assertEquals(LeaseInfo.DEFAULT_LEASE_DURATION, + registeredEvent.getLeaseDuration()); + } + + @Test + public void testInternalCancel() throws Exception { + // calling tested method + instanceRegistry.internalCancel(APP_NAME, HOST_NAME, false); + // event of proper type is registered + assertEquals(1, applicationEvents.size()); + assertTrue(applicationEvents.get(0) instanceof EurekaInstanceCanceledEvent); + // event details are correct + final EurekaInstanceCanceledEvent registeredEvent = + (EurekaInstanceCanceledEvent) (applicationEvents.get(0)); + assertEquals(APP_NAME, registeredEvent.getAppName()); + assertEquals(HOST_NAME, registeredEvent.getServerId()); + assertEquals(instanceRegistry, registeredEvent.getSource()); + assertFalse(registeredEvent.isReplication()); + } + + @Test + public void testRenew() throws Exception { + // creating application list + final LeaseInfo leaseInfo = getLeaseInfo(); + final InstanceInfo instanceInfo = getInstanceInfo(leaseInfo); + final List instances = new ArrayList<>(); + instances.add(instanceInfo); + final Application application = new Application(APP_NAME, instances); + final List applications = new ArrayList<>(); + applications.add(application); + // stubbing applications list + doReturn(applications).when(instanceRegistry).getSortedApplications(); + // calling tested method + instanceRegistry.renew(APP_NAME, HOST_NAME, false); + // event of proper type is registered + assertEquals(1, applicationEvents.size()); + assertTrue(applicationEvents.get(0) instanceof EurekaInstanceRenewedEvent); + // event details are correct + final EurekaInstanceRenewedEvent registeredEvent = (EurekaInstanceRenewedEvent) + (applicationEvents.get(0)); + assertEquals(APP_NAME, registeredEvent.getAppName()); + assertEquals(HOST_NAME, registeredEvent.getServerId()); + assertEquals(instanceRegistry, registeredEvent.getSource()); + assertEquals(instanceInfo, registeredEvent.getInstanceInfo()); + assertFalse(registeredEvent.isReplication()); + } + + @Configuration + @EnableAutoConfiguration + @EnableEurekaServer + protected static class TestApplication { + public static void main(String[] args) { + new SpringApplicationBuilder(TestApplication.class).run(args); + } + } + + private LeaseInfo getLeaseInfo() { + LeaseInfo.Builder leaseBuilder = LeaseInfo.Builder.newBuilder(); + leaseBuilder.setRenewalIntervalInSecs(10); + leaseBuilder.setDurationInSecs(15); + return leaseBuilder.build(); + } + + private InstanceInfo getInstanceInfo(LeaseInfo leaseInfo) { + InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(); + builder.setAppName(APP_NAME); + builder.setHostName(HOST_NAME); + builder.setPort(8008); + builder.setLeaseInfo(leaseInfo); + return builder.build(); + } + + private Answer prepareListenerMockAnswer() { + return new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return applicationEvents + .add((ApplicationEvent) invocation.getArguments()[0]); + } + }; + } +} \ No newline at end of file diff --git a/spring-cloud-netflix-hystrix-amqp/pom.xml b/spring-cloud-netflix-hystrix-amqp/pom.xml index b3dcd0375..1155ec05d 100644 --- a/spring-cloud-netflix-hystrix-amqp/pom.xml +++ b/spring-cloud-netflix-hystrix-amqp/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-hystrix-amqp diff --git a/spring-cloud-netflix-hystrix-dashboard/pom.xml b/spring-cloud-netflix-hystrix-dashboard/pom.xml index 64d5f7198..e300272cb 100644 --- a/spring-cloud-netflix-hystrix-dashboard/pom.xml +++ b/spring-cloud-netflix-hystrix-dashboard/pom.xml @@ -8,7 +8,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. diff --git a/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java b/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java index 38569b633..4431463f1 100644 --- a/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java +++ b/spring-cloud-netflix-hystrix-dashboard/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java @@ -53,7 +53,6 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; * @author Dave Syer * @author Roy Clarkson */ -@SuppressWarnings("deprecation") @Configuration @EnableConfigurationProperties(HystrixDashboardProperties.class) public class HystrixDashboardConfiguration { @@ -263,6 +262,7 @@ public class HystrixDashboardConfiguration { } } + @SuppressWarnings("deprecation") private static class ProxyConnectionManager { private final static PoolingClientConnectionManager threadSafeConnectionManager = new PoolingClientConnectionManager(); diff --git a/spring-cloud-netflix-hystrix-stream/pom.xml b/spring-cloud-netflix-hystrix-stream/pom.xml index dd4c8f517..852cc61ad 100644 --- a/spring-cloud-netflix-hystrix-stream/pom.xml +++ b/spring-cloud-netflix-hystrix-stream/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-hystrix-stream diff --git a/spring-cloud-netflix-sidecar/pom.xml b/spring-cloud-netflix-sidecar/pom.xml index c39d4005f..01f838d71 100644 --- a/spring-cloud-netflix-sidecar/pom.xml +++ b/spring-cloud-netflix-sidecar/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-sidecar diff --git a/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarConfiguration.java b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarConfiguration.java index 52357293b..17db83f53 100644 --- a/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarConfiguration.java +++ b/spring-cloud-netflix-sidecar/src/main/java/org/springframework/cloud/netflix/sidecar/SidecarConfiguration.java @@ -16,10 +16,13 @@ package org.springframework.cloud.netflix.sidecar; +import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceId; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.commons.util.InetUtils; @@ -32,10 +35,9 @@ import org.springframework.util.StringUtils; import com.netflix.appinfo.HealthCheckHandler; import com.netflix.discovery.EurekaClientConfig; -import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceId; - /** * @author Spencer Gibb + * @author Ryan Baxter */ @Configuration @EnableConfigurationProperties @@ -73,9 +75,16 @@ public class SidecarConfiguration { @Bean public EurekaInstanceConfigBean eurekaInstanceConfigBean() { EurekaInstanceConfigBean config = new EurekaInstanceConfigBean(inetUtils); + RelaxedPropertyResolver springPropertyResolver = new RelaxedPropertyResolver(env, "spring.application."); + String springAppName = springPropertyResolver.getProperty("name"); int port = this.sidecarProperties.getPort(); config.setNonSecurePort(port); config.setInstanceId(getDefaultInstanceId(this.env)); + if(StringUtils.hasText(springAppName)) { + config.setAppname(springAppName); + config.setVirtualHostName(springAppName); + config.setSecureVirtualHostName(springAppName); + } if (StringUtils.hasText(this.hostname)) { config.setHostname(this.hostname); } diff --git a/spring-cloud-netflix-spectator/pom.xml b/spring-cloud-netflix-spectator/pom.xml index 55715ac68..5c9f16c84 100644 --- a/spring-cloud-netflix-spectator/pom.xml +++ b/spring-cloud-netflix-spectator/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-spectator diff --git a/spring-cloud-netflix-turbine-stream/pom.xml b/spring-cloud-netflix-turbine-stream/pom.xml index 7fdeef11d..73d7fcba0 100644 --- a/spring-cloud-netflix-turbine-stream/pom.xml +++ b/spring-cloud-netflix-turbine-stream/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-turbine-stream diff --git a/spring-cloud-netflix-turbine/pom.xml b/spring-cloud-netflix-turbine/pom.xml index f4ba02b16..9d1a39e0b 100644 --- a/spring-cloud-netflix-turbine/pom.xml +++ b/spring-cloud-netflix-turbine/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-netflix-turbine diff --git a/spring-cloud-starter-archaius/pom.xml b/spring-cloud-starter-archaius/pom.xml index 29c05ed3a..1a9d18879 100644 --- a/spring-cloud-starter-archaius/pom.xml +++ b/spring-cloud-starter-archaius/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-archaius diff --git a/spring-cloud-starter-atlas/pom.xml b/spring-cloud-starter-atlas/pom.xml index ef07240ef..f35598d00 100644 --- a/spring-cloud-starter-atlas/pom.xml +++ b/spring-cloud-starter-atlas/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-atlas diff --git a/spring-cloud-starter-eureka-server/pom.xml b/spring-cloud-starter-eureka-server/pom.xml index 281d6840b..239c6d1f5 100644 --- a/spring-cloud-starter-eureka-server/pom.xml +++ b/spring-cloud-starter-eureka-server/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-eureka-server diff --git a/spring-cloud-starter-eureka/pom.xml b/spring-cloud-starter-eureka/pom.xml index 5db347ea8..dfe0cba5b 100644 --- a/spring-cloud-starter-eureka/pom.xml +++ b/spring-cloud-starter-eureka/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-eureka diff --git a/spring-cloud-starter-feign/pom.xml b/spring-cloud-starter-feign/pom.xml index cfced5bb3..d24c79f4c 100644 --- a/spring-cloud-starter-feign/pom.xml +++ b/spring-cloud-starter-feign/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-feign diff --git a/spring-cloud-starter-hystrix-dashboard/pom.xml b/spring-cloud-starter-hystrix-dashboard/pom.xml index ef126ed61..b78098b66 100644 --- a/spring-cloud-starter-hystrix-dashboard/pom.xml +++ b/spring-cloud-starter-hystrix-dashboard/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-hystrix-dashboard diff --git a/spring-cloud-starter-hystrix/pom.xml b/spring-cloud-starter-hystrix/pom.xml index 220ad2e3b..4034e8b46 100644 --- a/spring-cloud-starter-hystrix/pom.xml +++ b/spring-cloud-starter-hystrix/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-hystrix diff --git a/spring-cloud-starter-ribbon/pom.xml b/spring-cloud-starter-ribbon/pom.xml index 533583c0e..e2937c5f4 100644 --- a/spring-cloud-starter-ribbon/pom.xml +++ b/spring-cloud-starter-ribbon/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-ribbon @@ -20,6 +20,14 @@ ${basedir}/../.. + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.retry + spring-retry + org.springframework.cloud spring-cloud-starter diff --git a/spring-cloud-starter-spectator/pom.xml b/spring-cloud-starter-spectator/pom.xml index ee66b0eb2..56927535c 100644 --- a/spring-cloud-starter-spectator/pom.xml +++ b/spring-cloud-starter-spectator/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-spectator diff --git a/spring-cloud-starter-turbine-amqp/pom.xml b/spring-cloud-starter-turbine-amqp/pom.xml index 15e5a043a..f7da82435 100644 --- a/spring-cloud-starter-turbine-amqp/pom.xml +++ b/spring-cloud-starter-turbine-amqp/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-turbine-amqp diff --git a/spring-cloud-starter-turbine-stream/pom.xml b/spring-cloud-starter-turbine-stream/pom.xml index ef45f8330..fa71b1d2b 100644 --- a/spring-cloud-starter-turbine-stream/pom.xml +++ b/spring-cloud-starter-turbine-stream/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-turbine-stream diff --git a/spring-cloud-starter-turbine/pom.xml b/spring-cloud-starter-turbine/pom.xml index 3639be91c..03ec13466 100644 --- a/spring-cloud-starter-turbine/pom.xml +++ b/spring-cloud-starter-turbine/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-turbine diff --git a/spring-cloud-starter-zuul/pom.xml b/spring-cloud-starter-zuul/pom.xml index 8b664ac84..55436f6b2 100644 --- a/spring-cloud-starter-zuul/pom.xml +++ b/spring-cloud-starter-zuul/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-netflix - 1.2.1.BUILD-SNAPSHOT + 1.2.3.BUILD-SNAPSHOT .. spring-cloud-starter-zuul