io.scalecube.services.gateway.transport.rsocket.RSocketGatewayClient Maven / Gradle / Ivy
package io.scalecube.services.gateway.transport.rsocket;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.core.RSocketConnector;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.client.WebsocketClientTransport;
import io.scalecube.services.api.ServiceMessage;
import io.scalecube.services.exceptions.ConnectionClosedException;
import io.scalecube.services.gateway.transport.GatewayClient;
import io.scalecube.services.gateway.transport.GatewayClientCodec;
import io.scalecube.services.gateway.transport.GatewayClientSettings;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.LoopResources;
public final class RSocketGatewayClient implements GatewayClient {
private static final Logger LOGGER = LoggerFactory.getLogger(RSocketGatewayClient.class);
private static final AtomicReferenceFieldUpdater rSocketMonoUpdater =
AtomicReferenceFieldUpdater.newUpdater(RSocketGatewayClient.class, Mono.class, "rsocketMono");
private final GatewayClientSettings settings;
private final GatewayClientCodec codec;
private final LoopResources loopResources;
private final MonoProcessor close = MonoProcessor.create();
private final MonoProcessor onClose = MonoProcessor.create();
@SuppressWarnings("unused")
private volatile Mono> rsocketMono;
/**
* Constructor for gateway over rsocket client transport.
*
* @param settings client settings.
* @param codec client codec.
*/
public RSocketGatewayClient(GatewayClientSettings settings, GatewayClientCodec codec) {
this.settings = settings;
this.codec = codec;
this.loopResources = LoopResources.create("rsocket-gateway-client");
// Setup cleanup
close
.then(doClose())
.doFinally(s -> onClose.onComplete())
.doOnTerminate(() -> LOGGER.info("Closed RSocketGatewayClient resources"))
.subscribe(
null, ex -> LOGGER.warn("Exception occurred on RSocketGatewayClient close: " + ex));
}
@Override
public Mono requestResponse(ServiceMessage request) {
return Mono.defer(
() ->
getOrConnect()
.flatMap(
rsocket ->
rsocket
.requestResponse(toPayload(request))
.doOnSubscribe(s -> LOGGER.debug("Sending request {}", request))
.onErrorMap(
ClosedChannelException.class,
e -> new ConnectionClosedException("Connection closed")))
.map(this::toMessage));
}
@Override
public Flux requestStream(ServiceMessage request) {
return Flux.defer(
() ->
getOrConnect()
.flatMapMany(
rsocket ->
rsocket
.requestStream(toPayload(request))
.doOnSubscribe(s -> LOGGER.debug("Sending request {}", request))
.onErrorMap(
ClosedChannelException.class,
e -> new ConnectionClosedException("Connection closed")))
.map(this::toMessage));
}
@Override
public Flux requestChannel(Flux requests) {
return Flux.defer(
() ->
getOrConnect()
.flatMapMany(
rsocket ->
rsocket
.requestChannel(
requests
.doOnNext(r -> LOGGER.debug("Sending request {}", r))
.map(this::toPayload))
.onErrorMap(
ClosedChannelException.class,
e -> new ConnectionClosedException("Connection closed")))
.map(this::toMessage));
}
@Override
public void close() {
close.onComplete();
}
@Override
public Mono onClose() {
return onClose;
}
private Mono doClose() {
return Mono.defer(loopResources::disposeLater);
}
public GatewayClientCodec getCodec() {
return codec;
}
private Mono getOrConnect() {
// noinspection unchecked
return Mono.defer(() -> rSocketMonoUpdater.updateAndGet(this, this::getOrConnect0));
}
private Mono getOrConnect0(Mono prev) {
if (prev != null) {
// noinspection unchecked
return prev;
}
return RSocketConnector.create()
.payloadDecoder(PayloadDecoder.DEFAULT)
.metadataMimeType(settings.contentType())
.connect(createRSocketTransport(settings))
.doOnSuccess(
rsocket -> {
LOGGER.info("Connected successfully on {}:{}", settings.host(), settings.port());
// setup shutdown hook
rsocket
.onClose()
.doOnTerminate(
() -> {
rSocketMonoUpdater.getAndSet(this, null); // clear reference
LOGGER.info("Connection closed on {}:{}", settings.host(), settings.port());
})
.subscribe(
null, th -> LOGGER.warn("Exception on closing rsocket: {}", th.toString()));
})
.doOnError(
ex -> {
LOGGER.warn(
"Failed to connect on {}:{}, cause: {}",
settings.host(),
settings.port(),
ex.toString());
rSocketMonoUpdater.getAndSet(this, null); // clear reference
})
.cache();
}
private WebsocketClientTransport createRSocketTransport(GatewayClientSettings settings) {
String path = "/";
HttpClient httpClient =
HttpClient.newConnection()
.followRedirect(settings.followRedirect())
.tcpConfiguration(
tcpClient -> {
if (settings.sslProvider() != null) {
tcpClient = tcpClient.secure(settings.sslProvider());
}
return tcpClient.runOn(loopResources).host(settings.host()).port(settings.port());
});
return WebsocketClientTransport.create(httpClient, path);
}
private Payload toPayload(ServiceMessage message) {
return codec.encode(message);
}
private ServiceMessage toMessage(Payload payload) {
ServiceMessage message = codec.decode(payload);
LOGGER.debug("Received response {}", message);
return message;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy