org.springframework.messaging.rsocket.RSocketRequester Maven / Gradle / Ivy
/*
* Copyright 2002-2022 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.messaging.rsocket;
import java.net.URI;
import java.util.List;
import java.util.function.Consumer;
import io.rsocket.ConnectionSetupPayload;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.core.RSocketClient;
import io.rsocket.loadbalance.LoadbalanceStrategy;
import io.rsocket.loadbalance.LoadbalanceTarget;
import io.rsocket.transport.ClientTransport;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.transport.netty.client.WebsocketClientTransport;
import org.reactivestreams.Publisher;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.codec.Decoder;
import org.springframework.lang.Nullable;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.util.MimeType;
/**
* A thin wrapper around a sending {@link RSocket} with a fluent API accepting
* and returning higher level Objects for input and for output, along with
* methods to prepare routing and other metadata.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.2
*/
public interface RSocketRequester extends Disposable {
/**
* Return the underlying {@link RSocketClient} used to make requests with.
* @since 5.3
*/
RSocketClient rsocketClient();
/**
* Return the underlying {@link RSocket} if the requester was created with a
* "live" RSocket via {@link #wrap(RSocket, MimeType, MimeType, RSocketStrategies)}
* or via one of the (deprecated) connect methods on the
* {@code RSocketRequester} builder, or otherwise return {@code null}.
*/
@Nullable
RSocket rsocket();
/**
* Return the data {@code MimeType} selected for the underlying RSocket
* at connection time. On the client side this is configured via
* {@link RSocketRequester.Builder#dataMimeType(MimeType)} while on the
* server side it's obtained from the {@link ConnectionSetupPayload}.
*/
MimeType dataMimeType();
/**
* Return the metadata {@code MimeType} selected for the underlying RSocket
* at connection time. On the client side this is configured via
* {@link RSocketRequester.Builder#metadataMimeType(MimeType)} while on the
* server side it's obtained from the {@link ConnectionSetupPayload}.
*/
MimeType metadataMimeType();
/**
* Return the configured {@link RSocketStrategies}.
*/
RSocketStrategies strategies();
/**
* Begin to specify a new request with the given route to a remote handler.
* The route can be a template with placeholders, for example,
* {@code "flight.{code}"} in which case the supplied route variables are
* formatted via {@code toString()} and expanded into the template.
* If a formatted variable contains a "." it is replaced with the escape
* sequence "%2E" to avoid treating it as separator by the responder.
*
If the connection is set to use composite metadata, the route is
* encoded as {@code "message/x.rsocket.routing.v0"}. Otherwise, the route
* is encoded according to the mime type for the connection.
* @param route the route expressing a remote handler mapping
* @param routeVars variables to be expanded into the route template
* @return a spec for further defining and executing the request
*/
RequestSpec route(String route, Object... routeVars);
/**
* Begin to specify a new request with the given metadata value, which can
* be a concrete value or any producer of a single value that can be adapted
* to a {@link Publisher} via {@link ReactiveAdapterRegistry}.
* @param metadata the metadata value to encode
* @param mimeType the mime type that describes the metadata;
* This is required for connection using composite metadata. Otherwise, the
* value is encoded according to the mime type for the connection and this
* argument may be left as {@code null}.
*/
RequestSpec metadata(Object metadata, @Nullable MimeType mimeType);
/**
* Shortcut method that delegates to the same on the underlying
* {@link #rsocketClient()} in order to close the connection from the
* underlying transport and notify subscribers.
* @since 5.3.7
*/
@Override
default void dispose() {
rsocketClient().dispose();
}
/**
* Shortcut method that delegates to the same on the underlying
* {@link #rsocketClient()}.
* @since 5.3.7
*/
@Override
default boolean isDisposed() {
return rsocketClient().isDisposed();
}
/**
* Obtain a builder to create a client {@link RSocketRequester} by connecting
* to an RSocket server.
*/
static RSocketRequester.Builder builder() {
return new DefaultRSocketRequesterBuilder();
}
/**
* Wrap an existing {@link RSocket}. Typically for internal framework use,
* to wrap the remote {@code RSocket} in a client or server responder, but
* it can also be used to wrap any {@link RSocket}.
*/
static RSocketRequester wrap(
RSocket rsocket, MimeType dataMimeType, MimeType metadataMimeType,
RSocketStrategies strategies) {
return new DefaultRSocketRequester(null, rsocket, dataMimeType, metadataMimeType, strategies);
}
/**
* Builder to create a requester by connecting to a server.
*/
interface Builder {
/**
* Configure the payload data MimeType to specify on the {@code SETUP}
* frame that applies to the whole connection.
*
If not set, this will be initialized to the MimeType of the first
* {@link RSocketStrategies.Builder#decoder(Decoder[]) non-default}
* {@code Decoder}, or otherwise the MimeType of the first decoder.
*/
RSocketRequester.Builder dataMimeType(@Nullable MimeType mimeType);
/**
* Configure the payload metadata MimeType to specify on the {@code SETUP}
* frame that applies to the whole connection.
*
By default this is set to
* {@code "message/x.rsocket.composite-metadata.v0"} in which case the
* route, if provided, is encoded as a {@code "message/x.rsocket.routing.v0"}
* composite metadata entry. If this is set to any other MimeType, it is
* assumed that's the MimeType for the route, if provided.
*/
RSocketRequester.Builder metadataMimeType(MimeType mimeType);
/**
* Set the data for the setup payload. The data will be encoded
* according to the configured {@link #dataMimeType(MimeType)}.
* The data be a concrete value or any producer of a single value that
* can be adapted to a {@link Publisher} via {@link ReactiveAdapterRegistry}.
*
By default this is not set.
*/
RSocketRequester.Builder setupData(Object data);
/**
* Set the route for the setup payload. The rules for formatting and
* encoding the route are the same as those for a request route as
* described in {@link #route(String, Object...)}.
*
By default this is not set.
*/
RSocketRequester.Builder setupRoute(String route, Object... routeVars);
/**
* Add metadata entry to the setup payload. Composite metadata must be
* in use if this is called more than once or in addition to
* {@link #setupRoute(String, Object...)}. The metadata value be a
* concrete value or any producer of a single value that can be adapted
* to a {@link Publisher} via {@link ReactiveAdapterRegistry}.
*/
RSocketRequester.Builder setupMetadata(Object value, @Nullable MimeType mimeType);
/**
* Provide the {@link RSocketStrategies} to use.
*
This is useful for changing the default settings, yet still allowing
* further customizations via {@link #rsocketStrategies(Consumer)}.
* If not set, defaults are obtained from {@link RSocketStrategies#builder()}.
* @param strategies the strategies to use
*/
RSocketRequester.Builder rsocketStrategies(@Nullable RSocketStrategies strategies);
/**
* Customize the {@link RSocketStrategies}.
*
Allows further customization on {@link RSocketStrategies},
* mutating them if they were {@link #rsocketStrategies(RSocketStrategies) set},
* or starting from {@link RSocketStrategies#builder()} defaults}.
*/
RSocketRequester.Builder rsocketStrategies(Consumer configurer);
/**
* Callback to configure the {@code RSocketConnector} directly.
*
* - The data and metadata mime types cannot be set directly
* on the {@code RSocketConnector} and will be overridden. Use the
* shortcuts {@link #dataMimeType(MimeType)} and
* {@link #metadataMimeType(MimeType)} on this builder instead.
*
- The frame decoder also cannot be set directly and instead is set
* to match the configured {@code DataBufferFactory}.
*
- For the
* {@link io.rsocket.core.RSocketConnector#setupPayload(Payload)
* setupPayload}, consider using methods on this builder to specify the
* route, other metadata, and data as Object values to be encoded.
*
- To configure client side responding, see
* {@link RSocketMessageHandler#responder(RSocketStrategies, Object...)}.
*
* @since 5.2.6
*/
RSocketRequester.Builder rsocketConnector(RSocketConnectorConfigurer configurer);
/**
* Configure this builder through a {@code Consumer}. This enables
* libraries such as Spring Security to provide shortcuts for applying
* a set of related customizations.
* @param configurer the configurer to apply
*/
RSocketRequester.Builder apply(Consumer configurer);
/**
* Build an {@link RSocketRequester} with an
* {@link io.rsocket.core.RSocketClient} that connects over TCP to the
* given host and port. The requester can be used to make requests
* concurrently. Requests are made over a shared connection that is also
* re-established as needed when further requests are made.
* @param host the host to connect to
* @param port the port to connect to
* @return the created {@code RSocketRequester}
* @since 5.3
*/
RSocketRequester tcp(String host, int port);
/**
* Build an {@link RSocketRequester} with an
* {@link io.rsocket.core.RSocketClient} that connects over WebSocket to
* the given URL. The requester can be used to make requests
* concurrently. Requests are made over a shared connection that is also
* re-established as needed when further requests are made.
* @param uri the URL to connect to
* @return the created {@code RSocketRequester}
* @since 5.3
*/
RSocketRequester websocket(URI uri);
/**
* Variant of {@link #tcp(String, int)} and {@link #websocket(URI)}
* with an already initialized {@link ClientTransport}.
* @param transport the transport to connect with
* @return the created {@code RSocketRequester}
* @since 5.3
*/
RSocketRequester transport(ClientTransport transport);
/**
* Build an {@link RSocketRequester} with an
* {@link io.rsocket.loadbalance.LoadbalanceRSocketClient} that will
* connect to one of the given targets selected through the given
* {@link io.rsocket.loadbalance.LoadbalanceRSocketClient}.
* @param targetPublisher a {@code Publisher} that supplies a list of
* target transports to loadbalance against; the given list may be
* periodically updated by the {@code Publisher}.
* @param loadbalanceStrategy the strategy to use for selecting from
* the list of loadbalance targets.
* @return the created {@code RSocketRequester}
* @since 5.3
*/
RSocketRequester transports(
Publisher> targetPublisher,
LoadbalanceStrategy loadbalanceStrategy);
/**
* Connect to the server over TCP.
* @param host the server host
* @param port the server port
* @return an {@code RSocketRequester} for the connection
* @see TcpClientTransport
* @deprecated as of 5.3 in favor of {@link #tcp(String, int)}
*/
@Deprecated
Mono connectTcp(String host, int port);
/**
* Connect to the server over WebSocket.
* @param uri the RSocket server endpoint URI
* @return an {@code RSocketRequester} for the connection
* @see WebsocketClientTransport
* @deprecated as of 5.3 in favor of {@link #websocket(URI)}
*/
@Deprecated
Mono connectWebSocket(URI uri);
/**
* Connect to the server with the given {@code ClientTransport}.
* @param transport the client transport to use
* @return an {@code RSocketRequester} for the connection
* @deprecated as of 5.3 in favor of {@link #transport(ClientTransport)}
*/
@Deprecated
Mono connect(ClientTransport transport);
}
/**
* Spec to declare the input for an RSocket request.
*/
interface RequestSpec extends MetadataSpec, RetrieveSpec {
/**
* Append additional metadata entries through a {@code Consumer}.
* This enables libraries such as Spring Security to provide shortcuts
* for applying a set of customizations.
* @param configurer the configurer to apply
* @throws IllegalArgumentException if not using composite metadata.
*/
RequestSpec metadata(Consumer> configurer);
/**
* Perform a {@link RSocket#metadataPush(Payload) metadataPush}.
* @since 5.3
*/
Mono sendMetadata();
/**
* Provide payload data for the request. This can be one of:
*
* - Concrete value
*
- {@link Publisher} of value(s)
*
- Any other producer of value(s) that can be adapted to a
* {@link Publisher} via {@link ReactiveAdapterRegistry}
*
* @param data the Object value for the payload data
* @return spec to declare the expected response
*/
RetrieveSpec data(Object data);
/**
* Variant of {@link #data(Object)} that also accepts a hint for the
* types of values that will be produced. The class hint is used to
* find a compatible {@code Encoder} once, up front vs per value.
* @param producer the source of payload data value(s). This must be a
* {@link Publisher} or another producer adaptable to a
* {@code Publisher} via {@link ReactiveAdapterRegistry}
* @param elementClass the type of values to be produced
* @return spec to declare the expected response
*/
RetrieveSpec data(Object producer, Class> elementClass);
/**
* Variant of {@link #data(Object, Class)} for when the type hint has
* to have a generic type. See {@link ParameterizedTypeReference}.
* @param producer the source of payload data value(s). This must be a
* {@link Publisher} or another producer adaptable to a
* {@code Publisher} via {@link ReactiveAdapterRegistry}
* @param elementTypeRef the type of values to be produced
* @return spec to declare the expected response
*/
RetrieveSpec data(Object producer, ParameterizedTypeReference> elementTypeRef);
}
/**
* Spec for providing additional composite metadata entries.
*
* @param a self reference to the spec type
*/
interface MetadataSpec> {
/**
* Use this to append additional metadata entries when using composite
* metadata. An {@link IllegalArgumentException} is raised if this
* method is used when not using composite metadata.
* The metadata value be a concrete value or any producer of a single
* value that can be adapted to a {@link Publisher} via
* {@link ReactiveAdapterRegistry}.
* @param metadata an Object to be encoded with a suitable
* {@link org.springframework.core.codec.Encoder Encoder}, or a
* {@link org.springframework.core.io.buffer.DataBuffer DataBuffer}
* @param mimeType the mime type that describes the metadata
*/
S metadata(Object metadata, MimeType mimeType);
}
/**
* Spec to declare the expected output for an RSocket request.
* @since 5.2.2
*/
interface RetrieveSpec {
/**
* Perform a {@link RSocket#fireAndForget fireAndForget} sending the
* provided data and metadata.
* @return a completion that indicates if the payload was sent
* successfully or not. Note, however that is a one-way send and there
* is no indication of whether or how the even was handled on the
* remote end.
*/
Mono send();
/**
* Perform a {@link RSocket#requestResponse requestResponse} exchange.
* If the return type is {@code Mono}, the {@code Mono} will
* complete after all data is consumed.
* Note: This method will raise an error if
* the request payload is a multivalued {@link Publisher} as there is
* no many-to-one RSocket interaction.
* @param dataType the expected data type for the response
* @param parameter for the expected data type
* @return the decoded response
*/
Mono retrieveMono(Class dataType);
/**
* Variant of {@link #retrieveMono(Class)} for when the dataType has
* to have a generic type. See {@link ParameterizedTypeReference}.
*/
Mono retrieveMono(ParameterizedTypeReference dataTypeRef);
/**
* Perform an {@link RSocket#requestStream requestStream} or a
* {@link RSocket#requestChannel requestChannel} exchange depending on
* whether the request input is single or multi-payload.
* If the return type is {@code Flux}, the {@code Flux} will
* complete after all data is consumed.
* @param dataType the expected type for values in the response
* @param parameterize the expected type of values
* @return the decoded response
*/
Flux retrieveFlux(Class dataType);
/**
* Variant of {@link #retrieveFlux(Class)} for when the dataType has
* to have a generic type. See {@link ParameterizedTypeReference}.
*/
Flux retrieveFlux(ParameterizedTypeReference dataTypeRef);
}
}