Commit 20dfeddb authored by Brian Clozel's avatar Brian Clozel

Auto-configure RSocketRequester.Builder

This commit auto-configures a prototype `RSocketRequester.Builder` bean
for building requester instances. This builder is pre-configured with
auto-detected `RSocketStrategies` (same as the server side).

Closes gh-16280
parent 6544d19f
/*
* Copyright 2012-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
*
* https://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.boot.autoconfigure.rsocket;
import io.rsocket.RSocketFactory;
import io.rsocket.transport.netty.server.TcpServerTransport;
import reactor.netty.http.server.HttpServer;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link org.springframework.messaging.rsocket.RSocketRequester}. This auto-configuration
* creates {@link org.springframework.messaging.rsocket.RSocketRequester.Builder}
* prototype beans, as the builders are stateful and should not be reused to build
* requester instances with different configurations.
*
* @author Brian Clozel
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RSocketRequester.class, RSocketFactory.class, HttpServer.class,
TcpServerTransport.class })
@AutoConfigureAfter(RSocketStrategiesAutoConfiguration.class)
public class RSocketRequesterAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public RSocketRequester.Builder rsocketRequesterBuilder(
RSocketStrategies strategies) {
return RSocketRequester.builder().rsocketStrategies(strategies);
}
}
......@@ -100,6 +100,7 @@ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
......
/*
* Copyright 2012-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
*
* https://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.boot.autoconfigure.rsocket;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.rsocket.RSocketRequester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link RSocketRequesterAutoConfiguration}
*
* @author Brian Clozel
*/
public class RSocketRequesterAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(RSocketStrategiesAutoConfiguration.class,
RSocketRequesterAutoConfiguration.class));
@Test
public void shouldCreateBuilder() {
this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(RSocketRequester.Builder.class));
}
@Test
public void shouldGetPrototypeScopedBean() {
this.contextRunner.run((context) -> {
RSocketRequester.Builder first = context
.getBean(RSocketRequester.Builder.class);
RSocketRequester.Builder second = context
.getBean(RSocketRequester.Builder.class);
assertThat(first).isNotEqualTo(second);
});
}
@Test
public void shouldNotCreateBuilderIfAlreadyPresent() {
this.contextRunner.withUserConfiguration(CustomRSocketRequesterBuilder.class)
.run((context) -> {
RSocketRequester.Builder builder = context
.getBean(RSocketRequester.Builder.class);
assertThat(builder).isInstanceOf(MyRSocketRequesterBuilder.class);
});
}
@Configuration(proxyBeanMethods = false)
static class CustomRSocketRequesterBuilder {
@Bean
public MyRSocketRequesterBuilder myRSocketRequesterBuilder() {
return mock(MyRSocketRequesterBuilder.class);
}
}
interface MyRSocketRequesterBuilder extends RSocketRequester.Builder {
}
}
......@@ -3528,6 +3528,42 @@ about customization possibilities.
Developers can create `RSocketStrategiesCustomizer` beans to add other strategies,
assuming there are `Encoder` and `Decoder` implementations available.
[[boot-features-rsocket-requester]]
=== Calling RSocket Services with `RSocketRequester`
Once the `RSocket` channel is established between server and client, any party can send or
receive requests to the other.
As a server, you can get injected an `RSocketRequester` instance on any handler method of
an RSocket `@Controller`. As a client, you need to configure and establish an RSocket
connection first. Spring Boot auto-configures an `RSocketRequester.Builder` for such cases
with the expected codecs.
The `RSocketRequester.Builder` instance is a prototype bean, meaning each injection point
will provide you with a new instance - this is done on purpose since this builder is stateful
and you shouldn't create requesters with different setups using the same instance.
The following code shows a typical example:
[source,java,indent=0]
----
@Service
public class MyService {
private final RSocketRequester rsocketRequester;
public MyService(RSocketRequester.Builder rsocketRequesterBuilder) {
this.rsocketRequester = rsocketRequesterBuilder
.connectTcp("example.org", 9090).block();
}
public Mono<User> someRSocketCall(String name) {
return this.requester.route("user").data(payload)
.retrieveMono(User.class);
}
}
----
[[boot-features-security]]
......
......@@ -27,7 +27,6 @@ import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.RSocketFactory;
import io.rsocket.SocketAcceptor;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.transport.netty.client.WebsocketClientTransport;
import io.rsocket.util.DefaultPayload;
import org.assertj.core.api.Assertions;
......@@ -45,7 +44,6 @@ import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
......@@ -96,7 +94,7 @@ public class NettyRSocketServerFactoryTests {
factory.setPort(specificPort);
this.rSocketServer = factory.create(new EchoRequestResponseAcceptor());
this.rSocketServer.start();
this.requester = getRSocketRequester(createRSocketTcpClient());
this.requester = createRSocketTcpClient();
String payload = "test payload";
String response = this.requester.route("test").data(payload)
.retrieveMono(String.class).block(TIMEOUT);
......@@ -111,7 +109,7 @@ public class NettyRSocketServerFactoryTests {
factory.setTransport(RSocketServer.TRANSPORT.WEBSOCKET);
this.rSocketServer = factory.create(new EchoRequestResponseAcceptor());
this.rSocketServer.start();
this.requester = getRSocketRequester(createRSocketWebSocketClient());
this.requester = createRSocketWebSocketClient();
String payload = "test payload";
String response = this.requester.route("test").data(payload)
.retrieveMono(String.class).block(TIMEOUT);
......@@ -136,29 +134,28 @@ public class NettyRSocketServerFactoryTests {
}
}
private RSocket createRSocketTcpClient() {
private RSocketRequester createRSocketTcpClient() {
Assertions.assertThat(this.rSocketServer).isNotNull();
InetSocketAddress address = this.rSocketServer.address();
return RSocketFactory.connect().dataMimeType(MimeTypeUtils.TEXT_PLAIN_VALUE)
.transport(TcpClientTransport.create(address)).start().block();
return createRSocketRequesterBuilder()
.connectTcp(address.getHostString(), address.getPort()).block();
}
private RSocket createRSocketWebSocketClient() {
private RSocketRequester createRSocketWebSocketClient() {
Assertions.assertThat(this.rSocketServer).isNotNull();
InetSocketAddress address = this.rSocketServer.address();
return RSocketFactory.connect().dataMimeType(MimeTypeUtils.TEXT_PLAIN_VALUE)
.transport(WebsocketClientTransport.create(address)).start().block();
return createRSocketRequesterBuilder()
.connect(WebsocketClientTransport.create(address)).block();
}
private RSocketRequester getRSocketRequester(RSocket rSocketClient) {
private RSocketRequester.Builder createRSocketRequesterBuilder() {
RSocketStrategies strategies = RSocketStrategies.builder()
.decoder(StringDecoder.allMimeTypes())
.encoder(CharSequenceEncoder.allMimeTypes())
.dataBufferFactory(
new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT))
.build();
return RSocketRequester.create(rSocketClient, MimeTypeUtils.TEXT_PLAIN,
strategies);
return RSocketRequester.builder().rsocketStrategies(strategies);
}
static class EchoRequestResponseAcceptor implements SocketAcceptor {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment