Browse Source
CompositeHttpHandler is public and called ContextPathCompositeHandler. Also an overhaul of the Javadoc on HttpHandler, WebHttpHandlerAdapter, and ContextPathCompositeHandler.pull/1356/merge
16 changed files with 169 additions and 165 deletions
@ -1,73 +0,0 @@
@@ -1,73 +0,0 @@
|
||||
|
||||
package org.springframework.http.server.reactive; |
||||
|
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
/** |
||||
* Composite HttpHandler that selects the handler to use by context path. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
class CompositeHttpHandler implements HttpHandler { |
||||
|
||||
private final Map<String, HttpHandler> handlerMap; |
||||
|
||||
public CompositeHttpHandler(Map<String, ? extends HttpHandler> handlerMap) { |
||||
Assert.notEmpty(handlerMap, "Handler map must not be empty"); |
||||
this.handlerMap = initHandlerMap(handlerMap); |
||||
} |
||||
|
||||
private static Map<String, HttpHandler> initHandlerMap( |
||||
Map<String, ? extends HttpHandler> inputMap) { |
||||
inputMap.keySet().stream().forEach(CompositeHttpHandler::validateContextPath); |
||||
return new LinkedHashMap<>(inputMap); |
||||
} |
||||
|
||||
private static void validateContextPath(String contextPath) { |
||||
Assert.hasText(contextPath, "Context path must not be empty"); |
||||
if (!contextPath.equals("/")) { |
||||
Assert.isTrue(contextPath.startsWith("/"), |
||||
"Context path must begin with '/'"); |
||||
Assert.isTrue(!contextPath.endsWith("/"), |
||||
"Context path must not end with '/'"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) { |
||||
String path = getPathToUse(request); |
||||
return this.handlerMap.entrySet().stream().filter( |
||||
entry -> path.startsWith(entry.getKey())).findFirst().map(entry -> { |
||||
// Preserve "native" contextPath from underlying request..
|
||||
String contextPath = request.getContextPath() + entry.getKey(); |
||||
ServerHttpRequest mutatedRequest = request.mutate().contextPath( |
||||
contextPath).build(); |
||||
HttpHandler handler = entry.getValue(); |
||||
return handler.handle(mutatedRequest, response); |
||||
}).orElseGet(() -> { |
||||
response.setStatusCode(HttpStatus.NOT_FOUND); |
||||
response.setComplete(); |
||||
return Mono.empty(); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Strip the context path from the native request, if any. |
||||
*/ |
||||
private String getPathToUse(ServerHttpRequest request) { |
||||
String path = request.getURI().getRawPath(); |
||||
String contextPath = request.getContextPath(); |
||||
if (!StringUtils.hasText(contextPath)) { |
||||
return path; |
||||
} |
||||
int contextLength = contextPath.length(); |
||||
return (path.length() > contextLength ? path.substring(contextLength) : ""); |
||||
} |
||||
} |
||||
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
|
||||
package org.springframework.http.server.reactive; |
||||
|
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
/** |
||||
* {@code HttpHandler} delegating requests to one of several {@code HttpHandler}'s |
||||
* based on simple, prefix-based mappings. |
||||
* |
||||
* <p>This is intended as a coarse-grained mechanism for delegating requests to |
||||
* one of several applications -- each represented by an {@code HttpHandler}, with |
||||
* the application "context path" (the prefix-based mapping) exposed via |
||||
* {@link ServerHttpRequest#getContextPath()}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.0 |
||||
*/ |
||||
public class ContextPathCompositeHandler implements HttpHandler { |
||||
|
||||
private final Map<String, HttpHandler> handlerMap; |
||||
|
||||
|
||||
public ContextPathCompositeHandler(Map<String, ? extends HttpHandler> handlerMap) { |
||||
Assert.notEmpty(handlerMap, "Handler map must not be empty"); |
||||
this.handlerMap = initHandlers(handlerMap); |
||||
} |
||||
|
||||
private static Map<String, HttpHandler> initHandlers(Map<String, ? extends HttpHandler> map) { |
||||
map.keySet().forEach(ContextPathCompositeHandler::assertValidContextPath); |
||||
return new LinkedHashMap<>(map); |
||||
} |
||||
|
||||
private static void assertValidContextPath(String contextPath) { |
||||
Assert.hasText(contextPath, "Context path must not be empty"); |
||||
if (contextPath.equals("/")) { |
||||
return; |
||||
} |
||||
Assert.isTrue(contextPath.startsWith("/"), "Context path must begin with '/'"); |
||||
Assert.isTrue(!contextPath.endsWith("/"), "Context path must not end with '/'"); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) { |
||||
String path = getPathWithinApplication(request); |
||||
return this.handlerMap.entrySet().stream() |
||||
.filter(entry -> path.startsWith(entry.getKey())) |
||||
.findFirst() |
||||
.map(entry -> { |
||||
String contextPath = request.getContextPath() + entry.getKey(); |
||||
ServerHttpRequest newRequest = request.mutate().contextPath(contextPath).build(); |
||||
return entry.getValue().handle(newRequest, response); |
||||
}) |
||||
.orElseGet(() -> { |
||||
response.setStatusCode(HttpStatus.NOT_FOUND); |
||||
response.setComplete(); |
||||
return Mono.empty(); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Get the path within the "native" context path of the underlying server, |
||||
* for example when running on a Servlet container. |
||||
*/ |
||||
private String getPathWithinApplication(ServerHttpRequest request) { |
||||
String path = request.getURI().getRawPath(); |
||||
String contextPath = request.getContextPath(); |
||||
if (!StringUtils.hasText(contextPath)) { |
||||
return path; |
||||
} |
||||
int length = contextPath.length(); |
||||
return (path.length() > length ? path.substring(length) : ""); |
||||
} |
||||
|
||||
} |
||||
@ -1,4 +1,12 @@
@@ -1,4 +1,12 @@
|
||||
/** |
||||
* Core interfaces and classes for Spring Web Reactive. |
||||
* Top-level package for the {@code spring-webflux} module that contains |
||||
* {@link org.springframework.web.reactive.DispatcherHandler}, the main entry |
||||
* point for WebFlux server endpoint processing including key contracts used to |
||||
* map requests to handlers, invoke them, and process the result. |
||||
* |
||||
* <p>The module provides two programming models for reactive server endpoints. |
||||
* One based on annotated {@code @Controller}'s and another based on functional |
||||
* routing and handling. The module also contains a functional, reactive |
||||
* {@code WebClient} as well as client and server, reactive WebSocket support. |
||||
*/ |
||||
package org.springframework.web.reactive; |
||||
|
||||
Loading…
Reference in new issue