diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketProperties.java index f1921f23885..dd16caad719 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2023 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. @@ -73,6 +73,8 @@ public class RSocketProperties { @NestedConfigurationProperty private Ssl ssl; + private final Spec spec = new Spec(); + public Integer getPort() { return this.port; } @@ -121,6 +123,66 @@ public class RSocketProperties { this.ssl = ssl; } + public Spec getSpec() { + return this.spec; + } + + public static class Spec { + + /** + * Sub-protocol to use in websocket handshake signature. + */ + private String protocols; + + /** + * Maximum allowable frame payload length. + */ + private DataSize maxFramePayloadLength = DataSize.ofBytes(65536); + + /** + * Whether to proxy websocket ping frames or respond to them. + */ + private boolean handlePing; + + /** + * Whether the websocket compression extension is enabled. + */ + private boolean compress; + + public String getProtocols() { + return this.protocols; + } + + public void setProtocols(String protocols) { + this.protocols = protocols; + } + + public DataSize getMaxFramePayloadLength() { + return this.maxFramePayloadLength; + } + + public void setMaxFramePayloadLength(DataSize maxFramePayloadLength) { + this.maxFramePayloadLength = maxFramePayloadLength; + } + + public boolean isHandlePing() { + return this.handlePing; + } + + public void setHandlePing(boolean handlePing) { + this.handlePing = handlePing; + } + + public boolean isCompress() { + return this.compress; + } + + public void setCompress(boolean compress) { + this.compress = compress; + } + + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfiguration.java index 96bbbc454ba..e329ef099e3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfiguration.java @@ -16,10 +16,13 @@ package org.springframework.boot.autoconfigure.rsocket; +import java.util.function.Consumer; + import io.rsocket.core.RSocketServer; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.transport.netty.server.TcpServerTransport; import reactor.netty.http.server.HttpServer; +import reactor.netty.http.server.WebsocketServerSpec.Builder; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -31,6 +34,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.reactor.netty.ReactorNettyConfigurations; +import org.springframework.boot.autoconfigure.rsocket.RSocketProperties.Server.Spec; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.rsocket.context.RSocketServerBootstrap; @@ -46,6 +50,7 @@ import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.http.client.reactive.ReactorResourceFactory; import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; +import org.springframework.util.unit.DataSize; /** * {@link EnableAutoConfiguration Auto-configuration} for RSocket servers. In the case of @@ -73,7 +78,18 @@ public class RSocketServerAutoConfiguration { RSocketWebSocketNettyRouteProvider rSocketWebsocketRouteProvider(RSocketProperties properties, RSocketMessageHandler messageHandler, ObjectProvider customizers) { return new RSocketWebSocketNettyRouteProvider(properties.getServer().getMappingPath(), - messageHandler.responder(), customizers.orderedStream()); + messageHandler.responder(), customizeWebsocketServerSpec(properties.getServer().getSpec()), + customizers.orderedStream()); + } + + private Consumer customizeWebsocketServerSpec(Spec spec) { + return (builder) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(spec.getProtocols()).to(builder::protocols); + map.from(spec.getMaxFramePayloadLength()).asInt(DataSize::toBytes).to(builder::maxFramePayloadLength); + map.from(spec.isHandlePing()).to(builder::handlePing); + map.from(spec.isCompress()).to(builder::compress); + }; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketWebSocketNettyRouteProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketWebSocketNettyRouteProvider.java index 5cb8d7f374f..f70f9d41d54 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketWebSocketNettyRouteProvider.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketWebSocketNettyRouteProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2023 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. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.rsocket; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Stream; import io.rsocket.SocketAcceptor; @@ -24,6 +25,8 @@ import io.rsocket.core.RSocketServer; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.server.WebsocketRouteTransport; import reactor.netty.http.server.HttpServerRoutes; +import reactor.netty.http.server.WebsocketServerSpec; +import reactor.netty.http.server.WebsocketServerSpec.Builder; import org.springframework.boot.rsocket.server.RSocketServerCustomizer; import org.springframework.boot.web.embedded.netty.NettyRouteProvider; @@ -32,6 +35,7 @@ import org.springframework.boot.web.embedded.netty.NettyRouteProvider; * {@link NettyRouteProvider} that configures an RSocket Websocket endpoint. * * @author Brian Clozel + * @author Leo Li */ class RSocketWebSocketNettyRouteProvider implements NettyRouteProvider { @@ -41,10 +45,13 @@ class RSocketWebSocketNettyRouteProvider implements NettyRouteProvider { private final List customizers; + private final Consumer serverSpecCustomizer; + RSocketWebSocketNettyRouteProvider(String mappingPath, SocketAcceptor socketAcceptor, - Stream customizers) { + Consumer serverSpecCustomizer, Stream customizers) { this.mappingPath = mappingPath; this.socketAcceptor = socketAcceptor; + this.serverSpecCustomizer = serverSpecCustomizer; this.customizers = customizers.toList(); } @@ -53,7 +60,14 @@ class RSocketWebSocketNettyRouteProvider implements NettyRouteProvider { RSocketServer server = RSocketServer.create(this.socketAcceptor); this.customizers.forEach((customizer) -> customizer.customize(server)); ServerTransport.ConnectionAcceptor connectionAcceptor = server.asConnectionAcceptor(); - return httpServerRoutes.ws(this.mappingPath, WebsocketRouteTransport.newHandler(connectionAcceptor)); + return httpServerRoutes.ws(this.mappingPath, WebsocketRouteTransport.newHandler(connectionAcceptor), + createWebsocketServerSpec()); + } + + private WebsocketServerSpec createWebsocketServerSpec() { + WebsocketServerSpec.Builder builder = WebsocketServerSpec.builder(); + this.serverSpecCustomizer.accept(builder); + return builder.build(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketPropertiesTests.java new file mode 100644 index 00000000000..eefd1b7212d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketPropertiesTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2023 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 reactor.netty.http.server.WebsocketServerSpec; + +import org.springframework.boot.autoconfigure.rsocket.RSocketProperties.Server.Spec; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RSocketProperties}. + * + * @author Stephane Nicoll + */ +class RSocketPropertiesTests { + + @Test + void defaultServerSpecValuesAreConsistent() { + WebsocketServerSpec spec = WebsocketServerSpec.builder().build(); + Spec properties = new RSocketProperties().getServer().getSpec(); + assertThat(properties.getProtocols()).isEqualTo(spec.protocols()); + assertThat(properties.getMaxFramePayloadLength().toBytes()).isEqualTo(spec.maxFramePayloadLength()); + assertThat(properties.isHandlePing()).isEqualTo(spec.handlePing()); + assertThat(properties.isCompress()).isEqualTo(spec.compress()); + } + +}