Browse Source

Added RewritePath filter

pull/41/head
Spencer Gibb 8 years ago
parent
commit
646db0d8e0
No known key found for this signature in database
GPG Key ID: 7788A47380690861
  1. 6
      src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
  2. 30
      src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java
  3. 2
      src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java
  4. 9
      src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderFilterFactory.java
  5. 9
      src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderFilterFactory.java
  6. 10
      src/main/java/org/springframework/cloud/gateway/filter/factory/FilterFactory.java
  7. 29
      src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathFilterFactory.java
  8. 24
      src/main/java/org/springframework/cloud/gateway/handler/GatewayFilteringWebHandler.java
  9. 52
      src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathFilterFactoryTests.java
  10. 20
      src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java
  11. 13
      src/test/resources/application.yml

6
src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

@ -13,6 +13,7 @@ import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderFilterFa @@ -13,6 +13,7 @@ import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderFilterFa
import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderFilterFactory;
import org.springframework.cloud.gateway.filter.factory.FilterFactory;
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.cloud.gateway.filter.factory.RewritePathFilterFactory;
import org.springframework.cloud.gateway.handler.GatewayFilteringWebHandler;
import org.springframework.cloud.gateway.handler.GatewayPredicateHandlerMapping;
import org.springframework.cloud.gateway.handler.GatewayWebHandler;
@ -120,6 +121,11 @@ public class GatewayAutoConfiguration { @@ -120,6 +121,11 @@ public class GatewayAutoConfiguration {
return new AddResponseHeaderFilterFactory();
}
@Bean
public RewritePathFilterFactory rewritePathFilterFactory() {
return new RewritePathFilterFactory();
}
@Configuration
@ConditionalOnClass(Endpoint.class)
protected static class GatewayActuatorConfiguration {

30
src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
package org.springframework.cloud.gateway.filter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
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) {
this.delegate = delegate;
this.order = order;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return this.delegate.filter(exchange, chain);
}
@Override
public int getOrder() {
return this.order;
}
}

2
src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java

