How to dynamically attach retry advice on an http outbound gateway? - java

Hey i have an http outbound gateway which i've attached a retry advice to:
.handle(Http.outboundGateway(spelParser.parseExpression("headers." + HeaderKeys.TARGET_ENDPOINT))
.extractPayload(true)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.requestFactory(requestFactory())
.get()
, httpOutboundEndpointSpec())
The httpOutboundEndpointSpec:
#Bean
public Consumer<GenericEndpointSpec<HttpRequestExecutingMessageHandler>> httpOutboundEndpointSpec() {
return new Consumer<GenericEndpointSpec<HttpRequestExecutingMessageHandler>>() {
#Override
public void accept(GenericEndpointSpec<HttpRequestExecutingMessageHandler> spec) {
spec.advice(context.getBean("lengthy", RequestHandlerRetryAdvice.class));
spec.requiresReply(true);
}
};
}
Is there a way to hook up the advice dynamically depending on the contents of the message? Different clients now need different intervals of backoff. I could write one outbound gateway per client with a different retry advice each, but that would make a lot of gateways.

Probably the simplest solution would be a custom advice that has a set of RetryTemplates and uses one of them based on the message.
You could base your advice on the and choose which retry template to use.

Related

Queuing multiple request before making http call to external service - spring webflux and projectreactor

I am using spring webflux and I would like to queue multiple requests before sending an http call to external system using WebClient and would like to know what is the best practice to do such requirement.
#GetMapping
Mono<Result> get(#RequestParam Optional<String> input) {
//Here I have to somehow keep the input and make a call when I have X items, then I can write to response, how this need to be done? Also what sort of Mono has to be return here?
}
I have found this which can be a solution but still a bit unclear https://ducmanhphan.github.io/2019-08-25-How-to-use-Processor-in-Reactor-Java/
Would be appreciated if anybody explains.

Webflux websocketclient, How to send multiple requests in same session[design client library]

