Browse Source

Updates to RSocket[Strategies|Requester] defaults

1. RSocketStrategies hooks in the basic codecs from spring-core by
default. Now that we have support for composite metadata, it makes
sense to have multiple codecs available.

2. RSocketStrategies is pre-configured with NettyDataBufferFactory.

3. DefaultRSocketRequesterBuilder configures RSocket with a frame
decoder that matches the DataBufferFactory choice, i.e. ensuring
consistency of zero copy vs default (copy) choice.

4. DefaultRSocketRequesterBuilder now tries to find a single non-basic
decoder to select a default data MimeType (e.g. CBOR), or otherwise
fall back on the first default decoder (e.g. String).

See gh-23314
pull/23353/head
Rossen Stoyanchev 5 years ago
parent
commit
a780cad12e
  1. 49
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterBuilder.java
  2. 46
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketStrategies.java
  3. 33
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
  4. 53
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketStrategies.java
  5. 190
      spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterBuilderTests.java

49
spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterBuilder.java

@ -18,9 +18,9 @@ package org.springframework.messaging.rsocket;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Stream;
import io.rsocket.RSocketFactory; import io.rsocket.RSocketFactory;
import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.frame.decoder.PayloadDecoder;
@ -29,6 +29,9 @@ import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.transport.netty.client.WebsocketClientTransport; import io.rsocket.transport.netty.client.WebsocketClientTransport;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
@ -110,7 +113,10 @@ final class DefaultRSocketRequesterBuilder implements RSocketRequester.Builder {
MimeType dataMimeType = getDataMimeType(rsocketStrategies); MimeType dataMimeType = getDataMimeType(rsocketStrategies);
rsocketFactory.dataMimeType(dataMimeType.toString()); rsocketFactory.dataMimeType(dataMimeType.toString());
rsocketFactory.metadataMimeType(this.metadataMimeType.toString()); rsocketFactory.metadataMimeType(this.metadataMimeType.toString());
rsocketFactory.frameDecoder(PayloadDecoder.ZERO_COPY);
if (rsocketStrategies.dataBufferFactory() instanceof NettyDataBufferFactory) {
rsocketFactory.frameDecoder(PayloadDecoder.ZERO_COPY);
}
this.rsocketFactoryConfigurers.forEach(configurer -> { this.rsocketFactoryConfigurers.forEach(configurer -> {
configurer.configureWithStrategies(rsocketStrategies); configurer.configureWithStrategies(rsocketStrategies);
@ -139,16 +145,35 @@ final class DefaultRSocketRequesterBuilder implements RSocketRequester.Builder {
if (this.dataMimeType != null) { if (this.dataMimeType != null) {
return this.dataMimeType; return this.dataMimeType;
} }
return Stream // Look for non-basic Decoder (e.g. CBOR, Protobuf)
.concat( MimeType selected = null;
strategies.encoders().stream() List<Decoder<?>> decoders = strategies.decoders();
.flatMap(encoder -> encoder.getEncodableMimeTypes().stream()), for (Decoder<?> candidate : decoders) {
strategies.decoders().stream() if (!isCoreCodec(candidate) && !candidate.getDecodableMimeTypes().isEmpty()) {
.flatMap(encoder -> encoder.getDecodableMimeTypes().stream()) Assert.state(selected == null,
) () -> "Cannot select default data MimeType based on configured decoders: " + decoders);
.filter(MimeType::isConcrete) selected = getMimeType(candidate);
.findFirst() }
.orElseThrow(() -> new IllegalArgumentException("Failed to select data MimeType to use.")); }
if (selected != null) {
return selected;
}
// Fall back on 1st decoder (e.g. String)
for (Decoder<?> decoder : decoders) {
if (!decoder.getDecodableMimeTypes().isEmpty()) {
return getMimeType(decoder);
}
}
throw new IllegalArgumentException("Failed to select data MimeType to use.");
}
private static boolean isCoreCodec(Object codec) {
return codec.getClass().getPackage().equals(StringDecoder.class.getPackage());
}
private static MimeType getMimeType(Decoder<?> decoder) {
MimeType mimeType = decoder.getDecodableMimeTypes().get(0);
return new MimeType(mimeType, Collections.emptyMap());
} }
} }

46
spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketStrategies.java

@ -22,11 +22,21 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import io.netty.buffer.PooledByteBufAllocator;
import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.codec.ByteArrayDecoder;
import org.springframework.core.codec.ByteArrayEncoder;
import org.springframework.core.codec.ByteBufferDecoder;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.DataBufferDecoder;
import org.springframework.core.codec.DataBufferEncoder;
import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder; import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -90,18 +100,33 @@ final class DefaultRSocketStrategies implements RSocketStrategies {
private ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); private ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
@Nullable @Nullable
private DataBufferFactory dataBufferFactory; private DataBufferFactory bufferFactory;
DefaultRSocketStrategiesBuilder() {
// Order of decoders may be significant for default data MimeType
// selection in RSocketRequester.Builder
public DefaultRSocketStrategiesBuilder() { this.decoders.add(StringDecoder.allMimeTypes());
this.decoders.add(new ByteBufferDecoder());
this.decoders.add(new ByteArrayDecoder());
this.decoders.add(new DataBufferDecoder());
this.encoders.add(CharSequenceEncoder.allMimeTypes());
this.encoders.add(new ByteBufferEncoder());
this.encoders.add(new ByteArrayEncoder());
this.encoders.add(new DataBufferEncoder());
} }
public DefaultRSocketStrategiesBuilder(RSocketStrategies other) { DefaultRSocketStrategiesBuilder(RSocketStrategies other) {
this.encoders.addAll(other.encoders()); this.encoders.addAll(other.encoders());
this.decoders.addAll(other.decoders()); this.decoders.addAll(other.decoders());
this.adapterRegistry = other.reactiveAdapterRegistry(); this.adapterRegistry = other.reactiveAdapterRegistry();
this.dataBufferFactory = other.dataBufferFactory(); this.bufferFactory = other.dataBufferFactory();
} }
@Override @Override
public Builder encoder(Encoder<?>... encoders) { public Builder encoder(Encoder<?>... encoders) {
this.encoders.addAll(Arrays.asList(encoders)); this.encoders.addAll(Arrays.asList(encoders));
@ -135,14 +160,19 @@ final class DefaultRSocketStrategies implements RSocketStrategies {
@Override @Override
public Builder dataBufferFactory(DataBufferFactory bufferFactory) { public Builder dataBufferFactory(DataBufferFactory bufferFactory) {
this.dataBufferFactory = bufferFactory; this.bufferFactory = bufferFactory;
return this; return this;
} }
@Override @Override
public RSocketStrategies build() { public RSocketStrategies build() {
return new DefaultRSocketStrategies(this.encoders, this.decoders, this.adapterRegistry, return new DefaultRSocketStrategies(
this.dataBufferFactory != null ? this.dataBufferFactory : new DefaultDataBufferFactory()); this.encoders, this.decoders, this.adapterRegistry, initBufferFactory());
}
private DataBufferFactory initBufferFactory() {
return this.bufferFactory != null ? this.bufferFactory :
new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
} }
} }

33
spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java

@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.codec.Decoder;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.messaging.rsocket.annotation.support.AnnotationClientResponderConfigurer; import org.springframework.messaging.rsocket.annotation.support.AnnotationClientResponderConfigurer;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
@ -125,32 +126,32 @@ public interface RSocketRequester {
interface Builder { interface Builder {
/** /**
* Configure the MimeType for payload data which is then specified * Configure the payload data MimeType to specify on the {@code SETUP}
* on the {@code SETUP} frame and applies to the whole connection. * frame that applies to the whole connection.
* <p>By default this is set to the first concrete mime type supported * <p>If this is not set, the builder will try to select the mime type
* by the configured encoders and decoders. * based on the presence of a single
* @param mimeType the data MimeType to use * {@link RSocketStrategies.Builder#decoder(Decoder[]) non-default}
* {@code Decoder}, or the first default decoder otherwise
* (i.e. {@code String}) if no others are configured.
*/ */
RSocketRequester.Builder dataMimeType(@Nullable MimeType mimeType); RSocketRequester.Builder dataMimeType(@Nullable MimeType mimeType);
/** /**
* Configure the MimeType for payload metadata which is then specified * Configure the payload metadata MimeType to specify on the {@code SETUP}
* on the {@code SETUP} frame and applies to the whole connection. * frame and applies to the whole connection.
* <p>By default this is set to * <p>By default this is set to
* {@code "message/x.rsocket.composite-metadata.v0"} in which case the * {@code "message/x.rsocket.composite-metadata.v0"} in which case the
* route, if provided, is encoded as a * route, if provided, is encoded as a
* {@code "message/x.rsocket.routing.v0"} metadata entry, potentially * {@code "message/x.rsocket.routing.v0"} composite metadata entry.
* with other metadata entries added too. If this is set to any other * For any other MimeType, it is assumed to be the MimeType for the
* mime type, and a route is provided, it is assumed the mime type is * route, if provided.
* for the route.
* @param mimeType the data MimeType to use
*/ */
RSocketRequester.Builder metadataMimeType(MimeType mimeType); RSocketRequester.Builder metadataMimeType(MimeType mimeType);
/** /**
* Set the {@link RSocketStrategies} to use for access to encoders, * Set the {@link RSocketStrategies} to use.
* decoders, and a factory for {@code DataBuffer's}. * <p>By default this is set to {@code RSocketStrategies.builder().build()}
* @param strategies the codecs strategies to use * but may be further customized via {@link #rsocketStrategies(Consumer)}.
*/ */
RSocketRequester.Builder rsocketStrategies(@Nullable RSocketStrategies strategies); RSocketRequester.Builder rsocketStrategies(@Nullable RSocketStrategies strategies);
@ -159,7 +160,6 @@ public interface RSocketRequester {
* <p>By default this starts out with an empty builder, i.e. * <p>By default this starts out with an empty builder, i.e.
* {@link RSocketStrategies#builder()}, but the strategies can also be * {@link RSocketStrategies#builder()}, but the strategies can also be
* set via {@link #rsocketStrategies(RSocketStrategies)}. * set via {@link #rsocketStrategies(RSocketStrategies)}.
* @param configurer the configurer to apply
*/ */
RSocketRequester.Builder rsocketStrategies(Consumer<RSocketStrategies.Builder> configurer); RSocketRequester.Builder rsocketStrategies(Consumer<RSocketStrategies.Builder> configurer);
@ -172,7 +172,6 @@ public interface RSocketRequester {
* {@code ClientRSocketFactory}. Use the shortcuts on this builder * {@code ClientRSocketFactory}. Use the shortcuts on this builder
* instead since the created {@code RSocketRequester} needs to be aware * instead since the created {@code RSocketRequester} needs to be aware
* of those settings. * of those settings.
* @param configurer consumer to customize the factory
* @see AnnotationClientResponderConfigurer * @see AnnotationClientResponderConfigurer
*/ */
RSocketRequester.Builder rsocketFactory(ClientRSocketFactoryConfigurer configurer); RSocketRequester.Builder rsocketFactory(ClientRSocketFactoryConfigurer configurer);

53
spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketStrategies.java

@ -19,13 +19,17 @@ package org.springframework.messaging.rsocket;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import io.netty.buffer.PooledByteBufAllocator; import io.rsocket.Payload;
import io.rsocket.RSocketFactory.ClientRSocketFactory;
import io.rsocket.RSocketFactory.ServerRSocketFactory;
import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder; import org.springframework.core.codec.Encoder;
import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
@ -120,24 +124,28 @@ public interface RSocketStrategies {
interface Builder { interface Builder {
/** /**
* Add encoders to use for serializing Objects. * Append to the list of encoders to use for serializing Objects to the
* <p>By default this is empty. * data or metadata of a {@link Payload}.
* <p>By default this is initialized with encoders for {@code String},
* {@code byte[]}, {@code ByteBuffer}, and {@code DataBuffer}.
*/ */
Builder encoder(Encoder<?>... encoder); Builder encoder(Encoder<?>... encoder);
/** /**
* Access and manipulate the list of configured {@link #encoder encoders}. * Apply the consumer to the list of configured encoders, immediately.
*/ */
Builder encoders(Consumer<List<Encoder<?>>> consumer); Builder encoders(Consumer<List<Encoder<?>>> consumer);
/** /**
* Add decoders for de-serializing Objects. * Append to the list of decoders to use for de-serializing Objects from
* <p>By default this is empty. * the data or metadata of a {@link Payload}.
* <p>By default this is initialized with decoders for {@code String},
* {@code byte[]}, {@code ByteBuffer}, and {@code DataBuffer}.
*/ */
Builder decoder(Decoder<?>... decoder); Builder decoder(Decoder<?>... decoder);
/** /**
* Access and manipulate the list of configured {@link #encoder decoders}. * Apply the consumer to the list of configured decoders, immediately.
*/ */
Builder decoders(Consumer<List<Decoder<?>>> consumer); Builder decoders(Consumer<List<Decoder<?>>> consumer);
@ -146,28 +154,23 @@ public interface RSocketStrategies {
* to adapt to, and/or determine the semantics of a given * to adapt to, and/or determine the semantics of a given
* {@link org.reactivestreams.Publisher Publisher}. * {@link org.reactivestreams.Publisher Publisher}.
* <p>By default this {@link ReactiveAdapterRegistry#getSharedInstance()}. * <p>By default this {@link ReactiveAdapterRegistry#getSharedInstance()}.
* @param registry the registry to use
*/ */
Builder reactiveAdapterStrategy(ReactiveAdapterRegistry registry); Builder reactiveAdapterStrategy(ReactiveAdapterRegistry registry);
/** /**
* Configure the DataBufferFactory to use for allocating buffers, for * Configure the DataBufferFactory to use for allocating buffers when
* example when preparing requests or when responding. The choice here * preparing requests or creating responses.
* must be aligned with the frame decoder configured in * <p>By default this is set to {@link NettyDataBufferFactory} with
* {@link io.rsocket.RSocketFactory}. * pooled, allocated buffers for zero copy. RSocket must also be
* <p>By default this property is an instance of * <a href="https://github.com/rsocket/rsocket-java#zero-copy">configured</a>
* {@link org.springframework.core.io.buffer.DefaultDataBufferFactory * for zero copy. For client setup, {@link RSocketRequester.Builder}
* DefaultDataBufferFactory} matching to the default frame decoder in * adapts automatically to the {@code DataBufferFactory} configured
* {@link io.rsocket.RSocketFactory} which copies the payload. This * here, and sets the frame decoder in {@link ClientRSocketFactory
* comes at cost to performance but does not require reference counting * ClientRSocketFactory} accordingly. For server setup, the
* and eliminates possibility for memory leaks. * {@link ServerRSocketFactory ServerRSocketFactory} must be configured
* <p>To switch to a zero-copy strategy, * accordingly too for zero copy.
* <a href="https://github.com/rsocket/rsocket-java#zero-copy">configure RSocket</a> * <p>If using {@link DefaultDataBufferFactory} instead, there is no
* accordingly, and then configure this property with an instance of * need for related config changes in RSocket.
* {@link org.springframework.core.io.buffer.NettyDataBufferFactory
* NettyDataBufferFactory} with a pooled allocator such as
* {@link PooledByteBufAllocator#DEFAULT}.
* @param bufferFactory the DataBufferFactory to use
*/ */
Builder dataBufferFactory(DataBufferFactory bufferFactory); Builder dataBufferFactory(DataBufferFactory bufferFactory);

190
spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterBuilderTests.java

@ -17,11 +17,16 @@
package org.springframework.messaging.rsocket; package org.springframework.messaging.rsocket;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.rsocket.DuplexConnection; import io.rsocket.DuplexConnection;
import io.rsocket.RSocketFactory; import io.rsocket.RSocketFactory;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ClientTransport;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -29,13 +34,19 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.ResolvableType;
import org.springframework.core.codec.StringDecoder; import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils; import org.springframework.util.MimeTypeUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@ -52,6 +63,8 @@ public class DefaultRSocketRequesterBuilderTests {
private ClientTransport transport; private ClientTransport transport;
private final TestRSocketFactoryConfigurer rsocketFactoryConfigurer = new TestRSocketFactoryConfigurer();
@Before @Before
public void setup() { public void setup() {
@ -63,53 +76,113 @@ public class DefaultRSocketRequesterBuilderTests {
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void shouldApplyCustomizationsAtSubscription() { public void shouldApplyCustomizationsAtSubscription() {
ClientRSocketFactoryConfigurer factoryConfigurer = mock(ClientRSocketFactoryConfigurer.class);
Consumer<RSocketStrategies.Builder> strategiesConfigurer = mock(Consumer.class); Consumer<RSocketStrategies.Builder> strategiesConfigurer = mock(Consumer.class);
RSocketRequester.builder() RSocketRequester.builder()
.rsocketFactory(factoryConfigurer) .rsocketFactory(this.rsocketFactoryConfigurer)
.rsocketStrategies(strategiesConfigurer) .rsocketStrategies(strategiesConfigurer)
.connect(this.transport); .connect(this.transport);
verifyZeroInteractions(this.transport, factoryConfigurer, strategiesConfigurer);
verifyZeroInteractions(this.transport);
assertThat(this.rsocketFactoryConfigurer.rsocketFactory()).isNull();
} }
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void shouldApplyCustomizations() { public void shouldApplyCustomizations() {
RSocketStrategies strategies = RSocketStrategies.builder() Consumer<RSocketStrategies.Builder> rsocketStrategiesConfigurer = mock(Consumer.class);
.encoder(CharSequenceEncoder.allMimeTypes())
.decoder(StringDecoder.allMimeTypes())
.build();
ClientRSocketFactoryConfigurer factoryConfigurer = mock(ClientRSocketFactoryConfigurer.class);
Consumer<RSocketStrategies.Builder> strategiesConfigurer = mock(Consumer.class);
RSocketRequester.builder() RSocketRequester.builder()
.rsocketStrategies(strategies) .rsocketFactory(this.rsocketFactoryConfigurer)
.rsocketFactory(factoryConfigurer) .rsocketStrategies(rsocketStrategiesConfigurer)
.rsocketStrategies(strategiesConfigurer)
.connect(this.transport) .connect(this.transport)
.block(); .block();
// RSocketStrategies and RSocketFactory configurers should have been called
verify(this.transport).connect(anyInt()); verify(this.transport).connect(anyInt());
verify(factoryConfigurer).configureWithStrategies(any(RSocketStrategies.class)); verify(rsocketStrategiesConfigurer).accept(any(RSocketStrategies.Builder.class));
verify(factoryConfigurer).configure(any(RSocketFactory.ClientRSocketFactory.class)); assertThat(this.rsocketFactoryConfigurer.rsocketStrategies()).isNotNull();
verify(strategiesConfigurer).accept(any(RSocketStrategies.Builder.class)); assertThat(this.rsocketFactoryConfigurer.rsocketFactory()).isNotNull();
}
@Test
public void defaultDataMimeType() {
RSocketRequester requester = RSocketRequester.builder()
.connect(this.transport)
.block();
assertThat(requester.dataMimeType())
.as("Default data MimeType, based on the first configured Decoder")
.isEqualTo(MimeTypeUtils.TEXT_PLAIN);
} }
@Test @Test
public void dataMimeType() throws NoSuchFieldException { public void defaultDataMimeTypeWithCustomDecoderRegitered() {
RSocketStrategies strategies = RSocketStrategies.builder() RSocketStrategies strategies = RSocketStrategies.builder()
.encoder(CharSequenceEncoder.allMimeTypes()) .decoder(new TestJsonDecoder(MimeTypeUtils.APPLICATION_JSON))
.decoder(StringDecoder.allMimeTypes())
.build(); .build();
RSocketRequester requester = RSocketRequester.builder() RSocketRequester requester = RSocketRequester.builder()
.rsocketStrategies(strategies) .rsocketStrategies(strategies)
.connect(this.transport)
.block();
assertThat(requester.dataMimeType())
.as("Default data MimeType, based on the first configured, non-default Decoder")
.isEqualTo(MimeTypeUtils.APPLICATION_JSON);
}
@Test
public void defaultDataMimeTypeWithMultipleCustomDecoderRegitered() {
RSocketStrategies strategies = RSocketStrategies.builder()
.decoder(new TestJsonDecoder(MimeTypeUtils.APPLICATION_JSON))
.decoder(new TestJsonDecoder(MimeTypeUtils.APPLICATION_XML))
.build();
assertThatThrownBy(() ->
RSocketRequester
.builder()
.rsocketStrategies(strategies)
.connect(this.transport)
.block())
.hasMessageContaining("Cannot select default data MimeType");
}
@Test
public void dataMimeTypeSet() {
RSocketRequester requester = RSocketRequester.builder()
.dataMimeType(MimeTypeUtils.APPLICATION_JSON) .dataMimeType(MimeTypeUtils.APPLICATION_JSON)
.connect(this.transport) .connect(this.transport)
.block(); .block();
Field field = DefaultRSocketRequester.class.getDeclaredField("dataMimeType"); assertThat(requester.dataMimeType()).isEqualTo(MimeTypeUtils.APPLICATION_JSON);
}
@Test
public void frameDecoderMatchesDataBufferFactory() throws Exception {
testFrameDecoder(new NettyDataBufferFactory(ByteBufAllocator.DEFAULT), PayloadDecoder.ZERO_COPY);
testFrameDecoder(new DefaultDataBufferFactory(), PayloadDecoder.DEFAULT);
}
private void testFrameDecoder(DataBufferFactory bufferFactory, PayloadDecoder frameDecoder)
throws NoSuchFieldException {
RSocketStrategies strategies = RSocketStrategies.builder()
.dataBufferFactory(bufferFactory)
.build();
RSocketRequester.builder()
.rsocketStrategies(strategies)
.rsocketFactory(this.rsocketFactoryConfigurer)
.connect(this.transport)
.block();
RSocketFactory.ClientRSocketFactory factory = this.rsocketFactoryConfigurer.rsocketFactory();
assertThat(factory).isNotNull();
Field field = RSocketFactory.ClientRSocketFactory.class.getDeclaredField("payloadDecoder");
ReflectionUtils.makeAccessible(field); ReflectionUtils.makeAccessible(field);
MimeType dataMimeType = (MimeType) ReflectionUtils.getField(field, requester); PayloadDecoder decoder = (PayloadDecoder) ReflectionUtils.getField(field, factory);
assertThat(dataMimeType).isEqualTo(MimeTypeUtils.APPLICATION_JSON); assertThat(decoder).isSameAs(frameDecoder);
} }
@ -135,4 +208,75 @@ public class DefaultRSocketRequesterBuilderTests {
} }
} }
static class TestRSocketFactoryConfigurer implements ClientRSocketFactoryConfigurer {
private RSocketStrategies strategies;
private RSocketFactory.ClientRSocketFactory rsocketFactory;
public RSocketStrategies rsocketStrategies() {
return this.strategies;
}
public RSocketFactory.ClientRSocketFactory rsocketFactory() {
return this.rsocketFactory;
}
@Override
public void configureWithStrategies(RSocketStrategies strategies) {
this.strategies = strategies;
}
@Override
public void configure(RSocketFactory.ClientRSocketFactory rsocketFactory) {
this.rsocketFactory = rsocketFactory;
}
}
static class TestJsonDecoder implements Decoder<Object> {
private final MimeType mimeType;
TestJsonDecoder(MimeType mimeType) {
this.mimeType = mimeType;
}
@Override
public List<MimeType> getDecodableMimeTypes() {
return Collections.singletonList(this.mimeType);
}
@Override
public boolean canDecode(ResolvableType elementType, MimeType mimeType) {
return false;
}
@Override
public Mono<Object> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
MimeType mimeType, Map<String, Object> hints) {
throw new UnsupportedOperationException();
}
@Override
public Flux<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
MimeType mimeType, Map<String, Object> hints) {
throw new UnsupportedOperationException();
}
@Override
public Object decode(DataBuffer buffer, ResolvableType targetType, MimeType mimeType,
Map<String, Object> hints) throws DecodingException {
throw new UnsupportedOperationException();
}
}
} }

Loading…
Cancel
Save