Skip to content

Commit 8c33ed0

Browse files
committed
ServletHttpHandlerAdapter supports Serlvet path mapping
Issue: SPR-16155
1 parent aa653b2 commit 8c33ed0

File tree

6 files changed

+142
-64
lines changed

6 files changed

+142
-64
lines changed

spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package org.springframework.http.server.reactive;
1818

1919
import java.io.IOException;
20+
import java.util.Collection;
2021
import javax.servlet.AsyncContext;
2122
import javax.servlet.AsyncEvent;
2223
import javax.servlet.AsyncListener;
2324
import javax.servlet.Servlet;
2425
import javax.servlet.ServletConfig;
26+
import javax.servlet.ServletRegistration;
2527
import javax.servlet.ServletRequest;
2628
import javax.servlet.ServletResponse;
2729
import javax.servlet.annotation.WebServlet;
@@ -62,9 +64,9 @@ public class ServletHttpHandlerAdapter implements Servlet {
6264

6365
private int bufferSize = DEFAULT_BUFFER_SIZE;
6466

67+
@Nullable
68+
private String servletPath;
6569

66-
// Servlet is based on blocking I/O, hence the usage of non-direct, heap-based buffers
67-
// (i.e. 'false' as constructor argument)
6870
private DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(false);
6971

7072

@@ -90,6 +92,18 @@ public int getBufferSize() {
9092
return this.bufferSize;
9193
}
9294

95+
/**
96+
* Return the Servlet path under which the Servlet is deployed by checking
97+
* the Servlet registration from {@link #init(ServletConfig)}.
98+
* @return the path, or an empty string if the Servlet is deployed without
99+
* a prefix (i.e. "/" or "/*"), or {@code null} if this method is invoked
100+
* before the {@link #init(ServletConfig)} Servlet container callback.
101+
*/
102+
@Nullable
103+
public String getServletPath() {
104+
return this.servletPath;
105+
}
106+
93107
public void setDataBufferFactory(DataBufferFactory dataBufferFactory) {
94108
Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null");
95109
this.dataBufferFactory = dataBufferFactory;
@@ -100,7 +114,40 @@ public DataBufferFactory getDataBufferFactory() {
100114
}
101115

102116

103-
// The Servlet.service method
117+
// Servlet methods...
118+
119+
@Override
120+
public void init(ServletConfig config) {
121+
this.servletPath = getServletPath(config);
122+
}
123+
124+
@Nullable
125+
private String getServletPath(ServletConfig config) {
126+
String name = config.getServletName();
127+
ServletRegistration registration = config.getServletContext().getServletRegistration(name);
128+
Assert.notNull(registration, "ServletRegistration not found for Servlet '" + name + "'.");
129+
130+
Collection<String> mappings = registration.getMappings();
131+
if (mappings.size() == 1) {
132+
String mapping = mappings.iterator().next();
133+
if (mapping.equals("/")) {
134+
return "";
135+
}
136+
if (mapping.endsWith("/*")) {
137+
String path = mapping.substring(0, mapping.length() - 2);
138+
if (!path.isEmpty()) {
139+
logger.info("Found Servlet mapping '" + path + "' for Servlet '" + name + "'.");
140+
}
141+
return path;
142+
}
143+
}
144+
145+
throw new IllegalArgumentException("Expected a single Servlet mapping -- " +
146+
"either the default Servlet mapping (i.e. '/'), " +
147+
"or a path based mapping (e.g. '/*', '/foo/*'). " +
148+
"Actual mappings: " + mappings + " for Servlet '" + name + "'.");
149+
}
150+
104151

105152
@Override
106153
public void service(ServletRequest request, ServletResponse response) throws IOException {
@@ -121,21 +168,24 @@ public void service(ServletRequest request, ServletResponse response) throws IOE
121168
this.httpHandler.handle(httpRequest, httpResponse).subscribe(subscriber);
122169
}
123170

124-
protected ServerHttpRequest createRequest(HttpServletRequest request, AsyncContext context) throws IOException {
125-
return new ServletServerHttpRequest(
126-
request, context, getDataBufferFactory(), getBufferSize());
127-
}
171+
protected ServerHttpRequest createRequest(HttpServletRequest request, AsyncContext context)
172+
throws IOException {
128173

129-
protected ServerHttpResponse createResponse(HttpServletResponse response, AsyncContext context) throws IOException {
130-
return new ServletServerHttpResponse(
131-
response, context, getDataBufferFactory(), getBufferSize());
174+
Assert.notNull(this.servletPath, "servletPath is not initialized.");
175+
176+
return new ServletServerHttpRequest(
177+
request, context, this.servletPath, getDataBufferFactory(), getBufferSize());
132178
}
133179

180+
protected ServerHttpResponse createResponse(HttpServletResponse response, AsyncContext context)
181+
throws IOException {
134182

135-
// Other Servlet methods...
183+
return new ServletServerHttpResponse(response, context, getDataBufferFactory(), getBufferSize());
184+
}
136185

137186
@Override
138-
public void init(ServletConfig config) {
187+
public String getServletInfo() {
188+
return "";
139189
}
140190

141191
@Override
@@ -144,11 +194,6 @@ public ServletConfig getServletConfig() {
144194
return null;
145195
}
146196

147-
@Override
148-
public String getServletInfo() {
149-
return "";
150-
}
151-
152197
@Override
153198
public void destroy() {
154199
}

spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
7070

7171

7272
public ServletServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext,
73-
DataBufferFactory bufferFactory, int bufferSize) throws IOException {
73+
String servletPath, DataBufferFactory bufferFactory, int bufferSize) throws IOException {
7474

75-
super(initUri(request), request.getContextPath(), initHeaders(request));
75+
super(initUri(request), request.getContextPath() + servletPath, initHeaders(request));
7676

7777
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
7878
Assert.isTrue(bufferSize > 0, "'bufferSize' must be higher than 0");

spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.io.buffer.DataBuffer;
3232
import org.springframework.core.io.buffer.DataBufferFactory;
3333
import org.springframework.core.io.buffer.DataBufferUtils;
34+
import org.springframework.util.Assert;
3435

3536
/**
3637
* {@link ServletHttpHandlerAdapter} extension that uses Tomcat APIs for reading
@@ -42,28 +43,35 @@
4243
@WebServlet(asyncSupported = true)
4344
public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
4445

46+
4547
public TomcatHttpHandlerAdapter(HttpHandler httpHandler) {
4648
super(httpHandler);
4749
}
4850

4951

5052
@Override
51-
protected ServerHttpRequest createRequest(HttpServletRequest request, AsyncContext cxt) throws IOException {
52-
return new TomcatServerHttpRequest(request, cxt, getDataBufferFactory(), getBufferSize());
53+
protected ServerHttpRequest createRequest(HttpServletRequest request, AsyncContext asyncContext)
54+
throws IOException {
55+
56+
Assert.notNull(getServletPath(), "servletPath is not initialized.");
57+
return new TomcatServerHttpRequest(request, asyncContext, getServletPath(),
58+
getDataBufferFactory(), getBufferSize());
5359
}
5460

5561
@Override
56-
protected ServerHttpResponse createResponse(HttpServletResponse response, AsyncContext cxt) throws IOException {
62+
protected ServerHttpResponse createResponse(HttpServletResponse response, AsyncContext cxt)
63+
throws IOException {
64+
5765
return new TomcatServerHttpResponse(response, cxt, getDataBufferFactory(), getBufferSize());
5866
}
5967

6068

6169
private final class TomcatServerHttpRequest extends ServletServerHttpRequest {
6270

6371
public TomcatServerHttpRequest(HttpServletRequest request, AsyncContext context,
64-
DataBufferFactory factory, int bufferSize) throws IOException {
72+
String servletPath, DataBufferFactory factory, int bufferSize) throws IOException {
6573

66-
super(request, context, factory, bufferSize);
74+
super(request, context, servletPath, factory, bufferSize);
6775
}
6876

6977
@Override

spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public ServletInputStream getInputStream() {
9393
}
9494
};
9595
AsyncContext asyncContext = new MockAsyncContext(request, new MockHttpServletResponse());
96-
return new ServletServerHttpRequest(request, asyncContext, new DefaultDataBufferFactory(), 1024);
96+
return new ServletServerHttpRequest(request, asyncContext, "", new DefaultDataBufferFactory(), 1024);
9797
}
9898

9999
private static class TestServletInputStream extends DelegatingServletInputStream {

spring-web/src/test/java/org/springframework/http/server/reactive/bootstrap/TomcatHttpServer.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ public class TomcatHttpServer extends AbstractHttpServer {
3535

3636
private final Class<?> wsListener;
3737

38+
private String contextPath = "";
39+
40+
private String servletMapping = "/";
41+
3842
private Tomcat tomcatServer;
3943

4044

@@ -49,6 +53,15 @@ public TomcatHttpServer(String baseDir, Class<?> wsListener) {
4953
}
5054

5155

56+
public void setContextPath(String contextPath) {
57+
this.contextPath = contextPath;
58+
}
59+
60+
public void setServletMapping(String servletMapping) {
61+
this.servletMapping = servletMapping;
62+
}
63+
64+
5265
@Override
5366
protected void initServer() throws Exception {
5467
this.tomcatServer = new Tomcat();
@@ -59,9 +72,9 @@ protected void initServer() throws Exception {
5972
ServletHttpHandlerAdapter servlet = initServletAdapter();
6073

6174
File base = new File(System.getProperty("java.io.tmpdir"));
62-
Context rootContext = tomcatServer.addContext("", base.getAbsolutePath());
75+
Context rootContext = tomcatServer.addContext(this.contextPath, base.getAbsolutePath());
6376
Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
64-
rootContext.addServletMappingDecoded("/", "httpHandlerServlet");
77+
rootContext.addServletMappingDecoded(this.servletMapping, "httpHandlerServlet");
6578
if (wsListener != null) {
6679
rootContext.addApplicationListener(wsListener.getName());
6780
}

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ContextPathIntegrationTests.java

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.web.reactive.result.method.annotation;
1717

18+
import java.io.File;
19+
1820
import org.junit.After;
1921
import org.junit.Before;
2022
import org.junit.Test;
@@ -25,6 +27,7 @@
2527
import org.springframework.http.server.reactive.HttpHandler;
2628
import org.springframework.http.server.reactive.ServerHttpRequest;
2729
import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer;
30+
import org.springframework.http.server.reactive.bootstrap.TomcatHttpServer;
2831
import org.springframework.web.bind.annotation.GetMapping;
2932
import org.springframework.web.bind.annotation.RestController;
3033
import org.springframework.web.client.RestTemplate;
@@ -34,76 +37,85 @@
3437
import static org.junit.Assert.assertEquals;
3538

3639
/**
37-
* Integration tests that demonstrate running multiple applications under
38-
* different context paths.
40+
* Integration tests related to the use of context paths.
3941
*
4042
* @author Rossen Stoyanchev
4143
*/
4244
@SuppressWarnings({"unused", "WeakerAccess"})
4345
public class ContextPathIntegrationTests {
4446

45-
private ReactorHttpServer server;
46-
4747

48-
@Before
49-
public void setup() throws Exception {
48+
@Test
49+
public void multipleWebFluxApps() throws Exception {
5050
AnnotationConfigApplicationContext context1 = new AnnotationConfigApplicationContext();
51-
context1.register(WebApp1Config.class);
51+
context1.register(WebAppConfig.class);
5252
context1.refresh();
5353

5454
AnnotationConfigApplicationContext context2 = new AnnotationConfigApplicationContext();
55-
context2.register(WebApp2Config.class);
55+
context2.register(WebAppConfig.class);
5656
context2.refresh();
5757

5858
HttpHandler webApp1Handler = WebHttpHandlerBuilder.applicationContext(context1).build();
5959
HttpHandler webApp2Handler = WebHttpHandlerBuilder.applicationContext(context2).build();
6060

61-
this.server = new ReactorHttpServer();
61+
ReactorHttpServer server = new ReactorHttpServer();
62+
server.registerHttpHandler("/webApp1", webApp1Handler);
63+
server.registerHttpHandler("/webApp2", webApp2Handler);
64+
server.afterPropertiesSet();
65+
server.start();
6266

63-
this.server.registerHttpHandler("/webApp1", webApp1Handler);
64-
this.server.registerHttpHandler("/webApp2", webApp2Handler);
67+
try {
68+
RestTemplate restTemplate = new RestTemplate();
69+
String actual;
6570

66-
this.server.afterPropertiesSet();
67-
this.server.start();
68-
}
71+
String url = "http://localhost:" + server.getPort() + "/webApp1/test";
72+
actual = restTemplate.getForObject(url, String.class);
73+
assertEquals("Tested in /webApp1", actual);
6974

70-
@After
71-
public void shutdown() throws Exception {
72-
this.server.stop();
75+
url = "http://localhost:" + server.getPort() + "/webApp2/test";
76+
actual = restTemplate.getForObject(url, String.class);
77+
assertEquals("Tested in /webApp2", actual);
78+
}
79+
finally {
80+
server.stop();
81+
}
7382
}
7483

75-
7684
@Test
77-
public void basic() throws Exception {
78-
RestTemplate restTemplate = new RestTemplate();
79-
String actual;
85+
public void servletPathMapping() throws Exception {
86+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
87+
context.register(WebAppConfig.class);
88+
context.refresh();
8089

81-
actual = restTemplate.getForObject(createUrl("/webApp1/test"), String.class);
82-
assertEquals("Tested in /webApp1", actual);
90+
File base = new File(System.getProperty("java.io.tmpdir"));
91+
TomcatHttpServer server = new TomcatHttpServer(base.getAbsolutePath());
92+
server.setContextPath("/app");
93+
server.setServletMapping("/api/*");
8394

84-
actual = restTemplate.getForObject(createUrl("/webApp2/test"), String.class);
85-
assertEquals("Tested in /webApp2", actual);
86-
}
95+
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(context).build();
96+
server.setHandler(httpHandler);
8797

88-
private String createUrl(String path) {
89-
return "http://localhost:" + this.server.getPort() + path;
90-
}
98+
server.afterPropertiesSet();
99+
server.start();
91100

101+
try {
102+
RestTemplate restTemplate = new RestTemplate();
103+
String actual;
92104

93-
@EnableWebFlux
94-
@Configuration
95-
static class WebApp1Config {
96-
97-
@Bean
98-
public TestController testController() {
99-
return new TestController();
105+
String url = "http://localhost:" + server.getPort() + "/app/api/test";
106+
actual = restTemplate.getForObject(url, String.class);
107+
assertEquals("Tested in /app/api", actual);
108+
}
109+
finally {
110+
server.stop();
100111
}
101112
}
102113

103114

115+
104116
@EnableWebFlux
105117
@Configuration
106-
static class WebApp2Config {
118+
static class WebAppConfig {
107119

108120
@Bean
109121
public TestController testController() {

0 commit comments

Comments
 (0)