TL;DR;
We are trying to design a WebSocket server using spring webflux WebSocket implementation. The server has usual HTTP server operations e.g. create/fetch/update/fetchall. Using WebSockets we were trying to expose one endpoint so the clients could leverage a single connection for all sort of operations, given WebSockets are meant for this purpose. Is it a right design with webflux and WebSockets?
Long Version
We are starting a project which is going to use reactive web sockets from spring-webflux. We need to build a reactive client library which can be used by consumers to connect to the server.
On the server, we get a request, read a message, save it and return a static response:
public Mono<Void> handle(WebSocketSession webSocketSession) {
Flux<WebSocketMessage> response = webSocketSession.receive()
.map(WebSocketMessage::retain)
.concatMap(webSocketMessage -> Mono.just(webSocketMessage)
.map(parseBinaryToEvent) //logic to get domain object
.flatMap(e -> service.save(e))
.thenReturn(webSocketSession.textMessage(SAVE_SUCCESSFUL))
);
return webSocketSession.send(response);
}
On the client, We want to make a call when someone calls save method and return the response from server.
public Mono<String> save(Event message) {
new ReactorNettyWebSocketClient().execute(uri, session -> {
session
.send(Mono.just(session.binaryMessage(formatEventToMessage)))
.then(session.receive()
.map(WebSocketMessage::getPayloadAsText)
.doOnNext(System.out::println).then()); //how to return this to client
});
return null;
}
We are not sure, how to go about designing this. Ideally, we think there should be
1) client.execute should be called only once and somehow hold the session. The same session should be used to send data in subsequent calls.
2) How to return the response from the server which we get in session.receive?
3) How about in case of fetch when the response is huge(not just a static string but list of events) in session.receive?
We are doing some research but we are unable to find proper resources for webflux-websocket-client documentation/implementation online. Any pointers on how to move ahead.
Please! Use RSocket!
It is absolutely correct design, and it worths to save resources and use only a connection per client for all possible ops.
However, don't implement a wheel and use the Protocol which gives you all of these kinds of communications.
RSocket has a request-response model which allows you to do the most common client-servert interaction today.
RSocket has a request-stream communication model so you can fulfill all your need and return a stream of events asynchronously reusing the same connection. RSocket does all maping of logical stream to phisical connection and back, so you will not feel the pain of doing that yourself.
RSocket has far more interaction models such as
fire-and-forget and stream-stream which could be useful in case of
sending a stream of data in both ways.
How to use RSocket in Spring
One of the options to do so is using RSocket-Java implementation of RSocket protocol. RSocket-Java is built on top of Project Reactor, so it naturally fits Spring WebFlux ecosystem.
Unfortunately, there is no featured integration with Spring ecosystem. Fortunately, I spent a couple of hours to provide a simple RSocket Spring Boot Starter that integrates Spring WebFlux with RSocket and exposes WebSocket RSocket server along with WebFlux Http server.
Why RSocket is a better approach?
Basically, RSocket hides the complexity of implementing the same approach yourself. With RSocket we don't have to care about interaction model definition as a custom protocol and as an implementation in Java. RSocket does for us delivering of the data to a particular logical channel. It provides a built-in client that sends messages to the same WS connection, so we don't have to invent a custom implementation for that.
Make it even better with RSocket-RPC
Since RSocket just a protocol it does not provide any message format, so this challenge is for business logic. However, there is an RSocket-RPC project which provides a Protocol Buffer as a message format and reuses the same code generation technique as GRPC does. So using RSocket-RPC we can easily build an API for the client and server and careless about transport and protocol abstraction at all.
The same RSocket Spring Boot integration provides an example of RSocket-RPC usage as well.
Alright, It has not convinced me, I wanna have a custom WebSocket server still
So, for that purpose, you have to implement that hell yourself. I have already done that once before, but I can't point to that project since it is an enterprise one.
Nevertheless, I can share a couple of code samples that can help you in building a proper client and server.
Server side
Handler and Open logical Subscribers mapping
The first point that must be taken into account is that all logical streams within one physical connection should be stored somewhere:
class MyWebSocketRouter implements WebSocketHandler {
final Map<String, EnumMap<ActionMessage.Type, ChannelHandler>> channelsMapping;
#Override
public Mono<Void> handle(WebSocketSession session) {
final Map<String, Disposable> channelsIdsToDisposableMap = new HashMap<>();
...
}
}
There are two maps in the sample above. The first one is your routes mapping which allows you to identify route based on the incoming message params, or so. The second one is created for request-streams usecase (in my case it was map of active subscriptions), so you can send a message-frame that creates a subscription, or subscribes you to a specific action and keep that subscription so once the unsubscribe action is executed you will be unsubscribed if a subscription exists.
Use Processor for messages multiplexing
In order to send back messages from all logical streams, you have to multiplex messages to one stream. For example, using Reactor, you can do that using UnicastProcessor:
#Override
public Mono<Void> handle(WebSocketSession session) {
final UnicastProcessor<ResponseMessage<?>> funIn = UnicastProcessor.create(Queues.<ResponseMessage<?>>unboundedMultiproducer().get());
...
return Mono
.subscriberContext()
.flatMap(context -> Flux.merge(
session
.receive()
...
.cast(ActionMessage.class)
.publishOn(Schedulers.parallel())
.doOnNext(am -> {
switch (am.type) {
case CREATE:
case UPDATE:
case CANCEL: {
...
}
case SUBSCRIBE: {
Flux<ResponseMessage<?>> flux = Flux
.from(
channelsMapping.get(am.getChannelId())
.get(ActionMessage.Type.SUBSCRIBE)
.handle(am) // returns Publisher<>
);
if (flux != null) {
channelsIdsToDisposableMap.compute(
am.getChannelId() + am.getSymbol(), // you can generate a uniq uuid on the client side if needed
(cid, disposable) -> {
...
return flux
.subscriberContext(context)
.subscribe(
funIn::onNext, // send message to a Processor manually
e -> {
funIn.onNext(
new ResponseMessage<>( // send errors as a messages to Processor here
0,
e.getMessage(),
...
ResponseMessage.Type.ERROR
)
);
}
);
}
);
}
return;
}
case UNSABSCRIBE: {
Disposable disposable = channelsIdsToDisposableMap.get(am.getChannelId() + am.getSymbol());
if (disposable != null) {
disposable.dispose();
}
}
}
})
.then(Mono.empty()),
funIn
...
.map(p -> new WebSocketMessage(WebSocketMessage.Type.TEXT, p))
.as(session::send)
).then()
);
}
As we can see from the sample above, there is a bunch of things there:
The message should include route info
The message should include a unique stream id to which it relates.
Separate Processor for message multiplexing where error should be a message as well
Each channel should be stored somewhere, in this case all we have a simple use case where each message can provide a Flux of messages or just a Mono (in case of mono it could be implemented simpler on the server side, so you don't have to keep unique stream ID).
This sample does not include messages encoding-decoding, so this challenge is left on you.
Client side
The client is not that simple as well:
Handle session
To handle connection we have to allocate two processors so further we can use them to multiplex and demultiplex messages:
UnicastProcessor<> outgoing = ...
UnicastPorcessor<> incoming = ...
(session) -> {
return Flux.merge(
session.receive()
.subscribeWith(incoming)
.then(Mono.empty()),
session.send(outgoing)
).then();
}
Keep all logical streams somewhere
All created streams whether it is Mono or Flux should be stored somewhere so we will be capable of distinguishing to which stream message relates:
Map<String, MonoSink> monoSinksMap = ...;
Map<String, FluxSink> fluxSinksMap = ...;
we have to keep two maps since MonoSink, and FluxSink does not have the same parent interface.
Message Routing
In the above samples, we just considered the initial part of the client side. Now we have to build a message routing mechanism:
...
.subscribeWith(incoming)
.doOnNext(message -> {
if (monoSinkMap.containsKey(message.getStreamId())) {
MonoSink sink = monoSinkMap.get(message.getStreamId());
monoSinkMap.remove(message.getStreamId());
if (message.getType() == SUCCESS) {
sink.success(message.getData());
}
else {
sink.error(message.getCause());
}
} else if (fluxSinkMap.containsKey(message.getStreamId())) {
FluxSink sink = fluxSinkMap.get(message.getStreamId());
if (message.getType() == NEXT) {
sink.next(message.getData());
}
else if (message.getType() == COMPLETE) {
fluxSinkMap.remove(message.getStreamId());
sink.next(message.getData());
sink.complete();
}
else {
fluxSinkMap.remove(message.getStreamId());
sink.error(message.getCause());
}
}
})
The above code sample shows how we can route incoming messages.
Multiplex requests
The final part is messages multiplexing. For that purpose we are going to cover possible sender class impl:
class Sender {
UnicastProcessor<> outgoing = ...
UnicastPorcessor<> incoming = ...
Map<String, MonoSink> monoSinksMap = ...;
Map<String, FluxSink> fluxSinksMap = ...;
public Sender () {
// create websocket connection here and put code mentioned earlier
}
Mono<R> sendForMono(T data) {
//generate message with unique
return Mono.<R>create(sink -> {
monoSinksMap.put(streamId, sink);
outgoing.onNext(message); // send message to server only when subscribed to Mono
});
}
Flux<R> sendForFlux(T data) {
return Flux.<R>create(sink -> {
fluxSinksMap.put(streamId, sink);
outgoing.onNext(message); // send message to server only when subscribed to Flux
});
}
}
Sumup of Custom implementation
Hardcore
No Backpressure support implemented so that could be another challenge
Easy to shoot yourself in the foot
Takeaways
PLEASE, use RSocket, don't invent protocol yourself, it is HARD!!!
To learn more about RSocket from Pivotal guys - https://www.youtube.com/watch?v=WVnAbv65uCU
To learn more about RSocket from one of my talks - https://www.youtube.com/watch?v=XKMyj6arY2A
There is a featured framework built on top of RSocket called Proteus - you might be interested in that - https://www.netifi.com/
To learn more about Proteus from core developer of RSocket protocol - https://www.google.com/url?sa=t&source=web&rct=j&url=https://m.youtube.com/watch%3Fv%3D_rqQtkIeNIQ&ved=2ahUKEwjpyLTpsLzfAhXDDiwKHUUUA8gQt9IBMAR6BAgNEB8&usg=AOvVaw0B_VdOj42gjr0YrzLLUX1E
Not sure if is this case your problem??
im seeing that you are sending a static flux response (this is a close-able stream)
you need a opend stream to send messages to that session for example you can create a processor
public class SocketMessageComponent {
private DirectProcessor<String> emitterProcessor;
private Flux<String> subscriber;
public SocketMessageComponent() {
emitterProcessor = DirectProcessor.create();
subscriber = emitterProcessor.share();
}
public Flux<String> getSubscriber() {
return subscriber;
}
public void sendMessage(String mesage) {
emitterProcessor.onNext(mesage);
}
}
and then you can send
public Mono<Void> handle(WebSocketSession webSocketSession) {
this.webSocketSession = webSocketSession;
return webSocketSession.send(socketMessageComponent.getSubscriber()
.map(webSocketSession::textMessage))
.and(webSocketSession.receive()
.map(WebSocketMessage::getPayloadAsText).log());
}

Publish & Subscribe with Same Connection using Spring Integration MQTT

Due to the design of MQTT where you can only make a connection with a unique client id, is it possible to use the same connection to publish and subscribe in Spring Framework/Boot using Integration?
Taking this very simple example, it would connect to the MQTT broker to subscribe and get messages, but if you would want to publish a message, the first connection will disconnect and re-connect after the message is sent.
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
factory.setUserName("guest");
factory.setPassword("guest");
return factory;
}
// publisher
#Bean
public IntegrationFlow mqttOutFlow() {
return IntegrationFlows.from(CharacterStreamReadingMessageSource.stdin(),
e -> e.poller(Pollers.fixedDelay(1000)))
.transform(p -> p + " sent to MQTT")
.handle(mqttOutbound())
.get();
}
#Bean
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("siSamplePublisher", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("siSampleTopic");
return messageHandler;
}
// consumer
#Bean
public IntegrationFlow mqttInFlow() {
return IntegrationFlows.from(mqttInbound())
.transform(p -> p + ", received from MQTT")
.handle(logger())
.get();
}
private LoggingHandler logger() {
LoggingHandler loggingHandler = new LoggingHandler("INFO");
loggingHandler.setLoggerName("siSample");
return loggingHandler;
}
#Bean
public MessageProducerSupport mqttInbound() {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("siSampleConsumer",
mqttClientFactory(), "siSampleTopic");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
return adapter;
}
Working with 2 separate connections becomes difficult if you need to wait for an answer/result after publishing a message...
the first connection will disconnect and re-connect after the message is sent.
Not sure what you mean by that; both components will keep open a persistent connection.
Since the factory doesn't connect the client, the adapters do, it's not designed for using a shared client.
Using a single connection won't really help with coordination of requests/replies because the reply will still come back asynchronously on another thread.
If you have some data in the request/reply that you can use for correlation of replies to requests, you can use a BarrierMessageHandler to perform that task. See my answer here for an example; it uses the standard correlation id header, but that's not possible with MQTT, you need something in the message.
TL;DR
The answer is no, not with the current Spring Boot MQTT Integration implementation (and maybe not even with future ones).
Answer
I'm facing the same exact situation: I need an MQTT Client to be opened in both inbound and outbound, making the connection persistent and sharing the same configuration (client ID, credentials, etc.), using Spring Integration Flows as close to the design as possible.
In order to achieve this, I had to reimplement MqttPahoMessageDrivenChannelAdapter and MqttPahoMessageHandler and a Client Factory.
In both MqttPahoMessageDrivenChannelAdapter and MqttPahoMessageHandler I had to choose to use the Async one (IMqttAsyncClient) in order to fix which one to use. Then I had to review parts of code where the client instance is called/used in order to check if it was already instantiated by the other flow and checking the status (e.g. not trying to connect it if it was already connected).
Regarding the Client Factory, it was easier: I've reimplemented the getAsyncClientInstance(String url, String clientId) using the concatenation of url and clientId as hash as key to store the instance into a map that is used to retrieve the existing instance if the other flow requests it.
It somehow works, but it's just a test and I'm not even sure it's a good approach. (I've started another StackOverflow question in order to track my specific scenario).
Can you share how did you manage your situation?

