Conditional handler in integration flow - java

I'm developing web hook notification service that allows clients to subscribe/unsubscribe to messages flowing through middle-ware and get notified about the messages (according to provided criteria) by posting the message payload to provided callback URL.
The message delivery looks like this :
flowBuilder
.enrichHeaders(e->e.header(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE,true))
.handle(Http.outboundChannelAdapter(message-> {
String subscriptionId = message.getHeaders().get(SUBSCRIPTION_ID_HEADER_NAME, String.class);
return subscriptionsStore.get(UUID.fromString(subscriptionId)).getCallbackUrl(); //potential NPE if subscription was removed
},restTemplateBuilder.build())
.get()
As you can see, the implementation of uriFunction fetches the callback URL from subscriptionsStore by subscription id(part of the message header).
My question is about the situation where the client has already unsubscribed with his subscription id and I'm after the conditional handler.
I know that I can filter messages with subscription id are still present in subscription store, but this is not the proper solution, as client might unsubscribe between filter and handle operations still causing NRE in uriFunction.
Another solution is to enreach the header with callback URL and filter then by header having non-empty value, but I don't want to compromise neither header nor payload of original message.
I can think about another approach: to calculate the URI of non-existing subscriptions as some static value and add interceptor to RestTempalte to simulate the HTTP OK replay for this specific URI value...
So my question is about the proper way to handle this case by using the standard EIP or another Spring integration feature I'm not aware about...
Thanks
UPDATE
I've added the DedicatedMessage class that holds the context :
public static class DedicatedMessage extends GenericMessage<Object> implements MessageDecorator{
#Getter
#Transient
private Subscription subscription;
public DedicatedMessage(Subscription subscription,Object payload,Map<String,Object> headers) {
super(payload,headers);
this.subscription = subscription;
}
#Override
public Message<?> decorateMessage(Message<?> message) {
return new DedicatedMessage (subscription,message.getPayload(),message.getHeaders());
}
}
and changed the flow as :
flowBuilder
.enrichHeaders(e->e.header(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE,true))
.handle((payload, headers) -> {
String subscriptionId = (String) headers.get(SUBSCRIPTION_ID_HEADER_NAME);
Subscription subscription = subscriptionsCache.get(UUID.fromString(subscriptionId));
return Optional.ofNullable(subscription)
.map(s-> new DedicatedMessage(s, payload,headers))
.orElse(null);
})
.handle(Http.outboundChannelAdapter(message->((DedicatedMessage)message).getSubscription().getCallbackUrl()
,restTemplateBuilder.build())
.get()
Any issues with this apporach ?

I am not sure what your NRE abbreviation means, but you could throw a NoSuchSubscriptionException from your subscriptionsStore.get() method and then ignore/report that exception in an ExpressionEvaluatingRequestHandlerAdvice applied to the outbound channel adapter in its endpoint.advice() chain.

Related

How to send form data(like HTTP POST) containing image and text via Rsocket?

I want the functionality similar to HTTP POST using Rsocket. Using Rsocket's request/response pattern, the data(image file and text) needs to be send and confirmation will be sent back to requester.
I tried to create a simple CustomData class to test :
RSocket Requester :
RSocketRequester req = RSocketRequester.builder()
.websocket(URI.create("ws://localhost:7000/rsocket"));
req.route("message")
.data(Mono.just(new CustomData("name", 12)), CustomData.class)
.retrieveMono(String.class).subscribe((value) -> {
System.out.println(value);
});
Message Handler:
#MessageMapping("message")
public Mono<String> response(#Payload CustomData cData) {
return Mono.just("data received!");
}
I am getting this exception :
java.lang.IllegalArgumentException: No encoder for com.demo1.demo.customdata.CustomData
Can anyone please provide an example to fulfil this?

Using reactive #ServerRequestFilter with RestClient in Quarkus

I want to add some custom auth headers to my request, when the dev-mode is activated. This should make the developement easier for me, since I don't have to add them on my self manually.
What I have found is the method annotation ServerRequestFilter which intercepts an ongoing request before hitting the controller level. The annotated function provides ContainerRequestContext argument where I can add my headers easily.
Now the problem: To know the custom headers value, I have to make an external rest call (I'm using the RestClient for that) to an API. Since I'm using the reactive library I get the exception org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException because of this call.
Since I'm using Kotlin, i tried to mark my method as suspendable. But this lead to a build error Method 'preCall$suspendImpl of class 'com.kaz.request.service.RequestInterceptor' cannot be static as it is annotated with '#org.jboss.resteasy.reactive.server.ServerRequestFilter'
Here my code:
#ApplicationScoped
class RequestInterceptor #Inject constructor(val authService: AuthService) {
#ServerRequestFilter
suspend fun preCall(requestContext: ContainerRequestContext) {
validateIdTokenHeader(requestContext)
}
private suspend fun validateIdTokenHeader(requestContext: ContainerRequestContext) {
val isTokenHeaderAbsent = requestContext.getHeaderString(Headers.X_ID_TOKEN) == null
val isDevModeEnabled = LaunchMode.current() == LaunchMode.DEVELOPMENT
if (isTokenHeaderAbsent && !isDevModeEnabled) {
throw AuthExceptions.ID_TOKEN_IS_ABSENT
} else {
injectDevUserIdToken(requestContext)
}
}
private suspend fun injectDevUserIdToken(requestContext: ContainerRequestContext) {
// This call is making the request and block
val idToken = authService.getIdToken("someHash")
requestContext.headers.putSingle(Headers.X_ID_TOKEN, idToken)
}
}
What I also tried to do is using Mutiny in my RestClient. I subscribed to the Uni and added the header when the result was available. But then I had the problem, that my controller/endpoint was already called before the header could be added to the request.
An endpoint could look like this:
#Path("hello/{id}")
#GET
suspend fun get(
//This header is what I want to add automatically, when dev mode is active.
#RestHeader(Headers.X_ID_TOKEN) idToken: UserIdToken,
#RestPath id: UUID,
#Context httpRequest: HttpServerRequest
): Response {
val request = RequestDTO(id, excludeFields, idToken.userId)
val envelope = service.findById(request)
return ResponseBuilder.build(httpRequest, envelope)
}
how about create a defer Uni and emit it on worker pool?
val idToken = Uni.createFrom().item { authService.getIdToken("someHash") }
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
or call the blocking code with the default io coroutines dispatcher
withContext(Dispatchers.IO){
authService.getIdToken("someHash")
}
and finally, maybe use Vertx.executeBlocking will be the simplest way
vertx.executeBlocking(Uni.createFrom().item {
authService.getIdToken("someHash")
})
Something in your implementation is blocking IO. So you can try to find it and wrap it with Uni or you can mark the method with #Blocking annotation, then the filters will be run on the worker thread.
Sometimes it makes sense not just to read the online documentation page but also the inline comments of the according class.
ServerRequestFilter.java says:
The return type of the method must be either be of type void, Response, RestResponse, Optional<Response>, Optional<RestResponse>, Uni<Void>, Uni<Response> or Uni<RestResponse>.
...
Uni<Void> should be used when filtering needs to perform a
non-blocking operation but the filter cannot abort processing. Note
that Uni<Void> can easily be produced using: Uni.createFrom().nullItem()
So the entrypoint function has to return an Uni as well, not just the RestClient. Changing it like the following will be enough
#ServerRequestFilter
fun preCall(requestContext: ContainerRequestContext) : Uni<Void> {
//Here I return a Uni<Void> back using Uni.createFrom().nullItem()
return validateIdTokenHeader(requestContext)
}

How to process messages from Spring Boot #RabbitListener in different handlers?

I'm trying to use request/response pattern for Spring Boot using AMQP and Spring-web. I have client service that has #RestController and AsyncRabbit configuration with Direct Exchange, Routing key etc. and server that has simple listener for request queue.
Client (something like rest gateway controller):
#RestController
public class ClientController {
#GetMapping("/test1")
public String getRequest() {
ListenableFuture<String> listenableFuture = asyncRabbitTemplate.convertAndReceiveAsType(
directExchange.getName(),
routingKey,
testDto,
new ParameterizedTypeReference<>() {}
);
return listenableFuture.get(); // Here I receive response from server services
}
#GetMapping("/test2")
public String getRequest2() {
ListenableFuture<String> listenableFuture = asyncRabbitTemplate.convertAndReceiveAsType(
/* Same properties but I use another DTO object for request */
);
return listenableFuture.get()
}
Server:
#RabbitListener(queues = "#{queue.name}", concurrency = "10")
#Component
public class Consumer {
#RabbitHandler
public String receive(TestDto testDto) {
...
}
#RabbitHandler
public String receive2(AnotherTestDto anotherTestDto) {
...
}
}
How should I implement Rabbit listener to process each REST request?
I found only two ways to do that:
Using #RabbitHandler as in the example above. But for each request method (GET, POST, etc.) I need unique DTO class to send message and process it in correct handler even if "request body" is almost same (number of request methods = number of DTO class to send). I'm not sure that is right way.
Leave one Consumer and call desired method to process that depends on message body (trivial if-else):
...
#RabbitListener(queues = "#{queue.name}")
public String receive(MessageDto messageDto) {
if (messageDto.requestType == "get.method1") {
return serverService.processThat(messageDto);
} else if (messageDto.requestType == "post.method2") {
return serverService.processAnother(messageDto);
} else if ...
...
}
...
But add new if-else branch every time is not very convenient so I really out of ideas.
You may consider to use different queues for different request types. All of them are going to be bound to the same direct exchange, but with their respective routing key.
What you would need on the consumer side is just to add a new #RabbitListener for respective queue. And bind that queue to the exchange with its routing key.
That's actually a beauty of the AMQP protol by itself: the producer always publish to the same exchange with respective routing key. The consumer registers its interest for routing keys and binds a queue. The rest of routing logic is done on the AMQP broker.
See more info in docs: https://www.rabbitmq.com/tutorials/tutorial-four-spring-amqp.html

How to send message when Websocket opened on Webflux

I created WebsocketHandler as it was shown in the Webflux websocket doc.
#Override
public Mono<Void> handle(WebSocketSession session) {
Flux<String> messages =
session.receive()
.map(webSocketMessage -> "New message");
return session.send(messages.map(session::textMessage));
}
But the problem is, I want to send some kind of "Hello" message right after the websocket connection opened. How can I achieve that?
After some research I found that, this can be solved with the Flux itself. It is enough that we add startWith method to the Flux. As in the definition of the startWith method.
Prepend the given values before this Flux sequence.
So we prepend our Hello message to the start of the Flux and it will be published first.
#Override
public Mono<Void> handle(WebSocketSession session) {
Flux<String> messages =
session.receive()
.map(webSocketMessage -> "New message")
.startWith("Hello");
return session.send(messages.map(session::textMessage));
}
P.S. Bear in mind that you can also prepend Publisher<? extends T> publisher i.e. Flux or Mono, not just plain values.

Vertx event bus reply handler not getting invoked for a specific call

I'm running a local instance of vertx. Router redirects my requests to a worker verticle which has the following handler:
protected Handler<Message<JsonObject>> handler1() {
return msg -> {
final RequestBody req = mapper.readValue(msg.body().encode(), RequestBody.class);
processRequest(req, msg);
}
}
The processRequest function accepts the request body, makes calls to two external services, aggregates the responses, and returns back to the client.
private processRequest(RequestBody req, Message<JsonObject> msg) {
CompletableFuture<String> service1Response = getService1ResponseAsync(req); // Make async call to service 1
String service2Response = getService2ResponseSync(req); // Make sync call to service 2
ResponseBody response = aggregateResult(service1Response.join(), service2Response); // Tag1
msg.reply(mapper.writeValueAsString(response));
}
private CompletableFuture<String> getService1ResponseAsync(RequestBody req) {
CompletableFuture<String> result = new CompletableFuture();
// Below handler call makes GET call to service 1 using apache HTTP client and returns the response
vertx.eventBus().request("serviceVerticleAddr1", mapper.writeValueAsString(req), new DeliveryOptions(), reply -> { // Tag2
if (reply.succeeded())
result.complete(reply.result().body().toString());
else
result.completeExceptionally(result.cause());
}
}
When I hit the above API, my request times out. The thread from worker pool assigned for the execution of my request gets blocked at Tag1 forever. On debugging a little further, I found that the reply handler for the call in Tag2 is not getting invoked.
The handlers in service verticle (serviceVerticleAddr1) [i.e. Tag2] returns proper response for other APIs making use of it, but for me it's getting blocked. Can someone please help me identify the cause? Is there some kind of deadlock being formed when the thread calling vertx.eventBus().request [Tag2] starts to wait for future completion at service1Response.join() [Tag1]?
I think is blocked because of that sender is not being notified by the consumer that message has been processed.
I would recommend you to check inside the handler block registered for the consumer of serviceVerticleAddr1 address and ensure that is replying (notifying) to the sender that the requested message has successfully handled (or not). Consumer might look like
vertx.eventBus().consumer("serviceVerticleAddr1", message -> {
try{
doSomething();
message.reply("done");
} catch(){
message.fail(0, "fails");
}
});
This way, sender's async handler would be notified that consumer could process requested message

Categories

Resources