@ -18,7 +18,7 @@ import reactor.core.publisher.Mono; @@ -18,7 +18,7 @@ import reactor.core.publisher.Mono;
public class RouteToRequestUrlFilter implements GatewayFilter, Ordered {
private static final Log log = LogFactory.getLog(RouteToRequestUrlFilter.class);
public static final int ROUTE_TO_URL_FILTER_ORDER = 500;
public static final int ROUTE_TO_URL_FILTER_ORDER = 10000;
@Override
public int getOrder() {

9
src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderFilterFactory.java

@ -2,22 +2,15 @@ package org.springframework.cloud.gateway.filter.factory; @@ -2,22 +2,15 @@ package org.springframework.cloud.gateway.filter.factory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
/**
* @author Spencer Gibb
*/
public class AddRequestHeaderFilterFactory implements FilterFactory {
@Override
public String getName() {
return "AddRequestHeader";
}
@Override
public GatewayFilter apply(String header, String[] args) {
Assert.isTrue(args != null && args.length == 1,
"args must have one entry");
validate(args, 1);
//TODO: caching can happen here
return (exchange, chain) -> {

9
src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderFilterFactory.java

@ -1,22 +1,15 @@ @@ -1,22 +1,15 @@
package org.springframework.cloud.gateway.filter.factory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.util.Assert;
/**
* @author Spencer Gibb
*/
public class AddResponseHeaderFilterFactory implements FilterFactory {
@Override
public String getName() {
return "AddResponseHeader";
}
@Override
public GatewayFilter apply(String header, String[] args) {
Assert.isTrue(args != null && args.length == 1,
"args must have one entry");
validate(args, 1);
//TODO: caching can happen here
return (exchange, chain) -> {

10
src/main/java/org/springframework/cloud/gateway/filter/factory/FilterFactory.java

@ -1,13 +1,21 @@ @@ -1,13 +1,21 @@
package org.springframework.cloud.gateway.filter.factory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.util.Assert;
/**
* @author Spencer Gibb
*/
public interface FilterFactory {
String getName();
default String getName() {
return getClass().getSimpleName().replace(FilterFactory.class.getSimpleName(), "");
}
GatewayFilter apply(String value, String[] args);
default void validate(String[] args, int requiredSize) {
Assert.isTrue(args != null && args.length == requiredSize,
"args must have "+ requiredSize +" entry(s)");
}
}

29
src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathFilterFactory.java

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
package org.springframework.cloud.gateway.filter.factory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* @author Spencer Gibb
*/
public class RewritePathFilterFactory implements FilterFactory {
@Override
public GatewayFilter apply(String regex, String[] args) {
validate(args, 1);
String replacement = args[0].replace("$\\", "$");
//TODO: caching can happen here
return (exchange, chain) -> {
ServerHttpRequest req = exchange.getRequest();
String path = req.getURI().getPath();
String newPath = path.replaceAll(regex, replacement);
ServerHttpRequest request = req.mutate()
.path(newPath)
.build();
return chain.filter(exchange.mutate().request(request).build());
};
}
}

24
src/main/java/org/springframework/cloud/gateway/handler/GatewayFilteringWebHandler.java

@ -18,7 +18,6 @@ package org.springframework.cloud.gateway.handler; @@ -18,7 +18,6 @@ package org.springframework.cloud.gateway.handler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -30,17 +29,19 @@ import org.apache.commons.logging.Log; @@ -30,17 +29,19 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.config.Route;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.FilterFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.handler.WebHandlerDecorator;
import reactor.core.publisher.Mono;
import static java.util.Collections.emptyList;
import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_ROUTE_ATTR;
import reactor.core.publisher.Mono;
/**
* WebHandler that delegates to a chain of {@link GatewayFilter} instances and then
* to the target {@link WebHandler}.
@ -76,15 +77,19 @@ public class GatewayFilteringWebHandler extends WebHandlerDecorator { @@ -76,15 +77,19 @@ public class GatewayFilteringWebHandler extends WebHandlerDecorator {
public Mono<Void> handle(ServerWebExchange exchange) {
//TODO: probably a java 8 stream way of doing this
ArrayList<GatewayFilter> routeFilters = new ArrayList<>(this.filters);
Optional<Route> route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
if (route.isPresent() && !route.get().getFilters().isEmpty()) {
routeFilters.addAll(loadFilters(route.get()));
}
AnnotationAwareOrderComparator.sort(routeFilters);
return new DefaultWebFilterChain(routeFilters, getDelegate()).filter(exchange);
}
private Collection<GatewayFilter> loadFilters(Route route) {
return route.getFilters().stream()
private List<GatewayFilter> loadFilters(Route route) {
List<GatewayFilter> filters = route.getFilters().stream()
.map(definition -> {
FilterFactory filter = this.filterDefinitions.get(definition.getName());
if (filter == null) {
@ -97,12 +102,19 @@ public class GatewayFilteringWebHandler extends WebHandlerDecorator { @@ -97,12 +102,19 @@ public class GatewayFilteringWebHandler extends WebHandlerDecorator {
} else {
args = Collections.emptyList();
}
logger.debug("Route " + route.getId() + " applying filter "+ definition.getValue()
logger.debug("Route " + route.getId() + " applying filter " + definition.getValue()
+ ", " + args + " to " + definition.getName());
}
return filter.apply(definition.getValue(), definition.getArgs());
})
.collect(Collectors.toList());
ArrayList<GatewayFilter> ordered = new ArrayList<>(filters.size());
for (int i = 0; i < filters.size(); i++) {
ordered.add(new OrderedGatewayFilter(filters.get(i), i+1));
}
return ordered;
}

52
src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathFilterFactoryTests.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
package org.springframework.cloud.gateway.filter.factory;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import reactor.core.publisher.Mono;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Spencer Gibb
*/
public class RewritePathFilterFactoryTests {
@Test
public void rewritePathFilterWorks() {
testRewriteFilter("/foo", "/baz", "/foo/bar", "/baz/bar");
}
@Test
public void rewritePathFilterWithNamedGroupWorks() {
testRewriteFilter("/foo/(?<id>\\d.*)", "/bar/baz/$\\{id}", "/foo/123", "/bar/baz/123");
}
private void testRewriteFilter(String regex, String replacement, String actualPath, String expectedPath) {
GatewayFilter filter = new RewritePathFilterFactory().apply(regex, new String[]{replacement});
MockServerHttpRequest request = MockServerHttpRequest
.get("http://localhost"+ actualPath)
.build();
DefaultServerWebExchange exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
WebFilterChain filterChain = mock(WebFilterChain.class);
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor.forClass(ServerWebExchange.class);
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
filter.filter(exchange, filterChain);
ServerWebExchange webExchange = captor.getValue();
Assertions.assertThat(webExchange.getRequest().getURI().getPath()).isEqualTo(expectedPath);
}
}

20
src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java

@ -128,6 +128,26 @@ public class GatewayIntegrationTests { @@ -128,6 +128,26 @@ public class GatewayIntegrationTests {
);
}
@Test
public void rewritePathFilterWorks() {
Mono<ClientResponse> result = webClient.exchange(
GET("http://localhost:" + port + "/foo/get")
.header("Host", "www.baz.org")
.build()
);
verify( () ->
StepVerifier.create(result)
.consumeNextWith(
response -> {
HttpStatus statusCode = response.statusCode();
assertThat(statusCode).isEqualTo(HttpStatus.OK);
})
.expectComplete()
.verify(Duration.ofSeconds(3))
);
}
@Test
public void addRequestHeaderFilterWorks() {
Mono<Map> result = webClient.exchange(

13
src/test/resources/application.yml

@ -21,7 +21,7 @@ spring: @@ -21,7 +21,7 @@ spring:
- AddResponseHeader=X-Response-Foo, Bar
# =====================================
- id: host_bar_path_headers_to_httpbin
- id: add_request_header_test
uri: http://httpbin.org:80
predicates:
- Host=**.bar.org
@ -29,6 +29,17 @@ spring: @@ -29,6 +29,17 @@ spring:
filters:
- AddRequestHeader=X-Request-Foo, Bar
# =====================================
- id: rewrite_path_test
uri: http://httpbin.org:80
predicates:
- Host=**.baz.org
filters:
# $\ is being used as an escape
- RewritePath=/foo/(?<segment>.*), /$\{segment}
- AddRequestHeader=X-Request-Foo, Bar
- AddRequestHeader=X-Request-Baz, Bat
# =====================================
- id: default_path_to_httpbin
uri: http://httpbin.org:80

Loading…
Cancel
Save