Removes maintenance mode bits of ribbon and hystrix

This commit is contained in:
Spencer Gibb
2020-01-21 21:54:28 -05:00
parent 0b3c03dc74
commit 7d57ec259e
35 changed files with 106 additions and 2527 deletions

20
pom.xml
View File

@@ -82,21 +82,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>${spring-cloud-netflix.version}</version>
<exclusions>
<exclusion>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-transport</artifactId>
</exclusion>
<exclusion>
<groupId>io.reactivex</groupId>
<artifactId>rxnetty</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-test-support</artifactId>
@@ -108,11 +93,6 @@
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>${spring-cloud-netflix.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>${spring-cloud-netflix.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-circuitbreaker-dependencies</artifactId>

View File

@@ -38,16 +38,6 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>

View File

@@ -19,7 +19,6 @@ package org.springframework.cloud.gateway.config;
import java.security.cert.X509Certificate;
import java.util.List;
import com.netflix.hystrix.HystrixObservableCommand;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@@ -29,7 +28,6 @@ import reactor.core.publisher.Flux;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.ProxyProvider;
import rx.RxReactiveStreams;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
@@ -66,9 +64,7 @@ import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayF
import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
@@ -160,7 +156,7 @@ import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class,
WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
@AutoConfigureAfter({ GatewayReactiveLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
@@ -712,24 +708,6 @@ public class GatewayAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixObservableCommand.class, RxReactiveStreams.class })
protected static class HystrixConfiguration {
@Bean
public HystrixGatewayFilterFactory hystrixGatewayFilterFactory(
ObjectProvider<DispatcherHandler> dispatcherHandler) {
return new HystrixGatewayFilterFactory(dispatcherHandler);
}
@Bean
@ConditionalOnMissingBean(FallbackHeadersGatewayFilterFactory.class)
public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() {
return new FallbackHeadersGatewayFilterFactory();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Health.class)
protected static class GatewayActuatorConfiguration {

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.config;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerHystrixFilterFactory;
import org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerAutoConfiguration;
import org.springframework.cloud.netflix.hystrix.ReactiveHystrixCircuitBreakerAutoConfiguration;
import org.springframework.cloud.netflix.hystrix.ReactiveHystrixCircuitBreakerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;
/**
* @author Ryan Baxter
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@AutoConfigureAfter({ ReactiveHystrixCircuitBreakerAutoConfiguration.class })
@ConditionalOnClass({ DispatcherHandler.class,
HystrixCircuitBreakerAutoConfiguration.class, ReactiveCircuitBreakerFactory.class,
ReactiveHystrixCircuitBreakerFactory.class })
public class GatewayHystrixCircuitBreakerAutoConfiguration {
@Bean
@ConditionalOnBean(ReactiveHystrixCircuitBreakerFactory.class)
public SpringCloudCircuitBreakerHystrixFilterFactory springCloudCircuitBreakerHystrixFilterFactory(
ReactiveHystrixCircuitBreakerFactory reactiveCircuitBreakerFactory,
ObjectProvider<DispatcherHandler> dispatcherHandler) {
return new SpringCloudCircuitBreakerHystrixFilterFactory(
reactiveCircuitBreakerFactory, dispatcherHandler);
}
@Bean
@ConditionalOnMissingBean(FallbackHeadersGatewayFilterFactory.class)
public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() {
return new FallbackHeadersGatewayFilterFactory();
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.config;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;
/**
* AutoConfiguration for {@link LoadBalancerClientFilter}.
*
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ LoadBalancerClient.class, RibbonAutoConfiguration.class,
DispatcherHandler.class })
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class GatewayLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClient.class)
@ConditionalOnMissingBean({ LoadBalancerClientFilter.class,
ReactiveLoadBalancerClientFilter.class })
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
LoadBalancerProperties properties) {
return new LoadBalancerClientFilter(client, properties);
}
}

View File

@@ -24,17 +24,17 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER;
import static org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR;
@@ -42,14 +42,14 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
* @author Spencer Gibb
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration")
@ConditionalOnMissingBean(LoadBalancerClient.class)
@ConditionalOnMissingClass("org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer")
@ConditionalOnMissingBean(ReactiveLoadBalancer.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
@AutoConfigureAfter(GatewayLoadBalancerClientAutoConfiguration.class)
@AutoConfigureAfter(GatewayReactiveLoadBalancerClientAutoConfiguration.class)
public class GatewayNoLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean(LoadBalancerClientFilter.class)
@ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)
public NoLoadBalancerClientFilter noLoadBalancerClientFilter(
LoadBalancerProperties properties) {
return new NoLoadBalancerClientFilter(properties.isUse404());

View File

@@ -17,20 +17,15 @@
package org.springframework.cloud.gateway.config;
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.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;
@@ -43,7 +38,6 @@ import org.springframework.web.reactive.DispatcherHandler;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveLoadBalancer.class, LoadBalancerAutoConfiguration.class,
DispatcherHandler.class })
@AutoConfigureBefore(GatewayLoadBalancerClientAutoConfiguration.class)
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class GatewayReactiveLoadBalancerClientAutoConfiguration {
@@ -51,29 +45,9 @@ public class GatewayReactiveLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)
@Conditional(OnNoRibbonDefaultCondition.class)
public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(
LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
return new ReactiveLoadBalancerClientFilter(clientFactory, properties);
}
private static final class OnNoRibbonDefaultCondition extends AnyNestedCondition {
private OnNoRibbonDefaultCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
havingValue = "false")
static class RibbonNotEnabled {
}
@ConditionalOnMissingClass("org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient")
static class RibbonLoadBalancerNotPresent {
}
}
}

View File

@@ -1,115 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.filter;
import java.net.URI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
/**
* @deprecated in favour of {@link ReactiveLoadBalancerClientFilter}
* @author Spencer Gibb
* @author Tim Ysewyn
*/
@Deprecated
public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
/**
* Filter order for {@link LoadBalancerClientFilter}.
*/
public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10100;
private static final Log log = LogFactory.getLog(LoadBalancerClientFilter.class);
protected final LoadBalancerClient loadBalancer;
private LoadBalancerProperties properties;
public LoadBalancerClientFilter(LoadBalancerClient loadBalancer,
LoadBalancerProperties properties) {
this.loadBalancer = loadBalancer;
this.properties = properties;
}
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER;
}
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url before: " + url);
}
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
protected ServiceInstance choose(ServerWebExchange exchange) {
return loadBalancer.choose(
((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
}
}

View File

@@ -52,7 +52,10 @@ public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory
.getLog(ReactiveLoadBalancerClientFilter.class);
private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
/**
* Order of filter.
*/
public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
private final LoadBalancerClientFactory clientFactory;

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.gateway.filter.factory;
import java.util.ArrayList;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
@@ -23,10 +24,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang.exception.ExceptionUtils.getRootCause;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR;
/**
* @author Olga Maciaszek-Sharma
@@ -47,41 +45,50 @@ public class FallbackHeadersGatewayFilterFactory
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerWebExchange filteredExchange = ofNullable(ofNullable(
(Throwable) exchange.getAttribute(HYSTRIX_EXECUTION_EXCEPTION_ATTR))
.orElseGet(() -> exchange.getAttribute(
CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR)))
.map(executionException -> {
ServerHttpRequest.Builder requestBuilder = exchange
.getRequest().mutate();
requestBuilder.header(
config.executionExceptionTypeHeaderName,
executionException.getClass()
.getName());
requestBuilder.header(
config.executionExceptionMessageHeaderName,
executionException.getMessage());
ofNullable(
getRootCause(executionException))
.ifPresent(rootCause -> {
requestBuilder.header(
config.rootCauseExceptionTypeHeaderName,
rootCause
.getClass()
.getName());
requestBuilder.header(
config.rootCauseExceptionMessageHeaderName,
rootCause
.getMessage());
});
return exchange.mutate()
.request(requestBuilder.build())
.build();
}).orElse(exchange);
Throwable exception = exchange
.getAttribute(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR);
ServerWebExchange filteredExchange;
if (exception == null) {
filteredExchange = exchange;
}
else {
filteredExchange = addFallbackHeaders(config, exchange, exception);
}
return chain.filter(filteredExchange);
};
}
private ServerWebExchange addFallbackHeaders(Config config,
ServerWebExchange exchange, Throwable executionException) {
ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();
requestBuilder.header(config.executionExceptionTypeHeaderName,
executionException.getClass().getName());
requestBuilder.header(config.executionExceptionMessageHeaderName,
executionException.getMessage());
Throwable rootCause = getRootCause(executionException);
if (rootCause != null) {
requestBuilder.header(config.rootCauseExceptionTypeHeaderName,
rootCause.getClass().getName());
requestBuilder.header(config.rootCauseExceptionMessageHeaderName,
rootCause.getMessage());
}
return exchange.mutate().request(requestBuilder.build()).build();
}
private static Throwable getRootCause(final Throwable throwable) {
final List<Throwable> list = getThrowableList(throwable);
return list.isEmpty() ? null : list.get(list.size() - 1);
}
private static List<Throwable> getThrowableList(Throwable throwable) {
final List<Throwable> list = new ArrayList<>();
while (throwable != null && !list.contains(throwable)) {
list.add(throwable);
throwable = throwable.getCause();
}
return list;
}
public static class Config {
private static final String EXECUTION_EXCEPTION_TYPE = "Execution-Exception-Type";

View File

@@ -1,291 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.filter.factory;
import java.net.URI;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixObservableCommand;
import com.netflix.hystrix.HystrixObservableCommand.Setter;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import rx.Observable;
import rx.RxReactiveStreams;
import rx.Subscription;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.ServiceUnavailableException;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.removeAlreadyRouted;
/**
* Depends on `spring-cloud-starter-netflix-hystrix`,
* {@see https://cloud.spring.io/spring-cloud-netflix/}.
*
* @author Spencer Gibb
* @author Michele Mancioppi
* @author Olga Maciaszek-Sharma
*/
public class HystrixGatewayFilterFactory
extends AbstractGatewayFilterFactory<HystrixGatewayFilterFactory.Config> {
private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;
// do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
private volatile DispatcherHandler dispatcherHandler;
public HystrixGatewayFilterFactory(
ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
super(Config.class);
this.dispatcherHandlerProvider = dispatcherHandlerProvider;
}
private DispatcherHandler getDispatcherHandler() {
if (dispatcherHandler == null) {
dispatcherHandler = dispatcherHandlerProvider.getIfAvailable();
}
return dispatcherHandler;
}
@Override
public List<String> shortcutFieldOrder() {
return singletonList(NAME_KEY);
}
@Override
// TODO: make Config implement HasRouteId and remove this method.
public GatewayFilter apply(String routeId, Consumer<Config> consumer) {
Config config = newConfig();
consumer.accept(config);
if (StringUtils.isEmpty(config.getName()) && !StringUtils.isEmpty(routeId)) {
config.setName(routeId);
}
return apply(config);
}
/**
* Create a {@link Setter} based on incoming request attribute. <br>
* This could be useful for example to create a Setter with {@link HystrixCommandKey}
* being set as the target service's host:port, as obtained from
* {@link ServerWebExchange#getRequest()} to do per service instance level circuit
* breaking.
*/
protected Setter createCommandSetter(Config config, ServerWebExchange exchange) {
return config.setter;
}
@Override
public GatewayFilter apply(Config config) {
// TODO: if no name is supplied, generate one from command id (useful for default
// filter)
if (config.setter == null) {
Assert.notNull(config.name,
"A name must be supplied for the Hystrix Command Key");
HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory
.asKey(getClass().getSimpleName());
HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(config.name);
config.setter = Setter.withGroupKey(groupKey).andCommandKey(commandKey);
}
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
return Mono.deferWithContext(context -> {
RouteHystrixCommand command = new RouteHystrixCommand(
createCommandSetter(config, exchange), config.fallbackUri,
exchange, chain, context);
return Mono.create(s -> {
Subscription sub = command.toObservable().subscribe(s::success,
s::error, s::success);
s.onCancel(sub::unsubscribe);
}).onErrorResume((Function<Throwable, Mono<Void>>) throwable -> {
if (throwable instanceof HystrixRuntimeException) {
HystrixRuntimeException e = (HystrixRuntimeException) throwable;
HystrixRuntimeException.FailureType failureType = e
.getFailureType();
switch (failureType) {
case TIMEOUT:
return Mono.error(new TimeoutException());
case SHORTCIRCUIT:
return Mono.error(new ServiceUnavailableException());
case COMMAND_EXCEPTION: {
Throwable cause = e.getCause();
/*
* We forsake here the null check for cause as
* HystrixRuntimeException will always have a cause if the
* failure type is COMMAND_EXCEPTION.
*/
if (cause instanceof ResponseStatusException
|| AnnotatedElementUtils.findMergedAnnotation(
cause.getClass(),
ResponseStatus.class) != null) {
return Mono.error(cause);
}
}
default:
break;
}
}
return Mono.error(throwable);
}).then();
});
}
@Override
public String toString() {
return filterToStringCreator(HystrixGatewayFilterFactory.this)
.append("name", config.getName())
.append("fallback", config.fallbackUri).toString();
}
};
}
public static class Config {
private String name;
private Setter setter;
private URI fallbackUri;
public String getName() {
return name;
}
public Config setName(String name) {
this.name = name;
return this;
}
public Config setFallbackUri(String fallbackUri) {
if (fallbackUri != null) {
setFallbackUri(URI.create(fallbackUri));
}
return this;
}
public URI getFallbackUri() {
return fallbackUri;
}
public void setFallbackUri(URI fallbackUri) {
if (fallbackUri != null && !"forward".equals(fallbackUri.getScheme())) {
throw new IllegalArgumentException(
"Hystrix Filter currently only supports 'forward' URIs, found "
+ fallbackUri);
}
this.fallbackUri = fallbackUri;
}
public Config setSetter(Setter setter) {
this.setter = setter;
return this;
}
}
// TODO: replace with HystrixMonoCommand that we write
private class RouteHystrixCommand extends HystrixObservableCommand<Void> {
private final URI fallbackUri;
private final ServerWebExchange exchange;
private final GatewayFilterChain chain;
private final Context context;
RouteHystrixCommand(Setter setter, URI fallbackUri, ServerWebExchange exchange,
GatewayFilterChain chain, Context context) {
super(setter);
this.fallbackUri = fallbackUri;
this.exchange = exchange;
this.chain = chain;
this.context = context;
}
@Override
protected Observable<Void> construct() {
return RxReactiveStreams
.toObservable(this.chain.filter(exchange).subscriberContext(context));
}
@Override
protected Observable<Void> resumeWithFallback() {
if (this.fallbackUri == null) {
return super.resumeWithFallback();
}
// TODO: copied from RouteToRequestUrlFilter
URI uri = exchange.getRequest().getURI();
// TODO: assume always?
boolean encoded = containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null)
.uri(this.fallbackUri).scheme(null).build(encoded).toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
addExceptionDetails();
ServerHttpRequest request = this.exchange.getRequest().mutate()
.uri(requestUrl).build();
ServerWebExchange mutated = exchange.mutate().request(request).build();
// Before we continue on remove the already routed attribute since the
// fallback may go back through the route handler if the fallback
// is to another route in the Gateway
removeAlreadyRouted(mutated);
return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));
}
private void addExceptionDetails() {
Throwable executionException = getExecutionException();
ofNullable(executionException).ifPresent(exception -> exchange.getAttributes()
.put(HYSTRIX_EXECUTION_EXCEPTION_ATTR, exception));
}
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.filter.factory;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.cloud.gateway.support.ServiceUnavailableException;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ResponseStatusException;
/**
* @author Ryan Baxter
*/
public class SpringCloudCircuitBreakerHystrixFilterFactory
extends SpringCloudCircuitBreakerFilterFactory {
public SpringCloudCircuitBreakerHystrixFilterFactory(
ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory,
ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
super(reactiveCircuitBreakerFactory, dispatcherHandlerProvider);
}
@Override
protected Mono<Void> handleErrorWithoutFallback(Throwable throwable) {
if (throwable instanceof HystrixRuntimeException) {
HystrixRuntimeException e = (HystrixRuntimeException) throwable;
HystrixRuntimeException.FailureType failureType = e.getFailureType();
switch (failureType) {
case TIMEOUT:
return Mono.error(new TimeoutException());
case SHORTCIRCUIT:
return Mono.error(new ServiceUnavailableException());
case COMMAND_EXCEPTION: {
Throwable cause = e.getCause();
/*
* We forsake here the null check for cause as HystrixRuntimeException
* will always have a cause if the failure type is COMMAND_EXCEPTION.
*/
if (cause instanceof ResponseStatusException
|| AnnotatedElementUtils.findMergedAnnotation(cause.getClass(),
ResponseStatus.class) != null) {
return Mono.error(cause);
}
}
default:
break;
}
}
return Mono.error(throwable);
}
}

View File

@@ -42,7 +42,6 @@ import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGateway
import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory.Strategy;
import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
@@ -74,7 +73,6 @@ import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;
import org.springframework.web.server.ServerWebExchange;
@@ -199,27 +197,6 @@ public class GatewayFilterSpec extends UriSpec {
c -> c.setStrategy(Strategy.valueOf(strategy)).setName(headerName)));
}
/**
* Wraps the route in a Hystrix command. Depends on @{code
* org.springframework.cloud::spring-cloud-starter-netflix-hystrix} being on the
* classpath, {@see https://cloud.spring.io/spring-cloud-netflix/}
* @param configConsumer a {@link Consumer} which provides configuration for the
* Hystrix command
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
*/
public GatewayFilterSpec hystrix(
Consumer<HystrixGatewayFilterFactory.Config> configConsumer) {
HystrixGatewayFilterFactory factory;
try {
factory = getBean(HystrixGatewayFilterFactory.class);
}
catch (NoSuchBeanDefinitionException e) {
throw new NoSuchBeanDefinitionException(HystrixGatewayFilterFactory.class,
"This is probably because Hystrix is missing from the classpath, which can be resolved by adding dependency on 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'");
}
return filter(factory.apply(this.routeBuilder.getId(), configConsumer));
}
public GatewayFilterSpec circuitBreaker(
Consumer<SpringCloudCircuitBreakerFilterFactory.Config> configConsumer) {
SpringCloudCircuitBreakerFilterFactory filterFactory;

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.support;
import java.util.Map;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.core.convert.ConversionService;
import org.springframework.validation.Validator;
@Deprecated
public abstract class ConfigurationUtils {
@Deprecated
public static void bind(Object o, Map<String, Object> properties,
String configurationPropertyName, String bindingName, Validator validator) {
bind(o, properties, configurationPropertyName, bindingName, validator, null);
}
@Deprecated
public static void bind(Object o, Map<String, Object> properties,
String configurationPropertyName, String bindingName, Validator validator,
ConversionService conversionService) {
Object toBind = getTargetObject(o);
Bindable<?> bindable = Bindable.ofInstance(toBind);
ConfigurationService.bindOrCreate(bindable, properties,
configurationPropertyName/* , bindingName */, validator,
conversionService);
}
@Deprecated
@SuppressWarnings("unchecked")
public static <T> T getTargetObject(Object candidate) {
return ConfigurationService.getTargetObject(candidate);
}
}

View File

@@ -1,262 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.support;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClientResponseException;
/**
* Default implementation of {@link ClientResponse}.
*
* @author Arjen Poutsma
* @author Brian Clozel
* @since 5.0
* @deprecated Will be removed in future release.
*/
@Deprecated
public class DefaultClientResponse implements ClientResponse {
private final ClientHttpResponse response;
private final Headers headers;
private final ExchangeStrategies strategies;
public DefaultClientResponse(ClientHttpResponse response,
ExchangeStrategies strategies) {
this.response = response;
this.strategies = strategies;
this.headers = new DefaultHeaders();
}
@Override
public ExchangeStrategies strategies() {
return this.strategies;
}
@Override
public HttpStatus statusCode() {
return this.response.getStatusCode();
}
@Override
public int rawStatusCode() {
return this.response.getRawStatusCode();
}
@Override
public Headers headers() {
return this.headers;
}
@Override
public MultiValueMap<String, ResponseCookie> cookies() {
return this.response.getCookies();
}
@Override
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
return extractor.extract(this.response, new BodyExtractor.Context() {
@Override
public List<HttpMessageReader<?>> messageReaders() {
return strategies.messageReaders();
}
@Override
public Optional<ServerHttpResponse> serverResponse() {
return Optional.empty();
}
@Override
public Map<String, Object> hints() {
return Collections.emptyMap();
}
});
}
@Override
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
if (Void.class.isAssignableFrom(elementClass)) {
return consumeAndCancel();
}
else {
return body(BodyExtractors.toMono(elementClass));
}
}
@Override
public Mono<Void> releaseBody() {
return body(BodyExtractors.toDataBuffers()).map(DataBufferUtils::release).then();
}
@Override
public Mono<ResponseEntity<Void>> toBodilessEntity() {
return releaseBody().then(toEntityInternal(Mono.empty()));
}
@Override
public Mono<WebClientResponseException> createException() {
throw new UnsupportedOperationException();
}
@SuppressWarnings("unchecked")
private <T> Mono<T> consumeAndCancel() {
return (Mono<T>) this.response.getBody().map(buffer -> {
DataBufferUtils.release(buffer);
throw new ReadCancellationException();
}).onErrorResume(ReadCancellationException.class, ex -> Mono.empty()).then();
}
@Override
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
if (Void.class.isAssignableFrom(typeReference.getType().getClass())) {
return consumeAndCancel();
}
else {
return body(BodyExtractors.toMono(typeReference));
}
}
@Override
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
if (Void.class.isAssignableFrom(elementClass)) {
return Flux.from(consumeAndCancel());
}
else {
return body(BodyExtractors.toFlux(elementClass));
}
}
@Override
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
if (Void.class.isAssignableFrom(typeReference.getType().getClass())) {
return Flux.from(consumeAndCancel());
}
else {
return body(BodyExtractors.toFlux(typeReference));
}
}
@Override
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
if (Void.class.isAssignableFrom(bodyType)) {
return toEntityInternal(consumeAndCancel());
}
else {
return toEntityInternal(bodyToMono(bodyType));
}
}
@Override
public <T> Mono<ResponseEntity<T>> toEntity(
ParameterizedTypeReference<T> typeReference) {
if (Void.class.isAssignableFrom(typeReference.getType().getClass())) {
return toEntityInternal(consumeAndCancel());
}
else {
return toEntityInternal(bodyToMono(typeReference));
}
}
private <T> Mono<ResponseEntity<T>> toEntityInternal(Mono<T> bodyMono) {
HttpHeaders headers = headers().asHttpHeaders();
HttpStatus statusCode = statusCode();
return bodyMono.map(body -> new ResponseEntity<>(body, headers, statusCode))
.switchIfEmpty(Mono.defer(
() -> Mono.just(new ResponseEntity<>(headers, statusCode))));
}
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> responseType) {
return toEntityListInternal(bodyToFlux(responseType));
}
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(
ParameterizedTypeReference<T> typeReference) {
return toEntityListInternal(bodyToFlux(typeReference));
}
private <T> Mono<ResponseEntity<List<T>>> toEntityListInternal(Flux<T> bodyFlux) {
HttpHeaders headers = headers().asHttpHeaders();
HttpStatus statusCode = statusCode();
return bodyFlux.collectList()
.map(body -> new ResponseEntity<>(body, headers, statusCode));
}
@SuppressWarnings("serial")
private static class ReadCancellationException extends RuntimeException {
}
private class DefaultHeaders implements Headers {
private HttpHeaders delegate() {
return response.getHeaders();
}
@Override
public OptionalLong contentLength() {
return toOptionalLong(delegate().getContentLength());
}
@Override
public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType());
}
@Override
public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList());
}
@Override
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate());
}
private OptionalLong toOptionalLong(long value) {
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
}
}
}

