ServletHttpHandlerAdapter supports Serlvet path mapping
Issue: SPR-16155
This commit is contained in:
parent
aa653b23bc
commit
8c33ed02b3
|
@ -17,11 +17,13 @@
|
||||||
package org.springframework.http.server.reactive;
|
package org.springframework.http.server.reactive;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
import javax.servlet.AsyncContext;
|
import javax.servlet.AsyncContext;
|
||||||
import javax.servlet.AsyncEvent;
|
import javax.servlet.AsyncEvent;
|
||||||
import javax.servlet.AsyncListener;
|
import javax.servlet.AsyncListener;
|
||||||
import javax.servlet.Servlet;
|
import javax.servlet.Servlet;
|
||||||
import javax.servlet.ServletConfig;
|
import javax.servlet.ServletConfig;
|
||||||
|
import javax.servlet.ServletRegistration;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.annotation.WebServlet;
|
import javax.servlet.annotation.WebServlet;
|
||||||
|
@ -62,9 +64,9 @@ public class ServletHttpHandlerAdapter implements Servlet {
|
||||||
|
|
||||||
private int bufferSize = DEFAULT_BUFFER_SIZE;
|
private int bufferSize = DEFAULT_BUFFER_SIZE;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String servletPath;
|
||||||
|
|
||||||
// Servlet is based on blocking I/O, hence the usage of non-direct, heap-based buffers
|
|
||||||
// (i.e. 'false' as constructor argument)
|
|
||||||
private DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(false);
|
private DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(false);
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,6 +92,18 @@ public class ServletHttpHandlerAdapter implements Servlet {
|
||||||
return this.bufferSize;
|
return this.bufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Servlet path under which the Servlet is deployed by checking
|
||||||
|
* the Servlet registration from {@link #init(ServletConfig)}.
|
||||||
|
* @return the path, or an empty string if the Servlet is deployed without
|
||||||
|
* a prefix (i.e. "/" or "/*"), or {@code null} if this method is invoked
|
||||||
|
* before the {@link #init(ServletConfig)} Servlet container callback.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getServletPath() {
|
||||||
|
return this.servletPath;
|
||||||
|
}
|
||||||
|
|
||||||
public void setDataBufferFactory(DataBufferFactory dataBufferFactory) {
|
public void setDataBufferFactory(DataBufferFactory dataBufferFactory) {
|
||||||
Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null");
|
Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null");
|
||||||
this.dataBufferFactory = dataBufferFactory;
|
this.dataBufferFactory = dataBufferFactory;
|
||||||
|
@ -100,7 +114,40 @@ public class ServletHttpHandlerAdapter implements Servlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The Servlet.service method
|
// Servlet methods...
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ServletConfig config) {
|
||||||
|
this.servletPath = getServletPath(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getServletPath(ServletConfig config) {
|
||||||
|
String name = config.getServletName();
|
||||||
|
ServletRegistration registration = config.getServletContext().getServletRegistration(name);
|
||||||
|
Assert.notNull(registration, "ServletRegistration not found for Servlet '" + name + "'.");
|
||||||
|
|
||||||
|
Collection<String> mappings = registration.getMappings();
|
||||||
|
if (mappings.size() == 1) {
|
||||||
|
String mapping = mappings.iterator().next();
|
||||||
|
if (mapping.equals("/")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (mapping.endsWith("/*")) {
|
||||||
|
String path = mapping.substring(0, mapping.length() - 2);
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
logger.info("Found Servlet mapping '" + path + "' for Servlet '" + name + "'.");
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Expected a single Servlet mapping -- " +
|
||||||
|
"either the default Servlet mapping (i.e. '/'), " +
|
||||||
|
"or a path based mapping (e.g. '/*', '/foo/*'). " +
|
||||||
|
"Actual mappings: " + mappings + " for Servlet '" + name + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void service(ServletRequest request, ServletResponse response) throws IOException {
|
public void service(ServletRequest request, ServletResponse response) throws IOException {
|
||||||
|
@ -121,21 +168,24 @@ public class ServletHttpHandlerAdapter implements Servlet {
|
||||||
this.httpHandler.handle(httpRequest, httpResponse).subscribe(subscriber);
|
this.httpHandler.handle(httpRequest, httpResponse).subscribe(subscriber);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ServerHttpRequest createRequest(HttpServletRequest request, AsyncContext context) throws IOException {
|
protected ServerHttpRequest createRequest(HttpServletRequest request, AsyncContext context)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
Assert.notNull(this.servletPath, "servletPath is not initialized.");
|
||||||
|
|
||||||
return new ServletServerHttpRequest(
|
return new ServletServerHttpRequest(
|
||||||
request, context, getDataBufferFactory(), getBufferSize());
|
request, context, this.servletPath, getDataBufferFactory(), getBufferSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ServerHttpResponse createResponse(HttpServletResponse response, AsyncContext context) throws IOException {
|
protected ServerHttpResponse createResponse(HttpServletResponse response, AsyncContext context)
|
||||||
return new ServletServerHttpResponse(
|
throws IOException {
|
||||||
response, context, getDataBufferFactory(), getBufferSize());
|
|
||||||
|
return new ServletServerHttpResponse(response, context, getDataBufferFactory(), getBufferSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Other Servlet methods...
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ServletConfig config) {
|
public String getServletInfo() {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -144,11 +194,6 @@ public class ServletHttpHandlerAdapter implements Servlet {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServletInfo() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,9 +70,9 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||||
|
|
||||||
|
|
||||||
public ServletServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext,
|
public ServletServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext,
|
||||||
DataBufferFactory bufferFactory, int bufferSize) throws IOException {
|
String servletPath, DataBufferFactory bufferFactory, int bufferSize) throws IOException {
|
||||||
|
|
||||||
super(initUri(request), request.getContextPath(), initHeaders(request));
|
super(initUri(request), request.getContextPath() + servletPath, initHeaders(request));
|
||||||
|
|
||||||
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
|
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
|
||||||
Assert.isTrue(bufferSize > 0, "'bufferSize' must be higher than 0");
|
Assert.isTrue(bufferSize > 0, "'bufferSize' must be higher than 0");
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.catalina.connector.CoyoteOutputStream;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ServletHttpHandlerAdapter} extension that uses Tomcat APIs for reading
|
* {@link ServletHttpHandlerAdapter} extension that uses Tomcat APIs for reading
|
||||||
|
@ -42,18 +43,25 @@ import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
@WebServlet(asyncSupported = true)
|
@WebServlet(asyncSupported = true)
|
||||||
public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
||||||
|
|
||||||
|
|
||||||
public TomcatHttpHandlerAdapter(HttpHandler httpHandler) {
|
public TomcatHttpHandlerAdapter(HttpHandler httpHandler) {
|
||||||
super(httpHandler);
|
super(httpHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ServerHttpRequest createRequest(HttpServletRequest request, AsyncContext cxt) throws IOException {
|
protected ServerHttpRequest createRequest(HttpServletRequest request, AsyncContext asyncContext)
|
||||||
return new TomcatServerHttpRequest(request, cxt, getDataBufferFactory(), getBufferSize());
|
throws IOException {
|
||||||
|
|
||||||
|
Assert.notNull(getServletPath(), "servletPath is not initialized.");
|
||||||
|
return new TomcatServerHttpRequest(request, asyncContext, getServletPath(),
|
||||||
|
getDataBufferFactory(), getBufferSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ServerHttpResponse createResponse(HttpServletResponse response, AsyncContext cxt) throws IOException {
|
protected ServerHttpResponse createResponse(HttpServletResponse response, AsyncContext cxt)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
return new TomcatServerHttpResponse(response, cxt, getDataBufferFactory(), getBufferSize());
|
return new TomcatServerHttpResponse(response, cxt, getDataBufferFactory(), getBufferSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,9 +69,9 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
||||||
private final class TomcatServerHttpRequest extends ServletServerHttpRequest {
|
private final class TomcatServerHttpRequest extends ServletServerHttpRequest {
|
||||||
|
|
||||||
public TomcatServerHttpRequest(HttpServletRequest request, AsyncContext context,
|
public TomcatServerHttpRequest(HttpServletRequest request, AsyncContext context,
|
||||||
DataBufferFactory factory, int bufferSize) throws IOException {
|
String servletPath, DataBufferFactory factory, int bufferSize) throws IOException {
|
||||||
|
|
||||||
super(request, context, factory, bufferSize);
|
super(request, context, servletPath, factory, bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class ServerHttpRequestTests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
AsyncContext asyncContext = new MockAsyncContext(request, new MockHttpServletResponse());
|
AsyncContext asyncContext = new MockAsyncContext(request, new MockHttpServletResponse());
|
||||||
return new ServletServerHttpRequest(request, asyncContext, new DefaultDataBufferFactory(), 1024);
|
return new ServletServerHttpRequest(request, asyncContext, "", new DefaultDataBufferFactory(), 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestServletInputStream extends DelegatingServletInputStream {
|
private static class TestServletInputStream extends DelegatingServletInputStream {
|
||||||
|
|
|
@ -35,6 +35,10 @@ public class TomcatHttpServer extends AbstractHttpServer {
|
||||||
|
|
||||||
private final Class<?> wsListener;
|
private final Class<?> wsListener;
|
||||||
|
|
||||||
|
private String contextPath = "";
|
||||||
|
|
||||||
|
private String servletMapping = "/";
|
||||||
|
|
||||||
private Tomcat tomcatServer;
|
private Tomcat tomcatServer;
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,6 +53,15 @@ public class TomcatHttpServer extends AbstractHttpServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setContextPath(String contextPath) {
|
||||||
|
this.contextPath = contextPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServletMapping(String servletMapping) {
|
||||||
|
this.servletMapping = servletMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initServer() throws Exception {
|
protected void initServer() throws Exception {
|
||||||
this.tomcatServer = new Tomcat();
|
this.tomcatServer = new Tomcat();
|
||||||
|
@ -59,9 +72,9 @@ public class TomcatHttpServer extends AbstractHttpServer {
|
||||||
ServletHttpHandlerAdapter servlet = initServletAdapter();
|
ServletHttpHandlerAdapter servlet = initServletAdapter();
|
||||||
|
|
||||||
File base = new File(System.getProperty("java.io.tmpdir"));
|
File base = new File(System.getProperty("java.io.tmpdir"));
|
||||||
Context rootContext = tomcatServer.addContext("", base.getAbsolutePath());
|
Context rootContext = tomcatServer.addContext(this.contextPath, base.getAbsolutePath());
|
||||||
Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
|
Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
|
||||||
rootContext.addServletMappingDecoded("/", "httpHandlerServlet");
|
rootContext.addServletMappingDecoded(this.servletMapping, "httpHandlerServlet");
|
||||||
if (wsListener != null) {
|
if (wsListener != null) {
|
||||||
rootContext.addApplicationListener(wsListener.getName());
|
rootContext.addApplicationListener(wsListener.getName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.web.reactive.result.method.annotation;
|
package org.springframework.web.reactive.result.method.annotation;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -25,6 +27,7 @@ import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.server.reactive.HttpHandler;
|
import org.springframework.http.server.reactive.HttpHandler;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer;
|
import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer;
|
||||||
|
import org.springframework.http.server.reactive.bootstrap.TomcatHttpServer;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
@ -34,76 +37,85 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests that demonstrate running multiple applications under
|
* Integration tests related to the use of context paths.
|
||||||
* different context paths.
|
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
public class ContextPathIntegrationTests {
|
public class ContextPathIntegrationTests {
|
||||||
|
|
||||||
private ReactorHttpServer server;
|
|
||||||
|
|
||||||
|
@Test
|
||||||
@Before
|
public void multipleWebFluxApps() throws Exception {
|
||||||
public void setup() throws Exception {
|
|
||||||
AnnotationConfigApplicationContext context1 = new AnnotationConfigApplicationContext();
|
AnnotationConfigApplicationContext context1 = new AnnotationConfigApplicationContext();
|
||||||
context1.register(WebApp1Config.class);
|
context1.register(WebAppConfig.class);
|
||||||
context1.refresh();
|
context1.refresh();
|
||||||
|
|
||||||
AnnotationConfigApplicationContext context2 = new AnnotationConfigApplicationContext();
|
AnnotationConfigApplicationContext context2 = new AnnotationConfigApplicationContext();
|
||||||
context2.register(WebApp2Config.class);
|
context2.register(WebAppConfig.class);
|
||||||
context2.refresh();
|
context2.refresh();
|
||||||
|
|
||||||
HttpHandler webApp1Handler = WebHttpHandlerBuilder.applicationContext(context1).build();
|
HttpHandler webApp1Handler = WebHttpHandlerBuilder.applicationContext(context1).build();
|
||||||
HttpHandler webApp2Handler = WebHttpHandlerBuilder.applicationContext(context2).build();
|
HttpHandler webApp2Handler = WebHttpHandlerBuilder.applicationContext(context2).build();
|
||||||
|
|
||||||
this.server = new ReactorHttpServer();
|
ReactorHttpServer server = new ReactorHttpServer();
|
||||||
|
server.registerHttpHandler("/webApp1", webApp1Handler);
|
||||||
|
server.registerHttpHandler("/webApp2", webApp2Handler);
|
||||||
|
server.afterPropertiesSet();
|
||||||
|
server.start();
|
||||||
|
|
||||||
this.server.registerHttpHandler("/webApp1", webApp1Handler);
|
try {
|
||||||
this.server.registerHttpHandler("/webApp2", webApp2Handler);
|
|
||||||
|
|
||||||
this.server.afterPropertiesSet();
|
|
||||||
this.server.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void shutdown() throws Exception {
|
|
||||||
this.server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void basic() throws Exception {
|
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
String actual;
|
String actual;
|
||||||
|
|
||||||
actual = restTemplate.getForObject(createUrl("/webApp1/test"), String.class);
|
String url = "http://localhost:" + server.getPort() + "/webApp1/test";
|
||||||
|
actual = restTemplate.getForObject(url, String.class);
|
||||||
assertEquals("Tested in /webApp1", actual);
|
assertEquals("Tested in /webApp1", actual);
|
||||||
|
|
||||||
actual = restTemplate.getForObject(createUrl("/webApp2/test"), String.class);
|
url = "http://localhost:" + server.getPort() + "/webApp2/test";
|
||||||
|
actual = restTemplate.getForObject(url, String.class);
|
||||||
assertEquals("Tested in /webApp2", actual);
|
assertEquals("Tested in /webApp2", actual);
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
private String createUrl(String path) {
|
server.stop();
|
||||||
return "http://localhost:" + this.server.getPort() + path;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void servletPathMapping() throws Exception {
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
context.register(WebAppConfig.class);
|
||||||
|
context.refresh();
|
||||||
|
|
||||||
|
File base = new File(System.getProperty("java.io.tmpdir"));
|
||||||
|
TomcatHttpServer server = new TomcatHttpServer(base.getAbsolutePath());
|
||||||
|
server.setContextPath("/app");
|
||||||
|
server.setServletMapping("/api/*");
|
||||||
|
|
||||||
|
HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(context).build();
|
||||||
|
server.setHandler(httpHandler);
|
||||||
|
|
||||||
|
server.afterPropertiesSet();
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
String actual;
|
||||||
|
|
||||||
|
String url = "http://localhost:" + server.getPort() + "/app/api/test";
|
||||||
|
actual = restTemplate.getForObject(url, String.class);
|
||||||
|
assertEquals("Tested in /app/api", actual);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@EnableWebFlux
|
@EnableWebFlux
|
||||||
@Configuration
|
@Configuration
|
||||||
static class WebApp1Config {
|
static class WebAppConfig {
|
||||||
|
|
||||||
@Bean
|
|
||||||
public TestController testController() {
|
|
||||||
return new TestController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@EnableWebFlux
|
|
||||||
@Configuration
|
|
||||||
static class WebApp2Config {
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public TestController testController() {
|
public TestController testController() {
|
||||||
|
|
Loading…
Reference in New Issue