diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketRequesterAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketRequesterAutoConfiguration.java new file mode 100644 index 00000000000..fae2f4285f8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketRequesterAutoConfiguration.java @@ -0,0 +1,57 @@ +/* + * 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); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 2ccb0d34e97..55165252981 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -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,\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketRequesterAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketRequesterAutoConfigurationTests.java new file mode 100644 index 00000000000..571babbafa4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketRequesterAutoConfigurationTests.java @@ -0,0 +1,83 @@ +/* + * 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 { + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 94260694815..b595d58d40e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -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 someRSocketCall(String name) { + return this.requester.route("user").data(payload) + .retrieveMono(User.class); + } + + } +---- [[boot-features-security]] diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/netty/NettyRSocketServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/netty/NettyRSocketServerFactoryTests.java index fda11d6e7ec..98cab4ce5e8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/netty/NettyRSocketServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/netty/NettyRSocketServerFactoryTests.java @@ -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 {