Skip to content

Commit 430607c

Browse files
committed
Documents proper order for Servlet Filtters.
Fixes gh-3244
1 parent 9ea11b3 commit 430607c

File tree

3 files changed

+80
-0
lines changed

3 files changed

+80
-0
lines changed

docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
*** xref:spring-cloud-gateway-server-mvc/filters/setrequesthostheader.adoc[]
110110
*** xref:spring-cloud-gateway-server-mvc/filters/tokenrelay.adoc[]
111111
** xref:spring-cloud-gateway-server-mvc/writing-custom-predicates-and-filters.adoc[]
112+
** xref:spring-cloud-gateway-server-mvc/working-with-servlets-and-filters.adoc[]
112113
113114
// begin Gateway Proxy Exchange
114115
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[[working-with-servlets-and-filters]]
2+
= Working with Servlets and Servlet Filters
3+
4+
Spring Cloud Gateway Server MVC is built for Servlet-stack web applications built on the Servlet API and deployed to Servlet containers. If your applications uses Servlets or Servlet Filters you may need to take care in their ordering.
5+
6+
Because of the way Servlet Containers handle request parameters, when a Spring WebMVC application receives a content type of `application/x-www-form-urlencoded`, Servlet containers combine those with query parameters into "request" parameters. A special `FormFilter` bean is included in Spring Cloud Gateway Server MVC to rebuild the form body for downstream applications. Any Servlet Filter that reads request parameters before the filter chain is run will need to be ordered *before* `FormFilter`. See the example below.
7+
8+
.MyFilter.java
9+
[source,java]
10+
----
11+
import jakarta.servlet.Filter;
12+
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
13+
import org.springframework.core.Ordered;
14+
15+
class MyFilter implements Filter, Ordered {
16+
17+
@Override
18+
public int getOrder() {
19+
return FormFilter.FORM_FILTER_ORDER - 1;
20+
}
21+
22+
@Override
23+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
24+
throws IOException, ServletException {
25+
// ...
26+
filterChain.doFilter(request, response);
27+
// ...
28+
}
29+
}
30+
----

spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.cloud.gateway.server.mvc;
1818

19+
import java.io.IOException;
1920
import java.net.URI;
2021
import java.nio.charset.StandardCharsets;
2122
import java.time.Duration;
@@ -30,6 +31,12 @@
3031
import io.github.bucket4j.caffeine.CaffeineProxyManager;
3132
import io.github.bucket4j.distributed.proxy.AsyncProxyManager;
3233
import io.github.bucket4j.distributed.remote.RemoteBucketState;
34+
import jakarta.servlet.Filter;
35+
import jakarta.servlet.FilterChain;
36+
import jakarta.servlet.ServletException;
37+
import jakarta.servlet.ServletRequest;
38+
import jakarta.servlet.ServletResponse;
39+
import jakarta.servlet.http.HttpServletRequest;
3340
import org.apache.commons.logging.Log;
3441
import org.apache.commons.logging.LogFactory;
3542
import org.assertj.core.api.Assertions;
@@ -42,7 +49,9 @@
4249
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
4350
import org.springframework.boot.test.web.client.TestRestTemplate;
4451
import org.springframework.boot.test.web.server.LocalServerPort;
52+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
4553
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
54+
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
4655
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
4756
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
4857
import org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates;
@@ -53,9 +62,11 @@
5362
import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient;
5463
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
5564
import org.springframework.context.annotation.Bean;
65+
import org.springframework.core.Ordered;
5666
import org.springframework.core.io.ClassPathResource;
5767
import org.springframework.http.HttpEntity;
5868
import org.springframework.http.HttpHeaders;
69+
import org.springframework.http.HttpMethod;
5970
import org.springframework.http.HttpStatus;
6071
import org.springframework.http.MediaType;
6172
import org.springframework.http.RequestEntity;
@@ -1278,6 +1289,12 @@ public RouterFunction<ServerResponse> gatewayRouterFunctionsQuery() {
12781289
// @formatter:on
12791290
}
12801291

1292+
@Bean
1293+
public FilterRegistrationBean myFilter() {
1294+
FilterRegistrationBean<MyFilter> reg = new FilterRegistrationBean<>(new MyFilter());
1295+
return reg;
1296+
}
1297+
12811298
private Predicate<Event> eventPredicate(String foo) {
12821299
return new Predicate<>() {
12831300
@Override
@@ -1294,6 +1311,38 @@ public String toString() {
12941311

12951312
}
12961313

1314+
private static class MyFilter implements Filter, Ordered {
1315+
1316+
@Override
1317+
public int getOrder() {
1318+
return FormFilter.FORM_FILTER_ORDER - 1;
1319+
}
1320+
1321+
@Override
1322+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
1323+
throws IOException, ServletException {
1324+
if (isFormPost((HttpServletRequest) request)) {
1325+
// test for formUrlencodedWorks and
1326+
// https://github.com/spring-cloud/spring-cloud-gateway/issues/3244
1327+
assertThat(request.getParameter("foo")).isEqualTo("fooquery");
1328+
assertThat(request.getParameter("foo")).isEqualTo("fooquery");
1329+
}
1330+
filterChain.doFilter(request, response);
1331+
1332+
if (isFormPost((HttpServletRequest) request)) {
1333+
assertThat(request.getParameter("foo")).isEqualTo("fooquery");
1334+
assertThat(request.getParameter("foo")).isEqualTo("fooquery");
1335+
}
1336+
}
1337+
1338+
static boolean isFormPost(HttpServletRequest request) {
1339+
String contentType = request.getContentType();
1340+
return (contentType != null && contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
1341+
&& HttpMethod.POST.matches(request.getMethod()));
1342+
}
1343+
1344+
}
1345+
12971346
protected record Hello(String message) {
12981347

12991348
}

0 commit comments

Comments
 (0)