View File

@@ -1,311 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.support;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.Function;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.WebSession;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriComponentsBuilder;
/**
* {@code ServerRequest} implementation based on a {@link ServerWebExchange}.
*
* @author Arjen Poutsma
* @since 5.0
* @deprecated Will be removed in a future release.
*/
@Deprecated
public class DefaultServerRequest implements ServerRequest {
private static final Function<UnsupportedMediaTypeException, UnsupportedMediaTypeStatusException> ERROR_MAPPER = ex -> (ex
.getContentType() != null
? new UnsupportedMediaTypeStatusException(ex.getContentType(),
ex.getSupportedMediaTypes())
: new UnsupportedMediaTypeStatusException(ex.getMessage()));
private final ServerWebExchange exchange;
private final Headers headers;
private final List<HttpMessageReader<?>> messageReaders;
public DefaultServerRequest(ServerWebExchange exchange) {
this(exchange, HandlerStrategies.withDefaults().messageReaders());
}
public DefaultServerRequest(ServerWebExchange exchange,
List<HttpMessageReader<?>> messageReaders) {
this.exchange = exchange;
this.messageReaders = Collections
.unmodifiableList(new ArrayList<>(messageReaders));
this.headers = new DefaultHeaders();
}
@Override
public String methodName() {
return request().getMethodValue();
}
@Override
public URI uri() {
return request().getURI();
}
@Override
public UriBuilder uriBuilder() {
return UriComponentsBuilder.fromHttpRequest(new ServerRequestAdapter());
}
@Override
public PathContainer pathContainer() {
return request().getPath();
}
@Override
public Headers headers() {
return this.headers;
}
@Override
public MultiValueMap<String, HttpCookie> cookies() {
return request().getCookies();
}
@Override
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
return body(extractor, Collections.emptyMap());
}
@Override
public Optional<InetSocketAddress> remoteAddress() {
return Optional.of(request().getRemoteAddress());
}
@Override
public List<HttpMessageReader<?>> messageReaders() {
return this.messageReaders;
}
@Override
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor,
Map<String, Object> hints) {
return extractor.extract(request(), new BodyExtractor.Context() {
@Override
public List<HttpMessageReader<?>> messageReaders() {
return messageReaders;
}
@Override
public Optional<ServerHttpResponse> serverResponse() {
return Optional.of(exchange().getResponse());
}
@Override
public Map<String, Object> hints() {
return hints;
}
});
}
@Override
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
Mono<T> mono = body(BodyExtractors.toMono(elementClass));
return mono.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
}
@Override
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
Mono<T> mono = body(BodyExtractors.toMono(typeReference));
return mono.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
}
@Override
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
Flux<T> flux = body(BodyExtractors.toFlux(elementClass));
return flux.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
}
@Override
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
Flux<T> flux = body(BodyExtractors.toFlux(typeReference));
return flux.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
}
@Override
public Map<String, Object> attributes() {
return this.exchange.getAttributes();
}
@Override
public MultiValueMap<String, String> queryParams() {
return request().getQueryParams();
}
@Override
public Map<String, String> pathVariables() {
return this.exchange.getAttributeOrDefault(
RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
}
@Override
public Mono<WebSession> session() {
return this.exchange.getSession();
}
@Override
public Mono<? extends Principal> principal() {
return this.exchange.getPrincipal();
}
@Override
public Mono<MultiValueMap<String, String>> formData() {
return this.exchange.getFormData();
}
@Override
public Mono<MultiValueMap<String, Part>> multipartData() {
return this.exchange.getMultipartData();
}
private ServerHttpRequest request() {
return this.exchange.getRequest();
}
public ServerWebExchange exchange() {
return this.exchange;
}
@Override
public String toString() {
return String.format("%s %s", method(), path());
}
private class DefaultHeaders implements Headers {
private HttpHeaders delegate() {
return request().getHeaders();
}
@Override
public List<MediaType> accept() {
return delegate().getAccept();
}
@Override
public List<Charset> acceptCharset() {
return delegate().getAcceptCharset();
}
@Override
public List<Locale.LanguageRange> acceptLanguage() {
return delegate().getAcceptLanguage();
}
@Override
public OptionalLong contentLength() {
long value = delegate().getContentLength();
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
}
@Override
public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType());
}
@Override
public InetSocketAddress host() {
return delegate().getHost();
}
@Override
public List<HttpRange> range() {
return delegate().getRange();
}
@Override
public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList());
}
@Override
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate());
}
@Override
public String toString() {
return delegate().toString();
}
}
private final class ServerRequestAdapter implements HttpRequest {
@Override
public String getMethodValue() {
return methodName();
}
@Override
public URI getURI() {
return uri();
}
@Override
public HttpHeaders getHeaders() {
return request().getHeaders();
}
}
}

View File

@@ -1,126 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.support;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
@Deprecated
public class DefaultServerResponse<T> implements ServerResponse {
private final ServerWebExchange exchange;
private final BodyInserter<T, ? super ServerHttpResponse> inserter;
private final Map<String, Object> hints;
public DefaultServerResponse(ServerWebExchange exchange,
BodyInserter<T, ? super ServerHttpResponse> body, Map<String, Object> hints) {
this.exchange = exchange;
Assert.notNull(exchange, "ServerWebExchange must not be null");
Assert.notNull(body, "BodyInserter must not be null");
this.inserter = body;
this.hints = hints;
}
private ServerHttpResponse response() {
return exchange.getResponse();
}
@Override
public final HttpStatus statusCode() {
// TODO: non standard status code
return HttpStatus.valueOf(response().getStatusCode().value());
}
@Override
public int rawStatusCode() {
return response().getStatusCode().value();
}
@Override
public final HttpHeaders headers() {
return response().getHeaders();
}
@Override
public MultiValueMap<String, ResponseCookie> cookies() {
return response().getCookies();
}
@Override
public final Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
return this.inserter.insert(exchange.getResponse(), new BodyInserter.Context() {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return context.messageWriters();
}
@Override
public Optional<ServerHttpRequest> serverRequest() {
return Optional.of(exchange.getRequest());
}
@Override
public Map<String, Object> hints() {
return hints;
}
});
}
public static class HandlerStrategiesResponseContext
implements ServerResponse.Context {
private HandlerStrategies strategies = HandlerStrategies.withDefaults();
public HandlerStrategiesResponseContext() {
}
public HandlerStrategiesResponseContext(HandlerStrategies strategies) {
this.strategies = strategies;
}
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return this.strategies.messageWriters();
}
@Override
public List<ViewResolver> viewResolvers() {
return this.strategies.viewResolvers();
}
}
}

