diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java index 8980ad9ada..8fac1b1593 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java @@ -19,6 +19,8 @@ package org.springframework.messaging.rsocket; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -44,6 +46,7 @@ import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.MimeType; +import org.springframework.util.ObjectUtils; /** * Default, package-private {@link RSocketRequester} implementation. @@ -58,6 +61,9 @@ final class DefaultRSocketRequester implements RSocketRequester { static final MimeType ROUTING = new MimeType("message", "x.rsocket.routing.v0"); + /** For route variable replacement. */ + private static final Pattern VARS_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); + private static final Map EMPTY_HINTS = Collections.emptyMap(); @@ -105,11 +111,29 @@ final class DefaultRSocketRequester implements RSocketRequester { } @Override - public RequestSpec route(String route) { + public RequestSpec route(String route, Object... vars) { Assert.notNull(route, "'route' is required"); + route = expand(route, vars); return new DefaultRequestSpec(route, metadataMimeType().equals(COMPOSITE_METADATA) ? ROUTING : null); } + private static String expand(String route, Object... vars) { + if (ObjectUtils.isEmpty(vars)) { + return route; + } + StringBuffer sb = new StringBuffer(); + int index = 0; + Matcher matcher = VARS_PATTERN.matcher(route); + while (matcher.find()) { + Assert.isTrue(index < vars.length, () -> "No value for variable '" + matcher.group(1) + "'"); + String value = vars[index].toString(); + value = value.contains(".") ? value.replaceAll("\\.", "%2E") : value; + matcher.appendReplacement(sb, value); + index++; + } + return sb.toString(); + } + @Override public RequestSpec metadata(Object metadata, @Nullable MimeType mimeType) { return new DefaultRequestSpec(metadata, mimeType); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java index b3fb0751a9..071e4d481a 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java @@ -66,13 +66,19 @@ public interface RSocketRequester { /** * Begin to specify a new request with the given route to a remote handler. + *

The route can be a template with placeholders, e.g. + * {@code "flight.{code}"} in which case the supplied route variables are + * expanded into the template after being formatted via {@code toString()}. + * If a formatted variable contains a "." it is replaced with the escape + * sequence "%2E" to avoid treating it as separator by the responder . *

If the connection is set to use composite metadata, the route is * encoded as {@code "message/x.rsocket.routing.v0"}. Otherwise the route * is encoded according to the mime type for the connection. * @param route the route to a handler + * @param routeVars variables to be expanded into the route template * @return a spec for further defining and executing the request */ - RequestSpec route(String route); + RequestSpec route(String route, Object... routeVars); /** * Begin to specify a new request with the given metadata. diff --git a/spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java index 546844ffea..15db1059c8 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java @@ -211,13 +211,16 @@ public class DefaultRSocketRequesterTests { } @Test - public void supportedMetadataMimeTypes() { - - RSocketRequester.wrap(this.rsocket, TEXT_PLAIN, - COMPOSITE_METADATA, this.strategies); + public void routeWithVars() { + RSocketRequester requester = RSocketRequester.wrap(this.rsocket, TEXT_PLAIN, TEXT_PLAIN, this.strategies); + requester.route("a.{b}.{c}", "BBB", "C.C.C").data("body").send().block(); + assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("a.BBB.C%2EC%2EC"); + } - RSocketRequester.wrap(this.rsocket, TEXT_PLAIN, - ROUTING, this.strategies); + @Test + public void supportedMetadataMimeTypes() { + RSocketRequester.wrap(this.rsocket, TEXT_PLAIN, COMPOSITE_METADATA, this.strategies); + RSocketRequester.wrap(this.rsocket, TEXT_PLAIN, ROUTING, this.strategies); } @Test