Browse Source
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-22806pull/22829/head
5 changed files with 257 additions and 14 deletions
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
/* |
||||
* 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.net.URI; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.function.Consumer; |
||||
|
||||
import io.rsocket.RSocketFactory; |
||||
import io.rsocket.transport.ClientTransport; |
||||
import io.rsocket.transport.netty.client.TcpClientTransport; |
||||
import io.rsocket.transport.netty.client.WebsocketClientTransport; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.MimeType; |
||||
|
||||
/** |
||||
* Default implementation of {@link RSocketRequester.Builder}. |
||||
* |
||||
* @author Brian Clozel |
||||
* @since 5.2 |
||||
*/ |
||||
final class DefaultRSocketRequesterBuilder implements RSocketRequester.Builder { |
||||
|
||||
@Nullable |
||||
private List<Consumer<RSocketFactory.ClientRSocketFactory>> factoryConfigurers = new ArrayList<>(); |
||||
|
||||
@Nullable |
||||
private List<Consumer<RSocketStrategies.Builder>> strategiesConfigurers = new ArrayList<>(); |
||||
|
||||
@Override |
||||
public RSocketRequester.Builder rsocketFactory(Consumer<RSocketFactory.ClientRSocketFactory> configurer) { |
||||
this.factoryConfigurers.add(configurer); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public RSocketRequester.Builder rsocketStrategies(Consumer<RSocketStrategies.Builder> configurer) { |
||||
this.strategiesConfigurers.add(configurer); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public Mono<RSocketRequester> connect(ClientTransport transport, MimeType dataMimeType) { |
||||
return Mono.defer(() -> { |
||||
RSocketStrategies.Builder strategiesBuilder = RSocketStrategies.builder(); |
||||
this.strategiesConfigurers.forEach(configurer -> configurer.accept(strategiesBuilder)); |
||||
RSocketFactory.ClientRSocketFactory clientFactory = RSocketFactory.connect() |
||||
.dataMimeType(dataMimeType.toString()); |
||||
this.factoryConfigurers.forEach(configurer -> configurer.accept(clientFactory)); |
||||
return clientFactory.transport(transport).start() |
||||
.map(rsocket -> RSocketRequester.create(rsocket, dataMimeType, strategiesBuilder.build())); |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<RSocketRequester> connectTcp(String host, int port, MimeType dataMimeType) { |
||||
return connect(TcpClientTransport.create(host, port), dataMimeType); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<RSocketRequester> connectWebSocket(URI uri, MimeType dataMimeType) { |
||||
return connect(WebsocketClientTransport.create(uri), dataMimeType); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,105 @@
@@ -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() { |
||||
|
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue