I have a question about Springs RSocketRequester. I have a rsocket server and client. Client connects to this server and requests #MessageMapping endpoint. It works as expected.
But what if I restart the server. How to do automatic reconnect to rsocket server from client? Thanks
Server:
#Controller
class RSC {
#MessageMapping("pong")
public Mono<String> pong(String m) {
return Mono.just("PONG " + m);
}
}
Client:
#Bean
public RSocketRequester rSocketRequester() {
return RSocketRequester
.builder()
.connectTcp("localhost", 7000)
.block();
}
#RestController
class RST {
#Autowired
private RSocketRequester requester;
#GetMapping(path = "/ping")
public Mono<String> ping(){
return this.requester
.route("pong")
.data("TEST")
.retrieveMono(String.class)
.doOnNext(System.out::println);
}
}
Updated for Spring Framework 5.2.6+
You could achieve it with io.rsocket.core.RSocketConnector#reconnect.
#Bean
Mono<RSocketRequester> rSocketRequester(RSocketRequester.Builder rSocketRequesterBuilder) {
return rSocketRequesterBuilder
.rsocketConnector(connector -> connector
.reconnect(Retry.fixedDelay(Integer.MAX_VALUE, Duration.ofSeconds(1))))
.connectTcp("localhost", 7000);
}
#RestController
public class RST {
#Autowired
private Mono<RSocketRequester> rSocketRequesterMono;
#GetMapping(path = "/ping")
public Mono<String> ping() {
return rSocketRequesterMono.flatMap(rSocketRequester ->
rSocketRequester.route("pong")
.data("TEST")
.retrieveMono(String.class)
.doOnNext(System.out::println));
}
}
I don't think I would create a RSocketRequester bean in an application. Unlike WebClient (which has a pool of reusable connections), the RSocket requester wraps a single RSocket, i.e. a single network connection.
I think it's best to store a Mono<RSocketRequester> and subscribe to that to get an actual requester when needed. Because you don't want to create a new connection for each call, you can cache the result. Thanks to Mono retryXYZ operators, there are many ways you can refine the reconnection behavior.
You could try something like the following:
#Service
public class RSocketPingService {
private final Mono<RSocketRequester> requesterMono;
// Spring Boot is creating an auto-configured RSocketRequester.Builder bean
public RSocketPingService(RSocketRequester.Builder builder) {
this.requesterMono = builder
.dataMimeType(MediaType.APPLICATION_CBOR)
.connectTcp("localhost", 7000).retry(5).cache();
}
public Mono<String> ping() {
return this.requesterMono.flatMap(requester -> requester.route("pong")
.data("TEST")
.retrieveMono(String.class));
}
}
the answer here https://stackoverflow.com/a/58890649/2852528 is the right one. The only thing I would like to add is that reactor.util.retry.Retry has many options for configuring the logic of your retry including even logging.
So I would slightly improve the original answer, so we'd be increasing the time between the retry till riching the max value (16 sec) and before each retry log the failure - so we could monitor the activity of the connector:
#Bean
Mono<RSocketRequester> rSocketRequester(RSocketRequester.Builder builder) {
return builder.rsocketConnector(connector -> connector.reconnect(Retry.backoff(Integer.MAX_VALUE, Duration.ofSeconds(1L))
.maxBackoff(Duration.ofSeconds(16L))
.jitter(1.0D)
.doBeforeRetry((signal) -> log.error("connection error", signal.failure()))))
.connectTcp("localhost", 7000);
}
Related
I've set up rsocket metrics using rsocket-micrometer on the CLIENT side, by configuring the RSocketConnector with interceptors, like this (Kotlin):
rSocketReqesterBuilder.rsocketConnector { configureConnector(it) }
// ...
private fun configureConnector(rSocketConnector: RSocketConnector) {
rSocketConnector.interceptors { iRegistry ->
// This gives us the rsocket.* counter metrics, like rsocket.frame
iRegistry.forResponder(MicrometerRSocketInterceptor(registry, *localTags.toArray()))
iRegistry.forRequester(MicrometerRSocketInterceptor(registry, *localTags.toArray()))
iRegistry.forConnection(MicrometerDuplexConnectionInterceptor(registry, *localTags.toArray()))
}
}
But on the SERVER side, I'm using an annotated (#MessageMapping) Spring Boot RSocket Controller, like this (Java):
#MessageMapping("replace-channel-controller")
public Flux<TransformResponse> replace(Flux<String> texts) ...
Here, I'm not explicitly in control of the connector.
How do I add interceptors on the server side?
#Configuration
public class RSocketConfig implements RSocketServerCustomizer {
private final MeterRegistry registry;
public RSocketConfig(MeterRegistry registry) {
this.registry = registry;
}
#Override
public void customize(RSocketServer rSocketServer) {
rSocketServer.interceptors(
iRegistry -> {
log.info("Adding RSocket interceptors...");
iRegistry.forResponder(new MicrometerRSocketInterceptor(registry, tags));
iRegistry.forRequester(new MicrometerRSocketInterceptor(registry, tags));
iRegistry.forConnection(new MicrometerDuplexConnectionInterceptor(registry, tags));
}
);
}
}
Sonar complains because RestHighLevelClient is not closed explicitly, but I am using spring-data and client is used by the repository transparenty for me.
I have a configuration class
#Configuration
#EnableElasticsearchRepositories(basePackages = "foo.package.repository")
public class RestClientESConfig extends AbstractElasticsearchConfiguration {
// elastic search host
#Value("${spring.elasticsearch.rest.uris}")
private String elasticsearchHost;
#Override
public RestHighLevelClient elasticsearchClient() {
return RestClients.create(
ClientConfiguration.builder()
.connectedTo(elasticsearchHost)
.build())
.rest();
}
}
sonar says:
Resources should be closed
Blocker java:S2095
Connections, streams, files, and other classes that implement the Closeable interface or its super-interface, AutoCloseable, needs to be closed after use. Further, that close call must be made in a finally block otherwise an exception could keep the call from being made. Preferably, when class implements AutoCloseable, resource should be created using "try-with-resources" pattern and will be closed automatically.***
What can I do to avoid this sonar blocking issue?
You may close it in a method annotated by #PreDestroy or #Bean(destroyMethod = "close"). But Sonar is not able to detect this fix, so provide // NOSONAR hint.
#Bean(destroyMethod = "close")
public RestHighLevelClient restHighLevelClient() {
return new RestHighLevelClient(
RestClient.builder(new HttpHost(address, port, "http"))); // NOSONAR java:S2095 closed by spring #Bean(destroyMethod = "close")
}
Inspired by https://stackoverflow.com/a/55025393/204950
I'm not a Spring data user but can't you have RestClientESConfig implements Closeable so you can implement the close() method which would then call the client close() method?
Something like this untested code?
#Configuration
#EnableElasticsearchRepositories(basePackages = "foo.package.repository")
public class RestClientESConfig extends AbstractElasticsearchConfiguration implements Closeable {
// elastic search host
#Value("${spring.elasticsearch.rest.uris}")
private String elasticsearchHost;
private final RestHighLevelClient client;
public RestClientESConfig() {
client = RestClients.create(
ClientConfiguration.builder()
.connectedTo(elasticsearchHost)
.build())
.rest();
}
#Override
public RestHighLevelClient elasticsearchClient() {
return client;
}
#Override
public void close() {
client.close();
}
}
It looks like it is not a spring data problem, but a sonar rule one. Sonar should not check if client is closed manually because spring data does it automatically.
I have an application with Spring boot webflux and exposing endpoints as below.
#Configuration
public class BusinessServiceConfiguration {
#Autowired
private final RequestHandler reqHandler;
#Bean
public RouterFunction<ServerResponse> createUser() {
return route(POST("/api/v1/user/create").and(contentType(APPLICATION_JSON)), reqHandler::createUser);
}
}
Came across RSocket and it is promising. Planning to move to RSocket down the line.
Exposing end point with RSocket,
#Controller
public class RSocketController {
#Autowired
private final RequestService reqService;
#MessageMapping("/api/v1/user/create")
public Mono<String> createuser() {
return reqService.createUser .....
}
}
Here, the way we expose API is totally different b/w Webflux and RSocket and it need some effort.
Is there anyway to just add RSocket without changing the way we expose end points?
I have running Spring Boot#2.2.x server with exposed WebSocket endpoint. Here is my WebSocketConfiguration:
#Slf4j
#Configuration
public class WebSocketConfiguration {
private static final String WS_PATH = "/ws/notifications";
#Bean
public HandlerMapping webSocketHandlerMapping() {
Map<String, WebSocketHandler> handlersMap = new HashMap<>();
handlersMap.put(WS_PATH, session -> session.send(session.receive()
.map(WebSocketMessage::getPayloadAsText)
.doOnEach(logNext(log::info))
.map(msg -> format("notification for your msg: %s", msg))
.map(session::textMessage)));
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
handlerMapping.setUrlMap(handlersMap);
return handlerMapping;
}
#Bean
public WebSocketHandlerAdapter handlerAdapter(WebSocketService webSocketService) {
return new WebSocketHandlerAdapter(webSocketService);
}
#Bean
public WebSocketService webSocketService() {
return new HandshakeWebSocketService(new ReactorNettyRequestUpgradeStrategy());
}
}
The question is how I can implement authentication for establishing WS connection either using Basic Authentication or Bearer Authentication or access_token query parameter?
The preferable option is to avoid using Spring Security.
Thanks.
Websocket connection starts out life as an HTTP request that is Upgraded. You can do JWT token authentication before the upgrade happens. In spring boot it works as follows:
Expose a custom WebSocketService bean:
#Bean
public WebSocketService webSocketService(RequestUpgradeStrategy upgradeStrategy) {
return new HandshakeWebSocketService(upgradeStrategy);
}
Implement the RequestUpgradeStrategy interface in your own class:
#Override
public Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler handler, #Nullable String subProtocol, Supplier<HandshakeInfo> handshakeInfoFactory) {
ServerHttpResponse response = exchange.getResponse();
HttpServerResponse reactorResponse = getNativeResponse(response);
HandshakeInfo handshakeInfo = handshakeInfoFactory.get();
NettyDataBufferFactory bufferFactory = (NettyDataBufferFactory) response.bufferFactory();
var authResult = validateAuth(handshakeInfo);
if (authResult == unauthorised) return Mono.just(reactorResponse.status(rejectedStatus))
.flatMap(HttpServerResponse::send);
else return reactorResponse.sendWebsocket(subProtocol, //
this.maxFramePayloadLength,//
(in, out) -> {
ReactorNettyWebSocketSession session = new ReactorNettyWebSocketSession(in, out,
handshakeInfo,
bufferFactory,
this.maxFramePayloadLength);
return handler.handle(session);
});
}
Notes:
The above class is based on ReactorNettyRequestUpgradeStrategy.
Returning reactorResponse.sendWebsocket is the existing behaviour that upgrades the connection to a WebSocket connection
reactorResponse.status can be returned to stop the connection being upgraded. For example, you can return a 401 response in the case of an unauthorised connection.
Query params and Authentication headers can be found in handshake info. How to do the authentication itself is outside the scope of the question.
The idea is to make spring-vault use feign client (to take advantage of hystrix's fault tolerance capabilities).
Does spring-vault support feign client? or is there a work around if not supported out of the box
Or is there away to route spring-vault calls through feign client (sort of a proxy mode).
Following code snippet is an example for spring-vault config ; VaultTemplate bean is used for the vault operations
public VaultEndpoint vaultEndpoint() {
try {
return VaultEndpoint.from(new URI(vaultUri));
} catch (URISyntaxException uriSyntaxException) {
throw new IllegalStateException("Some Exception", uriSyntaxException);
}
}
#Bean
public VaultTemplate vaultTemplate() {
return new VaultTemplate(vaultEndpoint(), httpRequestFactory(), sessionManager());
}
#Bean
public VaultTransitOperations vaultTransitOperations() {
return vaultTemplate().opsForTransit(transitPath);
}
#Bean
public ClientHttpRequestFactory httpRequestFactory() {
ClientOptions clientOptions = new ClientOptions(1000, 1000);
return ClientHttpRequestFactoryFactory.create(clientOptions, SslConfiguration.NONE);
}
#Bean
public RestOperations vaultClient() {
return VaultClients.createRestTemplate(vaultEndpoint(), httpRequestFactory());
}
There's no feign (openfeign) support in Spring Vault but we're supporting various HTTP clients by accepting ClientHttpRequestFactory.
You can get fault tolerance with Ribbon by providing a RibbonClientHttpRequestFactory using Spring Cloud Netflix.