提交 97c2de91 编写于 作者: R Rossen Stoyanchev

Add RouteMatcher

Closes gh-22642
上级 afc0ae37
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util;
import java.util.Comparator;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* Contract for matching routes to patterns.
*
* <p>Equivalent to {@link PathMatcher}, but enables use of parsed
* representations of routes and patterns for efficiency reasons in scenarios
* where routes from incoming messages are continuously matched against a
* large number of message handler patterns.
*
* @author Rossen Stoyanchev
* @since 5.2
*/
public interface RouteMatcher {
/**
* Return a parsed representation of the given route.
* @param routeValue the route to parse
* @return the parsed representation of the route
*/
Route parseRoute(String routeValue);
/**
* Whether the given {@code route} contains pattern syntax which requires
* the {@link #match(String, Route)} method, or if it is a regular String
* that could be compared directly to others.
* @param route the route to check
* @return {@code true} if the given {@code route} represents a pattern
*/
boolean isPattern(String route);
/**
* Combines two patterns into a single pattern.
* @param pattern1 the first pattern
* @param pattern2 the second pattern
* @return the combination of the two patterns
* @throws IllegalArgumentException when the two patterns cannot be combined
*/
String combine(String pattern1, String pattern2);
/**
* Match the given route against the given pattern.
* @param pattern the pattern to try to match
* @param route the route to test against
* @return {@code true} if there is a match, {@code false} otherwise
*/
boolean match(String pattern, Route route);
/**
* Match the pattern to the route and extract template variables.
* @param pattern the pattern, possibly containing templates variables
* @param route the route to extract template variables from
* @return a map with template variables and values
*/
@Nullable
Map<String, String> matchAndExtract(String pattern, Route route);
/**
* Given a route, return a {@link Comparator} suitable for sorting patterns
* in order of explicitness for that route, so that more specific patterns
* come before more generic ones.
* @param route the full path to use for comparison
* @return a comparator capable of sorting patterns in order of explicitness
*/
Comparator<String> getPatternComparator(Route route);
/**
* A parsed representation of a route.
*/
interface Route {
/**
* The original route value.
*/
String value();
}
}
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util;
import java.util.Comparator;
import java.util.Map;
import org.springframework.lang.Nullable;
/**
* {@code RouteMatcher} that delegates to a {@link PathMatcher}.
*
* <p><strong>Note:</strong> This implementation is not efficient since
* {@code PathMatcher} treats paths and patterns as Strings. For more optimized
* performance use the {@code PathPatternRouteMatcher} from {@code spring-web}
* which enables use of parsed routes and patterns.
*
* @author Rossen Stoyanchev
* @since 5.2
*/
public class SimpleRouteMatcher implements RouteMatcher {
private final PathMatcher pathMatcher;
public SimpleRouteMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher is required");
this.pathMatcher = pathMatcher;
}
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
@Override
public Route parseRoute(String route) {
return new DefaultRoute(route);
}
@Override
public boolean isPattern(String route) {
return this.pathMatcher.isPattern(route);
}
@Override
public String combine(String pattern1, String pattern2) {
return this.pathMatcher.combine(pattern1, pattern2);
}
@Override
public boolean match(String pattern, Route route) {
return this.pathMatcher.match(pattern, route.value());
}
@Override
@Nullable
public Map<String, String> matchAndExtract(String pattern, Route route) {
if (!match(pattern, route)) {
return null;
}
return this.pathMatcher.extractUriTemplateVariables(pattern, route.value());
}
@Override
public Comparator<String> getPatternComparator(Route route) {
return this.pathMatcher.getPatternComparator(route.value());
}
private static class DefaultRoute implements Route {
private final String path;
DefaultRoute(String path) {
this.path = path;
}
@Override
public String value() {
return this.path;
}
}
}
\ No newline at end of file
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
package org.springframework.messaging.handler; package org.springframework.messaging.handler;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
...@@ -29,12 +28,15 @@ import java.util.Set; ...@@ -29,12 +28,15 @@ import java.util.Set;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.util.RouteMatcher;
import org.springframework.util.SimpleRouteMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* A {@link MessageCondition} for matching the destination of a Message * {@link MessageCondition} to match the destination header of a Message
* against one or more destination patterns using a {@link PathMatcher}. * against one or more patterns through a {@link RouteMatcher}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
...@@ -50,36 +52,41 @@ public class DestinationPatternsMessageCondition ...@@ -50,36 +52,41 @@ public class DestinationPatternsMessageCondition
private final Set<String> patterns; private final Set<String> patterns;
private final PathMatcher pathMatcher; private final RouteMatcher routeMatcher;
/** /**
* Creates a new instance with the given destination patterns. * Constructor with patterns only. Creates and uses an instance of
* Each pattern that is not empty and does not start with "/" is prepended with "/". * {@link AntPathMatcher} with default settings.
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request. * <p>Non-empty patterns that don't start with "/" are prepended with "/".
* @param patterns the URL patterns to match to, or if 0 then always match
*/ */
public DestinationPatternsMessageCondition(String... patterns) { public DestinationPatternsMessageCondition(String... patterns) {
this(patterns, null); this(patterns, (PathMatcher) null);
} }
/** /**
* Alternative constructor accepting a custom PathMatcher. * Constructor with patterns and a {@code PathMatcher} instance.
* @param patterns the URL patterns to use; if 0, the condition will match to every request. * @param patterns the URL patterns to match to, or if 0 then always match
* @param pathMatcher the PathMatcher to use * @param matcher the {@code PathMatcher} to use
*/ */
public DestinationPatternsMessageCondition(String[] patterns, @Nullable PathMatcher pathMatcher) { public DestinationPatternsMessageCondition(String[] patterns, @Nullable PathMatcher matcher) {
this(Arrays.asList(patterns), pathMatcher); this(patterns, new SimpleRouteMatcher(matcher != null ? matcher : new AntPathMatcher()));
} }
private DestinationPatternsMessageCondition(Collection<String> patterns, @Nullable PathMatcher pathMatcher) { /**
this.pathMatcher = (pathMatcher != null ? pathMatcher : new AntPathMatcher()); * Constructor with patterns and a {@code RouteMatcher} instance.
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns, this.pathMatcher)); * @param patterns the URL patterns to match to, or if 0 then always match
* @param routeMatcher the {@code RouteMatcher} to use
* @since 5.2
*/
public DestinationPatternsMessageCondition(String[] patterns, RouteMatcher routeMatcher) {
this(Collections.unmodifiableSet(prependLeadingSlash(patterns, routeMatcher)), routeMatcher);
} }
private static Set<String> prependLeadingSlash(String[] patterns, RouteMatcher routeMatcher) {
private static Set<String> prependLeadingSlash(Collection<String> patterns, PathMatcher pathMatcher) { boolean slashSeparator = routeMatcher.combine("a", "a").equals("a/a");
boolean slashSeparator = pathMatcher.combine("a", "a").equals("a/a"); Set<String> result = new LinkedHashSet<>(patterns.length);
Set<String> result = new LinkedHashSet<>(patterns.size());
for (String pattern : patterns) { for (String pattern : patterns) {
if (slashSeparator && StringUtils.hasLength(pattern) && !pattern.startsWith("/")) { if (slashSeparator && StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern; pattern = "/" + pattern;
...@@ -89,6 +96,12 @@ public class DestinationPatternsMessageCondition ...@@ -89,6 +96,12 @@ public class DestinationPatternsMessageCondition
return result; return result;
} }
private DestinationPatternsMessageCondition(Set<String> patterns, RouteMatcher routeMatcher) {
this.patterns = patterns;
this.routeMatcher = routeMatcher;
}
public Set<String> getPatterns() { public Set<String> getPatterns() {
return this.patterns; return this.patterns;
...@@ -121,7 +134,7 @@ public class DestinationPatternsMessageCondition ...@@ -121,7 +134,7 @@ public class DestinationPatternsMessageCondition
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) { if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
for (String pattern1 : this.patterns) { for (String pattern1 : this.patterns) {
for (String pattern2 : other.patterns) { for (String pattern2 : other.patterns) {
result.add(this.pathMatcher.combine(pattern1, pattern2)); result.add(this.routeMatcher.combine(pattern1, pattern2));
} }
} }
} }
...@@ -134,7 +147,7 @@ public class DestinationPatternsMessageCondition ...@@ -134,7 +147,7 @@ public class DestinationPatternsMessageCondition
else { else {
result.add(""); result.add("");
} }
return new DestinationPatternsMessageCondition(result, this.pathMatcher); return new DestinationPatternsMessageCondition(result, this.routeMatcher);
} }
/** /**
...@@ -149,7 +162,7 @@ public class DestinationPatternsMessageCondition ...@@ -149,7 +162,7 @@ public class DestinationPatternsMessageCondition
@Override @Override
@Nullable @Nullable
public DestinationPatternsMessageCondition getMatchingCondition(Message<?> message) { public DestinationPatternsMessageCondition getMatchingCondition(Message<?> message) {
String destination = (String) message.getHeaders().get(LOOKUP_DESTINATION_HEADER); Object destination = message.getHeaders().get(LOOKUP_DESTINATION_HEADER);
if (destination == null) { if (destination == null) {
return null; return null;
} }
...@@ -157,18 +170,33 @@ public class DestinationPatternsMessageCondition ...@@ -157,18 +170,33 @@ public class DestinationPatternsMessageCondition
return this; return this;
} }
List<String> matches = new ArrayList<>(); List<String> matches = null;
for (String pattern : this.patterns) { for (String pattern : this.patterns) {
if (pattern.equals(destination) || this.pathMatcher.match(pattern, destination)) { if (pattern.equals(destination) || matchPattern(pattern, destination)) {
if (matches == null) {
matches = new ArrayList<>();
}
matches.add(pattern); matches.add(pattern);
} }
} }
if (matches.isEmpty()) { if (CollectionUtils.isEmpty(matches)) {
return null; return null;
} }
matches.sort(this.pathMatcher.getPatternComparator(destination)); matches.sort(getPatternComparator(destination));
return new DestinationPatternsMessageCondition(matches, this.pathMatcher); return new DestinationPatternsMessageCondition(new LinkedHashSet<>(matches), this.routeMatcher);
}
private boolean matchPattern(String pattern, Object destination) {
return destination instanceof RouteMatcher.Route ?
this.routeMatcher.match(pattern, (RouteMatcher.Route) destination) :
((SimpleRouteMatcher) this.routeMatcher).getPathMatcher().match(pattern, (String) destination);
}
private Comparator<String> getPatternComparator(Object destination) {
return destination instanceof RouteMatcher.Route ?
this.routeMatcher.getPatternComparator((RouteMatcher.Route) destination) :
((SimpleRouteMatcher) this.routeMatcher).getPathMatcher().getPatternComparator((String) destination);
} }
/** /**
...@@ -183,12 +211,12 @@ public class DestinationPatternsMessageCondition ...@@ -183,12 +211,12 @@ public class DestinationPatternsMessageCondition
*/ */
@Override @Override
public int compareTo(DestinationPatternsMessageCondition other, Message<?> message) { public int compareTo(DestinationPatternsMessageCondition other, Message<?> message) {
String destination = (String) message.getHeaders().get(LOOKUP_DESTINATION_HEADER); Object destination = message.getHeaders().get(LOOKUP_DESTINATION_HEADER);
if (destination == null) { if (destination == null) {
return 0; return 0;
} }
Comparator<String> patternComparator = this.pathMatcher.getPatternComparator(destination); Comparator<String> patternComparator = getPatternComparator(destination);
Iterator<String> iterator = this.patterns.iterator(); Iterator<String> iterator = this.patterns.iterator();
Iterator<String> iteratorOther = other.patterns.iterator(); Iterator<String> iteratorOther = other.patterns.iterator();
while (iterator.hasNext() && iteratorOther.hasNext()) { while (iterator.hasNext() && iteratorOther.hasNext()) {
......
...@@ -56,7 +56,8 @@ import org.springframework.stereotype.Controller; ...@@ -56,7 +56,8 @@ import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher; import org.springframework.util.RouteMatcher;
import org.springframework.util.SimpleRouteMatcher;
import org.springframework.util.StringValueResolver; import org.springframework.util.StringValueResolver;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
...@@ -91,7 +92,7 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C ...@@ -91,7 +92,7 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C
@Nullable @Nullable
private Validator validator; private Validator validator;
private PathMatcher pathMatcher; private RouteMatcher routeMatcher;
private ConversionService conversionService = new DefaultFormattingConversionService(); private ConversionService conversionService = new DefaultFormattingConversionService();
...@@ -100,8 +101,9 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C ...@@ -100,8 +101,9 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C
public MessageMappingMessageHandler() { public MessageMappingMessageHandler() {
this.pathMatcher = new AntPathMatcher(); AntPathMatcher pathMatcher = new AntPathMatcher();
((AntPathMatcher) this.pathMatcher).setPathSeparator("."); pathMatcher.setPathSeparator(".");
this.routeMatcher = new SimpleRouteMatcher(pathMatcher);
} }
...@@ -187,20 +189,23 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C ...@@ -187,20 +189,23 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C
} }
/** /**
* Set the PathMatcher implementation to use for matching destinations * Set the {@code RouteMatcher} to use for mapping messages to handlers
* against configured destination patterns. * based on the route patterns they're configured with.
* <p>By default, {@link AntPathMatcher} is used with separator set to ".". * <p>By default, {@link SimpleRouteMatcher} is used, backed by
* {@link AntPathMatcher} with "." as separator. For greater
* efficiency consider using the {@code PathPatternRouteMatcher} from
* {@code spring-web} instead.
*/ */
public void setPathMatcher(PathMatcher pathMatcher) { public void setRouteMatcher(RouteMatcher routeMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null"); Assert.notNull(routeMatcher, "RouteMatcher must not be null");
this.pathMatcher = pathMatcher; this.routeMatcher = routeMatcher;
} }
/** /**
* Return the PathMatcher implementation to use for matching destinations. * Return the {@code RouteMatcher} used to map messages to handlers.
*/ */
public PathMatcher getPathMatcher() { public RouteMatcher getRouteMatcher() {
return this.pathMatcher; return this.routeMatcher;
} }
/** /**
...@@ -289,14 +294,15 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C ...@@ -289,14 +294,15 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C
.map(s -> this.valueResolver.resolveStringValue(s)) .map(s -> this.valueResolver.resolveStringValue(s))
.toArray(String[]::new); .toArray(String[]::new);
} }
return new CompositeMessageCondition(new DestinationPatternsMessageCondition(destinations, this.pathMatcher)); return new CompositeMessageCondition(
new DestinationPatternsMessageCondition(destinations, this.routeMatcher));
} }
@Override @Override
protected Set<String> getDirectLookupMappings(CompositeMessageCondition mapping) { protected Set<String> getDirectLookupMappings(CompositeMessageCondition mapping) {
Set<String> result = new LinkedHashSet<>(); Set<String> result = new LinkedHashSet<>();
for (String pattern : mapping.getCondition(DestinationPatternsMessageCondition.class).getPatterns()) { for (String pattern : mapping.getCondition(DestinationPatternsMessageCondition.class).getPatterns()) {
if (!this.pathMatcher.isPattern(pattern)) { if (!this.routeMatcher.isPattern(pattern)) {
result.add(pattern); result.add(pattern);
} }
} }
...@@ -304,8 +310,9 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C ...@@ -304,8 +310,9 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C
} }
@Override @Override
protected String getDestination(Message<?> message) { protected RouteMatcher.Route getDestination(Message<?> message) {
return (String) message.getHeaders().get(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER); return (RouteMatcher.Route) message.getHeaders()
.get(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER);
} }
@Override @Override
...@@ -324,13 +331,15 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C ...@@ -324,13 +331,15 @@ public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<C
} }
@Override @Override
protected Mono<Void> handleMatch(CompositeMessageCondition mapping, HandlerMethod handlerMethod, Message<?> message) { protected Mono<Void> handleMatch(
CompositeMessageCondition mapping, HandlerMethod handlerMethod, Message<?> message) {
Set<String> patterns = mapping.getCondition(DestinationPatternsMessageCondition.class).getPatterns(); Set<String> patterns = mapping.getCondition(DestinationPatternsMessageCondition.class).getPatterns();
if (!CollectionUtils.isEmpty(patterns)) { if (!CollectionUtils.isEmpty(patterns)) {
String pattern = patterns.iterator().next(); String pattern = patterns.iterator().next();
String destination = getDestination(message); RouteMatcher.Route destination = getDestination(message);
Assert.state(destination != null, "Missing destination header"); Assert.state(destination != null, "Missing destination header");
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(pattern, destination); Map<String, String> vars = getRouteMatcher().matchAndExtract(pattern, destination);
if (!CollectionUtils.isEmpty(vars)) { if (!CollectionUtils.isEmpty(vars)) {
MessageHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class); MessageHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class);
Assert.state(mha != null && mha.isMutable(), "Mutable MessageHeaderAccessor required"); Assert.state(mha != null && mha.isMutable(), "Mutable MessageHeaderAccessor required");
......
...@@ -53,6 +53,7 @@ import org.springframework.util.CollectionUtils; ...@@ -53,6 +53,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.RouteMatcher;
/** /**
* Abstract base class for reactive HandlerMethod-based message handling. * Abstract base class for reactive HandlerMethod-based message handling.
...@@ -393,8 +394,8 @@ public abstract class AbstractMethodMessageHandler<T> ...@@ -393,8 +394,8 @@ public abstract class AbstractMethodMessageHandler<T>
private Match<T> getHandlerMethod(Message<?> message) { private Match<T> getHandlerMethod(Message<?> message) {
List<Match<T>> matches = new ArrayList<>(); List<Match<T>> matches = new ArrayList<>();
String destination = getDestination(message); RouteMatcher.Route destination = getDestination(message);
List<T> mappingsByUrl = destination != null ? this.destinationLookup.get(destination) : null; List<T> mappingsByUrl = destination != null ? this.destinationLookup.get(destination.value()) : null;
if (mappingsByUrl != null) { if (mappingsByUrl != null) {
addMatchesToCollection(mappingsByUrl, message, matches); addMatchesToCollection(mappingsByUrl, message, matches);
} }
...@@ -418,23 +419,21 @@ public abstract class AbstractMethodMessageHandler<T> ...@@ -418,23 +419,21 @@ public abstract class AbstractMethodMessageHandler<T>
if (comparator.compare(bestMatch, secondBestMatch) == 0) { if (comparator.compare(bestMatch, secondBestMatch) == 0) {
HandlerMethod m1 = bestMatch.handlerMethod; HandlerMethod m1 = bestMatch.handlerMethod;
HandlerMethod m2 = secondBestMatch.handlerMethod; HandlerMethod m2 = secondBestMatch.handlerMethod;
throw new IllegalStateException("Ambiguous handler methods mapped for destination '" + throw new IllegalStateException(
destination + "': {" + m1.getShortLogMessage() + ", " + m2.getShortLogMessage() + "}"); "Ambiguous handler methods mapped for destination '" +
destination.value() + "': {" +
m1.getShortLogMessage() + ", " + m2.getShortLogMessage() + "}");
} }
} }
return bestMatch; return bestMatch;
} }
/** /**
* Extract a String-based destination, if any, that can be used to perform * Extract the destination from the given message.
* a direct look up into the registered mappings.
* <p><strong>Note:</strong> This is completely optional. The mapping
* metadata for a sub-class may support neither direct lookups, nor String
* based destinations.
* @see #getDirectLookupMappings(Object) * @see #getDirectLookupMappings(Object)
*/ */
@Nullable @Nullable
protected abstract String getDestination(Message<?> message); protected abstract RouteMatcher.Route getDestination(Message<?> message);
private void addMatchesToCollection( private void addMatchesToCollection(
Collection<T> mappingsToCheck, Message<?> message, List<Match<T>> matches) { Collection<T> mappingsToCheck, Message<?> message, List<Match<T>> matches) {
...@@ -470,8 +469,9 @@ public abstract class AbstractMethodMessageHandler<T> ...@@ -470,8 +469,9 @@ public abstract class AbstractMethodMessageHandler<T>
* @param destination the destination * @param destination the destination
* @param message the message * @param message the message
*/ */
protected void handleNoMatch(@Nullable String destination, Message<?> message) { protected void handleNoMatch(@Nullable RouteMatcher.Route destination, Message<?> message) {
logger.debug("No handlers for destination '" + destination + "'"); logger.debug("No handlers for destination '" +
(destination != null ? destination.value() : "") + "'");
} }
/** /**
......
...@@ -73,6 +73,7 @@ public final class MessageHandlerAcceptor extends RSocketMessageHandler ...@@ -73,6 +73,7 @@ public final class MessageHandlerAcceptor extends RSocketMessageHandler
private MessagingRSocket createRSocket(RSocket rsocket) { private MessagingRSocket createRSocket(RSocket rsocket) {
return new MessagingRSocket(this::handleMessage, return new MessagingRSocket(this::handleMessage,
route -> getRouteMatcher().parseRoute(route),
RSocketRequester.wrap(rsocket, this.defaultDataMimeType, getRSocketStrategies()), RSocketRequester.wrap(rsocket, this.defaultDataMimeType, getRSocketStrategies()),
this.defaultDataMimeType, this.defaultDataMimeType,
getRSocketStrategies().dataBufferFactory()); getRSocketStrategies().dataBufferFactory());
......
...@@ -42,6 +42,7 @@ import org.springframework.messaging.support.MessageHeaderAccessor; ...@@ -42,6 +42,7 @@ import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils; import org.springframework.util.MimeTypeUtils;
import org.springframework.util.RouteMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
...@@ -56,6 +57,8 @@ class MessagingRSocket extends AbstractRSocket { ...@@ -56,6 +57,8 @@ class MessagingRSocket extends AbstractRSocket {
private final Function<Message<?>, Mono<Void>> handler; private final Function<Message<?>, Mono<Void>> handler;
private final Function<String, RouteMatcher.Route> routeParser;
private final RSocketRequester requester; private final RSocketRequester requester;
@Nullable @Nullable
...@@ -64,10 +67,13 @@ class MessagingRSocket extends AbstractRSocket { ...@@ -64,10 +67,13 @@ class MessagingRSocket extends AbstractRSocket {
private final DataBufferFactory bufferFactory; private final DataBufferFactory bufferFactory;
MessagingRSocket(Function<Message<?>, Mono<Void>> handler, RSocketRequester requester, MessagingRSocket(Function<Message<?>, Mono<Void>> handler,
Function<String, RouteMatcher.Route> routeParser, RSocketRequester requester,
@Nullable MimeType defaultDataMimeType, DataBufferFactory bufferFactory) { @Nullable MimeType defaultDataMimeType, DataBufferFactory bufferFactory) {
this.routeParser = routeParser;
Assert.notNull(handler, "'handler' is required"); Assert.notNull(handler, "'handler' is required");
Assert.notNull(routeParser, "'routeParser' is required");
Assert.notNull(requester, "'requester' is required"); Assert.notNull(requester, "'requester' is required");
this.handler = handler; this.handler = handler;
this.requester = requester; this.requester = requester;
...@@ -181,7 +187,8 @@ class MessagingRSocket extends AbstractRSocket { ...@@ -181,7 +187,8 @@ class MessagingRSocket extends AbstractRSocket {
private MessageHeaders createHeaders(String destination, @Nullable MonoProcessor<?> replyMono) { private MessageHeaders createHeaders(String destination, @Nullable MonoProcessor<?> replyMono) {
MessageHeaderAccessor headers = new MessageHeaderAccessor(); MessageHeaderAccessor headers = new MessageHeaderAccessor();
headers.setLeaveMutable(true); headers.setLeaveMutable(true);
headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, destination); RouteMatcher.Route route = this.routeParser.apply(destination);
headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, route);
if (this.dataMimeType != null) { if (this.dataMimeType != null) {
headers.setContentType(this.dataMimeType); headers.setContentType(this.dataMimeType);
} }
......
...@@ -27,6 +27,7 @@ import org.springframework.messaging.MessageDeliveryException; ...@@ -27,6 +27,7 @@ import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.handler.annotation.reactive.MessageMappingMessageHandler; import org.springframework.messaging.handler.annotation.reactive.MessageMappingMessageHandler;
import org.springframework.messaging.handler.invocation.reactive.HandlerMethodReturnValueHandler; import org.springframework.messaging.handler.invocation.reactive.HandlerMethodReturnValueHandler;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.RouteMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
...@@ -110,16 +111,16 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler { ...@@ -110,16 +111,16 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
} }
@Override @Override
protected void handleNoMatch(@Nullable String destination, Message<?> message) { protected void handleNoMatch(@Nullable RouteMatcher.Route destination, Message<?> message) {
// MessagingRSocket will raise an error anyway if reply Mono is expected // MessagingRSocket will raise an error anyway if reply Mono is expected
// Here we raise a more helpful message a destination is present // Here we raise a more helpful message if a destination is present
// It is OK if some messages (ConnectionSetupPayload, metadataPush) are not handled // It is OK if some messages (ConnectionSetupPayload, metadataPush) are not handled
// We need a better way to avoid raising errors for those // This works but would be better to have a more explicit way to differentiate
if (StringUtils.hasText(destination)) { if (destination != null && StringUtils.hasText(destination.value())) {
throw new MessageDeliveryException("No handler for destination '" + destination + "'"); throw new MessageDeliveryException("No handler for destination '" + destination.value() + "'");
} }
} }
......
...@@ -47,6 +47,8 @@ import org.springframework.messaging.support.GenericMessage; ...@@ -47,6 +47,8 @@ import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.SimpleRouteMatcher;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
...@@ -117,11 +119,11 @@ public class MessageMappingMessageHandlerTests { ...@@ -117,11 +119,11 @@ public class MessageMappingMessageHandlerTests {
public void unhandledExceptionShouldFlowThrough() { public void unhandledExceptionShouldFlowThrough() {
GenericMessage<?> message = new GenericMessage<>(new Object(), GenericMessage<?> message = new GenericMessage<>(new Object(),
Collections.singletonMap(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, "string")); Collections.singletonMap(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER,
new SimpleRouteMatcher(new AntPathMatcher()).parseRoute("string")));
StepVerifier.create(initMesssageHandler().handleMessage(message)) StepVerifier.create(initMesssageHandler().handleMessage(message))
.expectErrorSatisfies(ex -> assertTrue( .expectErrorSatisfies(ex -> assertTrue("Actual: " + ex.getMessage(),
"Actual: " + ex.getMessage(),
ex.getMessage().startsWith("Could not resolve method parameter at index 0"))) ex.getMessage().startsWith("Could not resolve method parameter at index 0")))
.verify(Duration.ofSeconds(5)); .verify(Duration.ofSeconds(5));
} }
...@@ -156,7 +158,8 @@ public class MessageMappingMessageHandlerTests { ...@@ -156,7 +158,8 @@ public class MessageMappingMessageHandlerTests {
Flux<DataBuffer> payload = Flux.fromIterable(Arrays.asList(content)).map(parts -> toDataBuffer(parts)); Flux<DataBuffer> payload = Flux.fromIterable(Arrays.asList(content)).map(parts -> toDataBuffer(parts));
MessageHeaderAccessor headers = new MessageHeaderAccessor(); MessageHeaderAccessor headers = new MessageHeaderAccessor();
headers.setLeaveMutable(true); headers.setLeaveMutable(true);
headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, destination); headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER,
new SimpleRouteMatcher(new AntPathMatcher()).parseRoute(destination));
return MessageBuilder.createMessage(payload, headers.getMessageHeaders()); return MessageBuilder.createMessage(payload, headers.getMessageHeaders());
} }
......
...@@ -44,6 +44,8 @@ import org.springframework.util.AntPathMatcher; ...@@ -44,6 +44,8 @@ import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.util.RouteMatcher;
import org.springframework.util.SimpleRouteMatcher;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
...@@ -81,7 +83,8 @@ public class MethodMessageHandlerTests { ...@@ -81,7 +83,8 @@ public class MethodMessageHandlerTests {
handler.afterPropertiesSet(); handler.afterPropertiesSet();
Message<?> message = new GenericMessage<>("body", Collections.singletonMap( Message<?> message = new GenericMessage<>("body", Collections.singletonMap(
DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, "/bestmatch/bar/path")); DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER,
new SimpleRouteMatcher(new AntPathMatcher()).parseRoute("/bestmatch/bar/path")));
handler.handleMessage(message).block(Duration.ofSeconds(5)); handler.handleMessage(message).block(Duration.ofSeconds(5));
...@@ -102,7 +105,8 @@ public class MethodMessageHandlerTests { ...@@ -102,7 +105,8 @@ public class MethodMessageHandlerTests {
TestController.class); TestController.class);
Message<?> message = new GenericMessage<>("body", Collections.singletonMap( Message<?> message = new GenericMessage<>("body", Collections.singletonMap(
DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, "/handleMessageWithArgument")); DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER,
new SimpleRouteMatcher(new AntPathMatcher()).parseRoute("/handleMessageWithArgument")));
handler.handleMessage(message).block(Duration.ofSeconds(5)); handler.handleMessage(message).block(Duration.ofSeconds(5));
...@@ -118,7 +122,8 @@ public class MethodMessageHandlerTests { ...@@ -118,7 +122,8 @@ public class MethodMessageHandlerTests {
TestMethodMessageHandler handler = initMethodMessageHandler(TestController.class); TestMethodMessageHandler handler = initMethodMessageHandler(TestController.class);
Message<?> message = new GenericMessage<>("body", Collections.singletonMap( Message<?> message = new GenericMessage<>("body", Collections.singletonMap(
DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, "/handleMessageWithError")); DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER,
new SimpleRouteMatcher(new AntPathMatcher()).parseRoute("/handleMessageWithError")));
handler.handleMessage(message).block(Duration.ofSeconds(5)); handler.handleMessage(message).block(Duration.ofSeconds(5));
...@@ -238,22 +243,27 @@ public class MethodMessageHandlerTests { ...@@ -238,22 +243,27 @@ public class MethodMessageHandlerTests {
@Override @Override
@Nullable @Nullable
protected String getDestination(Message<?> message) { protected RouteMatcher.Route getDestination(Message<?> message) {
return (String) message.getHeaders().get(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER); return (RouteMatcher.Route) message.getHeaders().get(
DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER);
} }
@Override @Override
protected String getMatchingMapping(String mapping, Message<?> message) { protected String getMatchingMapping(String mapping, Message<?> message) {
String destination = getDestination(message); RouteMatcher.Route destination = getDestination(message);
Assert.notNull(destination, "No destination"); Assert.notNull(destination, "No destination");
return mapping.equals(destination) || this.pathMatcher.match(mapping, destination) ? mapping : null; return mapping.equals(destination.value()) ||
this.pathMatcher.match(mapping, destination.value()) ? mapping : null;
} }
@Override @Override
protected Comparator<String> getMappingComparator(Message<?> message) { protected Comparator<String> getMappingComparator(Message<?> message) {
return (info1, info2) -> { return (info1, info2) -> {
DestinationPatternsMessageCondition cond1 = new DestinationPatternsMessageCondition(info1); SimpleRouteMatcher routeMatcher = new SimpleRouteMatcher(new AntPathMatcher());
DestinationPatternsMessageCondition cond2 = new DestinationPatternsMessageCondition(info2); DestinationPatternsMessageCondition cond1 =
new DestinationPatternsMessageCondition(new String[] { info1 }, routeMatcher);
DestinationPatternsMessageCondition cond2 =
new DestinationPatternsMessageCondition(new String[] { info2 }, routeMatcher);
return cond1.compareTo(cond2, message); return cond1.compareTo(cond2, message);
}; };
} }
......
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.util.pattern;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.RouteMatcher;
/**
* {@code RouteMatcher} built on {@link PathPatternParser} that uses
* {@link PathContainer} and {@link PathPattern} as parsed representations of
* routes and patterns.
*
* @author Rossen Stoyanchev
* @since 5.2
*/
public class PathPatternRouteMatcher implements RouteMatcher {
private final PathPatternParser parser;
private final Map<String, PathPattern> pathPatternCache = new ConcurrentHashMap<>();
public PathPatternRouteMatcher(PathPatternParser parser) {
Assert.notNull(parser, "PathPatternParser must not be null");
this.parser = parser;
}
@Override
public Route parseRoute(String routeValue) {
return new PathContainerRoute(PathContainer.parsePath(routeValue));
}
@Override
public boolean isPattern(String route) {
return getPathPattern(route).hasPatternSyntax();
}
@Override
public String combine(String pattern1, String pattern2) {
return getPathPattern(pattern1).combine(getPathPattern(pattern2)).getPatternString();
}
@Override
public boolean match(String pattern, Route route) {
return getPathPattern(pattern).matches(getPathContainer(route));
}
@Override
@Nullable
public Map<String, String> matchAndExtract(String pattern, Route route) {
PathPattern.PathMatchInfo info = getPathPattern(pattern).matchAndExtract(getPathContainer(route));
return info != null ? info.getUriVariables() : null;
}
@Override
public Comparator<String> getPatternComparator(Route route) {
return Comparator.comparing(this::getPathPattern);
}
private PathPattern getPathPattern(String pattern) {
return this.pathPatternCache.computeIfAbsent(pattern, this.parser::parse);
}
private PathContainer getPathContainer(Route route) {
Assert.isInstanceOf(PathContainerRoute.class, route);
return ((PathContainerRoute) route).pathContainer;
}
private static class PathContainerRoute implements Route {
private final PathContainer pathContainer;
PathContainerRoute(PathContainer pathContainer) {
this.pathContainer = pathContainer;
}
@Override
public String value() {
return this.pathContainer.value();
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册