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:
committed by
GitHub
parent
eb95038952
commit
a6f48b0f65
@@ -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
34
pom.xml
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() + "\"");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<DataBuffer> 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<DataBuffer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user