Feign: Retry depending on response status

I am currently using Spring Cloud and Feign to consume a Microservice in my application. Since it can happen, that a database connection or the like fails in a single service instance, making it return 500 HTTP status code, I want to make sure, that the next server is retried by the service's clients. Currently, Ribbon's retry mechanism works like a charm when the service is not running at all, however it still returns instantly an error when it receives a 500 status code, without any retry.
Is it possible to configure the Feign clients or their underlying Ribbon load balancers to retry the next server, if an instance returns a 500 response?
The configuration is pretty much the same as in this thread: Does Feign retry require some sort of configuration?
I would love to use an implementation like Ribbons' HttpResponseValidator (https://github.com/Netflix/ribbon/blob/master/ribbon/src/main/java/com/netflix/ribbon/http/HttpResponseValidator.java), but I couldn't find anything usable for Spring Cloud and its Feign/Ribbon integration
This question is very old and the solution was probably already found or wasn't possible at the time. Anyway, I think that answer might still help someone 8 ).
Please use this as a reference, this code is not intended for production use.
Feign allows you to configure errorDecoder - this is the place where magic happens.
Feign.Builder builder = Feign.builder()
.errorDecoder(new RetryOnScaleErrorDecoder())
Here is the implementation, I use that class to retry request on HTTP error 429 I get from AWS when service is scaling
public static class RetryOnScaleErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
FeignException exception = errorStatus(methodKey, response);
// This is a terrible part please check how feign.codec.ErrorDecoder.RetryAfterDecoder is implemented for proper parsing of retry-after header
Collection<String> headers = response.headers().get("Retry-After");
String repeatAfterString = "0";
if (Objects.nonNull(headers)) {
repeatAfterString = Iterables.getFirst(headers, "0");
}
assert repeatAfterString != null;
Date repeatAfter = new Date(currentTimeMillis());
if (repeatAfterString.matches("^[0-9]+$")) {
try {
long deltaMillis = SECONDS.toMillis(Long.parseLong(repeatAfterString));
repeatAfter = new Date(currentTimeMillis() + deltaMillis);
} catch (NumberFormatException ignored) {
// TODO: logging
}
}
// That's the part where we decide to retry based on status code value
if (exception.status() == 429) {
return new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
exception,
repeatAfter
);
}
return exception;
}
}
I think that in conjunction with Ribbon it will produce desired result.
Try to this config:
MY-SPRING-API.ribbon.retryableStatusCodes=404,500
This is the same question:
Feign client and Spring retry
document is :
https://docs.spring.io/spring-cloud-netflix/docs/2.2.10.RELEASE/reference/html/#retrying-failed-requests

Decide at runtime for sync or async response using Jersey

Is it possible to decide at runtime whether a Jersey REST request to an resource endpoint should be handled synchronously or asynchronously? Let's take a simple example.
The synchronous version:
#Path("resource")
public class Resource {
#GET
#Produces({MediaType.TEXT_PLAIN})
public Response get() {
return Response.ok("Hello there!").build();
}
}
The asynchronous version:
#Path("resource")
public class Resource {
#GET
#Produces({MediaType.TEXT_PLAIN})
public void get(#Suspended final AsyncResponse r) {
r.resume(Response.ok("Hello there!").build()); // usually called somewhere from another thread
}
}
Depending on certain parameters, I would like to decide at runtime whether the GET request should be handled synchronously or asynchronously. The URL of the resource endpoint (http://server/resource) must be the same in both cases. Is this possible?
Of course, as you can see in the example above, the synchronous version can be faked in an asynchronous manner by simply calling AsyncResponse.resume(...). However, I would to avoid the overhead of creating the asynchronous response.
A step back
The JAX-RS Asynchronous Server API is all about how the container will manage the request. But it will still hold the request and won't affect the client experience.
Quoting the Jersey documentation about the Asynchronous Server API:
Note that the use of server-side asynchronous processing model will
not improve the request processing time perceived by the client. It
will however increase the throughput of the server, by releasing the
initial request processing thread back to the I/O container while the
request may still be waiting in a queue for processing or the
processing may still be running on another dedicated thread. The
released I/O container thread can be used to accept and process new
incoming request connections.
The approaches described below won't bring any benefits to your client.
Using a custom header
You could have different URLs for sync and async methods and create a pre-matching filter, which is executed before the request matching is started.
To do it, implement ContainerRequestFilter, annotate it with #PreMatching and, based on your conditions (headers, parameters, etc), change the requested URI:
#Provider
#PreMatching
public class PreMatchingFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (requestContext.getHeaders().get("X-Use-Async") != null) {
requestContext.setRequestUri(yourNewURI);
}
}
}
Have a look at the ContainerRequestContext API.
Using a custom media type
I haven't tested the following solution, but it should work. You can keep the same URL for both sync and async methods, just accepting a different content type for each method.
For example:
Sync method: #Consumes("application/vnd.example.sync+text")
Async method: #Consumes("application/vnd.example.async+text")
And use the PreMatchingFilter to change the Content-Type header based on your conditions, like the following:
if (useSync) {
requestContext.getHeaders().putSingle(
HttpHeaders.CONTENT_TYPE, "application/vnd.example.sync+text");
} else {
requestContext.getHeaders().putSingle(
HttpHeaders.CONTENT_TYPE, "application/vnd.example.async+text");
}
According to the documentation, ContainerRequestContext#getHeaders() returns a mutable map with the request headers.
You could use a custom MediaType...you can for example put #Produces("simple") on your simple get method and #Produces("asynch") on your asynchronous get method. In your client you then can set the Accept Header of your call to "simple" or "asynch" depending on what you need.

Categories

Resources