View File

@@ -122,12 +122,6 @@ public final class ServerWebExchangeUtils {
*/
public static final String ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR = "original_response_content_type";
/**
* Hystrix execution exception attribute name.
*/
public static final String HYSTRIX_EXECUTION_EXCEPTION_ATTR = qualify(
"hystrixExecutionException");
/**
* CircuitBreaker execution exception attribute name.
*/

View File

@@ -2,9 +2,7 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayHystrixCircuitBreakerAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\

View File

@@ -36,8 +36,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.SocketUtils;
@RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions({ "spring-cloud-netflix-ribbon-*.jar",
"spring-cloud-loadbalancer-*.jar" })
@ClassPathExclusions({ "spring-cloud-loadbalancer-*.jar" })
public class GatewayNoLoadBalancerClientAutoConfigurationTests {
private static int port;
@@ -65,7 +64,7 @@ public class GatewayNoLoadBalancerClientAutoConfigurationTests {
public static class Config {
@Bean
public RouteLocator hystrixRouteLocator(RouteLocatorBuilder builder) {
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("lb_fail", r -> r.host("**.lbfail.org").uri("lb://fail"))
.build();

View File

@@ -1,418 +0,0 @@
/*
* Copyright 2017-2019 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
*
* https://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.gateway.filter;
import java.net.URI;
import java.util.LinkedHashSet;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR;
/**
* @author Spencer Gibb
* @author Tim Ysewyn
*/
@RunWith(MockitoJUnitRunner.class)
public class LoadBalancerClientFilterTests {
private ServerWebExchange exchange;
private LoadBalancerProperties properties;
@Mock
private GatewayFilterChain chain;
@Mock
private LoadBalancerClient loadBalancerClient;
private LoadBalancerClientFilter loadBalancerClientFilter;
@Before
public void setup() {
properties = new LoadBalancerProperties();
loadBalancerClientFilter = new LoadBalancerClientFilter(loadBalancerClient,
properties);
exchange = MockServerWebExchange
.from(MockServerHttpRequest.get("loadbalancerclient.org").build());
}
@Test
public void shouldNotFilterWhenGatewayRequestUrlIsMissing() {
loadBalancerClientFilter.filter(exchange, chain);
verify(chain).filter(exchange);
verifyNoMoreInteractions(chain);
verifyNoInteractions(loadBalancerClient);
}
@Test
public void shouldNotFilterWhenGatewayRequestUrlSchemeIsNotLb() {
URI uri = UriComponentsBuilder.fromUriString("http://myservice").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
loadBalancerClientFilter.filter(exchange, chain);
verify(chain).filter(exchange);
verifyNoMoreInteractions(chain);
verifyNoInteractions(loadBalancerClient);
}
@Test
public void shouldThrowExceptionWhenNoServiceInstanceIsFound() {
URI uri = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
try {
loadBalancerClientFilter.filter(exchange, chain);
}
catch (NotFoundException e) {
assertThat(e.getStatus()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
}
catch (Exception e) {
throw e;
}
}
@Test
public void shouldThrow4O4ExceptionWhenNoServiceInstanceIsFound() {
URI uri = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
properties.setUse404(true);
try {
loadBalancerClientFilter.filter(exchange, chain);
}
catch (NotFoundException e) {
assertThat(e.getStatus()).isEqualTo(HttpStatus.NOT_FOUND);
}
catch (Exception e) {
throw e;
}
}
@Test
public void shouldFilter() {
URI url = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, url);
ServiceInstance serviceInstance = new DefaultServiceInstance("myservice1",
"myservice", "localhost", 8080, true);
when(loadBalancerClient.choose("myservice")).thenReturn(serviceInstance);
URI requestUrl = UriComponentsBuilder.fromUriString("https://localhost:8080")
.build().toUri();
when(loadBalancerClient.reconstructURI(any(ServiceInstance.class),
any(URI.class))).thenReturn(requestUrl);
loadBalancerClientFilter.filter(exchange, chain);
LinkedHashSet<URI> attribute = exchange
.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
assertThat(attribute).contains(url);
verify(loadBalancerClient).choose("myservice");
ArgumentCaptor<URI> urlArgumentCaptor = ArgumentCaptor.forClass(URI.class);
verify(loadBalancerClient).reconstructURI(any(), urlArgumentCaptor.capture());
URI uri = urlArgumentCaptor.getValue();
assertThat(uri).isNotNull();
assertThat(uri.toString()).isEqualTo("loadbalancerclient.org");
verifyNoMoreInteractions(loadBalancerClient);
assertThat((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR))
.isEqualTo(requestUrl);
verify(chain).filter(exchange);
verifyNoMoreInteractions(chain);
}
@Test
public void instanceOverrideNonSecureScheme() {
MockServerHttpRequest request = MockServerHttpRequest.get("https://localhost")
.build();
URI lbUri = URI.create("lb://service1");
ServerWebExchange webExchange = testFilter(request, lbUri);
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("service1-host1");
}
@Test
public void instanceOverrideSecureScheme() {
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost")
.build();
URI lbUri = URI.create("lb://service1");
ServerWebExchange webExchange = testFilter(request, lbUri, 443);
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("https").hasHost("service1-host1");
}
@Test
public void instanceOverrideSecureSchemePrefix() {
MockServerHttpRequest request = MockServerHttpRequest.get("https://localhost")
.build();
URI lbUri = URI.create("http://service1");
ServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "lb");
ServerWebExchange webExchange = testFilter(exchange, lbUri, 443);
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("service1-host1");
}
@Test
public void instanceOverrideNonSecureSchemePrefix() {
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost")
.build();
URI lbUri = URI.create("https://service1");
ServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "lb");
ServerWebExchange webExchange = testFilter(exchange, lbUri, 8081);
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("https").hasHost("service1-host1");
}
@Test
public void happyPath() {
MockServerHttpRequest request = MockServerHttpRequest
.get("http://localhost/get?a=b").build();
URI lbUri = URI.create("lb://service1?a=b");
ServerWebExchange webExchange = testFilter(request, lbUri);
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("service1-host1").hasParameter("a",
"b");
}
@Test
public void noQueryParams() {
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get")
.build();
ServerWebExchange webExchange = testFilter(request, URI.create("lb://service1"));
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("service1-host1");
}
@Test
public void encodedParameters() {
URI url = UriComponentsBuilder.fromUriString("http://localhost/get?a=b&c=d[]")
.buildAndExpand().encode().toUri();
MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url)
.build();
URI lbUrl = UriComponentsBuilder.fromUriString("lb://service1?a=b&c=d[]")
.buildAndExpand().encode().toUri();
// prove that it is encoded
assertThat(lbUrl.getRawQuery()).isEqualTo("a=b&c=d%5B%5D");
assertThat(lbUrl).hasParameter("c", "d[]");
ServerWebExchange webExchange = testFilter(request, lbUrl);
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("service1-host1").hasParameter("a", "b")
.hasParameter("c", "d[]");
// prove that it is not double encoded
assertThat(uri.getRawQuery()).isEqualTo("a=b&c=d%5B%5D");
}
@Test
public void unencodedParameters() {
URI url = URI.create("http://localhost/get?a=b&c=d[]");
MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url)
.build();
URI lbUrl = URI.create("lb://service1?a=b&c=d[]");
// prove that it is unencoded
assertThat(lbUrl.getRawQuery()).isEqualTo("a=b&c=d[]");
ServerWebExchange webExchange = testFilter(request, lbUrl);
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("http").hasHost("service1-host1").hasParameter("a", "b")
.hasParameter("c", "d[]");
// prove that it is NOT encoded
assertThat(uri.getRawQuery()).isEqualTo("a=b&c=d[]");
}
@Test
public void happyPathWithAttributeRatherThanScheme() {
MockServerHttpRequest request = MockServerHttpRequest
.get("ws://localhost/get?a=b").build();
URI lbUri = URI.create("ws://service1?a=b");
exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "lb");
ServerWebExchange webExchange = testFilter(exchange, lbUri, 8081);
URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(uri).hasScheme("ws").hasHost("service1-host1").hasParameter("a", "b");
}
@Test
public void shouldNotFilterWhenGatewaySchemePrefixAttrIsNotLb() {
URI uri = UriComponentsBuilder.fromUriString("http://myservice").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "xx");
loadBalancerClientFilter.filter(exchange, chain);
verify(chain).filter(exchange);
verifyNoMoreInteractions(chain);
verifyNoInteractions(loadBalancerClient);
}
@Test
public void shouldSelectSpecifiedServer() {
URI uri1 = UriComponentsBuilder.fromUriString("lb://myservice").port(11111)
.build().toUri();
URI uri2 = UriComponentsBuilder.fromUriString("lb://myservice").port(22222)
.build().toUri();
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
ILoadBalancer loadBalancer = mock(ILoadBalancer.class);
when(clientFactory.getLoadBalancerContext("myservice"))
.thenReturn(new RibbonLoadBalancerContext(loadBalancer));
when(clientFactory.getLoadBalancer("myservice")).thenReturn(loadBalancer);
when(loadBalancer.chooseServer("11111"))
.thenReturn(new Server("myservice-host1", 8081));
when(loadBalancer.chooseServer("22222"))
.thenReturn(new Server("myservice-host2", 8081));
LoadBalancerClient loadBalancerClient = new RibbonLoadBalancerClient(
clientFactory) {
private String loadBalancerKey;
public ServiceInstance choose(String serviceId) {
String[] strings = serviceId.split("<<>>");
loadBalancerKey = strings[1];
return super.choose(strings[0], loadBalancerKey);
}
protected Server getServer(ILoadBalancer loadBalancer) {
return loadBalancer == null ? null
: loadBalancer.chooseServer(StringUtils.isEmpty(loadBalancerKey)
? "default" : loadBalancerKey);
}
};
LoadBalancerClientFilter loadBalancerClientFilter = new LoadBalancerClientFilter(
loadBalancerClient, properties) {
protected ServiceInstance choose(ServerWebExchange exchange) {
URI attribute = (URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
return loadBalancer
.choose(attribute.getHost() + "<<>>" + attribute.getPort());
}
};
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get")
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri1);
loadBalancerClientFilter.filter(exchange, chain);
assertThat(
((URI) exchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR)).getHost())
.isEqualTo("myservice-host1");
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri2);
loadBalancerClientFilter.filter(exchange, chain);
assertThat(
((URI) exchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR)).getHost())
.isEqualTo("myservice-host2");
}
private ServerWebExchange testFilter(MockServerHttpRequest request, URI uri) {
return testFilter(MockServerWebExchange.from(request), uri, 8081);
}
private ServerWebExchange testFilter(MockServerHttpRequest request, URI uri,
int port) {
return testFilter(MockServerWebExchange.from(request), uri, port);
}
private ServerWebExchange testFilter(ServerWebExchange exchange, URI uri, int port) {
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor
.forClass(ServerWebExchange.class);
when(chain.filter(captor.capture())).thenReturn(Mono.empty());
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
ILoadBalancer loadBalancer = mock(ILoadBalancer.class);
when(clientFactory.getLoadBalancerContext("service1"))
.thenReturn(new RibbonLoadBalancerContext(loadBalancer));
when(clientFactory.getLoadBalancer("service1")).thenReturn(loadBalancer);
when(loadBalancer.chooseServer(any()))
.thenReturn(new Server("service1-host1", port));
RibbonLoadBalancerClient client = new RibbonLoadBalancerClient(clientFactory);
LoadBalancerClientFilter filter = new LoadBalancerClientFilter(client,
properties);
filter.filter(exchange, chain);
return captor.getValue();
}
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.filter.factory;
import java.security.Principal;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "debug=true")
@DirtiesContext
public class HystrixGatewayFilterFactoryPrincipalTests extends BaseWebClientTests {
@Autowired
private TestPrincipalGatewayFilterFactory testFilterFactory;
@Test
public void hystrixPrincipalNotLost() {
testClient.get().uri("/hystrixprincipal").headers(httpHeaders -> {
httpHeaders.setBasicAuth("user", "password");
httpHeaders.set("Host", "www.hystrixsecurity.org");
}).exchange().expectStatus().isOk().expectBody().jsonPath("$.principal")
.isEqualTo("user");
assertThat(testFilterFactory.resolvedPrincipal).isEqualTo("user");
}
@RestController
@SpringBootConfiguration
@EnableAutoConfiguration
@RibbonClients({
@RibbonClient(name = "testservice", configuration = TestRibbonConfig.class) })
public static class TestConfig {
@Value("${test.uri}")
private String uri;
@RequestMapping("/httpbin/hystrixprincipal")
public Mono<Map<String, String>> hystrixPrincipal(Mono<Principal> principal) {
return principal.map(Principal::getName).defaultIfEmpty("Unknown")
.map(s -> Collections.singletonMap("principal", s));
}
@Bean
public RouteLocator hystrixRouteLocator(RouteLocatorBuilder builder,
TestPrincipalGatewayFilterFactory filterFactory) {
return builder.routes()
.route("hystrix_security", r -> r.host("**.hystrixsecurity.org")
.filters(f -> f.prefixPath("/httpbin")
.hystrix(config -> config.setName("securitycmd"))
.filter(filterFactory.apply("")))
.uri(uri))
.build();
}
@Bean
public TestPrincipalGatewayFilterFactory testPrincipalGatewayFilterFactory() {
return new TestPrincipalGatewayFilterFactory();
}
@Bean
public RecursiveHttpbinFilter recursiveHttpbinFilter() {
return new RecursiveHttpbinFilter();
}
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
return http.httpBasic().and().authorizeExchange()
.pathMatchers("/hystrixprincipal").authenticated().anyExchange()
.permitAll().and().build();
}
@Bean
@SuppressWarnings("deprecation")
public MapReactiveUserDetailsService reactiveUserDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder().username("user")
.password("password").roles("USER").build();
return new MapReactiveUserDetailsService(user);
}
}
public static class TestPrincipalGatewayFilterFactory
extends AbstractGatewayFilterFactory<Object> {
private final Log log = LogFactory
.getLog(TestPrincipalGatewayFilterFactory.class);
private KeyResolver keyResolver = new PrincipalNameKeyResolver();
private String resolvedPrincipal;
public TestPrincipalGatewayFilterFactory() {
super(Object.class);
}
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> keyResolver.resolve(exchange)
.defaultIfEmpty("Empty Principal").flatMap(name -> {
resolvedPrincipal = name;
return chain.filter(exchange);
});
}
}
}

View File

@@ -1,149 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.filter.factory;
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.Hystrix;
import com.netflix.hystrix.metric.consumer.HealthCountsStream;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.gateway.filter.factory.ExceptionFallbackHandler.RETRIEVED_EXCEPTION;
import static org.springframework.http.MediaType.APPLICATION_JSON;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "debug=true")
@ContextConfiguration(classes = HystrixTestConfig.class)
@DirtiesContext
public class HystrixGatewayFilterFactoryTests extends BaseWebClientTests {
@Test
public void hystrixFilterWorks() {
testClient.get().uri("/get").header("Host", "www.hystrixsuccess.org").exchange()
.expectStatus().isOk().expectHeader()
.valueEquals(ROUTE_ID_HEADER, "hystrix_success_test");
}
@Test
public void hystrixFilterTimesout() {
testClient.get().uri("/delay/3").header("Host", "www.hystrixfailure.org")
.exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT)
.expectBody().jsonPath("$.status")
.isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value()));
}
@Test
public void hystrixFilterServiceUnavailable() {
HealthCountsStream.reset();
Hystrix.reset();
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.failcmd.circuitBreaker.forceOpen", true);
testClient.get().uri("/delay/3").header("Host", "www.hystrixfailure.org")
.exchange().expectStatus().isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
HealthCountsStream.reset();
Hystrix.reset();
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.failcmd.circuitBreaker.forceOpen", false);
}
/*
* Tests that timeouts bubbling from the underpinning WebClient are treated the same
* as Hystrix timeouts in terms of outside response. (Internally, timeouts from the
* WebClient are seen as command failures and trigger the opening of circuit breakers
* the same way timeouts do; it may be confusing in terms of the Hystrix metrics
* though)
*/
@Test
public void hystrixTimeoutFromWebClient() {
testClient.get().uri("/delay/10").header("Host", "www.hystrixresponsestall.org")
.exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT);
}
@Test
public void hystrixFilterFallback() {
testClient.get().uri("/delay/3?a=b").header("Host", "www.hystrixfallback.org")
.exchange().expectStatus().isOk().expectBody()
.json("{\"from\":\"fallbackcontroller\"}");
}
@Test
public void hystrixFilterExceptionFallback() {
testClient.get().uri("/delay/3")
.header("Host", "www.hystrixexceptionfallback.org").exchange()
.expectStatus().isOk().expectHeader()
.value(RETRIEVED_EXCEPTION, containsString("HystrixTimeoutException"));
}
@Test
public void hystrixFilterWorksJavaDsl() {
testClient.get().uri("/get").header("Host", "www.hystrixjava.org").exchange()
.expectStatus().isOk().expectHeader()
.valueEquals(ROUTE_ID_HEADER, "hystrix_java");
}
@Test
public void hystrixFilterFallbackJavaDsl() {
testClient.get().uri("/delay/3").header("Host", "www.hystrixjava.org").exchange()
.expectStatus().isOk().expectBody()
.json("{\"from\":\"fallbackcontroller2\"}");
}
@Test
public void hystrixFilterConnectFailure() {
testClient.get().uri("/delay/3").header("Host", "www.hystrixconnectfail.org")
.exchange().expectStatus().is5xxServerError();
}
@Test
public void hystrixFilterErrorPage() {
testClient.get().uri("/delay/3").header("Host", "www.hystrixconnectfail.org")
.accept(APPLICATION_JSON).exchange().expectStatus().is5xxServerError()
.expectBody().jsonPath("$.status")
.value(Matchers.greaterThanOrEqualTo(500)).jsonPath("$.message")
.isNotEmpty().jsonPath("$.error").isNotEmpty();
}
@Test
public void toStringFormat() {
HystrixGatewayFilterFactory.Config config = new HystrixGatewayFilterFactory.Config()
.setName("myname").setFallbackUri("forward:/myfallback");
GatewayFilter filter = new HystrixGatewayFilterFactory(null).apply(config);
assertThat(filter.toString()).contains("myname").contains("forward:/myfallback");
}
@Test
public void filterFallbackForward() {
testClient.get().uri("/delay/3?a=c").header("Host", "www.hystrixforward.org")
.exchange().expectStatus().isOk().expectBody()
.json("{\"from\":\"hystrixfallbackcontroller3\"}");
}
}

