Improved logging in functional web framework

This commit improves predicate and route logging in the functional web
framework.
This commit is contained in:
Arjen Poutsma 2017-02-17 11:32:30 +01:00
parent 63f261155a
commit 9ab8bd046c
7 changed files with 181 additions and 27 deletions

View File

@ -127,6 +127,11 @@ class DefaultServerRequest implements ServerRequest {
return this.exchange.getAttribute(name);
}
@Override
public Map<String, Object> attributes() {
return this.exchange.getAttributes();
}
@Override
public List<String> queryParams(String name) {
List<String> queryParams = request().getQueryParams().get(name);
@ -152,6 +157,10 @@ class DefaultServerRequest implements ServerRequest {
return this.exchange;
}
@Override
public String toString() {
return String.format("%s %s", method(), path());
}
private class DefaultHeaders implements Headers {
@ -205,6 +214,11 @@ class DefaultServerRequest implements ServerRequest {
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate());
}
@Override
public String toString() {
return delegate().toString();
}
}
}

View File

@ -57,8 +57,13 @@ public interface RequestPredicate {
}
@Override
public ServerRequest subRequest(ServerRequest request) {
return other.subRequest(RequestPredicate.this.subRequest(request));
public ServerRequest nestRequest(ServerRequest request) {
return other.nestRequest(RequestPredicate.this.nestRequest(request));
}
@Override
public String toString() {
return String.format("(%s && %s)", RequestPredicate.this, other);
}
};
}
@ -76,16 +81,47 @@ public interface RequestPredicate {
* Returns a composed request predicate that tests against both this predicate OR the {@code other} predicate.
* When evaluating the composed predicate, if this predicate is {@code true}, then the {@code other} predicate
* is not evaluated.
* @param other a predicate that will be logically-ORed with this predicate
* @return a predicate composed of this predicate OR the {@code other} predicate
*/
default RequestPredicate or(RequestPredicate other) {
Assert.notNull(other, "'other' must not be null");
return (t) -> test(t) || other.test(t);
return new RequestPredicate() {
@Override
public boolean test(ServerRequest t) {
return RequestPredicate.this.test(t) || other.test(t);
}
default ServerRequest subRequest(ServerRequest request) {
@Override
public ServerRequest nestRequest(ServerRequest request) {
if (RequestPredicate.this.test(request)) {
return RequestPredicate.this.nestRequest(request);
}
else if (other.test(request)) {
return other.nestRequest(request);
}
else {
throw new IllegalStateException("Neither " + RequestPredicate.this.toString() +
" nor " + other + "matches");
}
}
@Override
public String toString() {
return String.format("(%s || %s)", RequestPredicate.this, other);
}
};
}
/**
* Transforms the given request into a request used for a nested route. For instance, a
* path-based predicate can return a {@code ServerRequest} with a nested path.
* <p>Default implementation returns the given path.
* @param request the request to be nested
* @return the nested request
* @see RouterFunctions#nest(RequestPredicate, RouterFunction)
*/
default ServerRequest nestRequest(ServerRequest request) {
return request;
}
}

View File

@ -26,6 +26,8 @@ import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -33,6 +35,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.server.WebSession;
import org.springframework.web.util.UriUtils;
@ -48,6 +51,8 @@ import org.springframework.web.util.patterns.PathPatternParser;
*/
public abstract class RequestPredicates {
private static final Log logger = LogFactory.getLog(RequestPredicates.class);
private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser();
/**
@ -119,11 +124,21 @@ public abstract class RequestPredicates {
public static RequestPredicate contentType(MediaType... mediaTypes) {
Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
Set<MediaType> mediaTypeSet = new HashSet<>(Arrays.asList(mediaTypes));
return headers(headers -> {
MediaType contentType = headers.contentType().orElse(MediaType.APPLICATION_OCTET_STREAM);
return mediaTypeSet.stream()
return headers(new Predicate<ServerRequest.Headers>() {
@Override
public boolean test(ServerRequest.Headers headers) {
MediaType contentType =
headers.contentType().orElse(MediaType.APPLICATION_OCTET_STREAM);
boolean match = mediaTypeSet.stream()
.anyMatch(mediaType -> mediaType.includes(contentType));
traceMatch("Content-Type", mediaTypeSet, contentType, match);
return match;
}
@Override
public String toString() {
return String.format("Content-Type: %s", mediaTypeSet);
}
});
}
@ -138,12 +153,23 @@ public abstract class RequestPredicates {
public static RequestPredicate accept(MediaType... mediaTypes) {
Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
Set<MediaType> mediaTypeSet = new HashSet<>(Arrays.asList(mediaTypes));
return headers(headers -> {
return headers(new Predicate<ServerRequest.Headers>() {
@Override
public boolean test(ServerRequest.Headers headers) {
List<MediaType> acceptedMediaTypes = headers.accept();
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
return acceptedMediaTypes.stream()
boolean match = acceptedMediaTypes.stream()
.anyMatch(acceptedMediaType -> mediaTypeSet.stream()
.anyMatch(acceptedMediaType::isCompatibleWith));
traceMatch("Accept", mediaTypeSet, acceptedMediaTypes, match);
return match;
}
@Override
public String toString() {
return String.format("Accept: %s", mediaTypeSet);
}
});
}
@ -238,7 +264,11 @@ public abstract class RequestPredicates {
*/
public static RequestPredicate pathExtension(String extension) {
Assert.notNull(extension, "'extension' must not be null");
return pathExtension(extension::equalsIgnoreCase);
return pathExtension(pathExtension -> {
boolean match = extension.equalsIgnoreCase(pathExtension);
traceMatch("Extension", extension, pathExtension, match);
return match;
});
}
/**
@ -289,6 +319,14 @@ public abstract class RequestPredicates {
};
}
private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {
if (logger.isTraceEnabled()) {
String message = String.format("%s \"%s\" %s against value \"%s\"",
prefix, desired, match ? "matches" : "does not match", actual);
logger.trace(message);
}
}
private static class HttpMethodPredicate implements RequestPredicate {
@ -301,7 +339,14 @@ public abstract class RequestPredicates {
@Override
public boolean test(ServerRequest request) {
return this.httpMethod == request.method();
boolean match = this.httpMethod == request.method();
traceMatch("Method", this.httpMethod, request.method(), match);
return match;
}
@Override
public String toString() {
return this.httpMethod.toString();
}
}
@ -317,12 +362,11 @@ public abstract class RequestPredicates {
@Override
public boolean test(ServerRequest request) {
String path = request.path();
if (this.pattern.matches(path)) {
if (request instanceof DefaultServerRequest) {
DefaultServerRequest defaultRequest = (DefaultServerRequest) request;
boolean match = this.pattern.matches(path);
traceMatch("Pattern", this.pattern.getPatternString(), path, match);
if (match) {
Map<String, String> uriTemplateVariables = this.pattern.matchAndExtract(path);
defaultRequest.exchange().getAttributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
}
request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
return true;
}
else {
@ -331,11 +375,19 @@ public abstract class RequestPredicates {
}
@Override
public ServerRequest subRequest(ServerRequest request) {
public ServerRequest nestRequest(ServerRequest request) {
String requestPath = request.path();
String subPath = this.pattern.extractPathWithinPattern(requestPath);
if (!subPath.startsWith("/")) {
subPath = "/" + subPath;
}
return new SubPathServerRequestWrapper(request, subPath);
}
@Override
public String toString() {
return this.pattern.getPatternString();
}
}
private static class HeadersPredicate implements RequestPredicate {
@ -351,6 +403,11 @@ public abstract class RequestPredicates {
public boolean test(ServerRequest request) {
return this.headersPredicate.test(request.headers());
}
@Override
public String toString() {
return this.headersPredicate.toString();
}
}
private static class SubPathServerRequestWrapper implements ServerRequest {
@ -409,6 +466,11 @@ public abstract class RequestPredicates {
return this.request.attribute(name);
}
@Override
public Map<String, Object> attributes() {
return this.request.attributes();
}
@Override
public Optional<String> queryParam(String name) {
return this.request.queryParam(name);
@ -433,5 +495,11 @@ public abstract class RequestPredicates {
public Mono<WebSession> session() {
return this.request.session();
}
@Override
public String toString() {
return String.format("%s %s", method(), path());
}
}
}

View File

@ -19,6 +19,8 @@ package org.springframework.web.reactive.function.server;
import java.util.Map;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
@ -52,6 +54,8 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
*/
public abstract class RouterFunctions {
private static final Log logger = LogFactory.getLog(RouterFunctions.class);
/**
* Name of the {@link ServerWebExchange} attribute that contains the {@link ServerRequest}.
*/
@ -90,7 +94,18 @@ public abstract class RouterFunctions {
Assert.notNull(predicate, "'predicate' must not be null");
Assert.notNull(handlerFunction, "'handlerFunction' must not be null");
return request -> predicate.test(request) ? Mono.just(handlerFunction) : Mono.empty();
return request -> {
if (predicate.test(request)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Predicate \"%s\" matches against \"%s\"",
predicate, request));
}
return Mono.just(handlerFunction);
}
else {
return Mono.empty();
}
};
}
/**
@ -124,7 +139,11 @@ public abstract class RouterFunctions {
return request -> {
if (predicate.test(request)) {
ServerRequest subRequest = predicate.subRequest(request);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Nested predicate \"%s\" matches against \"%s\"",
predicate, request));
}
ServerRequest subRequest = predicate.nestRequest(request);
return routerFunction.route(subRequest);
}
else {

View File

@ -113,6 +113,12 @@ public interface ServerRequest {
*/
<T> Optional<T> attribute(String name);
/**
* Return a mutable map of request attributes.
* @return the request attributes
*/
Map<String, Object> attributes();
/**
* Return the first query parameter with the given name, if present.
* @param name the parameter name

View File

@ -114,6 +114,11 @@ public class ServerRequestWrapper implements ServerRequest {
return this.delegate.attribute(name);
}
@Override
public Map<String, Object> attributes() {
return this.delegate.attributes();
}
@Override
public Optional<String> queryParam(String name) {
return this.delegate.queryParam(name);

View File

@ -29,6 +29,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.concurrent.ConcurrentHashMap;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -127,6 +128,11 @@ public class MockServerRequest implements ServerRequest {
return Optional.ofNullable((S) this.attributes.get(name));
}
@Override
public Map<String, Object> attributes() {
return this.attributes;
}
@Override
public List<String> queryParams(String name) {
return Collections.unmodifiableList(this.queryParams.get(name));
@ -182,7 +188,7 @@ public class MockServerRequest implements ServerRequest {
private Object body;
private Map<String, Object> attributes = new LinkedHashMap<>();
private Map<String, Object> attributes = new ConcurrentHashMap<>();
private MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();