Checkstyle (#833)

Fixes gh-832

* Add formatting and checkstyle fixes.

* Fix checkstyle setup. Add more checkstyle fixes and suppressions.

* Fix license dates.
This commit is contained in:
Olga Maciaszek-Sharma
2019-02-12 14:48:31 +01:00
committed by GitHub
parent eb95038952
commit a6f48b0f65
272 changed files with 6505 additions and 5043 deletions

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>

34
pom.xml
View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -19,8 +20,11 @@
</parent>
<scm>
<url>https://github.com/spring-cloud-incubator/spring-cloud-gateway</url>
<connection>scm:git:git://github.com/spring-cloud-incubator/spring-cloud-gateway.git</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-cloud-incubator/spring-cloud-gateway.git
<connection>
scm:git:git://github.com/spring-cloud-incubator/spring-cloud-gateway.git
</connection>
<developerConnection>
scm:git:ssh://git@github.com/spring-cloud-incubator/spring-cloud-gateway.git
</developerConnection>
<tag>HEAD</tag>
</scm>
@@ -51,6 +55,11 @@
<spring-cloud-commons.version>2.1.1.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.1.1.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<embedded-redis.version>0.6</embedded-redis.version>
<maven-checkstyle-plugin.includeTestSourceDirectory>true
</maven-checkstyle-plugin.includeTestSourceDirectory>
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError>
<maven-checkstyle-plugin.failOnViolation>true
</maven-checkstyle-plugin.failOnViolation>
</properties>
<dependencyManagement>
@@ -130,6 +139,19 @@
<module>docs</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>spring</id>
@@ -207,7 +229,8 @@
</goals>
<configuration>
<propertyName>surefireArgLine</propertyName>
<destFile>${project.build.directory}/jacoco.exec</destFile>
<destFile>${project.build.directory}/jacoco.exec
</destFile>
</configuration>
</execution>
<execution>
@@ -218,7 +241,8 @@
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<dataFile>${project.build.directory}/jacoco.exec</dataFile>
<dataFile>${project.build.directory}/jacoco.exec
</dataFile>
</configuration>
</execution>
</executions>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -134,7 +135,9 @@
<executions>
<execution>
<id>compile</id>
<goals> <goal>compile</goal> </goals>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
@@ -144,7 +147,9 @@
</execution>
<execution>
<id>test-compile</id>
<goals> <goal>test-compile</goal> </goals>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
@@ -176,15 +181,15 @@
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.actuate;
@@ -25,11 +24,14 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
@@ -46,9 +48,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
@@ -58,15 +57,20 @@ public class GatewayControllerEndpoint implements ApplicationEventPublisherAware
private static final Log log = LogFactory.getLog(GatewayControllerEndpoint.class);
private RouteDefinitionLocator routeDefinitionLocator;
private List<GlobalFilter> globalFilters;
private List<GatewayFilterFactory> GatewayFilters;
private RouteDefinitionWriter routeDefinitionWriter;
private RouteLocator routeLocator;
private ApplicationEventPublisher publisher;
public GatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter,
RouteLocator routeLocator) {
public GatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator,
List<GlobalFilter> globalFilters, List<GatewayFilterFactory> GatewayFilters,
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
this.routeDefinitionLocator = routeDefinitionLocator;
this.globalFilters = globalFilters;
this.GatewayFilters = GatewayFilters;
@@ -83,7 +87,7 @@ public class GatewayControllerEndpoint implements ApplicationEventPublisherAware
@PostMapping("/refresh")
public Mono<Void> refresh() {
this.publisher.publishEvent(new RefreshRoutesEvent(this));
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return Mono.empty();
}
@@ -104,9 +108,9 @@ public class GatewayControllerEndpoint implements ApplicationEventPublisherAware
private HashMap<String, Object> putItem(HashMap<String, Object> map, Object o) {
Integer order = null;
if (o instanceof Ordered) {
order = ((Ordered)o).getOrder();
order = ((Ordered) o).getOrder();
}
//filters.put(o.getClass().getName(), order);
// filters.put(o.getClass().getName(), order);
map.put(o.toString(), order);
return map;
}
@@ -114,8 +118,8 @@ public class GatewayControllerEndpoint implements ApplicationEventPublisherAware
// TODO: Flush out routes without a definition
@GetMapping("/routes")
public Mono<List<Map<String, Object>>> routes() {
Mono<Map<String, RouteDefinition>> routeDefs = this.routeDefinitionLocator.getRouteDefinitions()
.collectMap(RouteDefinition::getId);
Mono<Map<String, RouteDefinition>> routeDefs = this.routeDefinitionLocator
.getRouteDefinitions().collectMap(RouteDefinition::getId);
Mono<List<Route>> routes = this.routeLocator.getRoutes().collectList();
return Mono.zip(routeDefs, routes).map(tuple -> {
Map<String, RouteDefinition> defs = tuple.getT1();
@@ -129,7 +133,8 @@ public class GatewayControllerEndpoint implements ApplicationEventPublisherAware
if (defs.containsKey(route.getId())) {
r.put("route_definition", defs.get(route.getId()));
} else {
}
else {
HashMap<String, Object> obj = new HashMap<>();
obj.put("predicate", route.getPredicate().toString());
@@ -154,43 +159,45 @@ public class GatewayControllerEndpoint implements ApplicationEventPublisherAware
});
}
/*
http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 predicates:='["Host=**.apiaddrequestheader.org", "Path=/headers"]' filters:='["AddRequestHeader=X-Request-ApiFoo, ApiBar"]'
*/
/*
* http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80
* predicates:='["Host=**.apiaddrequestheader.org", "Path=/headers"]'
* filters:='["AddRequestHeader=X-Request-ApiFoo, ApiBar"]'
*/
@PostMapping("/routes/{id}")
@SuppressWarnings("unchecked")
public Mono<ResponseEntity<Void>> save(@PathVariable String id, @RequestBody Mono<RouteDefinition> route) {
return this.routeDefinitionWriter.save(route.map(r -> {
public Mono<ResponseEntity<Void>> save(@PathVariable String id,
@RequestBody Mono<RouteDefinition> route) {
return this.routeDefinitionWriter.save(route.map(r -> {
r.setId(id);
log.debug("Saving route: " + route);
return r;
})).then(Mono.defer(() ->
Mono.just(ResponseEntity.created(URI.create("/routes/"+id)).build())
));
})).then(Mono.defer(() -> Mono
.just(ResponseEntity.created(URI.create("/routes/" + id)).build())));
}
@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
return this.routeDefinitionWriter.delete(Mono.just(id))
.then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
.onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build()));
.onErrorResume(t -> t instanceof NotFoundException,
t -> Mono.just(ResponseEntity.notFound().build()));
}
@GetMapping("/routes/{id}")
public Mono<ResponseEntity<RouteDefinition>> route(@PathVariable String id) {
//TODO: missing RouteLocator
// TODO: missing RouteLocator
return this.routeDefinitionLocator.getRouteDefinitions()
.filter(route -> route.getId().equals(id))
.singleOrEmpty()
.filter(route -> route.getId().equals(id)).singleOrEmpty()
.map(ResponseEntity::ok)
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
}
@GetMapping("/routes/{id}/combinedfilters")
public Mono<HashMap<String, Object>> combinedfilters(@PathVariable String id) {
//TODO: missing global filters
return this.routeLocator.getRoutes()
.filter(route -> route.getId().equals(id))
// TODO: missing global filters
return this.routeLocator.getRoutes().filter(route -> route.getId().equals(id))
.reduce(new HashMap<>(), this::putItem);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -25,7 +24,6 @@ import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.ProxyProvider;
@@ -128,14 +126,11 @@ import org.springframework.core.env.Environment;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.StringUtils;
import org.springframework.validation.Validator;
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import org.springframework.web.reactive.socket.server.WebSocketService;
import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED;
import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED;
@@ -146,130 +141,28 @@ import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class })
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class,
WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
@Configuration
@ConditionalOnClass(HttpClient.class)
protected static class NettyConfiguration {
@Bean
@ConditionalOnMissingBean
public HttpClient httpClient(HttpClientProperties properties) {
// configure pool resources
HttpClientProperties.Pool pool = properties.getPool();
ConnectionProvider connectionProvider;
if (pool.getType() == DISABLED) {
connectionProvider = ConnectionProvider.newConnection();
} else if (pool.getType() == FIXED) {
connectionProvider = ConnectionProvider.fixed(pool.getName(),
pool.getMaxConnections(), pool.getAcquireTimeout());
} else {
connectionProvider = ConnectionProvider.elastic(pool.getName());
}
HttpClient httpClient = HttpClient.create(connectionProvider)
.tcpConfiguration(tcpClient -> {
if (properties.getConnectTimeout() != null) {
tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout());
}
// configure proxy if proxy host is set.
HttpClientProperties.Proxy proxy = properties.getProxy();
if (StringUtils.hasText(proxy.getHost())) {
tcpClient = tcpClient.proxy(proxySpec -> {
ProxyProvider.Builder builder = proxySpec
.type(ProxyProvider.Proxy.HTTP)
.host(proxy.getHost());
PropertyMapper map = PropertyMapper.get();
map.from(proxy::getPort)
.whenNonNull()
.to(builder::port);
map.from(proxy::getUsername)
.whenHasText()
.to(builder::username);
map.from(proxy::getPassword)
.whenHasText()
.to(password -> builder.password(s -> password));
map.from(proxy::getNonProxyHostsPattern)
.whenHasText()
.to(builder::nonProxyHosts);
});
}
return tcpClient;
});
HttpClientProperties.Ssl ssl = properties.getSsl();
if (ssl.getTrustedX509CertificatesForTrustManager().length > 0
|| ssl.isUseInsecureTrustManager()) {
httpClient = httpClient.secure(sslContextSpec -> {
// configure ssl
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
X509Certificate[] trustedX509Certificates = ssl
.getTrustedX509CertificatesForTrustManager();
if (trustedX509Certificates.length > 0) {
sslContextBuilder.trustManager(trustedX509Certificates);
} else if (ssl.isUseInsecureTrustManager()) {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
}
sslContextSpec.sslContext(sslContextBuilder)
.defaultConfiguration(ssl.getDefaultConfigurationType())
.handshakeTimeout(ssl.getHandshakeTimeout())
.closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
.closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
});
}
return httpClient;
}
@Bean
public HttpClientProperties httpClientProperties() {
return new HttpClientProperties();
}
@Bean
public NettyRoutingFilter routingFilter(HttpClient httpClient,
ObjectProvider<List<HttpHeadersFilter>> headersFilters,
HttpClientProperties properties) {
return new NettyRoutingFilter(httpClient, headersFilters, properties);
}
@Bean
public NettyWriteResponseFilter nettyWriteResponseFilter(GatewayProperties properties) {
return new NettyWriteResponseFilter(properties.getStreamingMediaTypes());
}
@Bean
public ReactorNettyWebSocketClient reactorNettyWebSocketClient(HttpClient httpClient) {
return new ReactorNettyWebSocketClient(httpClient);
}
}
@Bean
public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() {
return new StringToZonedDateTimeConverter();
}
@Bean
public RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) {
public RouteLocatorBuilder routeLocatorBuilder(
ConfigurableApplicationContext context) {
return new RouteLocatorBuilder(context);
}
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(
GatewayProperties properties) {
return new PropertiesRouteDefinitionLocator(properties);
}
@@ -281,30 +174,33 @@ public class GatewayAutoConfiguration {
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
public RouteDefinitionLocator routeDefinitionLocator(
List<RouteDefinitionLocator> routeDefinitionLocators) {
return new CompositeRouteDefinitionLocator(
Flux.fromIterable(routeDefinitionLocators));
}
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> GatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator,
@Qualifier("webFluxConversionService")
ConversionService conversionService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters,
properties, conversionService);
List<GatewayFilterFactory> GatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator,
@Qualifier("webFluxConversionService") ConversionService conversionService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
GatewayFilters, properties, conversionService);
}
@Bean
@Primary
//TODO: property to disable composite?
// TODO: property to disable composite?
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
return new CachingRouteLocator(
new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}
@Bean
public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publisher) {
public RouteRefreshListener routeRefreshListener(
ApplicationEventPublisher publisher) {
return new RouteRefreshListener(publisher);
}
@@ -317,7 +213,7 @@ public class GatewayAutoConfiguration {
public GlobalCorsProperties globalCorsProperties() {
return new GlobalCorsProperties();
}
@Bean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(
FilteringWebHandler webHandler, RouteLocator routeLocator,
@@ -326,26 +222,26 @@ public class GatewayAutoConfiguration {
globalCorsProperties, environment);
}
// ConfigurationProperty beans
@Bean
public GatewayProperties gatewayProperties() {
return new GatewayProperties();
}
// ConfigurationProperty beans
@Bean
public SecureHeadersProperties secureHeadersProperties() {
return new SecureHeadersProperties();
}
// HttpHeaderFilter beans
@Bean
@ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true)
public ForwardedHeadersFilter forwardedHeadersFilter() {
return new ForwardedHeadersFilter();
}
// HttpHeaderFilter beans
@Bean
public RemoveHopByHopHeadersFilter removeHopByHopHeadersFilter() {
return new RemoveHopByHopHeadersFilter();
@@ -358,7 +254,7 @@ public class GatewayAutoConfiguration {
}
// GlobalFilter beans
@Bean
public AdaptCachedBodyGlobalFilter adaptCachedBodyGlobalFilter() {
return new AdaptCachedBodyGlobalFilter();
@@ -370,7 +266,8 @@ public class GatewayAutoConfiguration {
}
@Bean
public ForwardRoutingFilter forwardRoutingFilter(ObjectProvider<DispatcherHandler> dispatcherHandler) {
public ForwardRoutingFilter forwardRoutingFilter(
ObjectProvider<DispatcherHandler> dispatcherHandler) {
return new ForwardRoutingFilter(dispatcherHandler);
}
@@ -386,35 +283,34 @@ public class GatewayAutoConfiguration {
@Bean
public WebsocketRoutingFilter websocketRoutingFilter(WebSocketClient webSocketClient,
WebSocketService webSocketService,
ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
return new WebsocketRoutingFilter(webSocketClient, webSocketService, headersFilters);
WebSocketService webSocketService,
ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
return new WebsocketRoutingFilter(webSocketClient, webSocketService,
headersFilters);
}
@Bean
public WeightCalculatorWebFilter weightCalculatorWebFilter(Validator validator, ObjectProvider<RouteLocator> routeLocator) {
public WeightCalculatorWebFilter weightCalculatorWebFilter(Validator validator,
ObjectProvider<RouteLocator> routeLocator) {
return new WeightCalculatorWebFilter(validator, routeLocator);
}
/*@Bean
//TODO: default over netty? configurable
public WebClientHttpRoutingFilter webClientHttpRoutingFilter() {
//TODO: WebClient bean
return new WebClientHttpRoutingFilter(WebClient.routes().build());
}
@Bean
public WebClientWriteResponseFilter webClientWriteResponseFilter() {
return new WebClientWriteResponseFilter();
}*/
// Predicate Factory beans
@Bean
public AfterRoutePredicateFactory afterRoutePredicateFactory() {
return new AfterRoutePredicateFactory();
}
/*
* @Bean //TODO: default over netty? configurable public WebClientHttpRoutingFilter
* webClientHttpRoutingFilter() { //TODO: WebClient bean return new
* WebClientHttpRoutingFilter(WebClient.routes().build()); }
*
* @Bean public WebClientWriteResponseFilter webClientWriteResponseFilter() { return
* new WebClientWriteResponseFilter(); }
*/
// Predicate Factory beans
@Bean
public BeforeRoutePredicateFactory beforeRoutePredicateFactory() {
return new BeforeRoutePredicateFactory();
@@ -493,27 +389,15 @@ public class GatewayAutoConfiguration {
return new AddResponseHeaderGatewayFilterFactory();
}
@Configuration
@ConditionalOnClass({HystrixObservableCommand.class, RxReactiveStreams.class})
protected static class HystrixConfiguration {
@Bean
public HystrixGatewayFilterFactory hystrixGatewayFilterFactory(ObjectProvider<DispatcherHandler> dispatcherHandler) {
return new HystrixGatewayFilterFactory(dispatcherHandler);
}
@Bean
public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() {
return new FallbackHeadersGatewayFilterFactory();
}
}
@Bean
public ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
public ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory(
ServerCodecConfigurer codecConfigurer) {
return new ModifyRequestBodyGatewayFilterFactory(codecConfigurer);
}
@Bean
public ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
public ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory(
ServerCodecConfigurer codecConfigurer) {
return new ModifyResponseBodyGatewayFilterFactory(codecConfigurer);
}
@@ -550,8 +434,9 @@ public class GatewayAutoConfiguration {
}
@Bean
@ConditionalOnBean({RateLimiter.class, KeyResolver.class})
public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter, KeyResolver resolver) {
@ConditionalOnBean({ RateLimiter.class, KeyResolver.class })
public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(
RateLimiter rateLimiter, KeyResolver resolver) {
return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
}
@@ -571,7 +456,8 @@ public class GatewayAutoConfiguration {
}
@Bean
public SecureHeadersGatewayFilterFactory secureHeadersGatewayFilterFactory(SecureHeadersProperties properties) {
public SecureHeadersGatewayFilterFactory secureHeadersGatewayFilterFactory(
SecureHeadersProperties properties) {
return new SecureHeadersGatewayFilterFactory(properties);
}
@@ -615,18 +501,148 @@ public class GatewayAutoConfiguration {
return new RequestSizeGatewayFilterFactory();
}
@Configuration
@ConditionalOnClass(HttpClient.class)
protected static class NettyConfiguration {
@Bean
@ConditionalOnMissingBean
public HttpClient httpClient(HttpClientProperties properties) {
// configure pool resources
HttpClientProperties.Pool pool = properties.getPool();
ConnectionProvider connectionProvider;
if (pool.getType() == DISABLED) {
connectionProvider = ConnectionProvider.newConnection();
}
else if (pool.getType() == FIXED) {
connectionProvider = ConnectionProvider.fixed(pool.getName(),
pool.getMaxConnections(), pool.getAcquireTimeout());
}
else {
connectionProvider = ConnectionProvider.elastic(pool.getName());
}
HttpClient httpClient = HttpClient.create(connectionProvider)
.tcpConfiguration(tcpClient -> {
if (properties.getConnectTimeout() != null) {
tcpClient = tcpClient.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
properties.getConnectTimeout());
}
// configure proxy if proxy host is set.
HttpClientProperties.Proxy proxy = properties.getProxy();
if (StringUtils.hasText(proxy.getHost())) {
tcpClient = tcpClient.proxy(proxySpec -> {
ProxyProvider.Builder builder = proxySpec
.type(ProxyProvider.Proxy.HTTP)
.host(proxy.getHost());
PropertyMapper map = PropertyMapper.get();
map.from(proxy::getPort).whenNonNull().to(builder::port);
map.from(proxy::getUsername).whenHasText()
.to(builder::username);
map.from(proxy::getPassword).whenHasText()
.to(password -> builder.password(s -> password));
map.from(proxy::getNonProxyHostsPattern).whenHasText()
.to(builder::nonProxyHosts);
});
}
return tcpClient;
});
HttpClientProperties.Ssl ssl = properties.getSsl();
if (ssl.getTrustedX509CertificatesForTrustManager().length > 0
|| ssl.isUseInsecureTrustManager()) {
httpClient = httpClient.secure(sslContextSpec -> {
// configure ssl
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
X509Certificate[] trustedX509Certificates = ssl
.getTrustedX509CertificatesForTrustManager();
if (trustedX509Certificates.length > 0) {
sslContextBuilder.trustManager(trustedX509Certificates);
}
else if (ssl.isUseInsecureTrustManager()) {
sslContextBuilder
.trustManager(InsecureTrustManagerFactory.INSTANCE);
}
sslContextSpec.sslContext(sslContextBuilder)
.defaultConfiguration(ssl.getDefaultConfigurationType())
.handshakeTimeout(ssl.getHandshakeTimeout())
.closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
.closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
});
}
return httpClient;
}
@Bean
public HttpClientProperties httpClientProperties() {
return new HttpClientProperties();
}
@Bean
public NettyRoutingFilter routingFilter(HttpClient httpClient,
ObjectProvider<List<HttpHeadersFilter>> headersFilters,
HttpClientProperties properties) {
return new NettyRoutingFilter(httpClient, headersFilters, properties);
}
@Bean
public NettyWriteResponseFilter nettyWriteResponseFilter(
GatewayProperties properties) {
return new NettyWriteResponseFilter(properties.getStreamingMediaTypes());
}
@Bean
public ReactorNettyWebSocketClient reactorNettyWebSocketClient(
HttpClient httpClient) {
return new ReactorNettyWebSocketClient(httpClient);
}
}
@Configuration
@ConditionalOnClass({ HystrixObservableCommand.class, RxReactiveStreams.class })
protected static class HystrixConfiguration {
@Bean
public HystrixGatewayFilterFactory hystrixGatewayFilterFactory(
ObjectProvider<DispatcherHandler> dispatcherHandler) {
return new HystrixGatewayFilterFactory(dispatcherHandler);
}
@Bean
public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() {
return new FallbackHeadersGatewayFilterFactory();
}
}
@Configuration
@ConditionalOnClass(Health.class)
protected static class GatewayActuatorConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
public GatewayControllerEndpoint gatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter,
RouteLocator routeLocator) {
return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters, GatewayFilters, routeDefinitionWriter, routeLocator);
public GatewayControllerEndpoint gatewayControllerEndpoint(
RouteDefinitionLocator routeDefinitionLocator,
List<GlobalFilter> globalFilters,
List<GatewayFilterFactory> GatewayFilters,
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters,
GatewayFilters, routeDefinitionWriter, routeLocator);
}
}
}

View File

@@ -1,7 +1,24 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
@@ -11,7 +28,9 @@ import org.springframework.context.annotation.Configuration;
@AutoConfigureBefore(GatewayAutoConfiguration.class)
public class GatewayClassPathWarningAutoConfiguration {
private static final Log log = LogFactory.getLog(GatewayClassPathWarningAutoConfiguration.class);
private static final Log log = LogFactory
.getLog(GatewayClassPathWarningAutoConfiguration.class);
private static final String BORDER = "\n\n**********************************************************\n\n";
@Configuration
@@ -19,8 +38,9 @@ public class GatewayClassPathWarningAutoConfiguration {
protected static class SpringMvcFoundOnClasspathConfiguration {
public SpringMvcFoundOnClasspathConfiguration() {
log.warn(BORDER+"Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. "+
"Please remove spring-boot-starter-web dependency."+BORDER);
log.warn(BORDER
+ "Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. "
+ "Please remove spring-boot-starter-web dependency." + BORDER);
}
}
@@ -30,9 +50,11 @@ public class GatewayClassPathWarningAutoConfiguration {
protected static class WebfluxMissingFromClasspathConfiguration {
public WebfluxMissingFromClasspathConfiguration() {
log.warn(BORDER+"Spring Webflux is missing from the classpath, which is required for Spring Cloud Gateway at this time. "+
"Please add spring-boot-starter-webflux dependency."+BORDER);
log.warn(BORDER + "Spring Webflux is missing from the classpath, "
+ "which is required for Spring Cloud Gateway at this time. "
+ "Please add spring-boot-starter-webflux dependency." + BORDER);
}
}
}

View File

@@ -12,22 +12,25 @@
* 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 java.util.Collections;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.Collections;
public class GatewayEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication application) {
env.getPropertySources().addFirst(new MapPropertySource("gateway-properties",
Collections.singletonMap("spring.webflux.hiddenmethod.filter.enabled", "false")));
public void postProcessEnvironment(ConfigurableEnvironment env,
SpringApplication application) {
env.getPropertySources().addFirst(
new MapPropertySource("gateway-properties", Collections.singletonMap(
"spring.webflux.hiddenmethod.filter.enabled", "false")));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -23,7 +22,6 @@ 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.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.context.annotation.Bean;
@@ -34,7 +32,8 @@ import org.springframework.web.reactive.DispatcherHandler;
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnClass({LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class})
@ConditionalOnClass({ LoadBalancerClient.class, RibbonAutoConfiguration.class,
DispatcherHandler.class })
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
public class GatewayLoadBalancerClientAutoConfiguration {
@@ -44,7 +43,9 @@ public class GatewayLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClient.class)
@ConditionalOnMissingBean(LoadBalancerClientFilter.class)
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client,
LoadBalancerProperties properties) {
return new LoadBalancerClientFilter(client, properties);
}
}

View File

@@ -12,12 +12,12 @@
* 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 io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -36,12 +36,15 @@ import org.springframework.web.reactive.DispatcherHandler;
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
@AutoConfigureAfter({ MetricsAutoConfiguration.class,
CompositeMeterRegistryAutoConfiguration.class })
@ConditionalOnClass({ DispatcherHandler.class, MeterRegistry.class, MetricsAutoConfiguration.class})
@ConditionalOnClass({ DispatcherHandler.class, MeterRegistry.class,
MetricsAutoConfiguration.class })
public class GatewayMetricsAutoConfiguration {
@Bean
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnProperty(name = "spring.cloud.gateway.metrics.enabled", matchIfMissing = true)
public GatewayMetricsFilter gatewayMetricFilter(MeterRegistry meterRegistry) {
return new GatewayMetricsFilter(meterRegistry);
}
}

View File

@@ -12,7 +12,6 @@
* 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;
@@ -51,7 +50,8 @@ public class GatewayNoLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean(LoadBalancerClientFilter.class)
public NoLoadBalancerClientFilter noLoadBalancerClientFilter(LoadBalancerProperties properties) {
public NoLoadBalancerClientFilter noLoadBalancerClientFilter(
LoadBalancerProperties properties) {
return new NoLoadBalancerClientFilter(properties.isUse404());
}
@@ -73,11 +73,15 @@ public class GatewayNoLoadBalancerClientAutoConfiguration {
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))) {
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
throw NotFoundException.create(use404, "Unable to find instance for " + url.getHost());
throw NotFoundException.create(use404,
"Unable to find instance for " + url.getHost());
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -26,6 +25,7 @@ import javax.validation.constraints.NotNull;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
@@ -40,8 +40,9 @@ import org.springframework.validation.annotation.Validated;
public class GatewayProperties {
private final Log logger = LogFactory.getLog(getClass());
/**
* List of Routes
* List of Routes.
*/
@NotNull
@Valid
@@ -52,18 +53,17 @@ public class GatewayProperties {
*/
private List<FilterDefinition> defaultFilters = new ArrayList<>();
private List<MediaType> streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM,
MediaType.APPLICATION_STREAM_JSON);
private List<MediaType> streamingMediaTypes = Arrays
.asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);
public List<RouteDefinition> getRoutes() {
return routes;
}
public void setRoutes(List<RouteDefinition> routes) {
this.routes = routes;
if (routes != null && routes.size() > 0 && logger.isDebugEnabled()) {
logger.debug("Routes supplied from Gateway Properties: "+routes);
logger.debug("Routes supplied from Gateway Properties: " + routes);
}
}
@@ -85,10 +85,8 @@ public class GatewayProperties {
@Override
public String toString() {
return "GatewayProperties{" +
"routes=" + routes +
", defaultFilters=" + defaultFilters +
", streamingMediaTypes=" + streamingMediaTypes +
'}';
return "GatewayProperties{" + "routes=" + routes + ", defaultFilters="
+ defaultFilters + ", streamingMediaTypes=" + streamingMediaTypes + '}';
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.config;
import java.util.List;
@@ -29,39 +45,38 @@ import org.springframework.web.reactive.DispatcherHandler;
@AutoConfigureAfter(RedisReactiveAutoConfiguration.class)
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@ConditionalOnBean(ReactiveRedisTemplate.class)
@ConditionalOnClass({RedisTemplate.class, DispatcherHandler.class})
@ConditionalOnClass({ RedisTemplate.class, DispatcherHandler.class })
class GatewayRedisAutoConfiguration {
@Bean
@SuppressWarnings("unchecked")
public RedisScript redisRequestRateLimiterScript() {
DefaultRedisScript redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("META-INF/scripts/request_rate_limiter.lua")));
redisScript.setScriptSource(new ResourceScriptSource(
new ClassPathResource("META-INF/scripts/request_rate_limiter.lua")));
redisScript.setResultType(List.class);
return redisScript;
}
@Bean
//TODO: replace with ReactiveStringRedisTemplate in future
// TODO: replace with ReactiveStringRedisTemplate in future
public ReactiveRedisTemplate<String, String> stringReactiveRedisTemplate(
ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
RedisSerializer<String> serializer = new StringRedisSerializer();
RedisSerializationContext<String , String> serializationContext = RedisSerializationContext
.<String, String>newSerializationContext()
.key(serializer)
.value(serializer)
.hashKey(serializer)
.hashValue(serializer)
.build();
RedisSerializationContext<String, String> serializationContext = RedisSerializationContext
.<String, String>newSerializationContext().key(serializer)
.value(serializer).hashKey(serializer).hashValue(serializer).build();
return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory,
serializationContext);
}
@Bean
@ConditionalOnMissingBean
public RedisRateLimiter redisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
@Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
Validator validator) {
public RedisRateLimiter redisRateLimiter(
ReactiveRedisTemplate<String, String> redisTemplate,
@Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
Validator validator) {
return new RedisRateLimiter(redisTemplate, redisScript, validator);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,20 +12,10 @@
* 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.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.web.server.WebServerException;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.ResourceUtils;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.SslProvider;
import java.io.IOException;
import java.net.URL;
import java.security.cert.Certificate;
@@ -37,8 +27,17 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.SslProvider;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.web.server.WebServerException;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.ResourceUtils;
/**
* Configuration properties for the Netty {@link reactor.netty.http.client.HttpClient}
* Configuration properties for the Netty {@link reactor.netty.http.client.HttpClient}.
*/
@ConfigurationProperties("spring.cloud.gateway.httpclient")
public class HttpClientProperties {
@@ -49,19 +48,23 @@ public class HttpClientProperties {
/** The response timeout. */
private Duration responseTimeout;
/** Pool configuration for Netty HttpClient */
/** Pool configuration for Netty HttpClient. */
private Pool pool = new Pool();
/** Proxy configuration for Netty HttpClient */
/** Proxy configuration for Netty HttpClient. */
private Proxy proxy = new Proxy();
/** SSL configuration for Netty HttpClient */
/** SSL configuration for Netty HttpClient. */
private Ssl ssl = new Ssl();
public Integer getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
this.connectTimeout = connectTimeout;
}
public Duration getResponseTimeout() {
return responseTimeout;
}
@@ -70,10 +73,6 @@ public class HttpClientProperties {
this.responseTimeout = responseTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
this.connectTimeout = connectTimeout;
}
public Pool getPool() {
return pool;
}
@@ -98,9 +97,14 @@ public class HttpClientProperties {
this.ssl = ssl;
}
public static class Pool {
@Override
public String toString() {
return new ToStringCreator(this).append("connectTimeout", connectTimeout)
.append("responseTimeout", responseTimeout).append("pool", pool)
.append("proxy", proxy).append("ssl", ssl).toString();
}
public enum PoolType { ELASTIC, FIXED, DISABLED }
public static class Pool {
/** Type of pool for HttpClient to use, defaults to ELASTIC. */
private PoolType type = PoolType.ELASTIC;
@@ -108,7 +112,10 @@ public class HttpClientProperties {
/** The channel pool map name, defaults to proxy. */
private String name = "proxy";
/** Only for type FIXED, the maximum number of connections before starting pending acquisition on existing ones. */
/**
* Only for type FIXED, the maximum number of connections before starting pending
* acquisition on existing ones.
*/
private Integer maxConnections = ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS;
/** Only for type FIXED, the maximum time in millis to wait for aquiring. */
@@ -148,26 +155,50 @@ public class HttpClientProperties {
@Override
public String toString() {
return "Pool{" +
"type=" + type +
", name='" + name + '\'' +
", maxConnections=" + maxConnections +
", acquireTimeout=" + acquireTimeout +
'}';
return "Pool{" + "type=" + type + ", name='" + name + '\''
+ ", maxConnections=" + maxConnections + ", acquireTimeout="
+ acquireTimeout + '}';
}
public enum PoolType {
/**
* Elastic pool type.
*/
ELASTIC,
/**
* Fixed pool type.
*/
FIXED,
/**
* Disabled pool type.
*/
DISABLED
}
}
public class Proxy {
/** Hostname for proxy configuration of Netty HttpClient. */
private String host;
/** Port for proxy configuration of Netty HttpClient. */
private Integer port;
/** Username for proxy configuration of Netty HttpClient. */
private String username;
/** Password for proxy configuration of Netty HttpClient. */
private String password;
/** Regular expression (Java) for a configured list of hosts
* that should be reached directly, bypassing the proxy */
/**
* Regular expression (Java) for a configured list of hosts. that should be
* reached directly, bypassing the proxy
*/
private String nonProxyHostsPattern;
public String getHost() {
@@ -212,28 +243,31 @@ public class HttpClientProperties {
@Override
public String toString() {
return "Proxy{" +
"host='" + host + '\'' +
", port=" + port +
", username='" + username + '\'' +
", password='" + password + '\'' +
", nonProxyHostsPattern='" + nonProxyHostsPattern + '\'' +
'}';
return "Proxy{" + "host='" + host + '\'' + ", port=" + port + ", username='"
+ username + '\'' + ", password='" + password + '\''
+ ", nonProxyHostsPattern='" + nonProxyHostsPattern + '\'' + '}';
}
}
public class Ssl {
/** Installs the netty InsecureTrustManagerFactory. This is insecure and not suitable for production. */
/**
* Installs the netty InsecureTrustManagerFactory. This is insecure and not
* suitable for production.
*/
private boolean useInsecureTrustManager = false;
/** Trusted certificates for verifying the remote endpoint's certificate. */
private List<String> trustedX509Certificates = new ArrayList<>();
// use netty default SSL timeouts
/** SSL handshake timeout. Default to 10000 ms */
private Duration handshakeTimeout = Duration.ofMillis(10000);
/** SSL close_notify flush timeout. Default to 3000 ms. */
private Duration closeNotifyFlushTimeout = Duration.ofMillis(3000);
/** SSL close_notify read timeout. Default to 0 ms. */
private Duration closeNotifyReadTimeout = Duration.ZERO;
@@ -243,7 +277,11 @@ public class HttpClientProperties {
public List<String> getTrustedX509Certificates() {
return trustedX509Certificates;
}
public void setTrustedX509Certificates(List<String> trustedX509) {
this.trustedX509Certificates = trustedX509;
}
public X509Certificate[] getTrustedX509CertificatesForTrustManager() {
try {
CertificateFactory certificateFactory = CertificateFactory
@@ -269,11 +307,7 @@ public class HttpClientProperties {
}
}
public void setTrustedX509Certificates(List<String> trustedX509) {
this.trustedX509Certificates = trustedX509;
}
//TODO: support configuration of other trust manager factories
// TODO: support configuration of other trust manager factories
public boolean isUseInsecureTrustManager() {
return useInsecureTrustManager;
@@ -344,7 +378,8 @@ public class HttpClientProperties {
return defaultConfigurationType;
}
public void setDefaultConfigurationType(SslProvider.DefaultConfigurationType defaultConfigurationType) {
public void setDefaultConfigurationType(
SslProvider.DefaultConfigurationType defaultConfigurationType) {
this.defaultConfigurationType = defaultConfigurationType;
}
@@ -359,16 +394,7 @@ public class HttpClientProperties {
.append("defaultConfigurationType", defaultConfigurationType)
.toString();
}
}
@Override
public String toString() {
return new ToStringCreator(this)
.append("connectTimeout", connectTimeout)
.append("responseTimeout", responseTimeout)
.append("pool", pool)
.append("proxy", proxy)
.append("ssl", ssl)
.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,8 +12,8 @@
* 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.context.properties.ConfigurationProperties;
@@ -23,6 +23,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
*/
@ConfigurationProperties("spring.cloud.gateway.loadbalancer")
public class LoadBalancerProperties {
private boolean use404;
public boolean isUse404() {
@@ -32,4 +33,5 @@ public class LoadBalancerProperties {
public void setUse404(boolean use404) {
this.use404 = use404;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,16 +12,15 @@
* 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 reactor.core.publisher.Flux;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import reactor.core.publisher.Flux;
/**
* @author Spencer Gibb
*/
@@ -37,4 +36,5 @@ public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(this.properties.getRoutes());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.discovery;
@@ -41,27 +40,33 @@ import org.springframework.util.StringUtils;
/**
* TODO: change to RouteLocator? use java dsl
*
* @author Spencer Gibb
*/
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
private static final Log log = LogFactory.getLog(DiscoveryClientRouteDefinitionLocator.class);
private static final Log log = LogFactory
.getLog(DiscoveryClientRouteDefinitionLocator.class);
private final DiscoveryClient discoveryClient;
private final DiscoveryLocatorProperties properties;
private final String routeIdPrefix;
private final SimpleEvaluationContext evalCtxt;
public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient,
DiscoveryLocatorProperties properties) {
this.discoveryClient = discoveryClient;
this.properties = properties;
if (StringUtils.hasText(properties.getRouteIdPrefix())) {
this.routeIdPrefix = properties.getRouteIdPrefix();
} else {
}
else {
this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_";
}
evalCtxt = SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
evalCtxt = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods()
.build();
}
@@ -69,13 +74,16 @@ public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLoc
public Flux<RouteDefinition> getRouteDefinitions() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());
Expression includeExpr = parser
.parseExpression(properties.getIncludeExpression());
Expression urlExpr = parser.parseExpression(properties.getUrlExpression());
Predicate<ServiceInstance> includePredicate;
if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) {
if (properties.getIncludeExpression() == null
|| "true".equalsIgnoreCase(properties.getIncludeExpression())) {
includePredicate = instance -> true;
} else {
}
else {
includePredicate = instance -> {
Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
if (include == null) {
@@ -88,47 +96,53 @@ public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLoc
return Flux.fromIterable(discoveryClient.getServices())
.map(discoveryClient::getInstances)
.filter(instances -> !instances.isEmpty())
.map(instances -> instances.get(0))
.filter(includePredicate)
.map(instances -> instances.get(0)).filter(includePredicate)
.map(instance -> {
String serviceId = instance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));
final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);
final ServiceInstance instanceForEval = new DelegatingServiceInstance(
instance, properties);
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
for (Map.Entry<String, String> entry : original.getArgs()
.entrySet()) {
String value = getValueFromExpr(evalCtxt, parser,
instanceForEval, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs()
.entrySet()) {
String value = getValueFromExpr(evalCtxt, parser,
instanceForEval, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}
return routeDefinition;
return routeDefinition;
});
}
String getValueFromExpr(SimpleEvaluationContext evalCtxt, SpelExpressionParser parser, ServiceInstance instance, Map.Entry<String, String> entry) {
String getValueFromExpr(SimpleEvaluationContext evalCtxt, SpelExpressionParser parser,
ServiceInstance instance, Map.Entry<String, String> entry) {
try {
Expression valueExpr = parser.parseExpression(entry.getValue());
return valueExpr.getValue(evalCtxt, instance, String.class);
} catch (ParseException | EvaluationException e) {
}
catch (ParseException | EvaluationException e) {
if (log.isDebugEnabled()) {
log.debug("Unable to parse " + entry.getValue(), e);
}
@@ -139,9 +153,11 @@ public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLoc
private static class DelegatingServiceInstance implements ServiceInstance {
final ServiceInstance delegate;
private final DiscoveryLocatorProperties properties;
private DelegatingServiceInstance(ServiceInstance delegate, DiscoveryLocatorProperties properties) {
private DelegatingServiceInstance(ServiceInstance delegate,
DiscoveryLocatorProperties properties) {
this.delegate = delegate;
this.properties = properties;
}
@@ -186,10 +202,10 @@ public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLoc
@Override
public String toString() {
return new ToStringCreator(this)
.append("delegate", delegate)
.append("properties", properties)
.toString();
return new ToStringCreator(this).append("delegate", delegate)
.append("properties", properties).toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,12 +12,10 @@
* 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.discovery;
import java.util.ArrayList;
import java.util.List;
@@ -29,28 +27,30 @@ import org.springframework.core.style.ToStringCreator;
@ConfigurationProperties("spring.cloud.gateway.discovery.locator")
public class DiscoveryLocatorProperties {
/** Flag that enables DiscoveryClient gateway integration */
/** Flag that enables DiscoveryClient gateway integration. */
private boolean enabled = false;
/**
* The prefix for the routeId, defaults to discoveryClient.getClass().getSimpleName() + "_".
* Service Id will be appended to create the routeId.
* The prefix for the routeId, defaults to discoveryClient.getClass().getSimpleName()
* + "_". Service Id will be appended to create the routeId.
*/
private String routeIdPrefix;
/**
* SpEL expression that will evaluate whether to include a service in gateway integration or not,
* defaults to: true
* SpEL expression that will evaluate whether to include a service in gateway
* integration or not, defaults to: true.
*/
private String includeExpression = "true";
/** SpEL expression that create the uri for each route, defaults to: 'lb://'+serviceId */
/**
* SpEL expression that create the uri for each route, defaults to: 'lb://'+serviceId.
*/
private String urlExpression = "'lb://'+serviceId";
/**
* Option to lower case serviceId in predicates and filters, defaults to false.
* Useful with eureka when it automatically uppercases serviceId.
* so MYSERIVCE, would match /myservice/**
* Option to lower case serviceId in predicates and filters, defaults to false. Useful
* with eureka when it automatically uppercases serviceId. so MYSERIVCE, would match
* /myservice/**
*/
private boolean lowerCaseServiceId = false;
@@ -116,14 +116,12 @@ public class DiscoveryLocatorProperties {
@Override
public String toString() {
return new ToStringCreator(this)
.append("enabled", enabled)
return new ToStringCreator(this).append("enabled", enabled)
.append("routeIdPrefix", routeIdPrefix)
.append("includeExpression", includeExpression)
.append("urlExpression", urlExpression)
.append("lowerCaseServiceId", lowerCaseServiceId)
.append("predicates", predicates)
.append("filters", filters)
.toString();
.append("predicates", predicates).append("filters", filters).toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -16,6 +16,9 @@
package org.springframework.cloud.gateway.discovery;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -33,9 +36,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REGEXP_KEY;
import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REPLACEMENT_KEY;
import static org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory.PATTERN_KEY;
@@ -49,26 +49,10 @@ import static org.springframework.cloud.gateway.support.NameUtils.normalizeRoute
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@AutoConfigureAfter(CompositeDiscoveryClientAutoConfiguration.class)
@ConditionalOnClass({DispatcherHandler.class, DiscoveryClient.class})
@ConditionalOnClass({ DispatcherHandler.class, DiscoveryClient.class })
@EnableConfigurationProperties
public class GatewayDiscoveryClientAutoConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
@Bean
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}
public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList<>();
// TODO: add a predicate that matches the url at /serviceId?
@@ -96,5 +80,20 @@ public class GatewayDiscoveryClientAutoConfiguration {
return definitions;
}
}
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
@Bean
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.event;
@@ -22,9 +21,11 @@ import java.util.Map;
import org.springframework.context.ApplicationEvent;
public class FilterArgsEvent extends ApplicationEvent {
private String routeId;
private final Map<String, Object> args;
private String routeId;
public FilterArgsEvent(Object source, String routeId, Map<String, Object> args) {
super(source);
this.routeId = routeId;
@@ -38,4 +39,5 @@ public class FilterArgsEvent extends ApplicationEvent {
public Map<String, Object> getArgs() {
return args;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.event;
@@ -22,9 +21,11 @@ import java.util.Map;
import org.springframework.context.ApplicationEvent;
public class PredicateArgsEvent extends ApplicationEvent {
private String routeId;
private final Map<String, Object> args;
private String routeId;
public PredicateArgsEvent(Object source, String routeId, Map<String, Object> args) {
super(source);
this.routeId = routeId;
@@ -38,4 +39,5 @@ public class PredicateArgsEvent extends ApplicationEvent {
public Map<String, Object> getArgs() {
return args;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.event;
@@ -24,12 +23,12 @@ import org.springframework.context.ApplicationEvent;
*/
public class RefreshRoutesEvent extends ApplicationEvent {
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public RefreshRoutesEvent(Object source) {
super(source);
}
/**
* Create a new ApplicationEvent.
* @param source the object on which the event initially occurred (never {@code null})
*/
public RefreshRoutesEvent(Object source) {
super(source);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.event;
@@ -21,6 +20,7 @@ import org.springframework.cloud.gateway.support.WeightConfig;
import org.springframework.context.ApplicationEvent;
public class WeightDefinedEvent extends ApplicationEvent {
private final WeightConfig weightConfig;
public WeightDefinedEvent(Object source, WeightConfig weightConfig) {
@@ -31,4 +31,5 @@ public class WeightDefinedEvent extends ApplicationEvent {
public WeightConfig getWeightConfig() {
return weightConfig;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,28 +12,33 @@
* 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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class AdaptCachedBodyGlobalFilter implements GlobalFilter, Ordered {
/**
* Cached request body key.
*/
public static final String CACHED_REQUEST_BODY_KEY = "cachedRequestBody";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Flux<DataBuffer> body = exchange.getAttributeOrDefault(CACHED_REQUEST_BODY_KEY, null);
Flux<DataBuffer> body = exchange.getAttributeOrDefault(CACHED_REQUEST_BODY_KEY,
null);
if (body != null) {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return body;
@@ -50,4 +55,5 @@ public class AdaptCachedBodyGlobalFilter implements GlobalFilter, Ordered {
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1000;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -33,8 +32,10 @@ import static org.springframework.util.StringUtils.tokenizeToStringArray;
*/
@Validated
public class FilterDefinition {
@NotNull
private String name;
private Map<String, String> args = new LinkedHashMap<>();
public FilterDefinition() {
@@ -48,9 +49,9 @@ public class FilterDefinition {
}
setName(text.substring(0, eqIdx));
String[] args = tokenizeToStringArray(text.substring(eqIdx+1), ",");
String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");
for (int i=0; i < args.length; i++) {
for (int i = 0; i < args.length; i++) {
this.args.put(NameUtils.generateName(i), args[i]);
}
}
@@ -77,11 +78,14 @@ public class FilterDefinition {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FilterDefinition that = (FilterDefinition) o;
return Objects.equals(name, that.name) &&
Objects.equals(args, that.args);
return Objects.equals(name, that.name) && Objects.equals(args, that.args);
}
@Override
@@ -97,4 +101,5 @@ public class FilterDefinition {
sb.append('}');
return sb.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,13 +12,14 @@
* 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 reactor.core.publisher.Mono;
import java.net.URI;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
@@ -29,9 +30,11 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.i
/**
* Filter to set the path in the request URI if the {@link Route} URI has the scheme
* <code>forward</code>.
*
* @author Ryan Baxter
*/
public class ForwardPathFilter implements GlobalFilter, Ordered{
public class ForwardPathFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
@@ -40,8 +43,8 @@ public class ForwardPathFilter implements GlobalFilter, Ordered{
if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
return chain.filter(exchange);
}
exchange = exchange.mutate().request(
exchange.getRequest().mutate().path(routeUri.getPath()).build())
exchange = exchange.mutate()
.request(exchange.getRequest().mutate().path(routeUri.getPath()).build())
.build();
return chain.filter(exchange);
}
@@ -50,4 +53,5 @@ public class ForwardPathFilter implements GlobalFilter, Ordered{
public int getOrder() {
return 0;
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter;
import java.net.URI;
@@ -20,10 +36,12 @@ public class ForwardRoutingFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(ForwardRoutingFilter.class);
private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;
//do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
// do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
private volatile DispatcherHandler dispatcherHandler;
public ForwardRoutingFilter(ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
public ForwardRoutingFilter(
ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
this.dispatcherHandlerProvider = dispatcherHandlerProvider;
}
@@ -50,12 +68,13 @@ public class ForwardRoutingFilter implements GlobalFilter, Ordered {
}
setAlreadyRouted(exchange);
//TODO: translate url?
// TODO: translate url?
if (log.isTraceEnabled()) {
log.trace("Forwarding to URI: "+requestUrl);
log.trace("Forwarding to URI: " + requestUrl);
}
return this.getDispatcherHandler().handle(exchange);
}
}

View File

@@ -1,7 +1,5 @@
package org.springframework.cloud.gateway.filter;
/*
* Copyright 2002-2015 the original author or authors.
* 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.
@@ -16,15 +14,17 @@ package org.springframework.cloud.gateway.filter;
* limitations under the License.
*/
import org.springframework.cloud.gateway.support.ShortcutConfigurable;
import org.springframework.web.server.ServerWebExchange;
package org.springframework.cloud.gateway.filter;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.support.ShortcutConfigurable;
import org.springframework.web.server.ServerWebExchange;
/**
* Contract for interception-style, chained processing of Web requests that may
* be used to implement cross-cutting, application-agnostic requirements such
* as security, timeouts, and others. Specific to a Gateway
* Contract for interception-style, chained processing of Web requests that may be used to
* implement cross-cutting, application-agnostic requirements such as security, timeouts,
* and others. Specific to a Gateway
*
* Copied from WebFilter
*
@@ -33,12 +33,19 @@ import reactor.core.publisher.Mono;
*/
public interface GatewayFilter extends ShortcutConfigurable {
/**
* Name key.
*/
String NAME_KEY = "name";
/**
* Value key.
*/
String VALUE_KEY = "value";
/**
* Process the Web request and (optionally) delegate to the next
* {@code WebFilter} through the given {@link GatewayFilterChain}.
* Process the Web request and (optionally) delegate to the next {@code WebFilter}
* through the given {@link GatewayFilterChain}.
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
@@ -46,4 +53,3 @@ public interface GatewayFilter extends ShortcutConfigurable {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

View File

@@ -1,8 +1,25 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter;
import reactor.core.publisher.Mono;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import reactor.core.publisher.Mono;
/**
* Contract to allow a {@link WebFilter} to delegate to the next in the chain.

View File

@@ -12,7 +12,6 @@
* 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;
@@ -102,4 +101,5 @@ public class GatewayMetricsFilter implements GlobalFilter, Ordered {
}
sample.stop(meterRegistry.timer("gateway.requests", tags));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,20 +12,18 @@
* 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 org.springframework.web.server.ServerWebExchange;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import reactor.core.publisher.Mono;
import org.springframework.web.server.ServerWebExchange;
/**
* Contract for interception-style, chained processing of Web requests that may
* be used to implement cross-cutting, application-agnostic requirements such
* as security, timeouts, and others.
* Contract for interception-style, chained processing of Web requests that may be used to
* implement cross-cutting, application-agnostic requirements such as security, timeouts,
* and others.
*
* @author Rossen Stoyanchev
* @since 5.0
@@ -33,8 +31,8 @@ import reactor.core.publisher.Mono;
public interface GlobalFilter {
/**
* Process the Web request and (optionally) delegate to the next
* {@code WebFilter} through the given {@link GatewayFilterChain}.
* Process the Web request and (optionally) delegate to the next {@code WebFilter}
* through the given {@link GatewayFilterChain}.
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -41,14 +40,19 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a
*/
public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(LoadBalancerClientFilter.class);
/**
* 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) {
public LoadBalancerClientFilter(LoadBalancerClient loadBalancer,
LoadBalancerProperties properties) {
this.loadBalancer = loadBalancer;
this.properties = properties;
}
@@ -63,10 +67,11 @@ public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
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))) {
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
//preserve the original url
// preserve the original url
addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
@@ -74,7 +79,8 @@ public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(), "Unable to find instance for " + url.getHost());
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
@@ -86,7 +92,8 @@ public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
overrideScheme = url.getScheme();
}
URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
@@ -94,11 +101,14 @@ public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
}
protected ServiceInstance choose(ServerWebExchange exchange) {
return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
return loadBalancer.choose(
((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
}
class DelegatingServiceInstance implements ServiceInstance {
final ServiceInstance delegate;
private String overrideScheme;
DelegatingServiceInstance(ServiceInstance delegate, String overrideScheme) {
@@ -146,4 +156,5 @@ public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -22,7 +21,6 @@ import java.util.List;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.NettyPipeline;
@@ -42,6 +40,7 @@ import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.filterRequest;
@@ -61,14 +60,17 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.s
public class NettyRoutingFilter implements GlobalFilter, Ordered {
private final HttpClient httpClient;
private final ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider;
private final HttpClientProperties properties;
//do not use this headersFilters directly, use getHeadersFilters() instead.
// do not use this headersFilters directly, use getHeadersFilters() instead.
private volatile List<HttpHeadersFilter> headersFilters;
public NettyRoutingFilter(HttpClient httpClient,
ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider,
HttpClientProperties properties) {
ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider,
HttpClientProperties properties) {
this.httpClient = httpClient;
this.headersFiltersProvider = headersFiltersProvider;
this.properties = properties;
@@ -92,7 +94,8 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || (!"http".equals(scheme) && !"https".equals(scheme))) {
if (isAlreadyRouted(exchange)
|| (!"http".equals(scheme) && !"https".equals(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange);
@@ -107,15 +110,15 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
filtered.forEach(httpHeaders::set);
String transferEncoding = request.getHeaders().getFirst(HttpHeaders.TRANSFER_ENCODING);
String transferEncoding = request.getHeaders()
.getFirst(HttpHeaders.TRANSFER_ENCODING);
boolean chunkedTransfer = "chunked".equalsIgnoreCase(transferEncoding);
boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
boolean preserveHost = exchange
.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
Flux<HttpClientResponse> responseFlux = this.httpClient
.chunkedTransfer(chunkedTransfer)
.request(method)
.uri(url)
.chunkedTransfer(chunkedTransfer).request(method).uri(url)
.send((req, nettyOutbound) -> {
req.headers(httpHeaders);
@@ -123,49 +126,64 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
req.header(HttpHeaders.HOST, host);
}
return nettyOutbound
.options(NettyPipeline.SendOptions::flushOnEach)
.send(request.getBody().map(dataBuffer ->
((NettyDataBuffer) dataBuffer).getNativeBuffer()));
return nettyOutbound.options(NettyPipeline.SendOptions::flushOnEach)
.send(request.getBody()
.map(dataBuffer -> ((NettyDataBuffer) dataBuffer)
.getNativeBuffer()));
}).responseConnection((res, connection) -> {
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
HttpHeaders headers = new HttpHeaders();
res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue()));
res.responseHeaders().forEach(
entry -> headers.add(entry.getKey(), entry.getValue()));
String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue);
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR,
contentTypeValue);
}
HttpStatus status = HttpStatus.resolve(res.status().code());
if (status != null) {
response.setStatusCode(status);
} else if (response instanceof AbstractServerHttpResponse) {
}
else if (response instanceof AbstractServerHttpResponse) {
// https://jira.spring.io/browse/SPR-16748
((AbstractServerHttpResponse) response).setStatusCodeValue(res.status().code());
} else {
throw new IllegalStateException("Unable to set status code on response: " + res.status().code() + ", " + response.getClass());
((AbstractServerHttpResponse) response)
.setStatusCodeValue(res.status().code());
}
else {
throw new IllegalStateException(
"Unable to set status code on response: "
+ res.status().code() + ", "
+ response.getClass());
}
// make sure headers filters run after setting status so it is available in response
// make sure headers filters run after setting status so it is
// available in response
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
getHeadersFilters(), headers, exchange, Type.RESPONSE);
getHeadersFilters(), headers, exchange, Type.RESPONSE);
if(!filteredResponseHeaders.containsKey(HttpHeaders.TRANSFER_ENCODING) &&
filteredResponseHeaders.containsKey(HttpHeaders.CONTENT_LENGTH)) {
//It is not valid to have both the transfer-encoding header and the content-length header
//remove the transfer-encoding header in the response if the content-length header is presen
if (!filteredResponseHeaders
.containsKey(HttpHeaders.TRANSFER_ENCODING)
&& filteredResponseHeaders
.containsKey(HttpHeaders.CONTENT_LENGTH)) {
// It is not valid to have both the transfer-encoding header and
// the content-length header
// remove the transfer-encoding header in the response if the
// content-length header is presen
response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
}
exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());
exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES,
filteredResponseHeaders.keySet());
response.getHeaders().putAll(filteredResponseHeaders);
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write response later NettyWriteResponseFilter
// Put client response as ServerWebExchange attribute and write
// response later NettyWriteResponseFilter
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);
@@ -174,10 +192,11 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
if (properties.getResponseTimeout() != null) {
responseFlux = responseFlux.timeout(properties.getResponseTimeout(),
Mono.error(new TimeoutException("Response took longer than timeout: " +
properties.getResponseTimeout())))
Mono.error(new TimeoutException("Response took longer than timeout: "
+ properties.getResponseTimeout())))
.onErrorMap(TimeoutException.class,
th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th));
th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT,
th.getMessage(), th));
}
return responseFlux.then(chain.filter(exchange));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -40,10 +39,13 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.C
*/
public class NettyWriteResponseFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(NettyWriteResponseFilter.class);
/**
* Order for write response filter.
*/
public static final int WRITE_RESPONSE_FILTER_ORDER = -1;
private static final Log log = LogFactory.getLog(NettyWriteResponseFilter.class);
private final List<MediaType> streamingMediaTypes;
public NettyWriteResponseFilter(List<MediaType> streamingMediaTypes) {
@@ -68,29 +70,32 @@ public class NettyWriteResponseFilter implements GlobalFilter, Ordered {
log.trace("NettyWriteResponseFilter start");
ServerHttpResponse response = exchange.getResponse();
NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
//TODO: what if it's not netty
NettyDataBufferFactory factory = (NettyDataBufferFactory) response
.bufferFactory();
// TODO: what if it's not netty
final Flux<NettyDataBuffer> body = connection.inbound().receive()
.retain() //TODO: needed?
final Flux<NettyDataBuffer> body = connection.inbound().receive().retain() // TODO:
// needed?
.map(factory::wrap);
MediaType contentType = null;
try {
contentType = response.getHeaders().getContentType();
} catch (Exception e) {
}
catch (Exception e) {
log.trace("invalid media type", e);
}
return (isStreamingMediaType(contentType) ?
response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
return (isStreamingMediaType(contentType)
? response.writeAndFlushWith(body.map(Flux::just))
: response.writeWith(body));
}));
}
//TODO: use framework if possible
//TODO: port to WebClientWriteResponseFilter
// TODO: use framework if possible
// TODO: port to WebClientWriteResponseFilter
private boolean isStreamingMediaType(@Nullable MediaType contentType) {
return (contentType != null && this.streamingMediaTypes.stream()
.anyMatch(contentType::isCompatibleWith));
.anyMatch(contentType::isCompatibleWith));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,22 +12,22 @@
* 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 reactor.core.publisher.Mono;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
public class OrderedGatewayFilter implements GatewayFilter, Ordered {
private final GatewayFilter delegate;
private final int order;
public OrderedGatewayFilter(GatewayFilter delegate, int order) {
@@ -57,4 +57,5 @@ public class OrderedGatewayFilter implements GatewayFilter, Ordered {
sb.append('}');
return sb.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -22,6 +21,8 @@ import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
@@ -32,19 +33,27 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
public class RouteToRequestUrlFilter implements GlobalFilter, Ordered {
/**
* Order of Route to URL.
*/
public static final int ROUTE_TO_URL_FILTER_ORDER = 10000;
private static final Log log = LogFactory.getLog(RouteToRequestUrlFilter.class);
public static final int ROUTE_TO_URL_FILTER_ORDER = 10000;
private static final String SCHEME_REGEX = "[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*";
static final Pattern schemePattern = Pattern.compile(SCHEME_REGEX);
/* for testing */
static boolean hasAnotherScheme(URI uri) {
return schemePattern.matcher(uri.getSchemeSpecificPart()).matches()
&& uri.getHost() == null && uri.getRawPath() == null;
}
@Override
public int getOrder() {
return ROUTE_TO_URL_FILTER_ORDER;
@@ -64,29 +73,25 @@ public class RouteToRequestUrlFilter implements GlobalFilter, Ordered {
if (hasAnotherScheme(routeUri)) {
// this is a special url, save scheme to special attribute
// replace routeUri with schemeSpecificPart
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR,
routeUri.getScheme());
routeUri = URI.create(routeUri.getSchemeSpecificPart());
}
if("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {
//Load balanced URIs should always have a host. If the host is null it is most
//likely because the host name was invalid (for example included an underscore)
if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {
// Load balanced URIs should always have a host. If the host is null it is
// most
// likely because the host name was invalid (for example included an
// underscore)
throw new IllegalStateException("Invalid host: " + routeUri.toString());
}
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
// .uri(routeUri)
.scheme(routeUri.getScheme())
.host(routeUri.getHost())
.port(routeUri.getPort())
.build(encoded)
.toUri();
.scheme(routeUri.getScheme()).host(routeUri.getHost())
.port(routeUri.getPort()).build(encoded).toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
return chain.filter(exchange);
}
/* for testing */ static boolean hasAnotherScheme(URI uri) {
return schemePattern.matcher(uri.getSchemeSpecificPart()).matches() && uri.getHost() == null
&& uri.getRawPath() == null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,13 +12,14 @@
* 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 reactor.core.publisher.Mono;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -35,8 +36,6 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setAlreadyRouted;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
@@ -58,7 +57,8 @@ public class WebClientHttpRoutingFilter implements GlobalFilter, Ordered {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || (!"http".equals(scheme) && !"https".equals(scheme))) {
if (isAlreadyRouted(exchange)
|| (!"http".equals(scheme) && !"https".equals(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange);
@@ -67,18 +67,18 @@ public class WebClientHttpRoutingFilter implements GlobalFilter, Ordered {
HttpMethod method = request.getMethod();
RequestBodySpec bodySpec = this.webClient.method(method)
.uri(requestUrl)
RequestBodySpec bodySpec = this.webClient.method(method).uri(requestUrl)
.headers(httpHeaders -> {
httpHeaders.addAll(request.getHeaders());
//TODO: can this support preserviceHostHeader?
// TODO: can this support preserviceHostHeader?
httpHeaders.remove(HttpHeaders.HOST);
});
RequestHeadersSpec<?> headersSpec;
if (requiresBody(method)) {
headersSpec = bodySpec.body(BodyInserters.fromDataBuffers(request.getBody()));
} else {
}
else {
headersSpec = bodySpec;
}
@@ -89,7 +89,8 @@ public class WebClientHttpRoutingFilter implements GlobalFilter, Ordered {
response.getHeaders().putAll(res.headers().asHttpHeaders());
response.setStatusCode(res.statusCode());
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write response later NettyWriteResponseFilter
// Put client response as ServerWebExchange attribute and write
// response later NettyWriteResponseFilter
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
return chain.filter(exchange);
});
@@ -97,12 +98,13 @@ public class WebClientHttpRoutingFilter implements GlobalFilter, Ordered {
private boolean requiresBody(HttpMethod method) {
switch (method) {
case PUT:
case POST:
case PATCH:
return true;
default:
return false;
case PUT:
case POST:
case PATCH:
return true;
default:
return false;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,13 +12,14 @@
* 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.BodyExtractors;
@@ -27,17 +28,18 @@ import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
public class WebClientWriteResponseFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(WebClientWriteResponseFilter.class);
/**
* Order of Write Response Filter.
*/
public static final int WRITE_RESPONSE_FILTER_ORDER = -1;
private static final Log log = LogFactory.getLog(WebClientWriteResponseFilter.class);
@Override
public int getOrder() {
return WRITE_RESPONSE_FILTER_ORDER;
@@ -55,7 +57,8 @@ public class WebClientWriteResponseFilter implements GlobalFilter, Ordered {
log.trace("WebClientWriteResponseFilter start");
ServerHttpResponse response = exchange.getResponse();
return response.writeWith(clientResponse.body(BodyExtractors.toDataBuffers())).log("webClient response");
return response.writeWith(clientResponse.body(BodyExtractors.toDataBuffers()))
.log("webClient response");
}));
}

View File

@@ -1,3 +1,19 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter;
import java.net.URI;
@@ -33,23 +49,37 @@ import static org.springframework.util.StringUtils.commaDelimitedListToStringArr
* @author Spencer Gibb
*/
public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(WebsocketRoutingFilter.class);
/**
* Sec-Websocket protocol.
*/
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
private static final Log log = LogFactory.getLog(WebsocketRoutingFilter.class);
private final WebSocketClient webSocketClient;
private final WebSocketService webSocketService;
private final ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider;
//do not use this headersFilters directly, use getHeadersFilters() instead.
// do not use this headersFilters directly, use getHeadersFilters() instead.
private volatile List<HttpHeadersFilter> headersFilters;
public WebsocketRoutingFilter(WebSocketClient webSocketClient,
WebSocketService webSocketService,
ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider) {
WebSocketService webSocketService,
ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider) {
this.webSocketClient = webSocketClient;
this.webSocketService = webSocketService;
this.headersFiltersProvider = headersFiltersProvider;
}
/* for testing */
static String convertHttpToWs(String scheme) {
scheme = scheme.toLowerCase();
return "http".equals(scheme) ? "ws" : "https".equals(scheme) ? "wss" : scheme;
}
@Override
public int getOrder() {
// Before NettyRoutingFilter since this routes certain http requests
@@ -63,37 +93,38 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) {
if (isAlreadyRouted(exchange)
|| (!"ws".equals(scheme) && !"wss".equals(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange);
HttpHeaders headers = exchange.getRequest().getHeaders();
HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);
List<String> protocols = headers.get(SEC_WEBSOCKET_PROTOCOL);
if (protocols != null) {
protocols = headers.get(SEC_WEBSOCKET_PROTOCOL).stream()
.flatMap(header -> Arrays.stream(commaDelimitedListToStringArray(header)))
.map(String::trim)
.collect(Collectors.toList());
protocols = headers.get(SEC_WEBSOCKET_PROTOCOL).stream().flatMap(
header -> Arrays.stream(commaDelimitedListToStringArray(header)))
.map(String::trim).collect(Collectors.toList());
}
return this.webSocketService.handleRequest(exchange,
new ProxyWebSocketHandler(requestUrl, this.webSocketClient,
filtered, protocols));
return this.webSocketService.handleRequest(exchange, new ProxyWebSocketHandler(
requestUrl, this.webSocketClient, filtered, protocols));
}
private List<HttpHeadersFilter> getHeadersFilters() {
if (this.headersFilters == null) {
this.headersFilters = this.headersFiltersProvider.getIfAvailable(ArrayList::new);
this.headersFilters = this.headersFiltersProvider
.getIfAvailable(ArrayList::new);
headersFilters.add((headers, exchange) -> {
HttpHeaders filtered = new HttpHeaders();
headers.entrySet().stream()
.filter(entry -> !entry.getKey().toLowerCase().startsWith("sec-websocket"))
.forEach(header -> filtered.addAll(header.getKey(), header.getValue()));
.filter(entry -> !entry.getKey().toLowerCase()
.startsWith("sec-websocket"))
.forEach(header -> filtered.addAll(header.getKey(),
header.getValue()));
return filtered;
});
}
@@ -107,9 +138,11 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
String scheme = requestUrl.getScheme().toLowerCase();
String upgrade = exchange.getRequest().getHeaders().getUpgrade();
// change the scheme if the socket client send a "http" or "https"
if ("WebSocket".equalsIgnoreCase(upgrade) && ("http".equals(scheme) || "https".equals(scheme))) {
if ("WebSocket".equalsIgnoreCase(upgrade)
&& ("http".equals(scheme) || "https".equals(scheme))) {
String wsScheme = convertHttpToWs(scheme);
URI wsRequestUrl = UriComponentsBuilder.fromUri(requestUrl).scheme(wsScheme).build().toUri();
URI wsRequestUrl = UriComponentsBuilder.fromUri(requestUrl).scheme(wsScheme)
.build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, wsRequestUrl);
if (log.isTraceEnabled()) {
log.trace("changeSchemeTo:[" + wsRequestUrl + "]");
@@ -117,25 +150,25 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
}
}
/* for testing */ static String convertHttpToWs(String scheme) {
scheme = scheme.toLowerCase();
return "http".equals(scheme) ? "ws" : "https".equals(scheme) ? "wss" : scheme;
}
private static class ProxyWebSocketHandler implements WebSocketHandler {
private final WebSocketClient client;
private final URI url;
private final HttpHeaders headers;
private final List<String> subProtocols;
public ProxyWebSocketHandler(URI url, WebSocketClient client, HttpHeaders headers, List<String> protocols) {
ProxyWebSocketHandler(URI url, WebSocketClient client, HttpHeaders headers,
List<String> protocols) {
this.client = client;
this.url = url;
this.headers = headers;
if (protocols != null) {
this.subProtocols = protocols;
} else {
}
else {
this.subProtocols = Collections.emptyList();
}
}
@@ -154,10 +187,10 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
// Use retain() for Reactor Netty
Mono<Void> proxySessionSend = proxySession
.send(session.receive().doOnNext(WebSocketMessage::retain));
// .log("proxySessionSend", Level.FINE);
Mono<Void> serverSessionSend = session
.send(proxySession.receive().doOnNext(WebSocketMessage::retain));
// .log("sessionSend", Level.FINE);
// .log("proxySessionSend", Level.FINE);
Mono<Void> serverSessionSend = session.send(
proxySession.receive().doOnNext(WebSocketMessage::retain));
// .log("sessionSend", Level.FINE);
return Mono.zip(proxySessionSend, serverSessionSend).then();
}
@@ -171,5 +204,7 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
}
});
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -50,15 +49,22 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.W
/**
* @author Spencer Gibb
*/
public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartApplicationListener {
public class WeightCalculatorWebFilter
implements WebFilter, Ordered, SmartApplicationListener {
/**
* Order of Weight Calculator Web filter.
*/
public static final int WEIGHT_CALC_FILTER_ORDER = 10001;
private static final Log log = LogFactory.getLog(WeightCalculatorWebFilter.class);
public static final int WEIGHT_CALC_FILTER_ORDER = 10001;
private final Validator validator;
private final ObjectProvider<RouteLocator> routeLocator;
private Random random = new Random();
private int order = WEIGHT_CALC_FILTER_ORDER;
private Map<String, GroupWeightConfig> groupWeights = new ConcurrentHashMap<>();
@@ -72,11 +78,23 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
this(validator, null);
}
public WeightCalculatorWebFilter(Validator validator, ObjectProvider<RouteLocator> routeLocator) {
public WeightCalculatorWebFilter(Validator validator,
ObjectProvider<RouteLocator> routeLocator) {
this.validator = validator;
this.routeLocator = routeLocator;
}
/* for testing */
static Map<String, String> getWeights(ServerWebExchange exchange) {
Map<String, String> weights = exchange.getAttribute(WEIGHT_ATTR);
if (weights == null) {
weights = new ConcurrentHashMap<>();
exchange.getAttributes().put(WEIGHT_ATTR, weights);
}
return weights;
}
@Override
public int getOrder() {
return order;
@@ -93,8 +111,9 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return PredicateArgsEvent.class.isAssignableFrom(eventType) || // config file
WeightDefinedEvent.class.isAssignableFrom(eventType) || // java dsl
RefreshRoutesEvent.class.isAssignableFrom(eventType); // force initialization
WeightDefinedEvent.class.isAssignableFrom(eventType) || // java dsl
RefreshRoutesEvent.class.isAssignableFrom(eventType); // force
// initialization
}
@Override
@@ -106,10 +125,13 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof PredicateArgsEvent) {
handle((PredicateArgsEvent) event);
} else if (event instanceof WeightDefinedEvent) {
addWeightConfig(((WeightDefinedEvent)event).getWeightConfig());
} else if (event instanceof RefreshRoutesEvent && routeLocator != null) {
routeLocator.ifAvailable(locator -> locator.getRoutes().subscribe()); // forces initialization
}
else if (event instanceof WeightDefinedEvent) {
addWeightConfig(((WeightDefinedEvent) event).getWeightConfig());
}
else if (event instanceof RefreshRoutesEvent && routeLocator != null) {
routeLocator.ifAvailable(locator -> locator.getRoutes().subscribe()); // forces
// initialization
}
}
@@ -123,8 +145,8 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
WeightConfig config = new WeightConfig(event.getRouteId());
ConfigurationUtils.bind(config, args,
WeightConfig.CONFIG_PREFIX, WeightConfig.CONFIG_PREFIX, validator);
ConfigurationUtils.bind(config, args, WeightConfig.CONFIG_PREFIX,
WeightConfig.CONFIG_PREFIX, validator);
addWeightConfig(config);
}
@@ -144,10 +166,11 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
GroupWeightConfig config = c;
config.weights.put(weightConfig.getRouteId(), weightConfig.getWeight());
//recalculate
// recalculate
// normalize weights
int weightsSum = config.weights.values().stream().mapToInt(Integer::intValue).sum();
int weightsSum = config.weights.values().stream().mapToInt(Integer::intValue)
.sum();
final AtomicInteger index = new AtomicInteger(0);
config.weights.forEach((routeId, weight) -> {
@@ -158,7 +181,7 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
config.rangeIndexes.put(index.getAndIncrement(), routeId);
});
//TODO: calculate ranges
// TODO: calculate ranges
config.ranges.clear();
config.ranges.add(0.0);
@@ -172,7 +195,7 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
}
if (log.isTraceEnabled()) {
log.trace("Recalculated group weight config "+ config);
log.trace("Recalculated group weight config " + config);
}
}
@@ -199,11 +222,12 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
List<Double> ranges = config.ranges;
if (log.isTraceEnabled()) {
log.trace("Weight for group: "+group +", ranges: "+ranges +", r: "+r);
log.trace("Weight for group: " + group + ", ranges: " + ranges + ", r: "
+ r);
}
for (int i = 0; i < ranges.size() - 1; i++) {
if (r >= ranges.get(i) && r < ranges.get(i+1)) {
if (r >= ranges.get(i) && r < ranges.get(i + 1)) {
String routeId = config.rangeIndexes.get(i);
weights.put(group, routeId);
break;
@@ -212,23 +236,14 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
}
if (log.isTraceEnabled()) {
log.trace("Weights attr: "+weights);
log.trace("Weights attr: " + weights);
}
return chain.filter(exchange);
}
/* for testing */ static Map<String, String> getWeights(ServerWebExchange exchange) {
Map<String, String> weights = exchange.getAttribute(WEIGHT_ATTR);
if (weights == null) {
weights = new ConcurrentHashMap<>();
exchange.getAttributes().put(WEIGHT_ATTR, weights);
}
return weights;
}
/* for testing */ static class GroupWeightConfig {
String group;
LinkedHashMap<String, Integer> weights = new LinkedHashMap<>();
@@ -236,6 +251,7 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
LinkedHashMap<String, Double> normalizedWeights = new LinkedHashMap<>();
LinkedHashMap<Integer, String> rangeIndexes = new LinkedHashMap<>();
List<Double> ranges = new ArrayList<>();
GroupWeightConfig(String group) {
@@ -244,13 +260,12 @@ public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartAppli
@Override
public String toString() {
return new ToStringCreator(this)
.append("group", group)
return new ToStringCreator(this).append("group", group)
.append("weights", weights)
.append("normalizedWeights", normalizedWeights)
.append("rangeIndexes", rangeIndexes)
.toString();
.append("rangeIndexes", rangeIndexes).toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -36,6 +35,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
*/
public abstract class AbstractChangeRequestUriGatewayFilterFactory<T>
extends AbstractGatewayFilterFactory<T> {
private final int order;
public AbstractChangeRequestUriGatewayFilterFactory(Class<T> clazz, int order) {
@@ -60,4 +60,5 @@ public abstract class AbstractChangeRequestUriGatewayFilterFactory<T>
return chain.filter(exchange);
}, this.order);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -21,10 +20,11 @@ import org.springframework.cloud.gateway.support.AbstractConfigurable;
/**
* This class is BETA and may be subject to change in a future release.
* @param <C>
*
* @param <C> {@link AbstractConfigurable} subtype
*/
public abstract class AbstractGatewayFilterFactory<C>
extends AbstractConfigurable<C> implements GatewayFilterFactory<C> {
public abstract class AbstractGatewayFilterFactory<C> extends AbstractConfigurable<C>
implements GatewayFilterFactory<C> {
@SuppressWarnings("unchecked")
public AbstractGatewayFilterFactory() {
@@ -36,6 +36,7 @@ public abstract class AbstractGatewayFilterFactory<C>
}
public static class NameConfig {
private String name;
public String getName() {
@@ -45,5 +46,7 @@ public abstract class AbstractGatewayFilterFactory<C>
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -26,21 +25,23 @@ import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.core.style.ToStringCreator;
import org.springframework.validation.annotation.Validated;
public abstract class AbstractNameValueGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractNameValueGatewayFilterFactory.NameValueConfig> {
public abstract class AbstractNameValueGatewayFilterFactory extends
AbstractGatewayFilterFactory<AbstractNameValueGatewayFilterFactory.NameValueConfig> {
public AbstractNameValueGatewayFilterFactory() {
super(NameValueConfig.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList(GatewayFilter.NAME_KEY, GatewayFilter.VALUE_KEY);
}
return Arrays.asList(GatewayFilter.NAME_KEY, GatewayFilter.VALUE_KEY);
}
@Validated
public static class NameValueConfig {
@NotEmpty
protected String name;
@NotEmpty
protected String value;
@@ -64,10 +65,10 @@ public abstract class AbstractNameValueGatewayFilterFactory extends AbstractGate
@Override
public String toString() {
return new ToStringCreator(this)
.append("name", name)
.append("value", value)
return new ToStringCreator(this).append("name", name).append("value", value)
.toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -23,17 +22,17 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* @author Spencer Gibb
*/
public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public class AddRequestHeaderGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest().mutate()
.header(config.getName(), config.getValue())
.build();
.header(config.getName(), config.getValue()).build();
return chain.filter(exchange.mutate().request(request).build());
};
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -27,7 +26,8 @@ import org.springframework.web.util.UriComponentsBuilder;
/**
* @author Spencer Gibb
*/
public class AddRequestParameterGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public class AddRequestParameterGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
@@ -43,22 +43,23 @@ public class AddRequestParameterGatewayFilterFactory extends AbstractNameValueGa
}
}
//TODO urlencode?
// TODO urlencode?
query.append(config.getName());
query.append('=');
query.append(config.getValue());
try {
URI newUri = UriComponentsBuilder.fromUri(uri)
.replaceQuery(query.toString())
.build(true)
.toUri();
.replaceQuery(query.toString()).build(true).toUri();
ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri)
.build();
return chain.filter(exchange.mutate().request(request).build());
} catch (RuntimeException ex) {
throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\"");
}
catch (RuntimeException ex) {
throw new IllegalStateException(
"Invalid URI query: \"" + query.toString() + "\"");
}
};
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -22,7 +21,8 @@ import org.springframework.cloud.gateway.filter.GatewayFilter;
/**
* @author Spencer Gibb
*/
public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public class AddResponseHeaderGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
@@ -32,4 +32,5 @@ public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGate
return chain.filter(exchange);
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -31,7 +30,8 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.H
/**
* @author Olga Maciaszek-Sharma
*/
public class FallbackHeadersGatewayFilterFactory extends AbstractGatewayFilterFactory<FallbackHeadersGatewayFilterFactory.Config> {
public class FallbackHeadersGatewayFilterFactory
extends AbstractGatewayFilterFactory<FallbackHeadersGatewayFilterFactory.Config> {
public FallbackHeadersGatewayFilterFactory() {
super(Config.class);
@@ -45,18 +45,29 @@ public class FallbackHeadersGatewayFilterFactory extends AbstractGatewayFilterFa
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerWebExchange filteredExchange = ofNullable((Throwable) exchange
.getAttribute(HYSTRIX_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);
ServerWebExchange filteredExchange = ofNullable(
(Throwable) exchange.getAttribute(HYSTRIX_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);
return chain.filter(filteredExchange);
};
}
@@ -64,20 +75,27 @@ public class FallbackHeadersGatewayFilterFactory extends AbstractGatewayFilterFa
public static class Config {
private static final String EXECUTION_EXCEPTION_TYPE = "Execution-Exception-Type";
private static final String EXECUTION_EXCEPTION_MESSAGE = "Execution-Exception-Message";
private static final String ROOT_CAUSE_EXCEPTION_TYPE = "Root-Cause-Exception-Type";
private static final String ROOT_CAUSE_EXCEPTION_MESSAGE = "Root-Cause-Exception-Message";
private String executionExceptionTypeHeaderName = EXECUTION_EXCEPTION_TYPE;
private String executionExceptionMessageHeaderName = EXECUTION_EXCEPTION_MESSAGE;
private String rootCauseExceptionTypeHeaderName = ROOT_CAUSE_EXCEPTION_TYPE;
private String rootCauseExceptionMessageHeaderName = ROOT_CAUSE_EXCEPTION_MESSAGE;
public String getExecutionExceptionTypeHeaderName() {
return executionExceptionTypeHeaderName;
}
public void setExecutionExceptionTypeHeaderName(String executionExceptionTypeHeaderName) {
public void setExecutionExceptionTypeHeaderName(
String executionExceptionTypeHeaderName) {
this.executionExceptionTypeHeaderName = executionExceptionTypeHeaderName;
}
@@ -85,7 +103,8 @@ public class FallbackHeadersGatewayFilterFactory extends AbstractGatewayFilterFa
return executionExceptionMessageHeaderName;
}
public void setExecutionExceptionMessageHeaderName(String executionExceptionMessageHeaderName) {
public void setExecutionExceptionMessageHeaderName(
String executionExceptionMessageHeaderName) {
this.executionExceptionMessageHeaderName = executionExceptionMessageHeaderName;
}
@@ -93,7 +112,8 @@ public class FallbackHeadersGatewayFilterFactory extends AbstractGatewayFilterFa
return rootCauseExceptionTypeHeaderName;
}
public void setRootCauseExceptionTypeHeaderName(String rootCauseExceptionTypeHeaderName) {
public void setRootCauseExceptionTypeHeaderName(
String rootCauseExceptionTypeHeaderName) {
this.rootCauseExceptionTypeHeaderName = rootCauseExceptionTypeHeaderName;
}
@@ -101,8 +121,11 @@ public class FallbackHeadersGatewayFilterFactory extends AbstractGatewayFilterFa
return rootCauseExceptionMessageHeaderName;
}
public void setCauseExceptionMessageHeaderName(String causeExceptionMessageHeaderName) {
public void setCauseExceptionMessageHeaderName(
String causeExceptionMessageHeaderName) {
this.rootCauseExceptionMessageHeaderName = causeExceptionMessageHeaderName;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -31,7 +30,14 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
@FunctionalInterface
public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configurable<C> {
/**
* Name key.
*/
String NAME_KEY = "name";
/**
* Value key.
*/
String VALUE_KEY = "value";
// useful for javadsl
@@ -53,7 +59,7 @@ public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configura
GatewayFilter apply(C config);
default String name() {
//TODO: deal with proxys
// TODO: deal with proxys
return NameUtils.normalizeFilterFactoryName(getClass());
}
@@ -61,4 +67,5 @@ public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configura
default ServerHttpRequest.Builder mutate(ServerHttpRequest request) {
return request.mutate();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -53,18 +52,23 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.H
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
/**
* Depends on `spring-cloud-starter-netflix-hystrix`, {@see http://cloud.spring.io/spring-cloud-netflix/}
* Depends on `spring-cloud-starter-netflix-hystrix`,
* {@see http://cloud.spring.io/spring-cloud-netflix/}.
*
* @author Spencer Gibb
* @author Michele Mancioppi
* @author Olga Maciaszek-Sharma
*/
public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<HystrixGatewayFilterFactory.Config> {
public class HystrixGatewayFilterFactory
extends AbstractGatewayFilterFactory<HystrixGatewayFilterFactory.Config> {
private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;
//do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
// do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
private volatile DispatcherHandler dispatcherHandler;
public HystrixGatewayFilterFactory(ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
public HystrixGatewayFilterFactory(
ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
super(Config.class);
this.dispatcherHandlerProvider = dispatcherHandlerProvider;
}
@@ -95,21 +99,25 @@ public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<Hy
@Override
public GatewayFilter apply(Config config) {
//TODO: if no name is supplied, generate one from command id (useful for default filter)
// 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());
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);
config.setter = Setter.withGroupKey(groupKey).andCommandKey(commandKey);
}
return (exchange, chain) -> {
RouteHystrixCommand command = new RouteHystrixCommand(config.setter, config.fallbackUri, exchange, chain);
RouteHystrixCommand command = new RouteHystrixCommand(config.setter,
config.fallbackUri, exchange, chain);
return Mono.create(s -> {
Subscription sub = command.toObservable().subscribe(s::success, s::error, s::success);
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) {
@@ -117,21 +125,24 @@ public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<Hy
HystrixRuntimeException.FailureType failureType = e.getFailureType();
switch (failureType) {
case TIMEOUT:
return Mono.error(new TimeoutException());
case COMMAND_EXCEPTION: {
Throwable cause = e.getCause();
case TIMEOUT:
return Mono.error(new TimeoutException());
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);
}
/*
* 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;
}
default:
break;
}
}
return Mono.error(throwable);
@@ -139,59 +150,12 @@ public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<Hy
};
}
//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;
RouteHystrixCommand(Setter setter, URI fallbackUri, ServerWebExchange exchange, GatewayFilterChain chain) {
super(setter);
this.fallbackUri = fallbackUri;
this.exchange = exchange;
this.chain = chain;
}
@Override
protected Observable<Void> construct() {
return RxReactiveStreams.toObservable(this.chain.filter(exchange));
}
@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)
.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();
return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));
}
private void addExceptionDetails() {
Throwable executionException = getExecutionException();
ofNullable(executionException)
.ifPresent(exception -> exchange.getAttributes().put(HYSTRIX_EXECUTION_EXCEPTION_ATTR, exception));
}
}
public static class Config {
private String name;
private Setter setter;
private URI fallbackUri;
public String getName() {
@@ -216,7 +180,9 @@ public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<Hy
public void setFallbackUri(URI fallbackUri) {
if (fallbackUri != null && !"forward".equals(fallbackUri.getScheme())) {
throw new IllegalArgumentException("Hystrix Filter currently only supports 'forward' URIs, found " + fallbackUri);
throw new IllegalArgumentException(
"Hystrix Filter currently only supports 'forward' URIs, found "
+ fallbackUri);
}
this.fallbackUri = fallbackUri;
}
@@ -225,5 +191,58 @@ public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory<Hy
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;
RouteHystrixCommand(Setter setter, URI fallbackUri, ServerWebExchange exchange,
GatewayFilterChain chain) {
super(setter);
this.fallbackUri = fallbackUri;
this.exchange = exchange;
this.chain = chain;
}
@Override
protected Observable<Void> construct() {
return RxReactiveStreams.toObservable(this.chain.filter(exchange));
}
@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).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();
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,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -22,6 +21,7 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -32,12 +32,17 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a
/**
* @author Spencer Gibb
*/
public class PrefixPathGatewayFilterFactory extends AbstractGatewayFilterFactory<PrefixPathGatewayFilterFactory.Config> {
private static final Log log = LogFactory.getLog(PrefixPathGatewayFilterFactory.class);
public class PrefixPathGatewayFilterFactory
extends AbstractGatewayFilterFactory<PrefixPathGatewayFilterFactory.Config> {
/**
* Prefix key.
*/
public static final String PREFIX_KEY = "prefix";
private static final Log log = LogFactory
.getLog(PrefixPathGatewayFilterFactory.class);
public PrefixPathGatewayFilterFactory() {
super(Config.class);
}
@@ -51,7 +56,8 @@ public class PrefixPathGatewayFilterFactory extends AbstractGatewayFilterFactory
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
boolean alreadyPrefixed = exchange.getAttributeOrDefault(GATEWAY_ALREADY_PREFIXED_ATTR, false);
boolean alreadyPrefixed = exchange
.getAttributeOrDefault(GATEWAY_ALREADY_PREFIXED_ATTR, false);
if (alreadyPrefixed) {
return chain.filter(exchange);
}
@@ -61,14 +67,13 @@ public class PrefixPathGatewayFilterFactory extends AbstractGatewayFilterFactory
addOriginalRequestUrl(exchange, req.getURI());
String newPath = config.prefix + req.getURI().getRawPath();
ServerHttpRequest request = req.mutate()
.path(newPath)
.build();
ServerHttpRequest request = req.mutate().path(newPath).build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
if (log.isTraceEnabled()) {
log.trace("Prefixed URI with: "+config.prefix+" -> "+request.getURI());
log.trace("Prefixed URI with: " + config.prefix + " -> "
+ request.getURI());
}
return chain.filter(exchange.mutate().request(request).build());
@@ -76,6 +81,7 @@ public class PrefixPathGatewayFilterFactory extends AbstractGatewayFilterFactory
}
public static class Config {
private String prefix;
public String getPrefix() {
@@ -85,5 +91,7 @@ public class PrefixPathGatewayFilterFactory extends AbstractGatewayFilterFactory
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -27,13 +26,15 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.P
public class PreserveHostHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory {
public GatewayFilter apply() {
return apply(o -> {});
return apply(o -> {
});
}
public GatewayFilter apply(Object config) {
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
exchange.getAttributes().put(PRESERVE_HOST_HEADER_ATTRIBUTE, true);
return chain.filter(exchange);
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -21,6 +20,8 @@ import java.net.URI;
import java.util.Arrays;
import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.http.HttpHeaders;
@@ -30,14 +31,20 @@ import org.springframework.util.Assert;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
public class RedirectToGatewayFilterFactory extends AbstractGatewayFilterFactory<RedirectToGatewayFilterFactory.Config> {
public class RedirectToGatewayFilterFactory
extends AbstractGatewayFilterFactory<RedirectToGatewayFilterFactory.Config> {
/**
* Status key.
*/
public static final String STATUS_KEY = "status";
/**
* URL key.
*/
public static final String URL_KEY = "url";
public RedirectToGatewayFilterFactory() {
@@ -56,7 +63,8 @@ public class RedirectToGatewayFilterFactory extends AbstractGatewayFilterFactory
public GatewayFilter apply(String statusString, String urlString) {
HttpStatusHolder httpStatus = HttpStatusHolder.parse(statusString);
Assert.isTrue(httpStatus.is3xxRedirection(), "status must be a 3xx code, but was " + statusString);
Assert.isTrue(httpStatus.is3xxRedirection(),
"status must be a 3xx code, but was " + statusString);
final URI url = URI.create(urlString);
return apply(httpStatus, url);
}
@@ -66,21 +74,22 @@ public class RedirectToGatewayFilterFactory extends AbstractGatewayFilterFactory
}
public GatewayFilter apply(HttpStatusHolder httpStatus, URI uri) {
return (exchange, chain) ->
chain.filter(exchange).then(Mono.defer(() -> {
if (!exchange.getResponse().isCommitted()) {
setResponseStatus(exchange, httpStatus);
return (exchange, chain) -> chain.filter(exchange).then(Mono.defer(() -> {
if (!exchange.getResponse().isCommitted()) {
setResponseStatus(exchange, httpStatus);
final ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set(HttpHeaders.LOCATION, uri.toString());
return response.setComplete();
}
return Mono.empty();
}));
final ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set(HttpHeaders.LOCATION, uri.toString());
return response.setComplete();
}
return Mono.empty();
}));
}
public static class Config {
String status;
String url;
public String getStatus() {
@@ -98,6 +107,7 @@ public class RedirectToGatewayFilterFactory extends AbstractGatewayFilterFactory
public void setUrl(String url) {
this.url = url;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -26,7 +25,8 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* @author Spencer Gibb
*/
public class RemoveRequestHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
public class RemoveRequestHeaderGatewayFilterFactory
extends AbstractGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
public RemoveRequestHeaderGatewayFilterFactory() {
super(NameConfig.class);
@@ -41,10 +41,10 @@ public class RemoveRequestHeaderGatewayFilterFactory extends AbstractGatewayFilt
public GatewayFilter apply(NameConfig config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> httpHeaders.remove(config.getName()))
.build();
.headers(httpHeaders -> httpHeaders.remove(config.getName())).build();
return chain.filter(exchange.mutate().request(request).build());
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -20,14 +19,15 @@ package org.springframework.cloud.gateway.filter.factory;
import java.util.Arrays;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
/**
* @author Spencer Gibb
*/
public class RemoveResponseHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
public class RemoveResponseHeaderGatewayFilterFactory
extends AbstractGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
public RemoveResponseHeaderGatewayFilterFactory() {
super(NameConfig.class);
@@ -44,4 +44,5 @@ public class RemoveResponseHeaderGatewayFilterFactory extends AbstractGatewayFil
exchange.getResponse().getHeaders().remove(config.getName());
}));
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2018-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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.factory;
import java.net.MalformedURLException;
@@ -10,15 +26,17 @@ import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.server.ServerWebExchange;
/**
* This filter changes the request uri by a request header
* This filter changes the request uri by a request header.
*
* @author Toshiaki Maki
*/
public class RequestHeaderToRequestUriGatewayFilterFactory extends
AbstractChangeRequestUriGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
private final Logger log = LoggerFactory
.getLogger(RequestHeaderToRequestUriGatewayFilterFactory.class);
@@ -46,4 +64,5 @@ public class RequestHeaderToRequestUriGatewayFilterFactory extends
}
});
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -34,22 +33,30 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.s
* User Request Rate Limiter filter. See https://stripe.com/blog/rate-limiters and
*/
@ConfigurationProperties("spring.cloud.gateway.filter.request-rate-limiter")
public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestRateLimiterGatewayFilterFactory.Config> {
public class RequestRateLimiterGatewayFilterFactory extends
AbstractGatewayFilterFactory<RequestRateLimiterGatewayFilterFactory.Config> {
/**
* Key-Resolver key.
*/
public static final String KEY_RESOLVER_KEY = "keyResolver";
private static final String EMPTY_KEY = "____EMPTY_KEY__";
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
/** Switch to deny requests if the Key Resolver returns an empty key, defaults to true. */
/**
* Switch to deny requests if the Key Resolver returns an empty key, defaults to true.
*/
private boolean denyEmptyKey = true;
/** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */
private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();
public RequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter,
KeyResolver defaultKeyResolver) {
KeyResolver defaultKeyResolver) {
super(Config.class);
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
@@ -83,12 +90,15 @@ public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilte
@Override
public GatewayFilter apply(Config config) {
KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
RateLimiter<Object> limiter = getOrDefault(config.rateLimiter, defaultRateLimiter);
RateLimiter<Object> limiter = getOrDefault(config.rateLimiter,
defaultRateLimiter);
boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
HttpStatusHolder emptyKeyStatus = HttpStatusHolder.parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));
HttpStatusHolder emptyKeyStatus = HttpStatusHolder
.parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));
return (exchange, chain) -> {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
Route route = exchange
.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
return resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key -> {
if (EMPTY_KEY.equals(key)) {
@@ -100,8 +110,10 @@ public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilte
}
return limiter.isAllowed(route.getId(), key).flatMap(response -> {
for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
for (Map.Entry<String, String> header : response.getHeaders()
.entrySet()) {
exchange.getResponse().getHeaders().add(header.getKey(),
header.getValue());
}
if (response.isAllowed()) {
@@ -120,10 +132,15 @@ public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilte
}
public static class Config {
private KeyResolver keyResolver;
private RateLimiter rateLimiter;
private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;
private Boolean denyEmptyKey;
private String emptyKeyStatus;
public KeyResolver getKeyResolver() {
@@ -134,6 +151,7 @@ public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilte
this.keyResolver = keyResolver;
return this;
}
public RateLimiter getRateLimiter() {
return rateLimiter;
}
@@ -169,6 +187,7 @@ public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilte
this.emptyKeyStatus = emptyKeyStatus;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -24,13 +23,16 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* This filter blocks the request, if the request size is more than the permissible size.The default request size is 5 MB.
* This filter blocks the request, if the request size is more than the permissible size.
* The default request size is 5 MB.
*
* @author Arpan
*/
public class RequestSizeGatewayFilterFactory
extends AbstractGatewayFilterFactory<RequestSizeGatewayFilterFactory.RequestSizeConfig> {
public class RequestSizeGatewayFilterFactory extends
AbstractGatewayFilterFactory<RequestSizeGatewayFilterFactory.RequestSizeConfig> {
private static String PREFIX = "kMGTPE";
private static String ERROR = "Request size is larger than permissible limit."
+ " Request size is %s where permissible limit is %s";
@@ -38,8 +40,24 @@ public class RequestSizeGatewayFilterFactory
super(RequestSizeGatewayFilterFactory.RequestSizeConfig.class);
}
private static String getErrorMessage(Long currentRequestSize, Long maxSize) {
return String.format(ERROR, getReadableByteCount(currentRequestSize),
getReadableByteCount(maxSize));
}
private static String getReadableByteCount(long bytes) {
int unit = 1000;
if (bytes < unit) {
return bytes + " B";
}
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = Character.toString(PREFIX.charAt(exp - 1));
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
@Override
public GatewayFilter apply(RequestSizeGatewayFilterFactory.RequestSizeConfig requestSizeConfig) {
public GatewayFilter apply(
RequestSizeGatewayFilterFactory.RequestSizeConfig requestSizeConfig) {
requestSizeConfig.validate();
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
@@ -49,7 +67,8 @@ public class RequestSizeGatewayFilterFactory
if (currentRequestSize > requestSizeConfig.getMaxSize()) {
exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE);
exchange.getResponse().getHeaders().add("errorMessage",
getErrorMessage(currentRequestSize, requestSizeConfig.getMaxSize()));
getErrorMessage(currentRequestSize,
requestSizeConfig.getMaxSize()));
return exchange.getResponse().setComplete();
}
}
@@ -61,31 +80,22 @@ public class RequestSizeGatewayFilterFactory
private Long maxSize = 5000000L;
public RequestSizeGatewayFilterFactory.RequestSizeConfig setMaxSize(Long maxSize) {
this.maxSize = maxSize;
return this;
}
public Long getMaxSize() {
return maxSize;
}
public RequestSizeGatewayFilterFactory.RequestSizeConfig setMaxSize(
Long maxSize) {
this.maxSize = maxSize;
return this;
}
public void validate() {
Assert.isTrue(this.maxSize != null && this.maxSize > 0, "maxSize must be greater than 0");
Assert.isTrue(this.maxSize != null && this.maxSize > 0,
"maxSize must be greater than 0");
Assert.isInstanceOf(Long.class, maxSize, "maxSize must be a number");
}
}
private static String getErrorMessage(Long currentRequestSize, Long maxSize) {
return String.format(ERROR, getReadableByteCount(currentRequestSize), getReadableByteCount(maxSize));
}
private static String getReadableByteCount(long bytes) {
int unit = 1000;
if (bytes < unit)
return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = Character.toString(PREFIX.charAt(exp - 1));
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -28,8 +27,6 @@ import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import reactor.retry.Repeat;
import reactor.retry.RepeatContext;
@@ -37,7 +34,7 @@ import reactor.retry.Retry;
import reactor.retry.RetryContext;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
@@ -47,15 +44,24 @@ import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ALREADY_ROUTED_ATTR;
public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<RetryGatewayFilterFactory.RetryConfig> {
public class RetryGatewayFilterFactory
extends AbstractGatewayFilterFactory<RetryGatewayFilterFactory.RetryConfig> {
/**
* Retry iteration key.
*/
public static final String RETRY_ITERATION_KEY = "retry_iteration";
private static final Log log = LogFactory.getLog(RetryGatewayFilterFactory.class);
public RetryGatewayFilterFactory() {
super(RetryConfig.class);
}
private static <T> List<T> toList(T... items) {
return new ArrayList<>(Arrays.asList(items));
}
@Override
public GatewayFilter apply(RetryConfig retryConfig) {
retryConfig.validate();
@@ -70,16 +76,20 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
HttpStatus statusCode = exchange.getResponse().getStatusCode();
boolean retryableStatusCode = retryConfig.getStatuses().contains(statusCode);
boolean retryableStatusCode = retryConfig.getStatuses()
.contains(statusCode);
if (!retryableStatusCode && statusCode != null) { // null status code might mean a network exception?
if (!retryableStatusCode && statusCode != null) { // null status code
// might mean a
// network exception?
// try the series
retryableStatusCode = retryConfig.getSeries().stream()
.anyMatch(series -> statusCode.series().equals(series));
}
trace("retryableStatusCode: %b, statusCode %s, configured statuses %s, configured series %s",
retryableStatusCode, statusCode, retryConfig.getStatuses(), retryConfig.getSeries());
retryableStatusCode, statusCode, retryConfig.getStatuses(),
retryConfig.getSeries());
HttpMethod httpMethod = exchange.getRequest().getMethod();
boolean retryableMethod = retryConfig.getMethods().contains(httpMethod);
@@ -93,7 +103,7 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
.doOnRepeat(context -> reset(context.applicationContext()));
}
//TODO: support timeout, backoff, jitter, etc... in Builder
// TODO: support timeout, backoff, jitter, etc... in Builder
Retry<ServerWebExchange> exceptionRetry = null;
if (!retryConfig.getExceptions().isEmpty()) {
@@ -105,12 +115,14 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
for (Class<? extends Throwable> clazz : retryConfig.getExceptions()) {
if (clazz.isInstance(context.exception())) {
trace("exception is retryable %s, configured exceptions",
context.exception().getClass().getName(), retryConfig.getExceptions());
context.exception().getClass().getName(),
retryConfig.getExceptions());
return true;
}
}
trace("exception is not retryable %s, configured exceptions",
context.exception().getClass().getName(), retryConfig.getExceptions());
context.exception().getClass().getName(),
retryConfig.getExceptions());
return false;
};
exceptionRetry = Retry.onlyIf(retryContextPredicate)
@@ -118,53 +130,59 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
.retryMax(retryConfig.getRetries());
}
return apply(statusCodeRepeat, exceptionRetry);
}
public boolean exceedsMaxIterations(ServerWebExchange exchange, RetryConfig retryConfig) {
public boolean exceedsMaxIterations(ServerWebExchange exchange,
RetryConfig retryConfig) {
Integer iteration = exchange.getAttribute(RETRY_ITERATION_KEY);
//TODO: deal with null iteration
// TODO: deal with null iteration
boolean exceeds = iteration != null && iteration >= retryConfig.getRetries();
trace("exceedsMaxIterations %b, iteration %d, configured retries %d",
exceeds, iteration, retryConfig.getRetries());
trace("exceedsMaxIterations %b, iteration %d, configured retries %d", exceeds,
iteration, retryConfig.getRetries());
return exceeds;
}
public void reset(ServerWebExchange exchange) {
//TODO: what else to do to reset SWE?
Set<String> addedHeaders = exchange.getAttributeOrDefault(CLIENT_RESPONSE_HEADER_NAMES, Collections.emptySet());
addedHeaders.forEach(header -> exchange.getResponse().getHeaders().remove(header));
// TODO: what else to do to reset SWE?
Set<String> addedHeaders = exchange.getAttributeOrDefault(
CLIENT_RESPONSE_HEADER_NAMES, Collections.emptySet());
addedHeaders
.forEach(header -> exchange.getResponse().getHeaders().remove(header));
exchange.getAttributes().remove(GATEWAY_ALREADY_ROUTED_ATTR);
}
public GatewayFilter apply(Repeat<ServerWebExchange> repeat, Retry<ServerWebExchange> retry) {
public GatewayFilter apply(Repeat<ServerWebExchange> repeat,
Retry<ServerWebExchange> retry) {
return (exchange, chain) -> {
trace("Entering retry-filter");
// chain.filter returns a Mono<Void>
Publisher<Void> publisher = chain.filter(exchange)
//.log("retry-filter", Level.INFO)
.doOnSuccessOrError((aVoid, throwable) -> {
int iteration = exchange.getAttributeOrDefault(RETRY_ITERATION_KEY, -1);
Publisher<Void> publisher = chain.filter(exchange)
// .log("retry-filter", Level.INFO)
.doOnSuccessOrError((aVoid, throwable) -> {
int iteration = exchange
.getAttributeOrDefault(RETRY_ITERATION_KEY, -1);
int newIteration = iteration + 1;
trace("setting new iteration in attr %d", newIteration);
exchange.getAttributes().put(RETRY_ITERATION_KEY, newIteration);
});
});
if (retry != null) {
if (retry != null) {
// retryWhen returns a Mono<Void>
// retry needs to go before repeat
publisher = ((Mono<Void>)publisher).retryWhen(retry.withApplicationContext(exchange));
publisher = ((Mono<Void>) publisher)
.retryWhen(retry.withApplicationContext(exchange));
}
if (repeat != null) {
// repeatWhen returns a Flux<Void>
// repeatWhen returns a Flux<Void>
// so this needs to be last and the variable a Publisher<Void>
publisher = ((Mono<Void>)publisher).repeatWhen(repeat.withApplicationContext(exchange));
publisher = ((Mono<Void>) publisher)
.repeatWhen(repeat.withApplicationContext(exchange));
}
return Mono.fromDirect(publisher);
return Mono.fromDirect(publisher);
};
}
@@ -174,54 +192,29 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
}
}
private static <T> List<T> toList(T... items) {
return new ArrayList<>(Arrays.asList(items));
}
@SuppressWarnings("unchecked")
public static class RetryConfig {
private int retries = 3;
private List<Series> series = toList(Series.SERVER_ERROR);
private List<HttpStatus> statuses = new ArrayList<>();
private List<HttpMethod> methods = toList(HttpMethod.GET);
private List<Class<? extends Throwable>> exceptions = toList(IOException.class, TimeoutException.class);
public RetryConfig setRetries(int retries) {
this.retries = retries;
return this;
}
public RetryConfig setSeries(Series... series) {
this.series = Arrays.asList(series);
return this;
}
public RetryConfig setStatuses(HttpStatus... statuses) {
this.statuses = Arrays.asList(statuses);
return this;
}
public RetryConfig setMethods(HttpMethod... methods) {
this.methods = Arrays.asList(methods);
return this;
}
private List<Class<? extends Throwable>> exceptions = toList(IOException.class,
TimeoutException.class);
public RetryConfig allMethods() {
return setMethods(HttpMethod.values());
}
public RetryConfig setExceptions(Class<? extends Throwable>... exceptions) {
this.exceptions = Arrays.asList(exceptions);
return this;
}
public void validate() {
Assert.isTrue(this.retries > 0, "retries must be greater than 0");
Assert.isTrue(!this.series.isEmpty() || !this.statuses.isEmpty() || !this.exceptions.isEmpty(),
Assert.isTrue(
!this.series.isEmpty() || !this.statuses.isEmpty()
|| !this.exceptions.isEmpty(),
"series, status and exceptions may not all be empty");
Assert.notEmpty(this.methods, "methods may not be empty");
}
@@ -230,21 +223,47 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
return retries;
}
public RetryConfig setRetries(int retries) {
this.retries = retries;
return this;
}
public List<Series> getSeries() {
return series;
}
public RetryConfig setSeries(Series... series) {
this.series = Arrays.asList(series);
return this;
}
public List<HttpStatus> getStatuses() {
return statuses;
}
public RetryConfig setStatuses(HttpStatus... statuses) {
this.statuses = Arrays.asList(statuses);
return this;
}
public List<HttpMethod> getMethods() {
return methods;
}
public RetryConfig setMethods(HttpMethod... methods) {
this.methods = Arrays.asList(methods);
return this;
}
public List<Class<? extends Throwable>> getExceptions() {
return exceptions;
}
public RetryConfig setExceptions(Class<? extends Throwable>... exceptions) {
this.exceptions = Arrays.asList(exceptions);
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -29,9 +28,17 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a
/**
* @author Spencer Gibb
*/
public class RewritePathGatewayFilterFactory extends AbstractGatewayFilterFactory<RewritePathGatewayFilterFactory.Config> {
public class RewritePathGatewayFilterFactory
extends AbstractGatewayFilterFactory<RewritePathGatewayFilterFactory.Config> {
/**
* Regexp key.
*/
public static final String REGEXP_KEY = "regexp";
/**
* Replacement key.
*/
public static final String REPLACEMENT_KEY = "replacement";
public RewritePathGatewayFilterFactory() {
@@ -52,9 +59,7 @@ public class RewritePathGatewayFilterFactory extends AbstractGatewayFilterFactor
String path = req.getURI().getRawPath();
String newPath = path.replaceAll(config.regexp, replacement);
ServerHttpRequest request = req.mutate()
.path(newPath)
.build();
ServerHttpRequest request = req.mutate().path(newPath).build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
@@ -63,7 +68,9 @@ public class RewritePathGatewayFilterFactory extends AbstractGatewayFilterFactor
}
public static class Config {
private String regexp;
private String replacement;
public String getRegexp() {
@@ -83,5 +90,7 @@ public class RewritePathGatewayFilterFactory extends AbstractGatewayFilterFactor
this.replacement = replacement;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,24 +12,32 @@
* 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 org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.web.server.ServerWebExchange;
/**
* @author Vitaliy Pavlyuk
*/
public class RewriteResponseHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<RewriteResponseHeaderGatewayFilterFactory.Config> {
public class RewriteResponseHeaderGatewayFilterFactory extends
AbstractGatewayFilterFactory<RewriteResponseHeaderGatewayFilterFactory.Config> {
/**
* Regexp key.
*/
public static final String REGEXP_KEY = "regexp";
/**
* Replacement key.
*/
public static final String REPLACEMENT_KEY = "replacement";
public RewriteResponseHeaderGatewayFilterFactory() {
@@ -54,7 +62,8 @@ public class RewriteResponseHeaderGatewayFilterFactory extends AbstractGatewayFi
if (value == null) {
return;
}
final String newValue = rewrite(value, config.getRegexp(), config.getReplacement());
final String newValue = rewrite(value, config.getRegexp(),
config.getReplacement());
exchange.getResponse().getHeaders().set(name, newValue);
}
@@ -63,7 +72,9 @@ public class RewriteResponseHeaderGatewayFilterFactory extends AbstractGatewayFi
}
public static class Config extends AbstractGatewayFilterFactory.NameConfig {
private String regexp;
private String replacement;
public String getRegexp() {
@@ -83,5 +94,7 @@ public class RewriteResponseHeaderGatewayFilterFactory extends AbstractGatewayFi
this.replacement = replacement;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 the original author or authors.
* Copyright 2018-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.
@@ -13,25 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.factory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.web.server.WebSession;
/**
* Save the current {@link WebSession} before executing the rest of the {@link org.springframework.cloud.gateway.filter.GatewayFilterChain}.
* Save the current {@link WebSession} before executing the rest of the
* {@link org.springframework.cloud.gateway.filter.GatewayFilterChain}.
*
* Filter is very useful for situation where the WebSession is lazy (e.g. Spring Session
* MongoDB) and making a remote call requires that {@link WebSession#save()} be called
* before the remote call is made.
*
* Filter is very useful for situation where the WebSession is lazy (e.g. Spring Session MongoDB) and making a remote call requires
* that {@link WebSession#save()} be called before the remote call is made.
*
* @author Greg Turnquist
*/
public class SaveSessionGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> exchange.getSession()
.map(WebSession::save)
.then(chain.filter(exchange));
return (exchange, chain) -> exchange.getSession().map(WebSession::save)
.then(chain.filter(exchange));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -21,18 +20,50 @@ import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.http.HttpHeaders;
/**
* https://blog.appcanary.com/2017/http-security-headers.html
* https://blog.appcanary.com/2017/http-security-headers.html.
*
* @author Spencer Gibb
*/
public class SecureHeadersGatewayFilterFactory extends AbstractGatewayFilterFactory {
/**
* Xss-Protection header name.
*/
public static final String X_XSS_PROTECTION_HEADER = "X-Xss-Protection";
/**
* Strict transport security header name.
*/
public static final String STRICT_TRANSPORT_SECURITY_HEADER = "Strict-Transport-Security";
/**
* Frame options header name.
*/
public static final String X_FRAME_OPTIONS_HEADER = "X-Frame-Options";
/**
* Content-Type Options header name.
*/
public static final String X_CONTENT_TYPE_OPTIONS_HEADER = "X-Content-Type-Options";
/**
* Referrer Policy header name.
*/
public static final String REFERRER_POLICY_HEADER = "Referrer-Policy";
/**
* Content-Security Policy header name.
*/
public static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";
/**
* Download Options header name.
*/
public static final String X_DOWNLOAD_OPTIONS_HEADER = "X-Download-Options";
/**
* Permitted Cross-Domain Policies header name.
*/
public static final String X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER = "X-Permitted-Cross-Domain-Policies";
private final SecureHeadersProperties properties;
@@ -43,22 +74,27 @@ public class SecureHeadersGatewayFilterFactory extends AbstractGatewayFilterFact
@Override
public GatewayFilter apply(Object config) {
//TODO: allow args to override properties
// TODO: allow args to override properties
return (exchange, chain) -> {
HttpHeaders headers = exchange.getResponse().getHeaders();
//TODO: allow header to be disabled
// TODO: allow header to be disabled
headers.add(X_XSS_PROTECTION_HEADER, properties.getXssProtectionHeader());
headers.add(STRICT_TRANSPORT_SECURITY_HEADER, properties.getStrictTransportSecurity());
headers.add(STRICT_TRANSPORT_SECURITY_HEADER,
properties.getStrictTransportSecurity());
headers.add(X_FRAME_OPTIONS_HEADER, properties.getFrameOptions());
headers.add(X_CONTENT_TYPE_OPTIONS_HEADER, properties.getContentTypeOptions());
headers.add(X_CONTENT_TYPE_OPTIONS_HEADER,
properties.getContentTypeOptions());
headers.add(REFERRER_POLICY_HEADER, properties.getReferrerPolicy());
headers.add(CONTENT_SECURITY_POLICY_HEADER, properties.getContentSecurityPolicy());
headers.add(CONTENT_SECURITY_POLICY_HEADER,
properties.getContentSecurityPolicy());
headers.add(X_DOWNLOAD_OPTIONS_HEADER, properties.getDownloadOptions());
headers.add(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER, properties.getPermittedCrossDomainPolicies());
headers.add(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER,
properties.getPermittedCrossDomainPolicies());
return chain.filter(exchange);
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -24,22 +23,76 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
*/
@ConfigurationProperties("spring.cloud.gateway.filter.secure-headers")
public class SecureHeadersProperties {
/**
* Xss-Protection header default.
*/
public static final String X_XSS_PROTECTION_HEADER_DEFAULT = "1 ; mode=block";
public static final String STRICT_TRANSPORT_SECURITY_HEADER_DEFAULT = "max-age=631138519"; //; includeSubDomains preload")
public static final String X_FRAME_OPTIONS_HEADER_DEFAULT = "DENY"; //SAMEORIGIN = ALLOW-FROM
/**
* Strict transport security header default.
*/
public static final String STRICT_TRANSPORT_SECURITY_HEADER_DEFAULT = "max-age=631138519"; // ;
// includeSubDomains
// preload")
/**
* Frame Options header default.
*/
public static final String X_FRAME_OPTIONS_HEADER_DEFAULT = "DENY"; // SAMEORIGIN =
// ALLOW-FROM
/**
* Content-Type Options header default.
*/
public static final String X_CONTENT_TYPE_OPTIONS_HEADER_DEFAULT = "nosniff";
public static final String REFERRER_POLICY_HEADER_DEFAULT = "no-referrer"; //no-referrer-when-downgrade = origin = origin-when-cross-origin = same-origin = strict-origin = strict-origin-when-cross-origin = unsafe-url
/**
* Referrer Policy header default.
*/
public static final String REFERRER_POLICY_HEADER_DEFAULT = "no-referrer"; // no-referrer-when-downgrade
// =
// origin
// =
// origin-when-cross-origin
// =
// same-origin
// =
// strict-origin
// =
// strict-origin-when-cross-origin
// =
// unsafe-url
/**
* Content-Security Policy header default.
*/
public static final String CONTENT_SECURITY_POLICY_HEADER_DEFAULT = "default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'";
/**
* Download Options header default.
*/
public static final String X_DOWNLOAD_OPTIONS_HEADER_DEFAULT = "noopen";
/**
* Permitted Cross-Domain Policies header default.
*/
public static final String X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER_DEFAULT = "none";
private String xssProtectionHeader = X_XSS_PROTECTION_HEADER_DEFAULT;
private String strictTransportSecurity = STRICT_TRANSPORT_SECURITY_HEADER_DEFAULT;
private String frameOptions = X_FRAME_OPTIONS_HEADER_DEFAULT;
private String contentTypeOptions = X_CONTENT_TYPE_OPTIONS_HEADER_DEFAULT;
private String referrerPolicy = REFERRER_POLICY_HEADER_DEFAULT;
private String contentSecurityPolicy = CONTENT_SECURITY_POLICY_HEADER_DEFAULT;
private String downloadOptions = X_DOWNLOAD_OPTIONS_HEADER_DEFAULT;
private String permittedCrossDomainPolicies = X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER_DEFAULT;
public String getXssProtectionHeader() {
@@ -110,14 +163,17 @@ public class SecureHeadersProperties {
public String toString() {
final StringBuffer sb = new StringBuffer("SecureHeadersProperties{");
sb.append("xssProtectionHeader='").append(xssProtectionHeader).append('\'');
sb.append(", strictTransportSecurity='").append(strictTransportSecurity).append('\'');
sb.append(", strictTransportSecurity='").append(strictTransportSecurity)
.append('\'');
sb.append(", frameOptions='").append(frameOptions).append('\'');
sb.append(", contentTypeOptions='").append(contentTypeOptions).append('\'');
sb.append(", referrerPolicy='").append(referrerPolicy).append('\'');
sb.append(", contentSecurityPolicy='").append(contentSecurityPolicy).append('\'');
sb.append(", downloadOptions='").append(downloadOptions).append('\'');
sb.append(", permittedCrossDomainPolicies='").append(permittedCrossDomainPolicies).append('\'');
sb.append(", permittedCrossDomainPolicies='").append(permittedCrossDomainPolicies)
.append('\'');
sb.append('}');
return sb.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -33,8 +32,12 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.g
/**
* @author Spencer Gibb
*/
public class SetPathGatewayFilterFactory extends AbstractGatewayFilterFactory<SetPathGatewayFilterFactory.Config> {
public class SetPathGatewayFilterFactory
extends AbstractGatewayFilterFactory<SetPathGatewayFilterFactory.Config> {
/**
* Template key.
*/
public static final String TEMPLATE_KEY = "template";
public SetPathGatewayFilterFactory() {
@@ -61,15 +64,14 @@ public class SetPathGatewayFilterFactory extends AbstractGatewayFilterFactory<Se
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
ServerHttpRequest request = req.mutate()
.path(newPath)
.build();
ServerHttpRequest request = req.mutate().path(newPath).build();
return chain.filter(exchange.mutate().request(request).build());
};
}
public static class Config {
private String template;
public String getTemplate() {
@@ -79,5 +81,7 @@ public class SetPathGatewayFilterFactory extends AbstractGatewayFilterFactory<Se
public void setTemplate(String template) {
this.template = template;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -23,7 +22,8 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* @author Spencer Gibb
*/
public class SetRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public class SetRequestHeaderGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
@@ -35,4 +35,5 @@ public class SetRequestHeaderGatewayFilterFactory extends AbstractNameValueGatew
return chain.filter(exchange.mutate().request(request).build());
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,19 +12,19 @@
* 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 org.springframework.cloud.gateway.filter.GatewayFilter;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
/**
* @author Spencer Gibb
*/
public class SetResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public class SetResponseHeaderGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
@@ -32,4 +32,5 @@ public class SetResponseHeaderGatewayFilterFactory extends AbstractNameValueGate
exchange.getResponse().getHeaders().set(config.name, config.value);
}));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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;
@@ -30,8 +29,12 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.s
/**
* @author Spencer Gibb
*/
public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory<SetStatusGatewayFilterFactory.Config> {
public class SetStatusGatewayFilterFactory
extends AbstractGatewayFilterFactory<SetStatusGatewayFilterFactory.Config> {
/**
* Status key.
*/
public static final String STATUS_KEY = "status";
public SetStatusGatewayFilterFactory() {
@@ -49,11 +52,11 @@ public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory<
return (exchange, chain) -> {
// option 1 (runs in filter order)
/*exchange.getResponse().beforeCommit(() -> {
exchange.getResponse().setStatusCode(finalStatus);
return Mono.empty();
});
return chain.filter(exchange);*/
/*
* exchange.getResponse().beforeCommit(() -> {
* exchange.getResponse().setStatusCode(finalStatus); return Mono.empty(); });
* return chain.filter(exchange);
*/
// option 2 (runs in reverse filter order)
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
@@ -65,7 +68,8 @@ public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory<
}
public static class Config {
//TODO: relaxed HttpStatus converter
// TODO: relaxed HttpStatus converter
private String status;
public String getStatus() {
@@ -75,6 +79,7 @@ public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory<
public void setStatus(String status) {
this.status = status;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,8 +12,8 @@
* 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.Arrays;
@@ -29,11 +29,16 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a
/**
* This filter removes the first part of the path, known as the prefix, from the request
* before sending it downstream
* before sending it downstream.
*
* @author Ryan Baxter
*/
public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory<StripPrefixGatewayFilterFactory.Config> {
public class StripPrefixGatewayFilterFactory
extends AbstractGatewayFilterFactory<StripPrefixGatewayFilterFactory.Config> {
/**
* Parts key.
*/
public static final String PARTS_KEY = "parts";
public StripPrefixGatewayFilterFactory() {
@@ -47,16 +52,15 @@ public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactor
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
addOriginalRequestUrl(exchange, request.getURI());
String path = request.getURI().getRawPath();
String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(path, "/"))
.skip(config.parts).collect(Collectors.joining("/"));
String newPath = "/"
+ Arrays.stream(StringUtils.tokenizeToStringArray(path, "/"))
.skip(config.parts).collect(Collectors.joining("/"));
newPath += (newPath.length() > 1 && path.endsWith("/") ? "/" : "");
ServerHttpRequest newRequest = request.mutate()
.path(newPath)
.build();
ServerHttpRequest newRequest = request.mutate().path(newPath).build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
@@ -65,6 +69,7 @@ public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactor
}
public static class Config {
private int parts;
public int getParts() {
@@ -74,6 +79,7 @@ public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactor
public void setParts(int parts) {
this.parts = parts;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,12 +12,16 @@
* 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.rewrite;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
@@ -25,24 +29,21 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.function.Supplier;
/**
* This class is BETA and may be subject to change in a future release.
* Response who's job it is to gather the Publisher&lt;DataBuffer&gt; from the writeWith message
* during a call to HttpMessageWriter.write. Also gathers any headers set there.
* This class is BETA and may be subject to change in a future release. Response who's job
* it is to gather the Publisher&lt;DataBuffer&gt; from the writeWith message during a
* call to HttpMessageWriter.write. Also gathers any headers set there.
*/
public class HttpMessageWriterResponse implements ServerHttpResponse {
private final HttpHeaders headers = new HttpHeaders();
private final DataBufferFactory dataBufferFactory;
private Publisher<? extends DataBuffer> body;
public HttpMessageWriterResponse(DataBufferFactory dataBufferFactory) {
public HttpMessageWriterResponse(DataBufferFactory dataBufferFactory) {
this.dataBufferFactory = dataBufferFactory;
}
@@ -52,21 +53,21 @@ public class HttpMessageWriterResponse implements ServerHttpResponse {
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
this.body = body;
return Mono.empty();
}
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
this.body = body;
return Mono.empty();
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
//TODO: is this kosher?
return writeWith(Flux.from(body)
.flatMapSequential(p -> p));
}
@Override
public Mono<Void> writeAndFlushWith(
Publisher<? extends Publisher<? extends DataBuffer>> body) {
// TODO: is this kosher?
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
public Publisher<? extends DataBuffer> getBody() {
return body;
}
public Publisher<? extends DataBuffer> getBody() {
return body;
}
@Override
public boolean setStatusCode(HttpStatus status) {
@@ -107,4 +108,5 @@ public class HttpMessageWriterResponse implements ServerHttpResponse {
public Mono<Void> setComplete() {
return null;
}
}

View File

@@ -12,7 +12,6 @@
* 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.rewrite;
@@ -41,8 +40,8 @@ import org.springframework.web.reactive.function.server.ServerRequest;
/**
* This filter is BETA and may be subject to change in a future release.
*/
public class ModifyRequestBodyGatewayFilterFactory
extends AbstractGatewayFilterFactory<ModifyRequestBodyGatewayFilterFactory.Config> {
public class ModifyRequestBodyGatewayFilterFactory extends
AbstractGatewayFilterFactory<ModifyRequestBodyGatewayFilterFactory.Config> {
private final List<HttpMessageReader<?>> messageReaders;
@@ -61,14 +60,16 @@ public class ModifyRequestBodyGatewayFilterFactory
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
Class inClass = config.getInClass();
ServerRequest serverRequest = new DefaultServerRequest(exchange, this.messageReaders);
ServerRequest serverRequest = new DefaultServerRequest(exchange,
this.messageReaders);
//TODO: flux or mono
// TODO: flux or mono
Mono<?> modifiedBody = serverRequest.bodyToMono(inClass)
// .log("modify_request_mono", Level.INFO)
.flatMap(o -> config.rewriteFunction.apply(exchange, o));
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, config.getOutClass());
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
config.getOutClass());
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
@@ -76,12 +77,14 @@ public class ModifyRequestBodyGatewayFilterFactory
// and then set in the request decorator
headers.remove(HttpHeaders.CONTENT_LENGTH);
// if the body is changing content types, set it here, to the bodyInserter will know about it
// if the body is changing content types, set it here, to the bodyInserter
// will know about it
if (config.getContentType() != null) {
headers.set(HttpHeaders.CONTENT_TYPE, config.getContentType());
}
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,
headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
// .log("modify_request", Level.INFO)
.then(Mono.defer(() -> {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
@@ -93,9 +96,12 @@ public class ModifyRequestBodyGatewayFilterFactory
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
// TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
else {
// TODO: this causes a 'HTTP/1.1 411 Length Required'
// on httpbin.org
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING,
"chunked");
}
return httpHeaders;
}
@@ -112,13 +118,16 @@ public class ModifyRequestBodyGatewayFilterFactory
}
public static class Config {
private Class inClass;
private Class outClass;
private String contentType;
@Deprecated
private Map<String, Object> inHints;
@Deprecated
private Map<String, Object> outHints;
@@ -168,16 +177,16 @@ public class ModifyRequestBodyGatewayFilterFactory
return rewriteFunction;
}
public <T, R> Config setRewriteFunction(Class<T> inClass, Class<R> outClass,
RewriteFunction<T, R> rewriteFunction) {
setInClass(inClass);
setOutClass(outClass);
setRewriteFunction(rewriteFunction);
public Config setRewriteFunction(RewriteFunction rewriteFunction) {
this.rewriteFunction = rewriteFunction;
return this;
}
public Config setRewriteFunction(RewriteFunction rewriteFunction) {
this.rewriteFunction = rewriteFunction;
public <T, R> Config setRewriteFunction(Class<T> inClass, Class<R> outClass,
RewriteFunction<T, R> rewriteFunction) {
setInClass(inClass);
setOutClass(outClass);
setRewriteFunction(rewriteFunction);
return this;
}
@@ -189,5 +198,7 @@ public class ModifyRequestBodyGatewayFilterFactory
this.contentType = contentType;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.rewrite;
@@ -49,8 +48,8 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.O
/**
* This filter is BETA and may be subject to change in a future release.
*/
public class ModifyResponseBodyGatewayFilterFactory
extends AbstractGatewayFilterFactory<ModifyResponseBodyGatewayFilterFactory.Config> {
public class ModifyResponseBodyGatewayFilterFactory extends
AbstractGatewayFilterFactory<ModifyResponseBodyGatewayFilterFactory.Config> {
private final ServerCodecConfigurer codecConfigurer;
@@ -64,113 +63,16 @@ public class ModifyResponseBodyGatewayFilterFactory
return new ModifyResponseGatewayFilter(config);
}
public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered {
private final Config config;
public ModifyResponseGatewayFilter(Config config) {
this.config = config;
}
@Override
@SuppressWarnings("unchecked")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Class inClass = config.getInClass();
Class outClass = config.getOutClass();
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
//explicitly add it in this way instead of 'httpHeaders.setContentType(originalResponseContentType)'
//this will prevent exception in case of using non-standard media types like "Content-Type: image"
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());
//TODO: flux or mono
Mono modifiedBody = clientResponse.bodyToMono(inClass)
.flatMap(originalBody -> config.rewriteFunction.apply(exchange, originalBody));
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
Flux<DataBuffer> messageBody = outputMessage.getBody();
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
}
//TODO: use isStreamingMediaType?
return getDelegate().writeWith(messageBody);
}));
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body)
.flatMapSequential(p -> p));
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
}
public class ResponseAdapter implements ClientHttpResponse {
private final Flux<DataBuffer> flux;
private final HttpHeaders headers;
public ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {
this.headers = headers;
if (body instanceof Flux) {
flux = (Flux) body;
} else {
flux = ((Mono)body).flux();
}
}
@Override
public Flux<DataBuffer> getBody() {
return flux;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
@Override
public HttpStatus getStatusCode() {
return null;
}
@Override
public int getRawStatusCode() {
return 0;
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return null;
}
}
public static class Config {
private Class inClass;
private Class outClass;
private Map<String, Object> inHints;
private Map<String, Object> outHints;
private String newContentType;
private RewriteFunction rewriteFunction;
@@ -224,6 +126,11 @@ public class ModifyResponseBodyGatewayFilterFactory
return rewriteFunction;
}
public Config setRewriteFunction(RewriteFunction rewriteFunction) {
this.rewriteFunction = rewriteFunction;
return this;
}
public <T, R> Config setRewriteFunction(Class<T> inClass, Class<R> outClass,
RewriteFunction<T, R> rewriteFunction) {
setInClass(inClass);
@@ -232,9 +139,124 @@ public class ModifyResponseBodyGatewayFilterFactory
return this;
}
public Config setRewriteFunction(RewriteFunction rewriteFunction) {
this.rewriteFunction = rewriteFunction;
return this;
}
}
public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered {
private final Config config;
public ModifyResponseGatewayFilter(Config config) {
this.config = config;
}
@Override
@SuppressWarnings("unchecked")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(
exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Class inClass = config.getInClass();
Class outClass = config.getOutClass();
String originalResponseContentType = exchange
.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
// explicitly add it in this way instead of
// 'httpHeaders.setContentType(originalResponseContentType)'
// this will prevent exception in case of using non-standard media
// types like "Content-Type: image"
httpHeaders.add(HttpHeaders.CONTENT_TYPE,
originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body,
httpHeaders);
DefaultClientResponse clientResponse = new DefaultClientResponse(
responseAdapter, ExchangeStrategies.withDefaults());
// TODO: flux or mono
Mono modifiedBody = clientResponse.bodyToMono(inClass)
.flatMap(originalBody -> config.rewriteFunction
.apply(exchange, originalBody));
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
outClass);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
exchange, exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
Flux<DataBuffer> messageBody = outputMessage.getBody();
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
messageBody = messageBody.doOnNext(data -> headers
.setContentLength(data.readableByteCount()));
}
// TODO: use isStreamingMediaType?
return getDelegate().writeWith(messageBody);
}));
}
@Override
public Mono<Void> writeAndFlushWith(
Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
}
public class ResponseAdapter implements ClientHttpResponse {
private final Flux<DataBuffer> flux;
private final HttpHeaders headers;
public ResponseAdapter(Publisher<? extends DataBuffer> body,
HttpHeaders headers) {
this.headers = headers;
if (body instanceof Flux) {
flux = (Flux) body;
}
else {
flux = ((Mono) body).flux();
}
}
@Override
public Flux<DataBuffer> getBody() {
return flux;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
@Override
public HttpStatus getStatusCode() {
return null;
}
@Override
public int getRawStatusCode() {
return 0;
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return null;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.rewrite;
@@ -20,12 +19,16 @@ package org.springframework.cloud.gateway.filter.factory.rewrite;
import java.util.function.BiFunction;
import org.reactivestreams.Publisher;
import org.springframework.web.server.ServerWebExchange;
/**
* This interface is BETA and may be subject to change in a future release.
* @param <T>
* @param <R>
*
* @param <T> the type of the first argument to the function
* @param <R> the type of element signaled by the {@link Publisher}
*/
public interface RewriteFunction<T, R> extends BiFunction<ServerWebExchange, T, Publisher<R>> {
public interface RewriteFunction<T, R>
extends BiFunction<ServerWebExchange, T, Publisher<R>> {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.headers;
@@ -25,6 +24,7 @@ import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -36,8 +36,56 @@ import org.springframework.web.server.ServerWebExchange;
public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
/**
* Forwarded header.
*/
public static final String FORWARDED_HEADER = "Forwarded";
/* for testing */
static List<Forwarded> parse(List<String> values) {
ArrayList<Forwarded> forwardeds = new ArrayList<>();
if (CollectionUtils.isEmpty(values)) {
return forwardeds;
}
for (String value : values) {
Forwarded forwarded = parse(value);
forwardeds.add(forwarded);
}
return forwardeds;
}
/* for testing */
static Forwarded parse(String value) {
String[] pairs = StringUtils.tokenizeToStringArray(value, ";");
LinkedCaseInsensitiveMap<String> result = splitIntoCaseInsensitiveMap(pairs);
if (result == null) {
return null;
}
Forwarded forwarded = new Forwarded(result);
return forwarded;
}
@Nullable
/* for testing */ static LinkedCaseInsensitiveMap<String> splitIntoCaseInsensitiveMap(
String[] pairs) {
if (ObjectUtils.isEmpty(pairs)) {
return null;
}
LinkedCaseInsensitiveMap<String> result = new LinkedCaseInsensitiveMap<>();
for (String element : pairs) {
String[] splittedElement = StringUtils.split(element, "=");
if (splittedElement == null) {
continue;
}
result.put(splittedElement[0].trim(), splittedElement[1].trim());
}
return result;
}
@Override
public int getOrder() {
return 0;
@@ -50,8 +98,8 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
HttpHeaders updated = new HttpHeaders();
// copy all headers except Forwarded
original.entrySet().stream()
.filter(entry -> !entry.getKey().toLowerCase().equalsIgnoreCase(FORWARDED_HEADER))
original.entrySet().stream().filter(
entry -> !entry.getKey().toLowerCase().equalsIgnoreCase(FORWARDED_HEADER))
.forEach(entry -> updated.addAll(entry.getKey(), entry.getValue()));
List<Forwarded> forwardeds = parse(original.get(FORWARDED_HEADER));
@@ -60,12 +108,11 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
updated.add(FORWARDED_HEADER, f.toHeaderValue());
}
//TODO: add new forwarded
// TODO: add new forwarded
URI uri = request.getURI();
String host = original.getFirst(HttpHeaders.HOST);
Forwarded forwarded = new Forwarded()
.put("host", host)
.put("proto", uri.getScheme());
Forwarded forwarded = new Forwarded().put("host", host).put("proto",
uri.getScheme());
InetSocketAddress remoteAddress = request.getRemoteAddress();
if (remoteAddress != null) {
@@ -83,59 +130,19 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
return updated;
}
/* for testing */ static List<Forwarded> parse(List<String> values) {
ArrayList<Forwarded> forwardeds = new ArrayList<>();
if (CollectionUtils.isEmpty(values)) {
return forwardeds;
}
for (String value : values) {
Forwarded forwarded = parse(value);
forwardeds.add(forwarded);
}
return forwardeds;
}
/* for testing */ static Forwarded parse(String value) {
String[] pairs = StringUtils.tokenizeToStringArray(value, ";");
LinkedCaseInsensitiveMap<String> result = splitIntoCaseInsensitiveMap(pairs);
if (result == null) return null;
Forwarded forwarded = new Forwarded(result);
return forwarded;
}
@Nullable
/* for testing */ static LinkedCaseInsensitiveMap<String> splitIntoCaseInsensitiveMap(String[] pairs) {
if (ObjectUtils.isEmpty(pairs)) {
return null;
}
LinkedCaseInsensitiveMap<String> result = new LinkedCaseInsensitiveMap<>();
for (String element : pairs) {
String[] splittedElement = StringUtils.split(element, "=");
if (splittedElement == null) {
continue;
}
result.put(splittedElement[0].trim(), splittedElement[1].trim());
}
return result;
}
/* for testing */ static class Forwarded {
private static final char EQUALS = '=';
private static final char SEMICOLON = ';';
private final Map<String, String> values;
public Forwarded() {
Forwarded() {
this.values = new HashMap<>();
}
public Forwarded(Map<String, String> values) {
Forwarded(Map<String, String> values) {
this.values = values;
}
@@ -144,10 +151,9 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
return this;
}
private String quoteIfNeeded(String s) {
if (s != null && s.contains(":")) { //TODO: broaded quote
return "\""+s+"\"";
if (s != null && s.contains(":")) { // TODO: broaded quote
return "\"" + s + "\"";
}
return s;
}
@@ -162,9 +168,7 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
@Override
public String toString() {
return "Forwarded{" +
"values=" + this.values +
'}';
return "Forwarded{" + "values=" + this.values + '}';
}
public String toHeaderValue() {
@@ -173,12 +177,11 @@ public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
if (builder.length() > 0) {
builder.append(SEMICOLON);
}
builder.append(entry.getKey())
.append(EQUALS)
.append(entry.getValue());
builder.append(entry.getKey()).append(EQUALS).append(entry.getValue());
}
return builder.toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.headers;
@@ -24,21 +23,8 @@ import org.springframework.web.server.ServerWebExchange;
public interface HttpHeadersFilter {
enum Type {
REQUEST, RESPONSE
}
/**
* Filters a set of Http Headers
*
* @param input Http Headers
* @param exchange
* @return filtered Http Headers
*/
HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange);
static HttpHeaders filterRequest(List<HttpHeadersFilter> filters,
ServerWebExchange exchange) {
ServerWebExchange exchange) {
HttpHeaders headers = exchange.getRequest().getHeaders();
return filter(filters, headers, exchange, Type.REQUEST);
}
@@ -48,8 +34,7 @@ public interface HttpHeadersFilter {
HttpHeaders response = input;
if (filters != null) {
HttpHeaders reduce = filters.stream()
.filter(headersFilter -> headersFilter.supports(type))
.reduce(input,
.filter(headersFilter -> headersFilter.supports(type)).reduce(input,
(headers, filter) -> filter.filter(headers, exchange),
(httpHeaders, httpHeaders2) -> {
httpHeaders.addAll(httpHeaders2);
@@ -61,7 +46,22 @@ public interface HttpHeadersFilter {
return response;
}
/**
* Filters a set of Http Headers.
* @param input Http Headers
* @param exchange a {@link ServerWebExchange} that should be filtered
* @return filtered Http Headers
*/
HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange);
default boolean supports(Type type) {
return type.equals(Type.REQUEST);
}
enum Type {
REQUEST, RESPONSE
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.headers;
@@ -29,21 +28,18 @@ import org.springframework.web.server.ServerWebExchange;
@ConfigurationProperties("spring.cloud.gateway.filter.remove-hop-by-hop")
public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered {
public static final Set<String> HEADERS_REMOVED_ON_REQUEST =
new HashSet<>(Arrays.asList(
"connection",
"keep-alive",
"transfer-encoding",
"te",
"trailer",
"proxy-authorization",
"proxy-authenticate",
"x-application-context",
"upgrade"
// these two are not listed in https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3
//"proxy-connection",
// "content-length",
));
/**
* Headers to remove as the result of applying the filter.
*/
public static final Set<String> HEADERS_REMOVED_ON_REQUEST = new HashSet<>(
Arrays.asList("connection", "keep-alive", "transfer-encoding", "te",
"trailer", "proxy-authorization", "proxy-authenticate",
"x-application-context", "upgrade"
// these two are not listed in
// https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3
// "proxy-connection",
// "content-length",
));
private int order = Ordered.LOWEST_PRECEDENCE;
@@ -69,7 +65,7 @@ public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered {
@Override
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
HttpHeaders filtered = new HttpHeaders();
input.entrySet().stream()
.filter(entry -> !this.headers.contains(entry.getKey().toLowerCase()))
.forEach(entry -> filtered.addAll(entry.getKey(), entry.getValue()));
@@ -77,9 +73,9 @@ public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered {
return filtered;
}
@Override
@Override
public boolean supports(Type type) {
return type.equals(Type.REQUEST) ||
type.equals(Type.RESPONSE);
return type.equals(Type.REQUEST) || type.equals(Type.RESPONSE);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.headers;
@@ -20,6 +19,7 @@ package org.springframework.cloud.gateway.filter.headers;
import java.net.URI;
import java.util.LinkedHashSet;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
@@ -32,34 +32,34 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
@ConfigurationProperties("spring.cloud.gateway.x-forwarded")
public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
/** default http port */
/** Default http port. */
public static final int HTTP_PORT = 80;
/** default https port */
/** Default https port. */
public static final int HTTPS_PORT = 443;
/** http url scheme */
/** Http url scheme. */
public static final String HTTP_SCHEME = "http";
/** https url scheme */
/** Https url scheme. */
public static final String HTTPS_SCHEME = "https";
/** X-Forwarded-For Header */
/** X-Forwarded-For Header. */
public static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For";
/** X-Forwarded-Host Header */
/** X-Forwarded-Host Header. */
public static final String X_FORWARDED_HOST_HEADER = "X-Forwarded-Host";
/** X-Forwarded-Port Header */
/** X-Forwarded-Port Header. */
public static final String X_FORWARDED_PORT_HEADER = "X-Forwarded-Port";
/** X-Forwarded-Proto Header */
/** X-Forwarded-Proto Header. */
public static final String X_FORWARDED_PROTO_HEADER = "X-Forwarded-Proto";
/** X-Forwarded-Prefix Header */
/** X-Forwarded-Prefix Header. */
public static final String X_FORWARDED_PREFIX_HEADER = "X-Forwarded-Prefix";
/** The order of the XForwardedHeadersFilter. */
private int order = 0;
@@ -185,18 +185,17 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
this.protoAppend = protoAppend;
}
public void setPrefixAppend(boolean prefixAppend) {
this.prefixAppend = prefixAppend;
}
public boolean isPrefixAppend() {
return prefixAppend;
}
public void setPrefixAppend(boolean prefixAppend) {
this.prefixAppend = prefixAppend;
}
@Override
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders original = input;
HttpHeaders updated = new HttpHeaders();
@@ -204,14 +203,13 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
original.entrySet().stream()
.forEach(entry -> updated.addAll(entry.getKey(), entry.getValue()));
if (isForEnabled() &&
request.getRemoteAddress() != null && request.getRemoteAddress().getAddress() != null)
{
if (isForEnabled() && request.getRemoteAddress() != null
&& request.getRemoteAddress().getAddress() != null) {
String remoteAddr = request.getRemoteAddress().getAddress().getHostAddress();
List<String> xforwarded = original.get(X_FORWARDED_FOR_HEADER);
// prevent duplicates
if (remoteAddr != null &&
(xforwarded == null || !xforwarded.contains(remoteAddr))) {
if (remoteAddr != null
&& (xforwarded == null || !xforwarded.contains(remoteAddr))) {
write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, isForAppend());
}
}
@@ -221,32 +219,32 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
write(updated, X_FORWARDED_PROTO_HEADER, proto, isProtoAppend());
}
if(isPrefixEnabled()) {
//if the path of the url that the gw is routing to is a subset (and ending part) of the url that it is routing from then the difference is the prefix
//e.g. if request original.com/prefix/get/ is routed to routedservice:8090/get then /prefix is the prefix - see XForwardedHeadersFilterTests
//so first get uris, then extract paths and remove one from another if it's the ending part
if (isPrefixEnabled()) {
// If the path of the url that the gw is routing to is a subset
// (and ending part) of the url that it is routing from then the difference
// is the prefix e.g. if request original.com/prefix/get/ is routed
// to routedservice:8090/get then /prefix is the prefix
// - see XForwardedHeadersFilterTests, so first get uris, then extract paths
// and remove one from another if it's the ending part.
LinkedHashSet<URI> originalUris = exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
LinkedHashSet<URI> originalUris = exchange
.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
if(originalUris != null && requestUri != null) {
if (originalUris != null && requestUri != null) {
originalUris.stream().forEach(originalUri -> {
if(originalUri!=null && originalUri.getPath()!=null) {
if (originalUri != null && originalUri.getPath() != null) {
String prefix = originalUri.getPath();
//strip trailing slashes before checking if request path is end of original path
// strip trailing slashes before checking if request path is end
// of original path
String originalUriPath = stripTrailingSlash(originalUri);
String requestUriPath = stripTrailingSlash(requestUri);
if(requestUriPath!=null && (originalUriPath.endsWith(requestUriPath))) {
prefix = originalUriPath.replace(requestUriPath, "");
if (prefix != null && prefix.length() > 0 &&
prefix.length() <= originalUri.getPath().length()) {
write(updated, X_FORWARDED_PREFIX_HEADER, prefix, isPrefixAppend());
}
}
updateRequest(updated, originalUri, originalUriPath,
requestUriPath);
}
});
@@ -269,6 +267,18 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
return updated;
}
private void updateRequest(HttpHeaders updated, URI originalUri,
String originalUriPath, String requestUriPath) {
String prefix;
if (requestUriPath != null && (originalUriPath.endsWith(requestUriPath))) {
prefix = originalUriPath.replace(requestUriPath, "");
if (prefix != null && prefix.length() > 0
&& prefix.length() <= originalUri.getPath().length()) {
write(updated, X_FORWARDED_PREFIX_HEADER, prefix, isPrefixAppend());
}
}
}
private void write(HttpHeaders headers, String name, String value, boolean append) {
if (append) {
headers.add(name, value);
@@ -276,7 +286,8 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
List<String> values = headers.get(name);
String delimitedValue = StringUtils.collectionToCommaDelimitedString(values);
headers.set(name, delimitedValue);
} else {
}
else {
headers.set(name, value);
}
}
@@ -287,11 +298,9 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
private boolean hasHeader(ServerHttpRequest request, String name) {
HttpHeaders headers = request.getHeaders();
return headers.containsKey(name) &&
StringUtils.hasLength(headers.getFirst(name));
return headers.containsKey(name) && StringUtils.hasLength(headers.getFirst(name));
}
private String toHostHeader(ServerHttpRequest request) {
int port = request.getURI().getPort();
String host = request.getURI().getHost();
@@ -308,8 +317,10 @@ public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
private String stripTrailingSlash(URI uri) {
if (uri.getPath().endsWith("/")) {
return uri.getPath().substring(0, uri.getPath().length() - 1);
} else {
}
else {
return uri.getPath();
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.ratelimit;
@@ -26,11 +25,15 @@ import org.springframework.context.ApplicationListener;
import org.springframework.core.style.ToStringCreator;
import org.springframework.validation.Validator;
public abstract class AbstractRateLimiter<C> extends AbstractStatefulConfigurable<C> implements RateLimiter<C>, ApplicationListener<FilterArgsEvent> {
public abstract class AbstractRateLimiter<C> extends AbstractStatefulConfigurable<C>
implements RateLimiter<C>, ApplicationListener<FilterArgsEvent> {
private String configurationPropertyName;
private Validator validator;
protected AbstractRateLimiter(Class<C> configClass, String configurationPropertyName, Validator validator) {
protected AbstractRateLimiter(Class<C> configClass, String configurationPropertyName,
Validator validator) {
super(configClass);
this.configurationPropertyName = configurationPropertyName;
this.validator = validator;
@@ -58,8 +61,8 @@ public abstract class AbstractRateLimiter<C> extends AbstractStatefulConfigurabl
String routeId = event.getRouteId();
C routeConfig = newConfig();
ConfigurationUtils.bind(routeConfig, args,
configurationPropertyName, configurationPropertyName, validator);
ConfigurationUtils.bind(routeConfig, args, configurationPropertyName,
configurationPropertyName, validator);
getConfig().put(routeId, routeConfig);
}
@@ -72,8 +75,7 @@ public abstract class AbstractRateLimiter<C> extends AbstractStatefulConfigurabl
public String toString() {
return new ToStringCreator(this)
.append("configurationPropertyName", configurationPropertyName)
.append("config", getConfig())
.append("configClass", getConfigClass())
.append("config", getConfig()).append("configClass", getConfigClass())
.toString();
}

View File

@@ -1,11 +1,30 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.ratelimit;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public interface KeyResolver {
Mono<String> resolve(ServerWebExchange exchange);
}

View File

@@ -1,16 +1,38 @@
package org.springframework.cloud.gateway.filter.ratelimit;
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
package org.springframework.cloud.gateway.filter.ratelimit;
import java.security.Principal;
import reactor.core.publisher.Mono;
import org.springframework.web.server.ServerWebExchange;
public class PrincipalNameKeyResolver implements KeyResolver {
/**
* {@link PrincipalNameKeyResolver} bean name.
*/
public static final String BEAN_NAME = "principalNameKeyResolver";
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return exchange.getPrincipal().map(Principal::getName).switchIfEmpty(Mono.empty());
return exchange.getPrincipal().map(Principal::getName)
.switchIfEmpty(Mono.empty());
}
}

View File

@@ -1,13 +1,29 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.ratelimit;
import org.springframework.cloud.gateway.support.StatefulConfigurable;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.Map;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.support.StatefulConfigurable;
import org.springframework.util.Assert;
/**
* @author Spencer Gibb
*/
@@ -16,8 +32,11 @@ public interface RateLimiter<C> extends StatefulConfigurable<C> {
Mono<Response> isAllowed(String routeId, String id);
class Response {
private final boolean allowed;
private final long tokensRemaining;
private final Map<String, String> headers;
public Response(boolean allowed, Map<String, String> headers) {
@@ -56,5 +75,7 @@ public interface RateLimiter<C> extends StatefulConfigurable<C> {
sb.append('}');
return sb.toString();
}
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.ratelimit;
import java.time.Instant;
@@ -12,12 +28,12 @@ import javax.validation.constraints.Min;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
@@ -27,35 +43,72 @@ import org.springframework.validation.annotation.Validated;
/**
* See https://stripe.com/blog/rate-limiters and
* https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L11-L34
* https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L11-L34.
*
* @author Spencer Gibb
*/
@ConfigurationProperties("spring.cloud.gateway.redis-rate-limiter")
public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Config> implements ApplicationContextAware {
public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Config>
implements ApplicationContextAware {
/**
* @deprecated use {@link Config#replenishRate}
*/
@Deprecated
public static final String REPLENISH_RATE_KEY = "replenishRate";
/**
* @deprecated use {@link Config#burstCapacity}
*/
@Deprecated
public static final String BURST_CAPACITY_KEY = "burstCapacity";
/**
* Redis Rate Limiter property name.
*/
public static final String CONFIGURATION_PROPERTY_NAME = "redis-rate-limiter";
/**
* Redis Script name.
*/
public static final String REDIS_SCRIPT_NAME = "redisRequestRateLimiterScript";
/**
* Remaining Rate Limit header name.
*/
public static final String REMAINING_HEADER = "X-RateLimit-Remaining";
/**
* Replenish Rate Limit header name.
*/
public static final String REPLENISH_RATE_HEADER = "X-RateLimit-Replenish-Rate";
/**
* Burst Capacity Header name.
*/
public static final String BURST_CAPACITY_HEADER = "X-RateLimit-Burst-Capacity";
private Log log = LogFactory.getLog(getClass());
private ReactiveRedisTemplate<String, String> redisTemplate;
private RedisScript<List<Long>> script;
private AtomicBoolean initialized = new AtomicBoolean(false);
private Config defaultConfig;
// configuration properties
/** Whether or not to include headers containing rate limiter information, defaults to true. */
/**
* Whether or not to include headers containing rate limiter information, defaults to
* true.
*/
private boolean includeHeaders = true;
/** The name of the header that returns number of remaining requests during the current second. */
/**
* The name of the header that returns number of remaining requests during the current
* second.
*/
private String remainingHeader = REMAINING_HEADER;
/** The name of the header that returns the replenish rate configuration. */
@@ -65,7 +118,7 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
private String burstCapacityHeader = BURST_CAPACITY_HEADER;
public RedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
RedisScript<List<Long>> script, Validator validator) {
RedisScript<List<Long>> script, Validator validator) {
super(Config.class, CONFIGURATION_PROPERTY_NAME, validator);
this.redisTemplate = redisTemplate;
this.script = script;
@@ -74,11 +127,23 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
public RedisRateLimiter(int defaultReplenishRate, int defaultBurstCapacity) {
super(Config.class, CONFIGURATION_PROPERTY_NAME, null);
this.defaultConfig = new Config()
.setReplenishRate(defaultReplenishRate)
this.defaultConfig = new Config().setReplenishRate(defaultReplenishRate)
.setBurstCapacity(defaultBurstCapacity);
}
static List<String> getKeys(String id) {
// use `{}` around keys to use Redis Key hash tags
// this allows for using redis cluster
// Make a unique key per user.
String prefix = "request_rate_limiter.{" + id;
// You need two Redis keys for Token Bucket.
String tokenKey = prefix + "}.tokens";
String timestampKey = prefix + "}.timestamp";
return Arrays.asList(tokenKey, timestampKey);
}
public boolean isIncludeHeaders() {
return includeHeaders;
}
@@ -115,7 +180,8 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
@SuppressWarnings("unchecked")
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (initialized.compareAndSet(false, true)) {
this.redisTemplate = context.getBean("stringReactiveRedisTemplate", ReactiveRedisTemplate.class);
this.redisTemplate = context.getBean("stringReactiveRedisTemplate",
ReactiveRedisTemplate.class);
this.script = context.getBean(REDIS_SCRIPT_NAME, RedisScript.class);
if (context.getBeanNamesForType(Validator.class).length > 0) {
this.setValidator(context.getBean(Validator.class));
@@ -150,22 +216,23 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
try {
List<String> keys = getKeys(id);
// The arguments to the LUA script. time() returns unixtime in seconds.
List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
Instant.now().getEpochSecond() + "", "1");
List<String> scriptArgs = Arrays.asList(replenishRate + "",
burstCapacity + "", Instant.now().getEpochSecond() + "", "1");
// allowed, tokens_left = redis.eval(SCRIPT, keys, args)
Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
// .log("redisratelimiter", Level.FINER);
Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys,
scriptArgs);
// .log("redisratelimiter", Level.FINER);
return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
.reduce(new ArrayList<Long>(), (longs, l) -> {
longs.addAll(l);
return longs;
}) .map(results -> {
}).map(results -> {
boolean allowed = results.get(0) == 1L;
Long tokensLeft = results.get(1);
Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));
Response response = new Response(allowed,
getHeaders(routeConfig, tokensLeft));
if (log.isDebugEnabled()) {
log.debug("response: " + response);
@@ -192,7 +259,8 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
}
if (routeConfig == null) {
throw new IllegalArgumentException("No Configuration found for route " + routeId +" or defaultFilters");
throw new IllegalArgumentException(
"No Configuration found for route " + routeId + " or defaultFilters");
}
return routeConfig;
}
@@ -206,21 +274,9 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
return headers;
}
static List<String> getKeys(String id) {
// use `{}` around keys to use Redis Key hash tags
// this allows for using redis cluster
// Make a unique key per user.
String prefix = "request_rate_limiter.{" + id;
// You need two Redis keys for Token Bucket.
String tokenKey = prefix + "}.tokens";
String timestampKey = prefix + "}.timestamp";
return Arrays.asList(tokenKey, timestampKey);
}
@Validated
public static class Config {
@Min(1)
private int replenishRate;
@@ -247,10 +303,10 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
@Override
public String toString() {
return "Config{" +
"replenishRate=" + replenishRate +
", burstCapacity=" + burstCapacity +
'}';
return "Config{" + "replenishRate=" + replenishRate + ", burstCapacity="
+ burstCapacity + '}';
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler;

View File

@@ -12,7 +12,6 @@
* 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.handler;
@@ -47,6 +46,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
* @since 0.1
*/
public class FilteringWebHandler implements WebHandler {
protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class);
private final List<GatewayFilter> globalFilters;
@@ -56,21 +56,20 @@ public class FilteringWebHandler implements WebHandler {
}
private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
return filters.stream()
.map(filter -> {
GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
if (filter instanceof Ordered) {
int order = ((Ordered) filter).getOrder();
return new OrderedGatewayFilter(gatewayFilter, order);
}
return gatewayFilter;
}).collect(Collectors.toList());
return filters.stream().map(filter -> {
GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
if (filter instanceof Ordered) {
int order = ((Ordered) filter).getOrder();
return new OrderedGatewayFilter(gatewayFilter, order);
}
return gatewayFilter;
}).collect(Collectors.toList());
}
/* TODO: relocate @EventListener(RefreshRoutesEvent.class)
void handleRefresh() {
this.combinedFiltersForRoute.clear();
}*/
/*
* TODO: relocate @EventListener(RefreshRoutesEvent.class) void handleRefresh() {
* this.combinedFiltersForRoute.clear();
*/
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
@@ -79,11 +78,11 @@ public class FilteringWebHandler implements WebHandler {
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
//TODO: needed or cached?
// TODO: needed or cached?
AnnotationAwareOrderComparator.sort(combined);
if (logger.isDebugEnabled()) {
logger.debug("Sorted gatewayFilterFactories: "+ combined);
logger.debug("Sorted gatewayFilterFactories: " + combined);
}
return new DefaultGatewayFilterChain(combined).filter(exchange);
@@ -92,9 +91,10 @@ public class FilteringWebHandler implements WebHandler {
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
private final int index;
private final List<GatewayFilter> filters;
public DefaultGatewayFilterChain(List<GatewayFilter> filters) {
DefaultGatewayFilterChain(List<GatewayFilter> filters) {
this.filters = filters;
this.index = 0;
}
@@ -113,20 +113,23 @@ public class FilteringWebHandler implements WebHandler {
return Mono.defer(() -> {
if (this.index < filters.size()) {
GatewayFilter filter = filters.get(this.index);
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1);
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
this.index + 1);
return filter.filter(exchange, chain);
} else {
}
else {
return Mono.empty(); // complete
}
});
}
}
private static class GatewayFilterAdapter implements GatewayFilter {
private final GlobalFilter delegate;
public GatewayFilterAdapter(GlobalFilter delegate) {
GatewayFilterAdapter(GlobalFilter delegate) {
this.delegate = delegate;
}
@@ -142,6 +145,7 @@ public class FilteringWebHandler implements WebHandler {
sb.append('}');
return sb.toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler;
@@ -39,26 +38,33 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
private final FilteringWebHandler webHandler;
private final RouteLocator routeLocator;
private final Integer managmentPort;
public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
public RoutePredicateHandlerMapping(FilteringWebHandler webHandler,
RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties,
Environment environment) {
this.webHandler = webHandler;
this.routeLocator = routeLocator;
if (environment.containsProperty("management.server.port")) {
managmentPort = new Integer(environment.getProperty("management.server.port"));
} else {
managmentPort = new Integer(
environment.getProperty("management.server.port"));
}
else {
managmentPort = null;
}
setOrder(1);
setOrder(1);
setCorsConfigurations(globalCorsProperties.getCorsConfigurations());
}
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
// don't handle requests on the management port if set
if (managmentPort != null && exchange.getRequest().getURI().getPort() == managmentPort.intValue()) {
if (managmentPort != null
&& exchange.getRequest().getURI().getPort() == managmentPort.intValue()) {
return Mono.empty();
}
exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
@@ -68,7 +74,8 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
.flatMap((Function<Route, Mono<?>>) r -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isDebugEnabled()) {
logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
logger.debug(
"Mapping [" + getExchangeDesc(exchange) + "] to " + r);
}
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
@@ -76,21 +83,23 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
if (logger.isTraceEnabled()) {
logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
logger.trace("No RouteDefinition found for ["
+ getExchangeDesc(exchange) + "]");
}
})));
}
@Override
protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
protected CorsConfiguration getCorsConfiguration(Object handler,
ServerWebExchange exchange) {
// TODO: support cors configuration via properties on a route see gh-229
// see RequestMappingHandlerMapping.initCorsConfiguration()
// also see https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsWebFilterTests.java
// also see
// https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsWebFilterTests.java
return super.getCorsConfiguration(handler, exchange);
}
//TODO: get desc from factory?
// TODO: get desc from factory?
private String getExchangeDesc(ServerWebExchange exchange) {
StringBuilder out = new StringBuilder();
out.append("Exchange: ");
@@ -101,25 +110,25 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
}
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
return this.routeLocator
.getRoutes()
//individually filter routes so that filterWhen error delaying is not a problem
.concatMap(route -> Mono
.just(route)
.filterWhen(r -> {
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
return r.getPredicate().apply(exchange);
})
//instead of immediately stopping main flux due to error, log and swallow it
.doOnError(e -> logger.error("Error applying predicate for route: "+route.getId(), e))
.onErrorResume(e -> Mono.empty())
)
return this.routeLocator.getRoutes()
// individually filter routes so that filterWhen error delaying is not a
// problem
.concatMap(route -> Mono.just(route).filterWhen(r -> {
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
return r.getPredicate().apply(exchange);
})
// instead of immediately stopping main flux due to error, log and
// swallow it
.doOnError(e -> logger.error(
"Error applying predicate for route: " + route.getId(),
e))
.onErrorResume(e -> Mono.empty()))
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
.next()
//TODO: error handling
// TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
@@ -128,16 +137,17 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
return route;
});
/* TODO: trace logging
if (logger.isTraceEnabled()) {
logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
}*/
/*
* TODO: trace logging if (logger.isTraceEnabled()) {
* logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
*/
}
/**
* Validate the given handler against the current request.
* <p>The default implementation is empty. Can be overridden in subclasses,
* for example to enforce specific preconditions expressed in URL mappings.
* <p>
* The default implementation is empty. Can be overridden in subclasses, for example
* to enforce specific preconditions expressed in URL mappings.
* @param route the Route object to validate
* @param exchange current exchange
* @throws Exception if validation failed
@@ -149,4 +159,5 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
protected String getSimpleName() {
return "RoutePredicateHandlerMapping";
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -22,15 +21,19 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotNull;
import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<AfterRoutePredicateFactory.Config> {
public class AfterRoutePredicateFactory
extends AbstractRoutePredicateFactory<AfterRoutePredicateFactory.Config> {
/**
* DateTime key.
*/
public static final String DATETIME_KEY = "datetime";
public AfterRoutePredicateFactory() {
@@ -52,6 +55,7 @@ public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<Af
}
public static class Config {
@NotNull
private ZonedDateTime datetime;
@@ -62,6 +66,7 @@ public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<Af
public void setDatetime(ZonedDateTime datetime) {
this.datetime = datetime;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -27,8 +26,12 @@ import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory<BeforeRoutePredicateFactory.Config> {
public class BeforeRoutePredicateFactory
extends AbstractRoutePredicateFactory<BeforeRoutePredicateFactory.Config> {
/**
* DateTime key.
*/
public static final String DATETIME_KEY = "datetime";
public BeforeRoutePredicateFactory() {
@@ -50,6 +53,7 @@ public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory<B
}
public static class Config {
private ZonedDateTime datetime;
public ZonedDateTime getDatetime() {
@@ -59,5 +63,7 @@ public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory<B
public void setDatetime(ZonedDateTime datetime) {
this.datetime = datetime;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,13 +12,10 @@
* 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.handler.predicate;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;
@@ -33,9 +30,17 @@ import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<BetweenRoutePredicateFactory.Config> {
public class BetweenRoutePredicateFactory
extends AbstractRoutePredicateFactory<BetweenRoutePredicateFactory.Config> {
/**
* DateTime 1 key.
*/
public static final String DATETIME1_KEY = "datetime1";
/**
* DateTime 2 key.
*/
public static final String DATETIME2_KEY = "datetime2";
public BetweenRoutePredicateFactory() {
@@ -52,8 +57,7 @@ public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<
ZonedDateTime datetime1 = config.datetime1;
ZonedDateTime datetime2 = config.datetime2;
Assert.isTrue(datetime1.isBefore(datetime2),
config.datetime1 +
" must be before " + config.datetime2);
config.datetime1 + " must be before " + config.datetime2);
return exchange -> {
final ZonedDateTime now = ZonedDateTime.now();
@@ -63,8 +67,10 @@ public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<
@Validated
public static class Config {
@NotNull
private ZonedDateTime datetime1;
@NotNull
private ZonedDateTime datetime2;
@@ -85,6 +91,7 @@ public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<
this.datetime2 = datetime2;
return this;
}
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.handler.predicate;
import java.util.function.Predicate;
@@ -5,16 +21,31 @@ import java.util.function.Predicate;
import org.springframework.web.server.ServerWebExchange;
/**
* Creates a predicate which indicates if the request is intended for a Cloud Foundry Route Service.
* @see <a href="https://docs.cloudfoundry.org/services/route-services.html">Cloud Foundry Route Service documentation</a>.
* Creates a predicate which indicates if the request is intended for a Cloud Foundry
* Route Service.
*
* @author Andrew Fitzgerald
* @see <a href="https://docs.cloudfoundry.org/services/route-services.html">Cloud Foundry
* Route Service documentation</a>
*/
public class CloudFoundryRouteServiceRoutePredicateFactory extends
AbstractRoutePredicateFactory<Object> {
public class CloudFoundryRouteServiceRoutePredicateFactory
extends AbstractRoutePredicateFactory<Object> {
/**
* Forwarded URL header name.
*/
public static final String X_CF_FORWARDED_URL = "X-CF-Forwarded-Url";
/**
* Proxy signature header name.
*/
public static final String X_CF_PROXY_SIGNATURE = "X-CF-Proxy-Signature";
/**
* Proxy metadata header name.
*/
public static final String X_CF_PROXY_METADATA = "X-CF-Proxy-Metadata";
private final HeaderRoutePredicateFactory factory = new HeaderRoutePredicateFactory();
public CloudFoundryRouteServiceRoutePredicateFactory() {
@@ -22,8 +53,7 @@ public class CloudFoundryRouteServiceRoutePredicateFactory extends
}
@Override
public Predicate<ServerWebExchange> apply(
Object unused) {
public Predicate<ServerWebExchange> apply(Object unused) {
return headerPredicate(X_CF_FORWARDED_URL)
.and(headerPredicate(X_CF_PROXY_SIGNATURE))
.and(headerPredicate(X_CF_PROXY_METADATA));
@@ -35,4 +65,5 @@ public class CloudFoundryRouteServiceRoutePredicateFactory extends
config.setRegexp(".*");
return factory.apply(config);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -30,9 +29,17 @@ import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> {
public class CookieRoutePredicateFactory
extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> {
/**
* Name key.
*/
public static final String NAME_KEY = "name";
/**
* Regexp key.
*/
public static final String REGEXP_KEY = "regexp";
public CookieRoutePredicateFactory() {
@@ -47,7 +54,8 @@ public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory<C
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
List<HttpCookie> cookies = exchange.getRequest().getCookies().get(config.name);
List<HttpCookie> cookies = exchange.getRequest().getCookies()
.get(config.name);
if (cookies == null) {
return false;
}
@@ -65,6 +73,7 @@ public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory<C
@NotEmpty
private String name;
@NotEmpty
private String regexp;
@@ -85,5 +94,7 @@ public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory<C
this.regexp = regexp;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -31,9 +30,17 @@ import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {
public class HeaderRoutePredicateFactory
extends AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {
/**
* Header key.
*/
public static final String HEADER_KEY = "header";
/**
* Regexp key.
*/
public static final String REGEXP_KEY = "regexp";
public HeaderRoutePredicateFactory() {
@@ -50,7 +57,8 @@ public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<H
boolean hasRegex = !StringUtils.isEmpty(config.regexp);
return exchange -> {
List<String> values = exchange.getRequest().getHeaders().getOrDefault(config.header, Collections.emptyList());
List<String> values = exchange.getRequest().getHeaders()
.getOrDefault(config.header, Collections.emptyList());
if (values.isEmpty()) {
return false;
}
@@ -67,8 +75,10 @@ public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<H
@Validated
public static class Config {
@NotEmpty
private String header;
private String regexp;
public String getHeader() {
@@ -88,5 +98,7 @@ public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<H
this.regexp = regexp;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -35,7 +34,8 @@ import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory<HostRoutePredicateFactory.Config> {
public class HostRoutePredicateFactory
extends AbstractRoutePredicateFactory<HostRoutePredicateFactory.Config> {
private PathMatcher pathMatcher = new AntPathMatcher(".");
@@ -62,11 +62,11 @@ public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory<Hos
return exchange -> {
String host = exchange.getRequest().getHeaders().getFirst("Host");
Optional<String> optionalPattern = config.getPatterns().stream()
.filter(pattern -> this.pathMatcher.match(pattern, host))
.findFirst();
.filter(pattern -> this.pathMatcher.match(pattern, host)).findFirst();
if (optionalPattern.isPresent()) {
Map<String, String> variables = this.pathMatcher.extractUriTemplateVariables(optionalPattern.get(), host);
Map<String, String> variables = this.pathMatcher
.extractUriTemplateVariables(optionalPattern.get(), host);
ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables);
return true;
}
@@ -77,6 +77,7 @@ public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory<Hos
@Validated
public static class Config {
private List<String> patterns = new ArrayList<>();
@Deprecated
@@ -104,9 +105,9 @@ public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory<Hos
@Override
public String toString() {
return new ToStringCreator(this)
.append("patterns", patterns)
.toString();
return new ToStringCreator(this).append("patterns", patterns).toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -27,8 +26,12 @@ import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory<MethodRoutePredicateFactory.Config> {
public class MethodRoutePredicateFactory
extends AbstractRoutePredicateFactory<MethodRoutePredicateFactory.Config> {
/**
* Method key.
*/
public static final String METHOD_KEY = "method";
public MethodRoutePredicateFactory() {
@@ -49,6 +52,7 @@ public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory<M
}
public static class Config {
private HttpMethod method;
public HttpMethod getMethod() {
@@ -58,5 +62,7 @@ public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory<M
public void setMethod(HttpMethod method) {
this.method = method;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -41,8 +40,11 @@ import static org.springframework.http.server.PathContainer.parsePath;
/**
* @author Spencer Gibb
*/
public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {
public class PathRoutePredicateFactory
extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {
private static final Log log = LogFactory.getLog(RoutePredicateFactory.class);
private static final String MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY = "matchOptionalTrailingSeparator";
private PathPatternParser pathPatternParser = new PathPatternParser();
@@ -51,6 +53,15 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<Pat
super(Config.class);
}
private static void traceMatch(String prefix, Object desired, Object actual,
boolean match) {
if (log.isTraceEnabled()) {
String message = String.format("%s \"%s\" %s against value \"%s\"", prefix,
desired, match ? "matches" : "does not match", actual);
log.trace(message);
}
}
public void setPathPatternParser(PathPatternParser pathPatternParser) {
this.pathPatternParser = pathPatternParser;
}
@@ -69,7 +80,8 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<Pat
public Predicate<ServerWebExchange> apply(Config config) {
final ArrayList<PathPattern> pathPatterns = new ArrayList<>();
synchronized (this.pathPatternParser) {
pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchOptionalTrailingSeparator());
pathPatternParser.setMatchOptionalTrailingSeparator(
config.isMatchOptionalTrailingSeparator());
config.getPatterns().forEach(pattern -> {
PathPattern pathPattern = this.pathPatternParser.parse(pattern);
pathPatterns.add(pathPattern);
@@ -79,8 +91,7 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<Pat
PathContainer path = parsePath(exchange.getRequest().getURI().getPath());
Optional<PathPattern> optionalPathPattern = pathPatterns.stream()
.filter(pattern -> pattern.matches(path))
.findFirst();
.filter(pattern -> pattern.matches(path)).findFirst();
if (optionalPathPattern.isPresent()) {
PathPattern pathPattern = optionalPathPattern.get();
@@ -88,24 +99,19 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<Pat
PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path);
putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
return true;
} else {
}
else {
traceMatch("Pattern", config.getPatterns(), path, false);
return false;
}
};
}
private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {
if (log.isTraceEnabled()) {
String message = String.format("%s \"%s\" %s against value \"%s\"",
prefix, desired, match ? "matches" : "does not match", actual);
log.trace(message);
}
}
@Validated
public static class Config {
private List<String> patterns = new ArrayList<>();
private boolean matchOptionalTrailingSeparator = true;
@Deprecated
@@ -136,19 +142,20 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<Pat
return matchOptionalTrailingSeparator;
}
public Config setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) {
public Config setMatchOptionalTrailingSeparator(
boolean matchOptionalTrailingSeparator) {
this.matchOptionalTrailingSeparator = matchOptionalTrailingSeparator;
return this;
}
@Override
public String toString() {
return new ToStringCreator(this)
.append("patterns", patterns)
.append("matchOptionalTrailingSeparator", matchOptionalTrailingSeparator)
return new ToStringCreator(this).append("patterns", patterns)
.append("matchOptionalTrailingSeparator",
matchOptionalTrailingSeparator)
.toString();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -34,8 +33,10 @@ import static org.springframework.util.StringUtils.tokenizeToStringArray;
*/
@Validated
public class PredicateDefinition {
@NotNull
private String name;
private Map<String, String> args = new LinkedHashMap<>();
public PredicateDefinition() {
@@ -44,14 +45,14 @@ public class PredicateDefinition {
public PredicateDefinition(String text) {
int eqIdx = text.indexOf('=');
if (eqIdx <= 0) {
throw new ValidationException("Unable to parse PredicateDefinition text '" + text + "'" +
", must be of the form name=value");
throw new ValidationException("Unable to parse PredicateDefinition text '"
+ text + "'" + ", must be of the form name=value");
}
setName(text.substring(0, eqIdx));
String[] args = tokenizeToStringArray(text.substring(eqIdx+1), ",");
String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");
for (int i=0; i < args.length; i++) {
for (int i = 0; i < args.length; i++) {
this.args.put(NameUtils.generateName(i), args[i]);
}
}
@@ -78,11 +79,14 @@ public class PredicateDefinition {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PredicateDefinition that = (PredicateDefinition) o;
return Objects.equals(name, that.name) &&
Objects.equals(args, that.args);
return Objects.equals(name, that.name) && Objects.equals(args, that.args);
}
@Override
@@ -98,4 +102,5 @@ public class PredicateDefinition {
sb.append('}');
return sb.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -30,9 +29,17 @@ import org.springframework.web.server.ServerWebExchange;
/**
* @author Spencer Gibb
*/
public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<QueryRoutePredicateFactory.Config> {
public class QueryRoutePredicateFactory
extends AbstractRoutePredicateFactory<QueryRoutePredicateFactory.Config> {
/**
* Param key.
*/
public static final String PARAM_KEY = "param";
/**
* Regexp key.
*/
public static final String REGEXP_KEY = "regexp";
public QueryRoutePredicateFactory() {
@@ -52,8 +59,8 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
return exchange.getRequest().getQueryParams().containsKey(config.param);
}
List<String> values = exchange.getRequest().getQueryParams().get(config.param);
List<String> values = exchange.getRequest().getQueryParams()
.get(config.param);
if (values == null) {
return false;
}
@@ -68,6 +75,7 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
@Validated
public static class Config {
@NotEmpty
private String param;
@@ -90,5 +98,7 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
this.regexp = regexp;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -23,7 +22,6 @@ import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -33,7 +31,6 @@ import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
@@ -45,11 +42,15 @@ import static org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilt
*/
public class ReadBodyPredicateFactory
extends AbstractRoutePredicateFactory<ReadBodyPredicateFactory.Config> {
protected static final Log LOGGER = LogFactory.getLog(ReadBodyPredicateFactory.class);
private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies
.withDefaults().messageReaders();
public ReadBodyPredicateFactory() {
super(Config.class);
@@ -63,48 +64,61 @@ public class ReadBodyPredicateFactory
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
Mono<?> modifiedBody;
// We can only read the body from the request once, once that happens if we try to read the body again an
// exception will be thrown. The below if/else caches the body object as a request attribute in the ServerWebExchange
// so if this filter is run more than once (due to more than one route using it) we do not try to read the
// request body multiple times
// We can only read the body from the request once, once that happens if we
// try to read the body again an exception will be thrown. The below if/else
// caches the body object as a request attribute in the ServerWebExchange
// so if this filter is run more than once (due to more than one route
// using it) we do not try to read the request body multiple times
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put(TEST_ATTRIBUTE, test);
return Mono.just(test);
} catch (ClassCastException e) {
}
catch (ClassCastException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Predicate test failed because class in predicate does not match the cached body object",
e);
LOGGER.debug("Predicate test failed because class in predicate "
+ "does not match the cached body object", e);
}
}
return Mono.just(false);
} else {
//Join all the DataBuffers so we have a single DataBuffer for the body
}
else {
// Join all the DataBuffers so we have a single DataBuffer for the body
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
//Update the retain counts so we can read the body twice, once to parse into an object
//that we can test the predicate against and a second time when the HTTP client sends
//the request downstream
//Note: if we end up reading the body twice we will run into a problem, but as of right
//now there is no good use case for doing this
// Update the retain counts so we can read the body twice,
// once to parse into an object
// that we can test the predicate against and a second time
// when the HTTP client sends
// the request downstream
// Note: if we end up reading the body twice we will run into
// a problem, but as of right
// now there is no good use case for doing this
DataBufferUtils.retain(dataBuffer);
//Make a slice for each read so each read has its own read/write indexes
Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
// Make a slice for each read so each read has its own
// read/write indexes
Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(
dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), messageReaders)
.bodyToMono(inClass)
.doOnNext(objectValue -> {
exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue);
exchange.getAttributes().put(CACHED_REQUEST_BODY_KEY, cachedFlux);
})
.map(objectValue -> config.predicate.test(objectValue));
return ServerRequest
.create(exchange.mutate().request(mutatedRequest)
.build(), messageReaders)
.bodyToMono(inClass).doOnNext(objectValue -> {
exchange.getAttributes().put(
CACHE_REQUEST_BODY_OBJECT_KEY,
objectValue);
exchange.getAttributes()
.put(CACHED_REQUEST_BODY_KEY, cachedFlux);
}).map(objectValue -> config.predicate
.test(objectValue));
});
}
@@ -119,8 +133,11 @@ public class ReadBodyPredicateFactory
}
public static class Config {
private Class inClass;
private Predicate predicate;
private Map<String, Object> hints;
public Class getInClass() {
@@ -136,13 +153,13 @@ public class ReadBodyPredicateFactory
return predicate;
}
public <T> Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
setInClass(inClass);
public Config setPredicate(Predicate predicate) {
this.predicate = predicate;
return this;
}
public Config setPredicate(Predicate predicate) {
public <T> Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
setInClass(inClass);
this.predicate = predicate;
return this;
}
@@ -155,5 +172,7 @@ public class ReadBodyPredicateFactory
this.hints = hints;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,21 +12,10 @@
* 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.handler.predicate;
import io.netty.handler.ipfilter.IpFilterRuleType;
import io.netty.handler.ipfilter.IpSubnetFilterRule;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.support.ipresolver.RemoteAddressResolver;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
@@ -34,14 +23,28 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import io.netty.handler.ipfilter.IpFilterRuleType;
import io.netty.handler.ipfilter.IpSubnetFilterRule;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.support.ipresolver.RemoteAddressResolver;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType.GATHER_LIST;
/**
* @author Spencer Gibb
*/
public class RemoteAddrRoutePredicateFactory extends AbstractRoutePredicateFactory<RemoteAddrRoutePredicateFactory.Config> {
public class RemoteAddrRoutePredicateFactory
extends AbstractRoutePredicateFactory<RemoteAddrRoutePredicateFactory.Config> {
private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class);
private static final Log log = LogFactory
.getLog(RemoteAddrRoutePredicateFactory.class);
public RemoteAddrRoutePredicateFactory() {
super(Config.class);
@@ -60,24 +63,26 @@ public class RemoteAddrRoutePredicateFactory extends AbstractRoutePredicateFacto
@NotNull
private List<IpSubnetFilterRule> convert(List<String> values) {
List<IpSubnetFilterRule> sources = new ArrayList<>();
for (String arg : values) {
addSource(sources, arg);
}
for (String arg : values) {
addSource(sources, arg);
}
return sources;
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
List<IpSubnetFilterRule> sources = convert(config.sources);
List<IpSubnetFilterRule> sources = convert(config.sources);
return exchange -> {
InetSocketAddress remoteAddress = config.remoteAddressResolver.resolve(exchange);
InetSocketAddress remoteAddress = config.remoteAddressResolver
.resolve(exchange);
if (remoteAddress != null && remoteAddress.getAddress() != null) {
String hostAddress = remoteAddress.getAddress().getHostAddress();
String host = exchange.getRequest().getURI().getHost();
if (log.isDebugEnabled() && !hostAddress.equals(host)) {
log.debug("Remote addresses didn't match " + hostAddress + " != " + host);
log.debug("Remote addresses didn't match " + hostAddress + " != "
+ host);
}
for (IpSubnetFilterRule source : sources) {
@@ -96,20 +101,23 @@ public class RemoteAddrRoutePredicateFactory extends AbstractRoutePredicateFacto
source = source + "/32";
}
String[] ipAddressCidrPrefix = source.split("/",2);
String[] ipAddressCidrPrefix = source.split("/", 2);
String ipAddress = ipAddressCidrPrefix[0];
int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]);
sources.add(new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.ACCEPT));
sources.add(
new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.ACCEPT));
}
@Validated
public static class Config {
@NotEmpty
private List<String> sources = new ArrayList<>();
@NotNull
private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver(){};
private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver() {
};
public List<String> getSources() {
return sources;
@@ -125,10 +133,12 @@ public class RemoteAddrRoutePredicateFactory extends AbstractRoutePredicateFacto
return this;
}
public Config setRemoteAddressResolver(RemoteAddressResolver remoteAddressResolver) {
public Config setRemoteAddressResolver(
RemoteAddressResolver remoteAddressResolver) {
this.remoteAddressResolver = remoteAddressResolver;
return this;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -33,6 +32,10 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.t
*/
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {
/**
* Pattern key.
*/
String PATTERN_KEY = "pattern";
// useful for javadsl
@@ -59,7 +62,8 @@ public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configur
throw new UnsupportedOperationException("newConfig() not implemented");
}
default void beforeApply(C config) {}
default void beforeApply(C config) {
}
Predicate<ServerWebExchange> apply(C config);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2018 the original author or authors.
* 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.
@@ -12,7 +12,6 @@
* 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.handler.predicate;
@@ -25,6 +24,7 @@ import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.event.WeightDefinedEvent;
import org.springframework.cloud.gateway.support.WeightConfig;
import org.springframework.context.ApplicationEventPublisher;
@@ -37,14 +37,23 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.W
/**
* @author Spencer Gibb
*/
//TODO: make this a generic Choose out of group predicate?
public class WeightRoutePredicateFactory extends AbstractRoutePredicateFactory<WeightConfig> implements ApplicationEventPublisherAware {
// TODO: make this a generic Choose out of group predicate?
public class WeightRoutePredicateFactory
extends AbstractRoutePredicateFactory<WeightConfig>
implements ApplicationEventPublisherAware {
/**
* Weight config group key.
*/
public static final String GROUP_KEY = WeightConfig.CONFIG_PREFIX + ".group";
/**
* Weight config weight key.
*/
public static final String WEIGHT_KEY = WeightConfig.CONFIG_PREFIX + ".weight";
private static final Log log = LogFactory.getLog(WeightRoutePredicateFactory.class);
public static final String GROUP_KEY = WeightConfig.CONFIG_PREFIX + ".group";
public static final String WEIGHT_KEY = WeightConfig.CONFIG_PREFIX + ".weight";
private ApplicationEventPublisher publisher;
public WeightRoutePredicateFactory() {
@@ -63,7 +72,7 @@ public class WeightRoutePredicateFactory extends AbstractRoutePredicateFactory<W
@Override
public String shortcutFieldPrefix() {
return WeightConfig.CONFIG_PREFIX;
return WeightConfig.CONFIG_PREFIX;
}
@Override
@@ -88,15 +97,19 @@ public class WeightRoutePredicateFactory extends AbstractRoutePredicateFactory<W
String chosenRoute = weights.get(group);
if (log.isTraceEnabled()) {
log.trace("in group weight: "+ group + ", current route: " + routeId +", chosen route: " + chosenRoute);
log.trace("in group weight: " + group + ", current route: " + routeId
+ ", chosen route: " + chosenRoute);
}
return routeId.equals(chosenRoute);
} else if (log.isTraceEnabled()) {
log.trace("no weights found for group: "+ group + ", current route: " + routeId);
}
else if (log.isTraceEnabled()) {
log.trace("no weights found for group: " + group + ", current route: "
+ routeId);
}
return false;
};
}
}

Some files were not shown because too many files have changed in this diff Show More