Document RSocket support
* Fix some typos in `webflux.adoc` and `dsl.adoc` * Make `FunctionsTests.kt` more Kotlin friendly * Improve RSocket components and tests for them, especially rely on the default `RSocketStrategies` in the target SF RSocket components * Rework `ServerRSocketConnector.setClientRSocketKeyStrategy` to use the whole `MessageHeaders` for consultation. It turns out that just destination is not enough since it can be used from different clients * Make the key based on the provided `setupData` by default * Include all the `MessageHeaders` into the `RSocketConnectedEvent` * Improve Docs according review feedback and Docs in SF Doc polishing
This commit is contained in:
committed by
Gary Russell
parent
21335812a3
commit
b12657c599
@@ -152,14 +152,7 @@ class FunctionsTests {
|
||||
|
||||
@Bean
|
||||
fun flowFromSupplier() =
|
||||
IntegrationFlows.from<String>({ "bar" },
|
||||
{ e ->
|
||||
e.poller { p ->
|
||||
p.fixedDelay(10)
|
||||
.maxMessagesPerPoll(1)
|
||||
}
|
||||
|
||||
})
|
||||
IntegrationFlows.from<String>({ "bar" }) { e -> e.poller { p -> p.fixedDelay(10).maxMessagesPerPoll(1) } }
|
||||
.channel { c -> c.queue("fromSupplierQueue") }
|
||||
.get()
|
||||
}
|
||||
|
||||
@@ -23,10 +23,6 @@ import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.messaging.rsocket.MetadataExtractor;
|
||||
import org.springframework.messaging.rsocket.RSocketStrategies;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
@@ -38,7 +34,7 @@ import io.rsocket.metadata.WellKnownMimeType;
|
||||
* A base connector container for common RSocket client and server functionality.
|
||||
* <p>
|
||||
* It accepts {@link IntegrationRSocketEndpoint} instances for mapping registration via an internal
|
||||
* {@link IntegrationRSocketMessageHandler} or performs an auto-detection otherwise, when all bean are ready
|
||||
* {@link IntegrationRSocketMessageHandler} or performs an auto-detection otherwise, when all beans are ready
|
||||
* in the application context.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
@@ -58,12 +54,7 @@ public abstract class AbstractRSocketConnector
|
||||
private MimeType metadataMimeType =
|
||||
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.toString());
|
||||
|
||||
private RSocketStrategies rsocketStrategies =
|
||||
RSocketStrategies.builder()
|
||||
.decoder(StringDecoder.allMimeTypes())
|
||||
.encoder(CharSequenceEncoder.allMimeTypes())
|
||||
.dataBufferFactory(new DefaultDataBufferFactory())
|
||||
.build();
|
||||
private RSocketStrategies rsocketStrategies = RSocketStrategies.create();
|
||||
|
||||
private boolean autoStartup = true;
|
||||
|
||||
@@ -114,7 +105,7 @@ public abstract class AbstractRSocketConnector
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure {@link IntegrationRSocketEndpoint} instances for mapping nad handling requests.
|
||||
* Configure {@link IntegrationRSocketEndpoint} instances for mapping and handling requests.
|
||||
* @param endpoints the {@link IntegrationRSocketEndpoint} instances for handling inbound requests.
|
||||
* @see #addEndpoint(IntegrationRSocketEndpoint)
|
||||
*/
|
||||
@@ -125,20 +116,6 @@ public abstract class AbstractRSocketConnector
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link MetadataExtractor} to extract the route and possibly
|
||||
* other metadata from the first payload of incoming requests.
|
||||
* <p>By default this is a
|
||||
* {@link org.springframework.messaging.rsocket.DefaultMetadataExtractor}
|
||||
* with the configured {@link RSocketStrategies} (and decoders), extracting a route
|
||||
* from {@code "message/x.rsocket.routing.v0"} or {@code "text/plain"}
|
||||
* metadata entries.
|
||||
* @param extractor the extractor to use
|
||||
*/
|
||||
public void setMetadataExtractor(MetadataExtractor extractor) {
|
||||
this.rSocketMessageHandler.setMetadataExtractor(extractor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an {@link IntegrationRSocketEndpoint} for mapping and handling RSocket requests.
|
||||
* @param endpoint the {@link IntegrationRSocketEndpoint} to map.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.integration.rsocket;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.integration.events.IntegrationEvent;
|
||||
import org.springframework.messaging.rsocket.RSocketRequester;
|
||||
@@ -25,7 +27,7 @@ import org.springframework.messaging.rsocket.RSocketRequester;
|
||||
* to the server.
|
||||
* <p>
|
||||
* This event can be used for mapping {@link RSocketRequester} to the client by the
|
||||
* {@code destination} meta-data or connect payload {@code data}.
|
||||
* {@code headers} meta-data or connect payload {@code data}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
@@ -36,21 +38,23 @@ import org.springframework.messaging.rsocket.RSocketRequester;
|
||||
@SuppressWarnings("serial")
|
||||
public class RSocketConnectedEvent extends IntegrationEvent {
|
||||
|
||||
private final String destination;
|
||||
private final Map<String, Object> headers;
|
||||
|
||||
private final DataBuffer data;
|
||||
|
||||
private final RSocketRequester requester;
|
||||
|
||||
public RSocketConnectedEvent(Object source, String destination, DataBuffer data, RSocketRequester requester) {
|
||||
public RSocketConnectedEvent(Object source, Map<String, Object> headers, DataBuffer data,
|
||||
RSocketRequester requester) {
|
||||
|
||||
super(source);
|
||||
this.destination = destination;
|
||||
this.headers = headers;
|
||||
this.data = data;
|
||||
this.requester = requester;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return this.destination;
|
||||
public Map<String, Object> getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public DataBuffer getData() {
|
||||
@@ -64,7 +68,8 @@ public class RSocketConnectedEvent extends IntegrationEvent {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RSocketConnectedEvent{" +
|
||||
"destination='" + this.destination + '\'' +
|
||||
"headers=" + this.headers +
|
||||
", data=" + this.data +
|
||||
", requester=" + this.requester +
|
||||
'}';
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.rsocket;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -36,7 +37,6 @@ import org.springframework.messaging.rsocket.annotation.support.RSocketFrameType
|
||||
import org.springframework.messaging.rsocket.annotation.support.RSocketRequesterMethodArgumentResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.RouteMatcher;
|
||||
|
||||
import io.rsocket.RSocketFactory;
|
||||
import io.rsocket.transport.ServerTransport;
|
||||
@@ -108,7 +108,9 @@ public class ServerRSocketConnector extends AbstractRSocketConnector
|
||||
* Defaults to the {@code destination} the client is connected.
|
||||
* @param clientRSocketKeyStrategy the {@link BiFunction} to determine a key for client {@link RSocketRequester}s.
|
||||
*/
|
||||
public void setClientRSocketKeyStrategy(BiFunction<String, DataBuffer, Object> clientRSocketKeyStrategy) {
|
||||
public void setClientRSocketKeyStrategy(BiFunction<Map<String, Object>,
|
||||
DataBuffer, Object> clientRSocketKeyStrategy) {
|
||||
|
||||
Assert.notNull(clientRSocketKeyStrategy, "'clientRSocketKeyStrategy' must not be null");
|
||||
serverRSocketMessageHandler().clientRSocketKeyStrategy = clientRSocketKeyStrategy;
|
||||
}
|
||||
@@ -176,7 +178,8 @@ public class ServerRSocketConnector extends AbstractRSocketConnector
|
||||
|
||||
private final Map<Object, RSocketRequester> clientRSocketRequesters = new HashMap<>();
|
||||
|
||||
private BiFunction<String, DataBuffer, Object> clientRSocketKeyStrategy = (destination, data) -> destination;
|
||||
private BiFunction<Map<String, Object>, DataBuffer, Object> clientRSocketKeyStrategy =
|
||||
(headers, data) -> data.toString(StandardCharsets.UTF_8);
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
@@ -184,28 +187,20 @@ public class ServerRSocketConnector extends AbstractRSocketConnector
|
||||
registerHandlerMethod(this, HANDLE_CONNECTION_SETUP_METHOD,
|
||||
new CompositeMessageCondition(
|
||||
RSocketFrameTypeMessageCondition.CONNECT_CONDITION,
|
||||
new DestinationPatternsMessageCondition(new String[] { "*" }, getRouteMatcher()))); // NOSONAR
|
||||
new DestinationPatternsMessageCondition(new String[] { "*" }, obtainRouteMatcher())));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void handleConnectionSetup(Message<DataBuffer> connectMessage) {
|
||||
DataBuffer dataBuffer = connectMessage.getPayload();
|
||||
MessageHeaders messageHeaders = connectMessage.getHeaders();
|
||||
String destination = "";
|
||||
RouteMatcher.Route route =
|
||||
messageHeaders.get(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER,
|
||||
RouteMatcher.Route.class);
|
||||
if (route != null) {
|
||||
destination = route.value();
|
||||
}
|
||||
|
||||
Object rsocketRequesterKey = this.clientRSocketKeyStrategy.apply(destination, dataBuffer);
|
||||
Object rsocketRequesterKey = this.clientRSocketKeyStrategy.apply(messageHeaders, dataBuffer);
|
||||
RSocketRequester rsocketRequester =
|
||||
messageHeaders.get(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER,
|
||||
RSocketRequester.class);
|
||||
this.clientRSocketRequesters.put(rsocketRequesterKey, rsocketRequester);
|
||||
RSocketConnectedEvent rSocketConnectedEvent =
|
||||
new RSocketConnectedEvent(this, destination, dataBuffer, rsocketRequester); // NOSONAR
|
||||
new RSocketConnectedEvent(this, messageHeaders, dataBuffer, rsocketRequester); // NOSONAR
|
||||
if (this.applicationEventPublisher != null) {
|
||||
this.applicationEventPublisher.publishEvent(rSocketConnectedEvent);
|
||||
}
|
||||
|
||||
@@ -22,13 +22,10 @@ import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.integration.gateway.MessagingGatewaySupport;
|
||||
import org.springframework.integration.rsocket.AbstractRSocketConnector;
|
||||
import org.springframework.integration.rsocket.ClientRSocketConnector;
|
||||
@@ -77,12 +74,7 @@ public class RSocketInboundGateway extends MessagingGatewaySupport implements In
|
||||
|
||||
private final String[] path;
|
||||
|
||||
private RSocketStrategies rsocketStrategies =
|
||||
RSocketStrategies.builder()
|
||||
.decoder(StringDecoder.allMimeTypes())
|
||||
.encoder(CharSequenceEncoder.allMimeTypes())
|
||||
.dataBufferFactory(new DefaultDataBufferFactory())
|
||||
.build();
|
||||
private RSocketStrategies rsocketStrategies = RSocketStrategies.create();
|
||||
|
||||
@Nullable
|
||||
private AbstractRSocketConnector rsocketConnector;
|
||||
@@ -91,7 +83,7 @@ public class RSocketInboundGateway extends MessagingGatewaySupport implements In
|
||||
private ResolvableType requestElementType;
|
||||
|
||||
/**
|
||||
* Instantiate based on the provided Ant-style path patterns to map this endpoint for incoming RSocket requests.
|
||||
* Instantiate based on the provided path patterns to map this endpoint for incoming RSocket requests.
|
||||
* @param pathArg the mapping patterns to use.
|
||||
*/
|
||||
public RSocketInboundGateway(String... pathArg) {
|
||||
|
||||
@@ -26,21 +26,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.dsl.IntegrationFlow;
|
||||
import org.springframework.integration.dsl.IntegrationFlows;
|
||||
import org.springframework.integration.rsocket.ClientRSocketConnector;
|
||||
import org.springframework.integration.rsocket.ServerRSocketConnector;
|
||||
import org.springframework.integration.rsocket.outbound.RSocketOutboundGateway;
|
||||
import org.springframework.messaging.rsocket.RSocketStrategies;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.rsocket.frame.decoder.PayloadDecoder;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -66,29 +60,15 @@ public class RSocketDslTests {
|
||||
@EnableIntegration
|
||||
public static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.decoder(StringDecoder.allMimeTypes())
|
||||
.encoder(CharSequenceEncoder.allMimeTypes())
|
||||
.dataBufferFactory(new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerRSocketConnector serverRSocketConnector() {
|
||||
ServerRSocketConnector serverRSocketConnector = new ServerRSocketConnector("localhost", 0);
|
||||
serverRSocketConnector.setRSocketStrategies(rsocketStrategies());
|
||||
serverRSocketConnector.setFactoryConfigurer((factory) -> factory.frameDecoder(PayloadDecoder.ZERO_COPY));
|
||||
return serverRSocketConnector;
|
||||
return new ServerRSocketConnector("localhost", 0);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClientRSocketConnector clientRSocketConnector(ServerRSocketConnector serverRSocketConnector) {
|
||||
int port = serverRSocketConnector.getBoundPort().block();
|
||||
ClientRSocketConnector clientRSocketConnector = new ClientRSocketConnector("localhost", port);
|
||||
clientRSocketConnector.setFactoryConfigurer((factory) -> factory.frameDecoder(PayloadDecoder.ZERO_COPY));
|
||||
clientRSocketConnector.setRSocketStrategies(rsocketStrategies());
|
||||
clientRSocketConnector.setAutoStartup(false);
|
||||
return clientRSocketConnector;
|
||||
}
|
||||
@@ -107,8 +87,7 @@ public class RSocketDslTests {
|
||||
@Bean
|
||||
public IntegrationFlow rsocketUpperCaseFlow() {
|
||||
return IntegrationFlows
|
||||
.from(RSockets.inboundGateway("/uppercase")
|
||||
.rsocketStrategies(rsocketStrategies()))
|
||||
.from(RSockets.inboundGateway("/uppercase"))
|
||||
.<Flux<String>, Mono<String>>transform((flux) -> flux.next().map(String::toUpperCase))
|
||||
.get();
|
||||
}
|
||||
|
||||
@@ -27,15 +27,11 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.integration.annotation.Transformer;
|
||||
import org.springframework.integration.channel.FluxMessageChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.rsocket.ClientRSocketConnector;
|
||||
@@ -44,13 +40,9 @@ import org.springframework.integration.rsocket.ServerRSocketConnector;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.rsocket.RSocketRequester;
|
||||
import org.springframework.messaging.rsocket.RSocketStrategies;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.rsocket.frame.decoder.PayloadDecoder;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoProcessor;
|
||||
@@ -154,15 +146,6 @@ public class RSocketInboundGatewayIntegrationTests {
|
||||
|
||||
private abstract static class CommonConfig {
|
||||
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.decoder(StringDecoder.allMimeTypes())
|
||||
.encoder(CharSequenceEncoder.allMimeTypes())
|
||||
.dataBufferFactory(new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PollableChannel fireAndForgetChannelChannel() {
|
||||
return new QueueChannel();
|
||||
@@ -171,7 +154,6 @@ public class RSocketInboundGatewayIntegrationTests {
|
||||
@Bean
|
||||
public RSocketInboundGateway rsocketInboundGatewayFireAndForget() {
|
||||
RSocketInboundGateway rsocketInboundGateway = new RSocketInboundGateway("receive");
|
||||
rsocketInboundGateway.setRSocketStrategies(rsocketStrategies());
|
||||
rsocketInboundGateway.setRequestChannel(fireAndForgetChannelChannel());
|
||||
return rsocketInboundGateway;
|
||||
}
|
||||
@@ -179,16 +161,10 @@ public class RSocketInboundGatewayIntegrationTests {
|
||||
@Bean
|
||||
public RSocketInboundGateway rsocketInboundGatewayRequestReply() {
|
||||
RSocketInboundGateway rsocketInboundGateway = new RSocketInboundGateway("echo");
|
||||
rsocketInboundGateway.setRSocketStrategies(rsocketStrategies());
|
||||
rsocketInboundGateway.setRequestChannel(requestReplyChannel());
|
||||
rsocketInboundGateway.setRequestChannelName("requestReplyChannel");
|
||||
return rsocketInboundGateway;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FluxMessageChannel requestReplyChannel() {
|
||||
return new FluxMessageChannel();
|
||||
}
|
||||
|
||||
@Transformer(inputChannel = "requestReplyChannel")
|
||||
public Mono<String> echoTransformation(Flux<String> payload) {
|
||||
return payload.next().map(String::toUpperCase);
|
||||
@@ -198,22 +174,18 @@ public class RSocketInboundGatewayIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
static class ServerConfig extends CommonConfig implements ApplicationListener<RSocketConnectedEvent> {
|
||||
static class ServerConfig extends CommonConfig {
|
||||
|
||||
final MonoProcessor<RSocketRequester> clientRequester = MonoProcessor.create();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(RSocketConnectedEvent event) {
|
||||
this.clientRequester.onNext(event.getRequester());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerRSocketConnector serverRSocketConnector() {
|
||||
ServerRSocketConnector serverRSocketConnector = new ServerRSocketConnector("localhost", 0);
|
||||
serverRSocketConnector.setRSocketStrategies(rsocketStrategies());
|
||||
serverRSocketConnector.setMetadataMimeType(new MimeType("message", "x.rsocket.routing.v0"));
|
||||
serverRSocketConnector.setFactoryConfigurer((factory) -> factory.frameDecoder(PayloadDecoder.ZERO_COPY));
|
||||
return serverRSocketConnector;
|
||||
return new ServerRSocketConnector("localhost", 0);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onApplicationEvent(RSocketConnectedEvent event) {
|
||||
this.clientRequester.onNext(event.getRequester());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -227,10 +199,8 @@ public class RSocketInboundGatewayIntegrationTests {
|
||||
ClientRSocketConnector clientRSocketConnector =
|
||||
new ClientRSocketConnector("localhost",
|
||||
serverConfig.serverRSocketConnector().getBoundPort().block());
|
||||
clientRSocketConnector.setMetadataMimeType(new MimeType("message", "x.rsocket.routing.v0"));
|
||||
clientRSocketConnector.setFactoryConfigurer((factory) -> factory.frameDecoder(PayloadDecoder.ZERO_COPY));
|
||||
clientRSocketConnector.setRSocketStrategies(rsocketStrategies());
|
||||
clientRSocketConnector.setSetupRoute("clientConnect");
|
||||
clientRSocketConnector.setSetupRoute("clientConnect/{user}");
|
||||
clientRSocketConnector.setSetupRouteVariables("myUser");
|
||||
return clientRSocketConnector;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
import org.springframework.integration.channel.FluxMessageChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
@@ -59,7 +56,6 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.rsocket.RSocket;
|
||||
import io.rsocket.RSocketFactory;
|
||||
import io.rsocket.frame.decoder.PayloadDecoder;
|
||||
@@ -468,15 +464,6 @@ public class RSocketOutboundGatewayIntegrationTests {
|
||||
|
||||
private abstract static class CommonConfig {
|
||||
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.decoder(StringDecoder.allMimeTypes())
|
||||
.encoder(CharSequenceEncoder.allMimeTypes())
|
||||
.dataBufferFactory(new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestController controller() {
|
||||
return new TestController();
|
||||
@@ -515,10 +502,9 @@ public class RSocketOutboundGatewayIntegrationTests {
|
||||
@Bean(destroyMethod = "dispose")
|
||||
@Nullable
|
||||
public RSocket rsocketForServerRequests() {
|
||||
|
||||
return RSocketRequester.builder()
|
||||
.setupRoute("clientConnect")
|
||||
.rsocketFactory(RSocketMessageHandler.clientResponder(rsocketStrategies(), controller()))
|
||||
.rsocketFactory(RSocketMessageHandler.clientResponder(RSocketStrategies.create(), controller()))
|
||||
.connectTcp("localhost", server.address().getPort())
|
||||
.block()
|
||||
.rsocket();
|
||||
@@ -526,10 +512,7 @@ public class RSocketOutboundGatewayIntegrationTests {
|
||||
|
||||
@Bean
|
||||
public ClientRSocketConnector clientRSocketConnector() {
|
||||
ClientRSocketConnector clientRSocketConnector =
|
||||
new ClientRSocketConnector("localhost", server.address().getPort());
|
||||
clientRSocketConnector.setRSocketStrategies(rsocketStrategies());
|
||||
return clientRSocketConnector;
|
||||
return new ClientRSocketConnector("localhost", server.address().getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -548,9 +531,7 @@ public class RSocketOutboundGatewayIntegrationTests {
|
||||
|
||||
@Bean
|
||||
public RSocketMessageHandler messageHandler() {
|
||||
RSocketMessageHandler handler = new RSocketMessageHandler();
|
||||
handler.setRSocketStrategies(rsocketStrategies());
|
||||
return handler;
|
||||
return new RSocketMessageHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1142,7 +1142,7 @@ In this case, the message handler for the first flow can be referenced with bean
|
||||
NOTE: An `id` attribute is required when you usE `useFlowIdAsPrefix()`.
|
||||
|
||||
[[java-dsl-gateway]]
|
||||
=== `IntegrationFlow` as Gateway
|
||||
=== `IntegrationFlow` as a Gateway
|
||||
|
||||
The `IntegrationFlow` can start from the service interface that provides a `GatewayProxyFactoryBean` component, as the following example shows:
|
||||
|
||||
|
||||
328
src/reference/asciidoc/rsocket.adoc
Normal file
328
src/reference/asciidoc/rsocket.adoc
Normal file
@@ -0,0 +1,328 @@
|
||||
[[rsocket]]
|
||||
== RSocket Support
|
||||
|
||||
The RSocket Spring Integration module (`spring-integration-rsocket`) allows for executions of https://rsocket.io/[RSocket application protocol].
|
||||
|
||||
You need to include this dependency into your project:
|
||||
|
||||
====
|
||||
.Maven
|
||||
[source, xml, subs="normal"]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-rsocket</artifactId>
|
||||
<version>{project-version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
.Gradle
|
||||
[source, groovy, subs="normal"]
|
||||
----
|
||||
compile "org.springframework.integration:spring-integration-rsocket:{project-version}"
|
||||
----
|
||||
====
|
||||
|
||||
This module is available starting with version 5.2 and is based on the Spring Messaging foundation with its RSocket component implementations, such as `RSocketRequester`, `RSocketMessageHandler` and `RSocketStrategies`.
|
||||
See https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#rsocket[Spring Framework RSocket Support] for more information about the RSocket protocol, terminology and components.
|
||||
|
||||
Before starting an integration flow processing via channel adapters, we need to establish an RSocket connection between server and client.
|
||||
For this purpose, Spring Integration RSocket support provides the `ServerRSocketConnector` and `ClientRSocketConnector` implementations of the `AbstractRSocketConnector`.
|
||||
|
||||
The `ServerRSocketConnector` exposes a listener on the host and port according to provided `io.rsocket.transport.ServerTransport` for accepting connections from clients.
|
||||
An internal `RSocketFactory.ServerRSocketFactory` instance can be customized with the `setFactoryConfigurer()`, as well as other options that can be configured, e.g. `RSocketStrategies` and `MimeType` for payload data and headers metadata.
|
||||
When a `setupRoute` is provided from the client requester (see `ClientRSocketConnector` below), a connected client is stored as a `RSocketRequester` under the key determined by the `clientRSocketKeyStrategy` `BiFunction<Map<String, Object>, DataBuffer, Object>`.
|
||||
By default a connect data is used for the key as a converted value to string with UTF-8 charset.
|
||||
Such an `RSocketRequester` registry can be used in the application logic to determine a particular client connection for interaction with it, or for publishing the same message to all connected clients.
|
||||
When a connection is established from the client, an `RSocketConnectedEvent` is emitted from the `ServerRSocketConnector`.
|
||||
This is similar to what is provided by the `@ConnectMapping` annotation in Spring Messaging module.
|
||||
The mapping pattern `*` means accept all the client routes.
|
||||
The `RSocketConnectedEvent` can be used to distinguish different routes via `DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER` header.
|
||||
|
||||
A typical server configuration might look like this:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.decoder(StringDecoder.textPlainOnly())
|
||||
.encoder(CharSequenceEncoder.allMimeTypes())
|
||||
.dataBufferFactory(new DefaultDataBufferFactory(true))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerRSocketConnector serverRSocketConnector() {
|
||||
ServerRSocketConnector serverRSocketConnector = new ServerRSocketConnector("localhost", 0);
|
||||
serverRSocketConnector.setRSocketStrategies(rsocketStrategies());
|
||||
serverRSocketConnector.setMetadataMimeType(new MimeType("message", "x.rsocket.routing.v0"));
|
||||
serverRSocketConnector.setFactoryConfigurer((factory) -> factory.frameDecoder(PayloadDecoder.ZERO_COPY));
|
||||
serverRSocketConnector.setClientRSocketKeyStrategy((headers, data) -> ""
|
||||
+ headers.get(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER));
|
||||
return serverRSocketConnector;
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onApplicationEvent(RSocketConnectedEvent event) {
|
||||
...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
All the options, including `RSocketStrategies` bean and `@EventListener` for `RSocketConnectedEvent`, are optional.
|
||||
See `ServerRSocketConnector` JavaDocs for more information.
|
||||
|
||||
The `ClientRSocketConnector` serves as a holder for `RSocketRequester` based on the `RSocket` connected via the provided `ClientTransport`.
|
||||
The `RSocketFactory.ClientRSocketFactory` can be customized with the provided `ClientRSocketFactoryConfigurer`.
|
||||
The `setupRoute` (with optional templates variables) and `setupData` with metadata can be also configured on this component.
|
||||
|
||||
A typical client configuration might look like this:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.decoder(StringDecoder.textPlainOnly())
|
||||
.encoder(CharSequenceEncoder.allMimeTypes())
|
||||
.dataBufferFactory(new DefaultDataBufferFactory(true))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClientRSocketConnector clientRSocketConnector() {
|
||||
ClientRSocketConnector clientRSocketConnector =
|
||||
new ClientRSocketConnector("localhost", serverRSocketConnector().getBoundPort().block());
|
||||
clientRSocketConnector.setRSocketStrategies(rsocketStrategies());
|
||||
clientRSocketConnector.setSetupRoute("clientConnect/{user}");
|
||||
clientRSocketConnector.setSetupRouteVariables("myUser");
|
||||
return clientRSocketConnector;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Most of these options (including `RSocketStrategies` bean) are optional.
|
||||
Note how we connect to the locally started RSocket server on the arbitrary port.
|
||||
See `ServerRSocketConnector.clientRSocketKeyStrategy` for `setupData` use cases.
|
||||
Also see `ClientRSocketConnector` and its `AbstractRSocketConnector` superclass JavaDocs for more information.
|
||||
|
||||
Both `ClientRSocketConnector` and `ServerRSocketConnector` are responsible for mapping inbound channel adapters to their `path` configuration for routing incoming RSocket requests.
|
||||
See the next section for more information.
|
||||
|
||||
[[rsocket-inbound]]
|
||||
=== RSocket Inbound Gateway
|
||||
|
||||
The `RSocketInboundGateway` is responsible for receiving RSocket requests and producing responses (if any).
|
||||
It requires an array of `path` mapping which could be as patterns similar to MVC request mapping or `@MessageMapping` semantics.
|
||||
Such a bean, according its `IntegrationRSocketEndpoint` implementation (extension of a `ReactiveMessageHandler`), is auto detected either by the `ServerRSocketConnector` or `ClientRSocketConnector` for a routing logic in the internal `IntegrationRSocketMessageHandler` for incoming requests.
|
||||
An `AbstractRSocketConnector` can be provided to the `RSocketInboundGateway` for explicit endpoint registration.
|
||||
This way, the auto-detection option is disabled on that `AbstractRSocketConnector`.
|
||||
The `RSocketStrategies` can also be injected into the `RSocketInboundGateway` or they are obtained from the provided `AbstractRSocketConnector` overriding any explicit injection.
|
||||
Decoders are used from those `RSocketStrategies` to decode a request payload according to the provided `requestElementType`.
|
||||
If an `RSocketPayloadReturnValueHandler.RESPONSE_HEADER` header is not provided in incoming the `Message`, the `RSocketInboundGateway` treats a request as a `fireAndForget` RSocket interaction model.
|
||||
In this case, an `RSocketInboundGateway` performs a plain `send` operation into the `outputChannel`.
|
||||
Otherwise a `MonoProcessor` value from the `RSocketPayloadReturnValueHandler.RESPONSE_HEADER` header is used for sending a reply to the RSocket.
|
||||
For this purpose, an `RSocketInboundGateway` performs a `sendAndReceiveMessageReactive` operation on the `outputChannel`.
|
||||
The `payload` of the message to send downstream is always a `Flux` according to `MessagingRSocket` logic.
|
||||
When in a `fireAndForget` RSocket interaction model, the messsage has a plain converted `payload`.
|
||||
The reply `payload` could be a plain object or a `Publisher` - the `RSocketInboundGateway` converts both of them properly into an RSocket response according to the encoders provided in the `RSocketStrategies`.
|
||||
|
||||
See <<rsocket-java-config>> for samples how to configure an `RSocketInboundGateway` endpoint and deal with payloads downstream.
|
||||
|
||||
[[rsocket-outbound]]
|
||||
=== RSocket Outbound Gateway
|
||||
|
||||
The `RSocketOutboundGateway` is an `AbstractReplyProducingMessageHandler` to perform requests into RSocket and produce replies based on the RSocket replies (if any).
|
||||
A low level RSocket protocol interaction is delegated into an `RSocketRequester` resolved from the provided `ClientRSocketConnector` or from the `RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER` header in the request message on the server side.
|
||||
A target `RSocketRequester` on the server side can be resolved from an `RSocketConnectedEvent` or using `ServerRSocketConnector.getClientRSocketRequester()` API according some business key selected for connect request mappings via `ServerRSocketConnector.setClientRSocketKeyStrategy()`.
|
||||
See `ServerRSocketConnector` JavaDocs for more information.
|
||||
|
||||
The `route` to send request has to be configured explicitly (together with path variables) or via a SpEL expression which is evaluated against request message.
|
||||
|
||||
The RSocket communication command can be provided via `RSocketOutboundGateway.Command` option or respective expression setting.
|
||||
By default a `requestResponse` is used for common gateway use-cases.
|
||||
|
||||
When request message payload is a `Publisher`, a `publisherElementType` option can be provided to encode its elements according an `RSocketStrategies` supplied in the target `RSocketRequester`.
|
||||
An expression for this option can evaluate to a `ParameterizedTypeReference`.
|
||||
See the `RSocketRequester.RequestSpec.data()` JavaDocs for more information about data and its type.
|
||||
|
||||
An RSocket request can also be enhanced with a `metadata`.
|
||||
For this purpose a `metadataExpression` against request message can be configured on the `RSocketOutboundGateway`.
|
||||
Such an expression must evaluate to a `Map<Object, MimeType>`.
|
||||
|
||||
When `command` is not `fireAndForget`, an `expectedResponseType` must be supplied.
|
||||
It is a `String.class` by default.
|
||||
An expression for this option can evaluate to a `ParameterizedTypeReference`.
|
||||
See `RSocketRequester.RequestSpec.retrieveMono()` and `RSocketRequester.RequestSpec.retrieveFlux()` JavaDocs for more information about reply data and its type.
|
||||
|
||||
A reply `payload` from the `RSocketOutboundGateway` is always `Mono` (even for a `fireAndForget` command it is `Mono<Void>`) always making this component as `async`.
|
||||
Such a `Mono` is subscribed before producing into the `outputChannel` for regular channels or processed on demand by the `FluxMessageChannel`.
|
||||
A `Flux` response for the `requestStreamOrChannel` command is also wrapped into a mentioned reply `Mono`.
|
||||
It can be flatten downstream by the `FluxMessageChannel` with a passthrough service activator:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@ServiceActivator(inputChannel = "rsocketReplyChannel", outputChannel ="fluxMessageChannel")
|
||||
public Flux<?> flattenRSocketResponse(Flux<?> payload) {
|
||||
return payload;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Or subscribed explicitly in the target application logic.
|
||||
|
||||
See <<rsocket-java-config>> for samples how to configure an `RSocketOutboundGateway` endpoint a deal with payloads downstream.
|
||||
|
||||
[[rsocket-namespace]]
|
||||
=== RSocket Namespace Support
|
||||
|
||||
Spring Integration provides an `rsocket` namespace and the corresponding schema definition.
|
||||
To include it in your configuration, add the following namespace declaration in your application context configuration file:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-rsocket="http://www.springframework.org/schema/integration/rsocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/rsocket
|
||||
https://www.springframework.org/schema/integration/rsocket/spring-integration-rsocket.xsd">
|
||||
...
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
==== Inbound
|
||||
|
||||
To configure Spring Integration RSocket inbound channel adapters with XML, you need to use an appropriate `inbound-gateway` components from the `int-rsocket` namespace.
|
||||
The following example shows how to configure it:
|
||||
|
||||
====
|
||||
[source, xml]
|
||||
----
|
||||
<int-rsocket:inbound-gateway id="inboundGateway"
|
||||
path="testPath"
|
||||
rsocket-connector="clientRSocketConnector"
|
||||
request-channel="requestChannel"
|
||||
rsocket-strategies="rsocketStrategies"
|
||||
request-element-type="byte[]"/>
|
||||
----
|
||||
====
|
||||
|
||||
A `ClientRSocketConnector` and `ServerRSocketConnector` should be configured as generic `<bean>` definitions.
|
||||
|
||||
==== Outbound
|
||||
|
||||
====
|
||||
[source, xml]
|
||||
----
|
||||
<int-rsocket:outbound-gateway id="outboundGateway"
|
||||
client-rsocket-connector="clientRSocketConnector"
|
||||
auto-startup="false"
|
||||
command="fireAndForget"
|
||||
route-expression="'testRoute'"
|
||||
request-channel="requestChannel"
|
||||
publisher-element-type="byte[]"
|
||||
expected-response-type="java.util.Date"
|
||||
metadata-expression="{'metadata': new org.springframework.util.MimeType('*')}"/>
|
||||
----
|
||||
====
|
||||
|
||||
See `spring-integration-rsocket.xsd` for description for all those XML attributes.
|
||||
|
||||
[[rsocket-java-config]]
|
||||
=== Configuring RSocket Endpoints with Java
|
||||
|
||||
The following example shows how to configure an RSocket inbound endpoint with Java:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public RSocketInboundGateway rsocketInboundGatewayRequestReply() {
|
||||
RSocketInboundGateway rsocketInboundGateway = new RSocketInboundGateway("echo");
|
||||
rsocketInboundGateway.setRequestChannelName("requestReplyChannel");
|
||||
return rsocketInboundGateway;
|
||||
}
|
||||
|
||||
@Transformer(inputChannel = "requestReplyChannel")
|
||||
public Mono<String> echoTransformation(Flux<String> payload) {
|
||||
return payload.next().map(String::toUpperCase);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
A `ClientRSocketConnector` or `ServerRSocketConnector` is assumed in this configuration with meaning for auto-detection of such an endpoint on the "`echo`" path.
|
||||
Pay attention to the `@Transformer` signature with its fully reactive processing of the RSocket requests and producing reactive replies.
|
||||
|
||||
The following example shows how to configure a RSocket inbound gateway with the Java DSL:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public IntegrationFlow rsocketUpperCaseFlow() {
|
||||
return IntegrationFlows
|
||||
.from(RSockets.inboundGateway("/uppercase"))
|
||||
.<Flux<String>, Mono<String>>transform((flux) -> flux.next().map(String::toUpperCase))
|
||||
.get();
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
A `ClientRSocketConnector` or `ServerRSocketConnector` is assumed in this configuration with meaning for auto-detection of such an endpoint on the "`/uppercase`" path.
|
||||
|
||||
The following example shows how to configure a RSocket outbound gateway with Java:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
@ServiceActivator(inputChannel = "requestChannel", outputChannel = "replyChannel")
|
||||
public RSocketOutboundGateway rsocketOutboundGateway() {
|
||||
RSocketOutboundGateway rsocketOutboundGateway =
|
||||
new RSocketOutboundGateway(
|
||||
new FunctionExpression<Message<?>>((m) ->
|
||||
m.getHeaders().get("route_header")));
|
||||
rsocketOutboundGateway.setCommandExpression(
|
||||
new FunctionExpression<Message<?>>((m) -> m.getHeaders().get("rsocket_command")));
|
||||
rsocketOutboundGateway.setClientRSocketConnector(clientRSocketConnector());
|
||||
return rsocketOutboundGateway;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The `setClientRSocketConnector()` is required only for the client side.
|
||||
On the server side, the `RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER` header with an `RSocketRequester` value must be supplied in the request message.
|
||||
|
||||
|
||||
The following example shows how to configure a RSocket outbound gateway with the Java DSL:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public IntegrationFlow rsocketUpperCaseRequestFlow(ClientRSocketConnector clientRSocketConnector) {
|
||||
return IntegrationFlows
|
||||
.from(Function.class)
|
||||
.handle(RSockets.outboundGateway("/uppercase")
|
||||
.command(RSocketOutboundGateway.Command.requestResponse)
|
||||
.expectedResponseType(String.class)
|
||||
.clientRSocketConnector(clientRSocketConnector))
|
||||
.get();
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
See <<./dsl.adoc#java-dsl-gateway,`IntegrationFlow` as a Gateway>> for more information how to use a mentioned `Function` interface in the beginning of the flow above.
|
||||
@@ -160,7 +160,7 @@ See <<./http.adoc#http-outbound,HTTP Outbound Components>> for more possible con
|
||||
=== WebFlux Namespace Support
|
||||
|
||||
Spring Integration provides a `webflux` namespace and the corresponding schema definition.
|
||||
To include it in your configuration, include the following namespace declaration in your application context configuration file:
|
||||
To include it in your configuration, add the following namespace declaration in your application context configuration file:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
@@ -184,7 +184,7 @@ To include it in your configuration, include the following namespace declaration
|
||||
|
||||
==== Inbound
|
||||
|
||||
To configure Spring Integration WebFlux with XML, you caus use appropriate components from the `int-webflux` namespace: `inbound-channel-adapter` or `inbound-gateway`, corresponding to request and response requirements, respectively.
|
||||
To configure Spring Integration WebFlux with XML, you need to use appropriate components from the `int-webflux` namespace: `inbound-channel-adapter` or `inbound-gateway`, corresponding to request and response requirements, respectively.
|
||||
The following example shows how to configure both an inbound channel adapter and an inbound gateway:
|
||||
|
||||
====
|
||||
|
||||
@@ -25,6 +25,12 @@ See the https://github.com/spring-projects/spring-integration/wiki/Spring-Integr
|
||||
[[x5.2-new-components]]
|
||||
=== New Components
|
||||
|
||||
[[x5.2-rsocket-support]]
|
||||
==== RSocket Support
|
||||
|
||||
The `spring-integration-rsocket` module is now available with channel adapter implementations for RSocket protocol support.
|
||||
See <<./rsocket.adoc#rsocket,RSocket Support>> for more information.
|
||||
|
||||
[[x5.2-rate-limit-advice]]
|
||||
==== Rate Limit Advice Support
|
||||
|
||||
|
||||
Reference in New Issue
Block a user