Add RSocketRequest.Builder in Spring Messaging

Prior to this commit, `RSocketRequester` would have a single
`RSocketRequester.create` static method taking a fully built
`RSocket` as an argument. Developers need to build an `RSocket`
instance using the `RSocketFactory` and then use it to create
a requester.

To help developers set up a requester, this commit adds a new
`RSocketRequester.Builder` interface and implementation. The
`RSocket` building phase and codecs configuration are part of a
single call chain. Subscribing to the returned
`Mono<RSocketRequester>` will configure and connect to the remote
RSocket server.

This design should be improved in gh-22798, since we will need to
support metadata in a broader fashion.

Closes gh-22806
This commit is contained in:
Brian Clozel
2019-04-23 10:55:23 +02:00
parent 900abfce47
commit 02904121a3
5 changed files with 257 additions and 14 deletions

View File

@@ -0,0 +1,105 @@
/*
* 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
*
* http://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.messaging.rsocket;
import java.util.function.Consumer;
import io.netty.buffer.ByteBuf;
import io.rsocket.DuplexConnection;
import io.rsocket.RSocketFactory;
import io.rsocket.transport.ClientTransport;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.util.MimeTypeUtils;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link DefaultRSocketRequesterBuilder}.
*
* @author Brian Clozel
*/
public class DefaultRSocketRequesterBuilderTests {
private ClientTransport transport;
@Before
public void setup() {
this.transport = mock(ClientTransport.class);
when(this.transport.connect(anyInt())).thenReturn(Mono.just(new MockConnection()));
}
@SuppressWarnings("unchecked")
@Test
public void shouldApplyCustomizationsAtSubscription() {
Consumer<RSocketFactory.ClientRSocketFactory> factoryConfigurer = mock(Consumer.class);
Consumer<RSocketStrategies.Builder> strategiesConfigurer = mock(Consumer.class);
Mono<RSocketRequester> requester = RSocketRequester.builder()
.rsocketFactory(factoryConfigurer)
.rsocketStrategies(strategiesConfigurer)
.connect(this.transport, MimeTypeUtils.APPLICATION_JSON);
verifyZeroInteractions(this.transport, factoryConfigurer, strategiesConfigurer);
}
@SuppressWarnings("unchecked")
@Test
public void shouldApplyCustomizations() {
Consumer<RSocketFactory.ClientRSocketFactory> factoryConfigurer = mock(Consumer.class);
Consumer<RSocketStrategies.Builder> strategiesConfigurer = mock(Consumer.class);
RSocketRequester requester = RSocketRequester.builder()
.rsocketFactory(factoryConfigurer)
.rsocketStrategies(strategiesConfigurer)
.connect(this.transport, MimeTypeUtils.APPLICATION_JSON)
.block();
verify(this.transport).connect(anyInt());
verify(factoryConfigurer).accept(any(RSocketFactory.ClientRSocketFactory.class));
verify(strategiesConfigurer).accept(any(RSocketStrategies.Builder.class));
}
static class MockConnection implements DuplexConnection {
@Override
public Mono<Void> send(Publisher<ByteBuf> frames) {
return Mono.empty();
}
@Override
public Flux<ByteBuf> receive() {
return Flux.empty();
}
@Override
public Mono<Void> onClose() {
return Mono.empty();
}
@Override
public void dispose() {
}
}
}

View File

@@ -19,10 +19,8 @@ package org.springframework.messaging.rsocket;
import java.time.Duration;
import io.netty.buffer.PooledByteBufAllocator;
import io.rsocket.RSocket;
import io.rsocket.RSocketFactory;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.transport.netty.server.CloseableChannel;
import io.rsocket.transport.netty.server.TcpServerTransport;
import org.junit.AfterClass;
@@ -59,8 +57,6 @@ public class RSocketClientToServerIntegrationTests {
private static FireAndForgetCountingInterceptor interceptor = new FireAndForgetCountingInterceptor();
private static RSocket client;
private static RSocketRequester requester;
@@ -77,20 +73,19 @@ public class RSocketClientToServerIntegrationTests {
.start()
.block();
client = RSocketFactory.connect()
.dataMimeType(MimeTypeUtils.TEXT_PLAIN_VALUE)
.frameDecoder(PayloadDecoder.ZERO_COPY)
.transport(TcpClientTransport.create("localhost", 7000))
.start()
requester = RSocketRequester.builder()
.rsocketFactory(factory -> factory.frameDecoder(PayloadDecoder.ZERO_COPY))
.rsocketStrategies(strategies -> strategies
.decoder(StringDecoder.allMimeTypes())
.encoder(CharSequenceEncoder.allMimeTypes())
.dataBufferFactory(new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT)))
.connectTcp("localhost", 7000, MimeTypeUtils.TEXT_PLAIN)
.block();
requester = RSocketRequester.create(
client, MimeTypeUtils.TEXT_PLAIN, context.getBean(RSocketStrategies.class));
}
@AfterClass
public static void tearDownOnce() {
client.dispose();
requester.rsocket().dispose();
server.dispose();
}