Commit e8d39a15 authored by Brian Clozel's avatar Brian Clozel

Add support for CBOR codecs in RSocket

This commit auto-configures CBOR (see https://cbor.io/) codecs in the
RSocketStrategies, using Jackson binary format support.

The required dependency is added to the rsocket starter. Binary codecs
are well suited for RSocket payloads, so this codec is added first to
the list of codecs (before the JSON one already supported).

Closes gh-16830
parent 08cb8368
...@@ -42,6 +42,11 @@ ...@@ -42,6 +42,11 @@
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId> <groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId> <artifactId>jackson-dataformat-xml</artifactId>
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.rsocket; package org.springframework.boot.autoconfigure.rsocket;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator;
import io.rsocket.RSocketFactory; import io.rsocket.RSocketFactory;
...@@ -34,8 +35,11 @@ import org.springframework.core.ReactiveAdapterRegistry; ...@@ -34,8 +35,11 @@ import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.codec.cbor.Jackson2CborDecoder;
import org.springframework.http.codec.cbor.Jackson2CborEncoder;
import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.messaging.rsocket.RSocketStrategies;
/** /**
...@@ -56,20 +60,41 @@ public class RSocketStrategiesAutoConfiguration { ...@@ -56,20 +60,41 @@ public class RSocketStrategiesAutoConfiguration {
ObjectProvider<RSocketStrategiesCustomizer> customizers) { ObjectProvider<RSocketStrategiesCustomizer> customizers) {
RSocketStrategies.Builder builder = RSocketStrategies.builder(); RSocketStrategies.Builder builder = RSocketStrategies.builder();
builder.reactiveAdapterStrategy(ReactiveAdapterRegistry.getSharedInstance()); builder.reactiveAdapterStrategy(ReactiveAdapterRegistry.getSharedInstance());
customizers.stream().forEach((customizer) -> customizer.customize(builder)); customizers.orderedStream()
.forEach((customizer) -> customizer.customize(builder));
builder.dataBufferFactory( builder.dataBufferFactory(
new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT)); new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT));
return builder.build(); return builder.build();
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class) @ConditionalOnClass({ ObjectMapper.class, CBORFactory.class })
protected static class JacksonStrategyConfiguration { protected static class JacksonCborStrategyConfiguration {
@Bean @Bean
@Order(0) @Order(0)
@ConditionalOnBean(Jackson2ObjectMapperBuilder.class)
public RSocketStrategiesCustomizer jacksonCborStrategyCustomizer(
Jackson2ObjectMapperBuilder builder) {
return (strategy) -> {
ObjectMapper objectMapper = builder.factory(new CBORFactory()).build();
MediaType[] supportedTypes = new MediaType[] {
new MediaType("application", "cbor") };
strategy.decoder(new Jackson2CborDecoder(objectMapper, supportedTypes));
strategy.encoder(new Jackson2CborEncoder(objectMapper, supportedTypes));
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
protected static class JacksonJsonStrategyConfiguration {
@Bean
@Order(1)
@ConditionalOnBean(ObjectMapper.class) @ConditionalOnBean(ObjectMapper.class)
public RSocketStrategiesCustomizer jacksonStrategyCustomizer( public RSocketStrategiesCustomizer jacksonJsonStrategyCustomizer(
ObjectMapper objectMapper) { ObjectMapper objectMapper) {
return (strategy) -> { return (strategy) -> {
MediaType[] supportedTypes = new MediaType[] { MediaType.APPLICATION_JSON, MediaType[] supportedTypes = new MediaType[] { MediaType.APPLICATION_JSON,
......
...@@ -26,8 +26,11 @@ import org.springframework.context.annotation.Bean; ...@@ -26,8 +26,11 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.StringDecoder; import org.springframework.core.codec.StringDecoder;
import org.springframework.http.codec.cbor.Jackson2CborDecoder;
import org.springframework.http.codec.cbor.Jackson2CborEncoder;
import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.messaging.rsocket.RSocketStrategies;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -48,10 +51,16 @@ public class RSocketStrategiesAutoConfigurationTests { ...@@ -48,10 +51,16 @@ public class RSocketStrategiesAutoConfigurationTests {
this.contextRunner.run((context) -> { this.contextRunner.run((context) -> {
assertThat(context).getBeans(RSocketStrategies.class).hasSize(1); assertThat(context).getBeans(RSocketStrategies.class).hasSize(1);
RSocketStrategies strategies = context.getBean(RSocketStrategies.class); RSocketStrategies strategies = context.getBean(RSocketStrategies.class);
assertThat(strategies.decoders()).hasSize(1) assertThat(strategies.decoders()).hasSize(2);
.hasOnlyElementsOfType(Jackson2JsonDecoder.class); assertThat(strategies.decoders().get(0))
assertThat(strategies.encoders()).hasSize(1) .isInstanceOf(Jackson2CborDecoder.class);
.hasOnlyElementsOfType(Jackson2JsonEncoder.class); assertThat(strategies.decoders().get(1))
.isInstanceOf(Jackson2JsonDecoder.class);
assertThat(strategies.encoders()).hasSize(2);
assertThat(strategies.encoders().get(0))
.isInstanceOf(Jackson2CborEncoder.class);
assertThat(strategies.encoders().get(1))
.isInstanceOf(Jackson2JsonEncoder.class);
}); });
} }
...@@ -71,9 +80,9 @@ public class RSocketStrategiesAutoConfigurationTests { ...@@ -71,9 +80,9 @@ public class RSocketStrategiesAutoConfigurationTests {
assertThat(context).getBeans(RSocketStrategies.class).hasSize(1); assertThat(context).getBeans(RSocketStrategies.class).hasSize(1);
RSocketStrategies strategies = context RSocketStrategies strategies = context
.getBean(RSocketStrategies.class); .getBean(RSocketStrategies.class);
assertThat(strategies.decoders()).hasSize(2) assertThat(strategies.decoders()).hasSize(3)
.hasAtLeastOneElementOfType(StringDecoder.class); .hasAtLeastOneElementOfType(StringDecoder.class);
assertThat(strategies.encoders()).hasSize(2) assertThat(strategies.encoders()).hasSize(3)
.hasAtLeastOneElementOfType(CharSequenceEncoder.class); .hasAtLeastOneElementOfType(CharSequenceEncoder.class);
}); });
} }
...@@ -86,6 +95,11 @@ public class RSocketStrategiesAutoConfigurationTests { ...@@ -86,6 +95,11 @@ public class RSocketStrategiesAutoConfigurationTests {
return new ObjectMapper(); return new ObjectMapper();
} }
@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return new Jackson2ObjectMapperBuilder();
}
} }
@Configuration @Configuration
......
...@@ -135,6 +135,11 @@ ...@@ -135,6 +135,11 @@
<artifactId>jackson-datatype-joda</artifactId> <artifactId>jackson-datatype-joda</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId> <groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId> <artifactId>jackson-dataformat-xml</artifactId>
......
...@@ -3483,6 +3483,22 @@ The following code shows a typical `@Controller`: ...@@ -3483,6 +3483,22 @@ The following code shows a typical `@Controller`:
} }
---- ----
[[boot-features-rsocket-strategies-auto-configuration]]
=== RSocket Strategies Auto-configuration
Spring Boot auto-configures an `RSocketStrategies` bean that provides all the required
infrastructure for encoding and decoding RSocket payloads. By default, the
auto-configuration will try to configure the following (in order):
1. https://cbor.io/[CBOR] codecs with Jackson
2. JSON codecs with Jackson
The `spring-boot-starter-rsocket` Starter provides both dependencies.
Developers can customize the `RSocketStrategies` component by creating beans that
implement the `RSocketStrategiesCustomizer` interface. Note that their `@Order` is
important, as it determines the order of codecs.
[[boot-features-rsocket-server-auto-configuration]] [[boot-features-rsocket-server-auto-configuration]]
=== RSocket server Auto-configuration === RSocket server Auto-configuration
Spring Boot provides auto-configuration for RSocket servers. The required dependencies Spring Boot provides auto-configuration for RSocket servers. The required dependencies
......
...@@ -54,5 +54,9 @@ ...@@ -54,5 +54,9 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId> <artifactId>spring-boot-starter-json</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>
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