View File

@@ -1,139 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.filter.factory;
import java.util.Collections;
import java.util.Map;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(BaseWebClientTests.DefaultTestConfig.class)
@RestController
@RibbonClient(name = "badservice", configuration = TestBadRibbonConfig.class)
public class HystrixTestConfig {
@Value("${test.uri}")
private String uri;
@RequestMapping("/fallbackcontroller")
public Map<String, String> fallbackcontroller(@RequestParam("a") String a) {
return Collections.singletonMap("from", "fallbackcontroller");
}
@RequestMapping("/fallbackcontroller2")
public Map<String, String> fallbackcontroller2() {
return Collections.singletonMap("from", "fallbackcontroller2");
}
@RequestMapping("/hystrixFallbackController3")
public Map<String, String> fallbackcontroller3() {
return Collections.singletonMap("from", "hystrixfallbackcontroller3");
}
@Bean
public RouteLocator hystrixRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("hystrix_java", r -> r.host("**.hystrixjava.org")
.filters(f -> f.prefixPath("/httpbin").hystrix(
config -> config.setFallbackUri("forward:/fallbackcontroller2")))
.uri(uri))
.route("hystrix_fallback_forward", r -> r.host("**.hystrixforward.org")
.filters(f -> f.hystrix(
config -> config.setFallbackUri("forward:/fallback")))
.uri(uri))
.route("hystrix_fallback_controller_3", r -> r.path("/fallback")
.filters(f -> f.setPath("/hystrixFallbackController3")).uri(uri))
.route("hystrix_connection_failure",
r -> r.host("**.hystrixconnectfail.org")
.filters(f -> f.prefixPath("/httpbin").hystrix(config -> {
})).uri("lb://badservice"))
/*
* This is a route encapsulated in a hystrix command that is ready to wait
* for a response far longer than the underpinning WebClient would.
*/
.route("hystrix_response_stall",
r -> r.host("**.hystrixresponsestall.org")
.filters(f -> f.prefixPath("/httpbin").hystrix(
config -> config.setName("stalling-command")))
.uri(uri))
.build();
}
@Bean
ExceptionFallbackHandler exceptionFallbackHandler() {
return new ExceptionFallbackHandler();
}
@Bean
RouterFunction<ServerResponse> routerFunction(
ExceptionFallbackHandler exceptionFallbackHandler) {
return route(GET("/exceptionFallback"),
exceptionFallbackHandler::retrieveExceptionInfo);
}
}
class ExceptionFallbackHandler {
static final String RETRIEVED_EXCEPTION = "Retrieved-Exception";
Mono<ServerResponse> retrieveExceptionInfo(ServerRequest serverRequest) {
String exceptionName = serverRequest.attribute(HYSTRIX_EXECUTION_EXCEPTION_ATTR)
.map(exception -> exception.getClass().getName()).orElse("");
return ServerResponse.ok().header(RETRIEVED_EXCEPTION, exceptionName).build();
}
}
@Configuration(proxyBeanMethods = false)
class TestBadRibbonConfig {
@LocalServerPort
protected int port = 0;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("https", "localhost", this.port));
}
}

