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 @@ |
|||||||
|
|
||||||
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 @@ |
|||||||
|
|
||||||
|
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 @@ |
|||||||
/** |
/** |
||||||
* 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; |
package org.springframework.web.reactive; |
||||||
|
|||||||
Loading…
Reference in new issue