Allow async metadata in RSocketRequester

This commit allows single-value async producers for the values of
metadata entries in both the SETUP and for requests. The same is also
enabled for data in the SETUP frame.

Close gh-23640
This commit is contained in:
Rossen Stoyanchev
2019-11-20 19:40:55 +00:00
parent 82f4e933e0
commit 996f7290cf
8 changed files with 310 additions and 121 deletions

View File

@@ -84,7 +84,7 @@ public class DefaultMetadataExtractorTests {
.metadata("html data", TEXT_HTML)
.metadata("xml data", TEXT_XML);
DataBuffer metadata = metadataEncoder.encode();
DataBuffer metadata = metadataEncoder.encode().block();
Payload payload = createPayload(metadata);
Map<String, Object> result = this.extractor.extract(payload, COMPOSITE_METADATA);
payload.release();
@@ -104,7 +104,7 @@ public class DefaultMetadataExtractorTests {
.metadata("html data", TEXT_HTML)
.metadata("xml data", TEXT_XML);
DataBuffer metadata = metadataEncoder.encode();
DataBuffer metadata = metadataEncoder.encode().block();
Payload payload = createPayload(metadata);
Map<String, Object> result = this.extractor.extract(payload, COMPOSITE_METADATA);
payload.release();
@@ -120,7 +120,7 @@ public class DefaultMetadataExtractorTests {
public void route() {
MimeType metaMimeType = MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_ROUTING.getString());
MetadataEncoder metadataEncoder = new MetadataEncoder(metaMimeType, this.strategies).route("toA");
DataBuffer metadata = metadataEncoder.encode();
DataBuffer metadata = metadataEncoder.encode().block();
Payload payload = createPayload(metadata);
Map<String, Object> result = this.extractor.extract(payload, metaMimeType);
payload.release();
@@ -133,7 +133,7 @@ public class DefaultMetadataExtractorTests {
this.extractor.metadataToExtract(TEXT_PLAIN, String.class, ROUTE_KEY);
MetadataEncoder metadataEncoder = new MetadataEncoder(TEXT_PLAIN, this.strategies).route("toA");
DataBuffer metadata = metadataEncoder.encode();
DataBuffer metadata = metadataEncoder.encode().block();
Payload payload = createPayload(metadata);
Map<String, Object> result = this.extractor.extract(payload, TEXT_PLAIN);
payload.release();
@@ -151,7 +151,7 @@ public class DefaultMetadataExtractorTests {
});
MetadataEncoder encoder = new MetadataEncoder(TEXT_PLAIN, this.strategies).metadata("toA:text data", null);
DataBuffer metadata = encoder.encode();
DataBuffer metadata = encoder.encode().block();
Payload payload = createPayload(metadata);
Map<String, Object> result = this.extractor.extract(payload, TEXT_PLAIN);
payload.release();
@@ -167,7 +167,7 @@ public class DefaultMetadataExtractorTests {
extractor.metadataToExtract(TEXT_PLAIN, String.class, "name");
MetadataEncoder encoder = new MetadataEncoder(TEXT_PLAIN, this.strategies).metadata("value", null);
DataBuffer metadata = encoder.encode();
DataBuffer metadata = encoder.encode().block();
Payload payload = createPayload(metadata);
Map<String, Object> result = extractor.extract(payload, TEXT_PLAIN);

View File

@@ -17,6 +17,7 @@
package org.springframework.messaging.rsocket;
import java.lang.reflect.Field;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -39,6 +40,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
@@ -191,6 +193,39 @@ public class DefaultRSocketRequesterBuilderTests {
assertThat(setupPayload.getDataUtf8()).isEqualTo("My data");
}
@Test
public void setupWithAsyncValues() {
Mono<String> asyncMeta1 = Mono.delay(Duration.ofMillis(1)).map(aLong -> "Async Metadata 1");
Mono<String> asyncMeta2 = Mono.delay(Duration.ofMillis(1)).map(aLong -> "Async Metadata 2");
Mono<String> data = Mono.delay(Duration.ofMillis(1)).map(aLong -> "Async data");
RSocketRequester.builder()
.dataMimeType(MimeTypeUtils.TEXT_PLAIN)
.setupRoute("toA")
.setupMetadata(asyncMeta1, new MimeType("text", "x.test.metadata1"))
.setupMetadata(asyncMeta2, new MimeType("text", "x.test.metadata2"))
.setupData(data)
.connect(this.transport)
.block();
ConnectionSetupPayload payload = Mono.from(this.connection.sentFrames())
.map(ConnectionSetupPayload::create)
.block();
MimeType compositeMimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(StringDecoder.allMimeTypes());
extractor.metadataToExtract(new MimeType("text", "x.test.metadata1"), String.class, "asyncMeta1");
extractor.metadataToExtract(new MimeType("text", "x.test.metadata2"), String.class, "asyncMeta2");
Map<String, Object> metadataValues = extractor.extract(payload, compositeMimeType);
assertThat(metadataValues.get("asyncMeta1")).isEqualTo("Async Metadata 1");
assertThat(metadataValues.get("asyncMeta2")).isEqualTo("Async Metadata 2");
assertThat(payload.getDataUtf8()).isEqualTo("Async data");
}
@Test
public void frameDecoderMatchesDataBufferFactory() throws Exception {
testFrameDecoder(new NettyDataBufferFactory(ByteBufAllocator.DEFAULT), PayloadDecoder.ZERO_COPY);

View File

@@ -20,6 +20,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
@@ -28,6 +29,7 @@ import io.reactivex.Observable;
import io.reactivex.Single;
import io.rsocket.AbstractRSocket;
import io.rsocket.Payload;
import io.rsocket.metadata.WellKnownMimeType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
@@ -38,10 +40,12 @@ import reactor.test.StepVerifier;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.lang.Nullable;
import org.springframework.messaging.rsocket.RSocketRequester.RequestSpec;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.springframework.util.MimeTypeUtils.TEXT_PLAIN;
/**
@@ -131,6 +135,54 @@ public class DefaultRSocketRequesterTests {
}
}
@Test
public void sendWithoutData() {
this.requester.route("toA").send().block(Duration.ofSeconds(5));
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("fireAndForget");
assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("toA");
assertThat(this.rsocket.getSavedPayload().getDataUtf8()).isEqualTo("");
}
@Test
public void sendMonoWithoutData() {
this.requester.route("toA").retrieveMono(String.class).block(Duration.ofSeconds(5));
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("requestResponse");
assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("toA");
assertThat(this.rsocket.getSavedPayload().getDataUtf8()).isEqualTo("");
}
@Test
public void testSendWithAsyncMetadata() {
MimeType compositeMimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
Mono<String> asyncMeta1 = Mono.delay(Duration.ofMillis(1)).map(aLong -> "Async Metadata 1");
Mono<String> asyncMeta2 = Mono.delay(Duration.ofMillis(1)).map(aLong -> "Async Metadata 2");
TestRSocket rsocket = new TestRSocket();
RSocketRequester.wrap(rsocket, TEXT_PLAIN, compositeMimeType, this.strategies)
.route("toA")
.metadata(asyncMeta1, new MimeType("text", "x.test.metadata1"))
.metadata(asyncMeta2, new MimeType("text", "x.test.metadata2"))
.data("data")
.send()
.block(Duration.ofSeconds(5));
Payload payload = rsocket.getSavedPayload();
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(this.strategies.decoders());
extractor.metadataToExtract(new MimeType("text", "x.test.metadata1"), String.class, "asyncMeta1");
extractor.metadataToExtract(new MimeType("text", "x.test.metadata2"), String.class, "asyncMeta2");
Map<String, Object> metadataValues = extractor.extract(payload, compositeMimeType);
assertThat(metadataValues.get("asyncMeta1")).isEqualTo("Async Metadata 1");
assertThat(metadataValues.get("asyncMeta2")).isEqualTo("Async Metadata 2");
assertThat(payload.getDataUtf8()).isEqualTo("data");
}
@Test
public void retrieveMono() {
String value = "bodyA";
@@ -176,7 +228,7 @@ public class DefaultRSocketRequesterTests {
@Test
public void fluxToMonoIsRejected() {
assertThatIllegalArgumentException()
assertThatIllegalStateException()
.isThrownBy(() -> this.requester.route("").data(Flux.just("a", "b")).retrieveMono(String.class))
.withMessage("No RSocket interaction model for Flux request to Mono response.");
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.messaging.rsocket;
import java.time.Duration;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
@@ -26,6 +27,7 @@ import io.rsocket.metadata.CompositeMetadata;
import io.rsocket.metadata.RoutingMetadata;
import io.rsocket.metadata.WellKnownMimeType;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
@@ -56,11 +58,17 @@ public class MetadataEncoderTests {
@Test
public void compositeMetadata() {
Mono<String> asyncMeta1 = Mono.delay(Duration.ofMillis(1)).map(aLong -> "Async Metadata 1");
Mono<String> asyncMeta2 = Mono.delay(Duration.ofMillis(1)).map(aLong -> "Async Metadata 2");
DataBuffer buffer = new MetadataEncoder(COMPOSITE_METADATA, this.strategies)
.route("toA")
.metadata("My metadata", MimeTypeUtils.TEXT_PLAIN)
.metadata(asyncMeta1, new MimeType("text", "x.test.metadata1"))
.metadata(Unpooled.wrappedBuffer("Raw data".getBytes(UTF_8)), MimeTypeUtils.APPLICATION_OCTET_STREAM)
.encode();
.metadata(asyncMeta2, new MimeType("text", "x.test.metadata2"))
.encode()
.block();
CompositeMetadata entries = new CompositeMetadata(((NettyDataBuffer) buffer).getNativeBuffer(), false);
Iterator<CompositeMetadata.Entry> iterator = entries.iterator();
@@ -75,11 +83,21 @@ public class MetadataEncoderTests {
assertThat(entry.getMimeType()).isEqualTo(MimeTypeUtils.TEXT_PLAIN_VALUE);
assertThat(entry.getContent().toString(UTF_8)).isEqualTo("My metadata");
assertThat(iterator.hasNext()).isTrue();
entry = iterator.next();
assertThat(entry.getMimeType()).isEqualTo("text/x.test.metadata1");
assertThat(entry.getContent().toString(UTF_8)).isEqualTo("Async Metadata 1");
assertThat(iterator.hasNext()).isTrue();
entry = iterator.next();
assertThat(entry.getMimeType()).isEqualTo(MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE);
assertThat(entry.getContent().toString(UTF_8)).isEqualTo("Raw data");
assertThat(iterator.hasNext()).isTrue();
entry = iterator.next();
assertThat(entry.getMimeType()).isEqualTo("text/x.test.metadata2");
assertThat(entry.getContent().toString(UTF_8)).isEqualTo("Async Metadata 2");
assertThat(iterator.hasNext()).isFalse();
}
@@ -92,7 +110,8 @@ public class MetadataEncoderTests {
DataBuffer buffer =
new MetadataEncoder(mimeType, this.strategies)
.route("toA")
.encode();
.encode()
.block();
assertRoute("toA", ((NettyDataBuffer) buffer).getNativeBuffer());
}
@@ -102,7 +121,8 @@ public class MetadataEncoderTests {
DataBuffer buffer =
new MetadataEncoder(MimeTypeUtils.TEXT_PLAIN, this.strategies)
.route("toA")
.encode();
.encode()
.block();
assertThat(dumpString(buffer)).isEqualTo("toA");
}
@@ -112,7 +132,8 @@ public class MetadataEncoderTests {
DataBuffer buffer =
new MetadataEncoder(MimeTypeUtils.TEXT_PLAIN, this.strategies)
.route("a.{b}.{c}", "BBB", "C.C.C")
.encode();
.encode()
.block();
assertThat(dumpString(buffer)).isEqualTo("a.BBB.C%2EC%2EC");
}
@@ -122,7 +143,8 @@ public class MetadataEncoderTests {
DataBuffer buffer =
new MetadataEncoder(MimeTypeUtils.TEXT_PLAIN, this.strategies)
.metadata(Unpooled.wrappedBuffer("Raw data".getBytes(UTF_8)), null)
.encode();
.encode()
.block();
assertThat(dumpString(buffer)).isEqualTo("Raw data");
}
@@ -132,7 +154,8 @@ public class MetadataEncoderTests {
DataBuffer buffer =
new MetadataEncoder(MimeTypeUtils.TEXT_PLAIN, this.strategies)
.metadata("toA", null)
.encode();
.encode()
.block();
assertThat(dumpString(buffer)).isEqualTo("toA");
}
@@ -175,8 +198,8 @@ public class MetadataEncoderTests {
MetadataEncoder encoder = new MetadataEncoder(MimeTypeUtils.TEXT_PLAIN, this.strategies);
assertThatThrownBy(() -> encoder.metadata("toA", MimeTypeUtils.APPLICATION_JSON))
.hasMessage("Mime type is optional (may be null) " +
"but was provided and does not match the connection metadata mime type.");
.hasMessage("Mime type is optional when not using composite metadata, " +
"but it was provided and does not match the connection metadata mime type 'text/plain'.");
}
@Test
@@ -186,7 +209,8 @@ public class MetadataEncoderTests {
DataBuffer buffer = new MetadataEncoder(COMPOSITE_METADATA, strategies)
.route("toA")
.encode();
.encode()
.block();
ByteBuf byteBuf = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT)
.wrap(buffer.asByteBuffer())