View File

@@ -22,8 +22,6 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.CoreMatchers;
@@ -43,8 +41,6 @@ import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactor
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
@@ -177,7 +173,8 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
@RibbonClient(name = "badservice2", configuration = TestBadRibbonConfig.class)
// @RibbonClient(name = "badservice2", configuration =
// TestBadLoadBalancerConfig.class)
public static class TestConfig {
Log log = LogFactory.getLog(getClass());
@@ -269,17 +266,17 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
}
protected static class TestBadRibbonConfig {
protected static class TestBadLoadBalancerConfig {
@LocalServerPort
protected int port = 0;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(
new Server("https", "localhost.domain.doesnot.exist", this.port),
new Server("localhost", this.port));
}
// @Bean
// public ServerList<Server> serverList() {
// return new StaticServerList<>(
// new Server("https", "localhost.domain.doesnot.exist", this.port),
// new Server("localhost", this.port));
// }
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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.gateway.filter.factory;
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.Hystrix;
import com.netflix.hystrix.metric.consumer.HealthCountsStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.netflix.hystrix.ReactiveHystrixCircuitBreakerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.gateway.filter.factory.ExceptionFallbackHandler.RETRIEVED_EXCEPTION;
/**
* @author Ryan Baxter
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = { "debug=true",
"spring.cloud.circuitbreaker.resilience4j.enabled=false" })
@ContextConfiguration(classes = SpringCloudCircuitBreakerTestConfig.class)
@DirtiesContext
public class SpringCloudCircuitBreakerHystrixFilterFactoryTests
extends SpringCloudCircuitBreakerFilterFactoryTests {
@Test
public void hystrixFilterServiceUnavailable() {
HealthCountsStream.reset();
Hystrix.reset();
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.failcmd.circuitBreaker.forceOpen", true);
testClient.get().uri("/delay/3").header("Host", "www.sccbfailure.org").exchange()
.expectStatus().isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
HealthCountsStream.reset();
Hystrix.reset();
ConfigurationManager.getConfigInstance()
.setProperty("hystrix.command.failcmd.circuitBreaker.forceOpen", false);
}
@Test
public void hystrixFilterExceptionFallback() {
testClient.get().uri("/delay/3")
.header("Host", "www.circuitbreakerexceptionfallback.org").exchange()
.expectStatus().isOk().expectHeader()
.value(RETRIEVED_EXCEPTION, containsString("HystrixTimeoutException"));
}
@Test
public void toStringFormat() {
SpringCloudCircuitBreakerFilterFactory.Config config = new SpringCloudCircuitBreakerFilterFactory.Config()
.setName("myname").setFallbackUri("forward:/myfallback");
GatewayFilter filter = new SpringCloudCircuitBreakerHystrixFilterFactory(
new ReactiveHystrixCircuitBreakerFactory(), null).apply(config);
assertThat(filter.toString()).contains("myname").contains("forward:/myfallback");
}
}

View File

@@ -39,7 +39,6 @@ import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.gateway.filter.factory.ExceptionFallbackHandler.RETRIEVED_EXCEPTION;
/**
* @author Ryan Baxter
@@ -53,6 +52,8 @@ import static org.springframework.cloud.gateway.filter.factory.ExceptionFallback
public class SpringCloudCircuitBreakerResilience4JFilterFactoryTests
extends SpringCloudCircuitBreakerFilterFactoryTests {
private static final String RETRIEVED_EXCEPTION = "Retrieved-Exception";
@Autowired
private Resilience4JCircuitBreakerFactory factory;

View File

@@ -27,7 +27,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -48,7 +47,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
@SpringBootConfiguration
@Import(BaseWebClientTests.DefaultTestConfig.class)
@RestController
@RibbonClient(name = "badservice", configuration = TestBadRibbonConfig.class)
// @RibbonClient(name = "badservice", configuration = TestBadLoadBalancerConfig.class)
public class SpringCloudCircuitBreakerTestConfig {
@Value("${test.uri}")
@@ -113,17 +112,17 @@ public class SpringCloudCircuitBreakerTestConfig {
exceptionFallbackHandler::retrieveExceptionInfo);
}
}
private static class CircuitBreakerExceptionFallbackHandler {
class CircuitBreakerExceptionFallbackHandler {
static final String RETRIEVED_EXCEPTION = "Retrieved-Exception";
static final String RETRIEVED_EXCEPTION = "Retrieved-Exception";
Mono<ServerResponse> retrieveExceptionInfo(ServerRequest serverRequest) {
String exceptionName = serverRequest
.attribute(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR)
.map(exception -> exception.getClass().getName()).orElse("");
return ServerResponse.ok().header(RETRIEVED_EXCEPTION, exceptionName).build();
}
Mono<ServerResponse> retrieveExceptionInfo(ServerRequest serverRequest) {
String exceptionName = serverRequest
.attribute(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR)
.map(exception -> exception.getClass().getName()).orElse("");
return ServerResponse.ok().header(RETRIEVED_EXCEPTION, exceptionName).build();
}
}

View File

@@ -18,8 +18,6 @@ package org.springframework.cloud.gateway.handler.predicate;
import java.util.function.Predicate;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,9 +31,6 @@ import org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFact
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
@@ -90,10 +85,10 @@ public class ReadBodyPredicateFactoryTest {
@EnableAutoConfiguration
@SpringBootConfiguration
@RibbonClients({
@RibbonClient(name = "message", configuration = TestRibbonConfig.class),
@RibbonClient(name = "messageChannel",
configuration = TestRibbonConfig.class) })
// @RibbonClients({
// @RibbonClient(name = "message", configuration = TestLoadBalancerConfig.class),
// @RibbonClient(name = "messageChannel",
// configuration = TestLoadBalancerConfig.class) })
@Import(PermitAllSecurityConfiguration.class)
@RestController
public static class TestConfig {
@@ -129,15 +124,15 @@ public class ReadBodyPredicateFactoryTest {
}
protected static class TestRibbonConfig {
protected static class TestLoadBalancerConfig {
@LocalServerPort
protected int port = 0;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
// @Bean
// public ServerList<Server> serverList() {
// return new StaticServerList<>(new Server("localhost", this.port));
// }
}

View File

@@ -56,7 +56,6 @@ import static org.junit.Assume.assumeThat;
org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactoryTest.class,
org.springframework.cloud.gateway.handler.RoutePredicateHandlerMappingTests.class,
org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactoryTests.class,
org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactoryTests.class,
org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactoryTests.class,
org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactoryTests.class,
org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactoryTests.class,
@@ -96,7 +95,6 @@ import static org.junit.Assume.assumeThat;
org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolverIntegrationTests.class,
org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiterConfigTests.class,
org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiterTests.class,
org.springframework.cloud.gateway.filter.LoadBalancerClientFilterTests.class,
org.springframework.cloud.gateway.filter.NettyRoutingFilterIntegrationTests.class,
GatewayMetricsFilterTests.class,
org.springframework.cloud.gateway.filter.ForwardRoutingFilterTests.class,

View File

@@ -18,8 +18,6 @@ package org.springframework.cloud.gateway.test;
import java.time.Duration;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
@@ -31,9 +29,6 @@ import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -81,8 +76,9 @@ public class BaseWebClientTests {
}
@Configuration(proxyBeanMethods = false)
@RibbonClients({
@RibbonClient(name = "testservice", configuration = TestRibbonConfig.class) })
// @RibbonClients({
// @RibbonClient(name = "testservice", configuration = TestLoadBalancerConfig.class)
// })
@Import(PermitAllSecurityConfiguration.class)
public static class DefaultTestConfig {
@@ -141,15 +137,15 @@ public class BaseWebClientTests {
}
protected static class TestRibbonConfig {
protected static class TestLoadBalancerConfig {
@LocalServerPort
protected int port = 0;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
// @Bean
// public ServerList<Server> serverList() {
// return new StaticServerList<>(new Server("localhost", this.port));
// }
}

View File

@@ -25,8 +25,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
@@ -47,8 +45,6 @@ import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration;
import org.springframework.cloud.gateway.test.support.HttpServer;
import org.springframework.cloud.gateway.test.support.ReactorHttpServer;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.Lifecycle;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -361,8 +357,8 @@ public class WebSocketIntegrationTests {
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@Import(PermitAllSecurityConfiguration.class)
@RibbonClient(name = "wsservice",
configuration = LocalRibbonClientConfiguration.class)
// @RibbonClient(name = "wsservice",
// configuration = LocalLoadBalancerClientConfiguration.class)
protected static class GatewayConfig {
@Bean
@@ -374,15 +370,15 @@ public class WebSocketIntegrationTests {
}
public static class LocalRibbonClientConfiguration {
public static class LocalLoadBalancerClientConfiguration {
@Value("${ws.server.port}")
private int wsPort;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.wsPort));
}
// @Bean
// public ServerList<Server> serverList() {
// return new StaticServerList<>(new Server("localhost", this.wsPort));
// }
}

View File

@@ -81,13 +81,6 @@ public class CustomBlockHoundIntegration implements BlockHoundIntegration {
builder.allowBlockingCallsInside("io.netty.handler.ssl.SslContext",
"newClientContextInternal");
// Uses
// org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientConnectionManagerFactory#newConnectionManager
// Uses javax.net.ssl.SSLContext#init
builder.allowBlockingCallsInside(
"org.springframework.cloud.netflix.ribbon.SpringClientFactory",
"getContext");
// Uses org.springframework.security.crypto.bcrypt.BCrypt#gensalt
// Uses java.security.SecureRandom#nextBytes
builder.allowBlockingCallsInside(

View File

@@ -33,10 +33,6 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.isomorphism</groupId>
<artifactId>token-bucket</artifactId>

View File

@@ -22,8 +22,6 @@ import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -34,12 +32,9 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.gateway.test.HttpBinCompatibleController;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.SocketUtils;
@@ -180,7 +175,7 @@ public class GatewaySampleApplicationTests {
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@RibbonClient(name = "httpbin", configuration = RibbonConfig.class)
//@RibbonClient(name = "httpbin", configuration = LoadBalancerConfig.class)
@Import(GatewaySampleApplication.class)
protected static class TestConfig {
@@ -191,16 +186,16 @@ public class GatewaySampleApplicationTests {
}
protected static class RibbonConfig {
protected static class LoadBalancerConfig {
@LocalServerPort
int port;
@Bean
@Primary
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", port));
}
//@Bean
//@Primary
//public ServerList<Server> serverList() {
// return new StaticServerList<>(new Server("localhost", port));
//}
}