mirror of
https://github.com/spring-cloud/spring-cloud-netflix.git
synced 2026-09-21 01:59:01 +00:00
Merge remote-tracking branch 'Upstream/1.2.x' into feign-retry
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-docs</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -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<HystrixClient> {
|
||||
@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
|
||||
<<hystrix-fallbacks-for-routes, hystrix command>>, 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
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud Netflix</name>
|
||||
<description>Spring Cloud Netflix</description>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.2.BUILD-SNAPSHOT</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -24,8 +24,8 @@
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<netty.version>4.0.27.Final</netty.version>
|
||||
<jackson.version>2.7.3</jackson.version>
|
||||
<spring-cloud-commons.version>1.1.4.BUILD-SNAPSHOT</spring-cloud-commons.version>
|
||||
<spring-cloud-config.version>1.2.1.BUILD-SNAPSHOT</spring-cloud-config.version>
|
||||
<spring-cloud-commons.version>1.1.6.BUILD-SNAPSHOT</spring-cloud-commons.version>
|
||||
<spring-cloud-config.version>1.2.2.BUILD-SNAPSHOT</spring-cloud-config.version>
|
||||
<spring-cloud-stream.version>Brooklyn.BUILD-SNAPSHOT</spring-cloud-stream.version>
|
||||
|
||||
<!-- Sonar -->
|
||||
@@ -79,6 +79,13 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
<version>${spring-cloud-commons.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-config-dependencies</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-core</artifactId>
|
||||
@@ -44,6 +44,16 @@
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
@@ -183,6 +193,12 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
|
||||
+38
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+10
@@ -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
|
||||
* <code>@RibbonClient</code>.
|
||||
|
||||
+9
-9
@@ -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<Object>, 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<Object>, 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<Object>, 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<Object>, 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<Object>, InitializingBean,
|
||||
}
|
||||
builder.client(client);
|
||||
}
|
||||
Targeter targeter = get(context, Targeter.class);
|
||||
return targeter.target(this, builder, context, new HardCodedTarget<>(
|
||||
this.type, this.name, url));
|
||||
}
|
||||
|
||||
+21
@@ -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<FeignFormatterRegistrar> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -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";
|
||||
|
||||
+36
@@ -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);
|
||||
|
||||
}
|
||||
+55
-11
@@ -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> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
Target.HardCodedTarget<T> 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> T targetWithFallbackFactory(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target,
|
||||
HystrixFeign.Builder builder,
|
||||
Class<?> fallbackFactoryClass) {
|
||||
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
|
||||
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> T targetWithFallback(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target,
|
||||
HystrixFeign.Builder builder, Class<?> fallback) {
|
||||
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
|
||||
return builder.target(target, fallbackInstance);
|
||||
}
|
||||
|
||||
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
|
||||
Class<?> beanType, Class<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-29
@@ -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<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> implements
|
||||
LoadBalanceChooser {
|
||||
ServiceInstanceChooser {
|
||||
|
||||
private final int connectTimeout;
|
||||
private final int readTimeout;
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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() {
|
||||
|
||||
+12
-15
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+154
@@ -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<ZuulFallbackProvider> 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<ZuulFallbackProvider> 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<ZuulFallbackProvider> 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 {}
|
||||
}
|
||||
|
||||
}
|
||||
+13
-118
@@ -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<RibbonRequestCustomizer> 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<ApplicationEvent> {
|
||||
private static class ZuulDiscoveryRefreshListener implements ApplicationListener<ApplicationEvent> {
|
||||
|
||||
private HeartbeatMonitor monitor = new HeartbeatMonitor();
|
||||
|
||||
|
||||
+20
-3
@@ -249,6 +249,12 @@ public class ProxyRequestHelper {
|
||||
MultiValueMap<String, String> 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<String, String> 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++;
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ public class ZuulProperties {
|
||||
private Set<String> 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
|
||||
|
||||
+6
-7
@@ -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<String> 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("="));
|
||||
}
|
||||
|
||||
+72
-45
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -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<RestClient, H
|
||||
super(commandKey, client, context, zuulProperties);
|
||||
}
|
||||
|
||||
public RestClientRibbonCommand(String commandKey, RestClient client,
|
||||
RibbonCommandContext context, ZuulProperties zuulProperties,
|
||||
ZuulFallbackProvider zuulFallbackProvider) {
|
||||
super(commandKey, client, context, zuulProperties, zuulFallbackProvider);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public RestClientRibbonCommand(String commandKey, RestClient restClient,
|
||||
HttpRequest.Verb verb, String uri, Boolean retryable,
|
||||
|
||||
+15
-6
@@ -17,27 +17,34 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommandFactory;
|
||||
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class RestClientRibbonCommandFactory implements RibbonCommandFactory<RestClientRibbonCommand> {
|
||||
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.<ZuulFallbackProvider>emptySet());
|
||||
}
|
||||
|
||||
public RestClientRibbonCommandFactory(SpringClientFactory clientFactory,
|
||||
ZuulProperties zuulProperties) {
|
||||
ZuulProperties zuulProperties,
|
||||
Set<ZuulFallbackProvider> zuulFallbackProviders) {
|
||||
super(zuulFallbackProviders);
|
||||
this.clientFactory = clientFactory;
|
||||
this.zuulProperties = zuulProperties;
|
||||
}
|
||||
@@ -45,10 +52,12 @@ public class RestClientRibbonCommandFactory implements RibbonCommandFactory<Rest
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public RestClientRibbonCommand create(RibbonCommandContext context) {
|
||||
RestClient restClient = this.clientFactory.getClient(context.getServiceId(),
|
||||
String serviceId = context.getServiceId();
|
||||
ZuulFallbackProvider fallbackProvider = getFallbackProvider(serviceId);
|
||||
RestClient restClient = this.clientFactory.getClient(serviceId,
|
||||
RestClient.class);
|
||||
return new RestClientRibbonCommand(context.getServiceId(), restClient, context,
|
||||
this.zuulProperties);
|
||||
this.zuulProperties, fallbackProvider);
|
||||
}
|
||||
|
||||
public SpringClientFactory getClientFactory() {
|
||||
|
||||
+40
-23
@@ -63,6 +63,7 @@ import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
|
||||
@@ -271,34 +272,13 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
URL host = RequestContext.getCurrentContext().getRouteHost();
|
||||
HttpHost httpHost = getHttpHost(host);
|
||||
uri = StringUtils.cleanPath((host.getPath() + uri).replaceAll("/{2,}", "/"));
|
||||
HttpRequest httpRequest;
|
||||
int contentLength = request.getContentLength();
|
||||
InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
|
||||
request.getContentType() != null
|
||||
? ContentType.create(request.getContentType()) : null);
|
||||
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;
|
||||
default:
|
||||
httpRequest = new BasicHttpRequest(verb,
|
||||
uri + this.helper.getQueryString(params));
|
||||
log.debug(uri + this.helper.getQueryString(params));
|
||||
}
|
||||
|
||||
HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params);
|
||||
try {
|
||||
httpRequest.setHeaders(convertHeaders(headers));
|
||||
log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " "
|
||||
+ httpHost.getSchemeName());
|
||||
HttpResponse zuulResponse = forwardRequest(httpclient, httpHost, httpRequest);
|
||||
@@ -314,6 +294,43 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
|
||||
}
|
||||
}
|
||||
|
||||
protected HttpRequest buildHttpRequest (String verb, String uri, InputStreamEntity entity,
|
||||
MultiValueMap<String, String> headers, MultiValueMap<String, String> 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<String, String> revertHeaders(Header[] headers) {
|
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
|
||||
for (Header header : headers) {
|
||||
|
||||
+40
@@ -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();
|
||||
}
|
||||
+10
@@ -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<RibbonLoadBalancingHttpClient, RibbonApacheHttpRequest, RibbonApacheHttpResponse> {
|
||||
|
||||
@@ -36,6 +38,14 @@ public class HttpClientRibbonCommand extends AbstractRibbonCommand<RibbonLoadBal
|
||||
super(commandKey, client, context, zuulProperties);
|
||||
}
|
||||
|
||||
public HttpClientRibbonCommand(final String commandKey,
|
||||
final RibbonLoadBalancingHttpClient client,
|
||||
final RibbonCommandContext context,
|
||||
final ZuulProperties zuulProperties,
|
||||
final ZuulFallbackProvider zuulFallbackProvider) {
|
||||
super(commandKey, client, context, zuulProperties, zuulFallbackProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RibbonApacheHttpRequest createRequest() throws Exception {
|
||||
return new RibbonApacheHttpRequest(this.context);
|
||||
|
||||
+20
-7
@@ -16,33 +16,46 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.apache;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommandFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class HttpClientRibbonCommandFactory implements
|
||||
RibbonCommandFactory<HttpClientRibbonCommand> {
|
||||
public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
|
||||
private final ZuulProperties zuulProperties;
|
||||
|
||||
public HttpClientRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
|
||||
this(clientFactory, zuulProperties, Collections.<ZuulFallbackProvider>emptySet());
|
||||
}
|
||||
|
||||
public HttpClientRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties,
|
||||
Set<ZuulFallbackProvider> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-3
@@ -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<OkHttpLoadBalancingClient, OkHttpRibbonRequest, OkHttpRibbonResponse> {
|
||||
|
||||
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);
|
||||
|
||||
+22
-9
@@ -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<OkHttpRibbonCommand> {
|
||||
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.<ZuulFallbackProvider>emptySet());
|
||||
}
|
||||
|
||||
public OkHttpRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties,
|
||||
Set<ZuulFallbackProvider> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-1
@@ -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<LBC extends AbstractLoadBalancerAwar
|
||||
|
||||
protected final LBC client;
|
||||
protected RibbonCommandContext context;
|
||||
protected ZuulFallbackProvider zuulFallbackProvider;
|
||||
|
||||
public AbstractRibbonCommand(LBC client, RibbonCommandContext context,
|
||||
ZuulProperties zuulProperties) {
|
||||
@@ -52,9 +53,16 @@ public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwar
|
||||
|
||||
public AbstractRibbonCommand(String commandKey, LBC client,
|
||||
RibbonCommandContext context, ZuulProperties zuulProperties) {
|
||||
this(commandKey, client, context, zuulProperties, null);
|
||||
}
|
||||
|
||||
public AbstractRibbonCommand(String commandKey, LBC client,
|
||||
RibbonCommandContext context, ZuulProperties zuulProperties,
|
||||
ZuulFallbackProvider fallbackProvider) {
|
||||
super(getSetter(commandKey, zuulProperties));
|
||||
this.client = client;
|
||||
this.context = context;
|
||||
this.zuulFallbackProvider = fallbackProvider;
|
||||
}
|
||||
|
||||
protected static Setter getSetter(final String commandKey,
|
||||
@@ -101,6 +109,14 @@ public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwar
|
||||
return new RibbonHttpResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse getFallback() {
|
||||
if(zuulFallbackProvider != null) {
|
||||
return zuulFallbackProvider.fallbackResponse();
|
||||
}
|
||||
return super.getFallback();
|
||||
}
|
||||
|
||||
public LBC getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
+44
@@ -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.support;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public abstract class AbstractRibbonCommandFactory implements RibbonCommandFactory {
|
||||
|
||||
private Map<String, ZuulFallbackProvider> fallbackProviderCache;
|
||||
|
||||
public AbstractRibbonCommandFactory(Set<ZuulFallbackProvider> fallbackProviders){
|
||||
this.fallbackProviderCache = new HashMap<String, ZuulFallbackProvider>();
|
||||
for(ZuulFallbackProvider provider : fallbackProviders) {
|
||||
fallbackProviderCache.put(provider.getRoute(), provider);
|
||||
}
|
||||
}
|
||||
|
||||
protected ZuulFallbackProvider getFallbackProvider(String route) {
|
||||
return fallbackProviderCache.get(route);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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 {
|
||||
|
||||
+14
-4
@@ -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]);
|
||||
|
||||
+7
-5
@@ -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 {
|
||||
|
||||
|
||||
+126
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+107
-17
@@ -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<Client> {
|
||||
|
||||
@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<String> {
|
||||
|
||||
@Override
|
||||
public String create(Throwable cause) {
|
||||
return "tryinToTrickYa";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+44
-3
@@ -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<Hello> failFuture();
|
||||
}
|
||||
|
||||
@FeignClient(name = "localapp4", fallbackFactory = HystrixClientFallbackFactory.class)
|
||||
protected interface HystrixClientWithFallBackFactory {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/fail")
|
||||
Hello fail();
|
||||
}
|
||||
|
||||
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClientWithFallBackFactory> {
|
||||
|
||||
@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);
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+5
-10
@@ -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);
|
||||
|
||||
+26
@@ -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() {
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
+63
@@ -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<String, LoadBalancedRetryPolicyFactory> factories = context.getBeansOfType(LoadBalancedRetryPolicyFactory.class);
|
||||
assertThat(factories.values(), hasSize(0));
|
||||
}
|
||||
}
|
||||
+56
@@ -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<String, LoadBalancedRetryPolicyFactory> 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;
|
||||
}
|
||||
}
|
||||
+40
-5
@@ -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<String> 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<String> 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<String> 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<String> 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<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/app/strip", HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
ResponseEntity<String> 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 + "!";
|
||||
}
|
||||
|
||||
+22
@@ -259,6 +259,28 @@ public class ProxyRequestHelperTests {
|
||||
assertThat(queryString, is("?wsdl"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQueryStringEncoded() {
|
||||
MultiValueMap<String, String> 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<String, String> 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";
|
||||
|
||||
+17
@@ -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");
|
||||
|
||||
+18
@@ -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<String, String>(), new LinkedMultiValueMap<String, String>());
|
||||
|
||||
assertTrue(httpRequest instanceof HttpEntityEnclosingRequest);
|
||||
HttpEntityEnclosingRequest httpEntityEnclosingRequest = (HttpEntityEnclosingRequest) httpRequest;
|
||||
assertTrue(httpEntityEnclosingRequest.getEntity() != null);
|
||||
}
|
||||
|
||||
private void setupContext() {
|
||||
this.context.register(PropertyPlaceholderAutoConfiguration.class,
|
||||
TestConfiguration.class);
|
||||
|
||||
+46
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+10
-1
@@ -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<ZuulFallbackProvider> 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
|
||||
|
||||
+44
@@ -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();
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -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<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
|
||||
|
||||
@Bean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
final SpringClientFactory clientFactory) {
|
||||
return new OkHttpRibbonCommandFactory(clientFactory, new ZuulProperties());
|
||||
return new OkHttpRibbonCommandFactory(clientFactory, new ZuulProperties(),
|
||||
zuulFallbackProviders);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+44
@@ -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();
|
||||
}
|
||||
}
|
||||
+29
-6
@@ -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<String> 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<ZuulFallbackProvider> 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<ZuulFallbackProvider> fallbackProviders) {
|
||||
super(clientFactory, new ZuulProperties(), fallbackProviders);
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
|
||||
+58
@@ -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<String> 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<String> 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());
|
||||
}
|
||||
}
|
||||
+76
-8
@@ -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
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.2.BUILD-SNAPSHOT</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-dependencies</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-netflix-dependencies</name>
|
||||
<description>Spring Cloud Netflix Dependencies</description>
|
||||
@@ -17,10 +17,10 @@
|
||||
<archaius.version>0.7.4</archaius.version>
|
||||
<eureka.version>1.4.11</eureka.version>
|
||||
<feign.version>9.3.1</feign.version>
|
||||
<hystrix.version>1.5.5</hystrix.version>
|
||||
<hystrix.version>1.5.6</hystrix.version>
|
||||
<ribbon.version>2.2.0</ribbon.version>
|
||||
<servo.version>0.10.1</servo.version>
|
||||
<zuul.version>1.2.2</zuul.version>
|
||||
<zuul.version>1.3.0</zuul.version>
|
||||
<rxjava.version>1.1.10</rxjava.version>
|
||||
<java.version>1.7</java.version>
|
||||
<turbine.version>1.0.0</turbine.version>
|
||||
@@ -270,6 +270,10 @@
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>*</artifactId>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Eureka core dep that is now optional -->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
|
||||
|
||||
+3
-1
@@ -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)) {
|
||||
|
||||
+29
-8
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
@@ -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();
|
||||
|
||||
+24
-2
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
|
||||
|
||||
+6
-14
@@ -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
|
||||
|
||||
+54
-24
@@ -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<Application> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -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;
|
||||
}
|
||||
}
|
||||
+186
@@ -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<ApplicationEvent> 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<EurekaInstanceRegisteredEvent>
|
||||
instanceRegisteredEventListenerMock;
|
||||
|
||||
@MockBean
|
||||
private ApplicationListener<EurekaInstanceCanceledEvent>
|
||||
instanceCanceledEventListenerMock;
|
||||
|
||||
@MockBean
|
||||
private ApplicationListener<EurekaInstanceRenewedEvent> 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<InstanceInfo> instances = new ArrayList<>();
|
||||
instances.add(instanceInfo);
|
||||
final Application application = new Application(APP_NAME, instances);
|
||||
final List<Application> 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]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-hystrix-amqp</artifactId>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<properties>
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-sidecar</artifactId>
|
||||
|
||||
+11
-2
@@ -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);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-spectator</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-turbine-stream</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-turbine</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-archaius</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-atlas</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-eureka-server</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-eureka</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-feign</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-hystrix</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-ribbon</artifactId>
|
||||
@@ -20,6 +20,14 @@
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-spectator</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-turbine-amqp</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-turbine-stream</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-turbine</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.2.1.BUILD-SNAPSHOT</version>
|
||||
<version>1.2.3.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-zuul</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user