I have two spring boot services that communicate using RabbitMQ.
Service1 sends request for session creation to Service2.
Service2 handles request and should return response.
Service1 should handle the response.
Service1 method for requesting session:
public void startSession()
{
ListenableFuture<SessionCreationResponseDTO> sessionCreationResponse = sessionGateway.requestNewSession();
sessionCreationResponse.addCallback(response -> {
//handle success
}, ex -> {
// handle exception
});
}
On Service1 I have defined AsyncOutboundGateway, like:
#Bean
public IntegrationFlow requestSessionFlow(MessageChannel requestNewSessionChannel,
AsyncRabbitTemplate amqpTemplate,
SessionProperties sessionProperties)
{
return flow -> flow.channel(requestNewSessionChannel)
.handle(Amqp.asyncOutboundGateway(amqpTemplate)
.exchangeName(sessionProperties.getRequestSession().getExchangeName())
.routingKey(sessionProperties.getRequestSession().getRoutingKey()));
}
On Service2, I have flow for receiving these messages:
#Bean
public IntegrationFlow requestNewSessionFlow(ConnectionFactory connectionFactory,
SessionProperties sessionProperties,
MessageConverter messageConverter,
RequestNewSessionHandler requestNewSessionHandler)
{
return IntegrationFlows.from(Amqp.inboundGateway(connectionFactory,
sessionProperties.requestSessionProperties().queueName())
.handle(requestNewSessionHandler)
.get();
Service2 handles there requests:
#ServiceActivator(async = "true")
public ListenableFuture<SessionCreationResponseDTO> handleRequestNewSession()
{
SettableListenableFuture<SessionCreationResponseDTO> settableListenableFuture = new SettableListenableFuture<>();
// Goes through asynchronous process of creating session and sets value in listenable future
return settableListenableFuture;
}
Problem is that Service2 immediately returns ListenableFuture to Service1 as message payload, instead of waiting for result of future and sending back result.
If I understood documentation correctly Docs by setting async parameter in #ServiceActivator to true, successful result should be returned and in case of exception, error channel would be used.
Probably I misunderstood documentation, so that I need to unpack ListenableFuture in flow of Service2 before returning it as response, but I am not sure how to achieve that.
I tried something with publishSubscribeChannel but without much luck.
Your problem is here:
.handle(requestNewSessionHandler)
Such a configuration doesn't see your #ServiceActivator(async = "true") and uses it as a regular blocking service-activator.
Let's see if this helps you:
.handle(requestNewSessionHandler, "handleRequestNewSession", e -> e.async(true))
It is better to think about it like: or only annotation configuration. or only programmatic, via Java DSL.
Related
On my quarkus rest project i have a restclient that uses mutiny:
#Path("/")
#RegisterRestClient(configKey = "my-api")
#RegisterClientHeaders
#RegisterProvider(MyExceptionMapper.class)
public interface MyClient {
#POST
#Path("path")
Uni<MyBean> get(String body);
}
I wanna handle propery non 2XX httpError so i have made my ExceptionMaper
public class MyExceptionMapper implements ResponseExceptionMapper<MyException> {
#Override
public MyException toThrowable(Response response) {
//TODO
return new MyException();
}
}
a bad call on the client shows that MyExceptionMapper handle the response but the exception raises and does not became a failure on my Uni Client response object
Uni<MyBean> bean = myClient.get("") // i do not have a failure in case of 4XX http
.onFailure().invoke(fail -> System.out.println("how can i get here?"));
Am i using mutiny on a rest client in the wrong way?
Thanks
UPDATE
ok i forgot to add the dependency quarkus-rest-client-mutiny, adding this i notice 2 things,
i still pass through Myexceptionmapper
i also produce a Uni.failure, but the exception into the failure is not the custom exception i created into MyExceptionmapper but a RestEasyWebApplicationException
Failure : org.jboss.resteasy.client.exception.ResteasyWebApplicationException: Unknown error, status code 400
at org.jboss.resteasy.client.exception.WebApplicationExceptionWrapper.wrap(WebApplicationExceptionWrapper.java:107)
at org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:21)
Does the ExceptionMapper becomes useless in this context?
I think this is a bug in quarkus-rest-client-mutiny. I created an Github issue based on your findings.
It will work as you expect if you switch to quarkus-rest-client-reactive
I'd like to generate unique traceId per request and pass it through all services. In Spring MVC it was fairly easy by using MDC context and putting traceId in header, but in reactive stack it isn't working at all because of ThreadLocal.
In general I'd like to log each request and response on every service I have with single traceId which can identify specific action in whole system.
I tried to create custom filter based on article: https://azizulhaq-ananto.medium.com/how-to-handle-logs-and-tracing-in-spring-webflux-and-microservices-a0b45adc4610 but it's seems to not working.
My current solution only log responses and traceId are losing after making request, so there is no on response.
Let's try imagine that there are two services: service1 and service2. Below I tried to sketch how it should work.
How should it work
client -> service1 - service1 should generate traceId and log request
service1 -> service2 - service2 should fetch traceId from request, then log request
service1 <- service2 - after some calculation service2 should log response and return response to service1
client <- service1 - at the end service1 should log response (still with the same traceId) and return response to client
How it actually works
client -> service1 - nothing in logs
service1 -> service2 - nothings in logs
service1 <- service2 - service2 is logging correctly and return response to service1
client <- service1 - service1 is logging response (but without traceId)
Here is my approach
#Component
public class TraceIdFilter implements WebFilter {
private static final Logger log = LoggerFactory.getLogger(TraceIdFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
Map<String, String> headers = exchange.getRequest().getHeaders().toSingleValueMap();
return Mono.fromCallable(() -> {
final long startTime = System.currentTimeMillis();
return new ServerWebExchangeDecorator(exchange) {
#Override
public ServerHttpRequest getRequest() {
return new RequestLoggingInterceptor(super.getRequest(), false);
}
#Override
public ServerHttpResponse getResponse() {
return new ResponseLoggingInterceptor(super.getResponse(), startTime, false);
}
};
}).contextWrite(context -> {
var traceId = "";
if (headers.containsKey("X-B3-TRACEID")) {
traceId = headers.get("X-B3-TRACEID");
MDC.put("X-B3-TraceId", traceId);
} else if (!exchange.getRequest().getURI().getPath().contains("/actuator")) {
traceId = UUID.randomUUID().toString();
MDC.put("X-B3-TraceId", traceId);
}
Context contextTmp = context.put("X-B3-TraceId", traceId);
exchange.getAttributes().put("X-B3-TraceId", traceId);
return contextTmp;
}).flatMap(chain::filter);
}
}
Github: https://github.com/Faelivrinx/kotlin-spring-boot
There is any existing solution do that?
In Spring Webflux you don't have anymore a ThreadLocal but you have a unique context for each chain request. You can attach a traceId to this context as following:
#Component
public class TraceIdFilter implements WebFilter {
private static final Logger log = LoggerFactory.getLogger(TraceIdFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.subscriberContext(
ctx -> {
.....
var traceId = UUID.randomUUID().toString();
return ctx.put("X-B3-TraceId", traceId);
.....
}
);
}
}
Now the chain in your service will have in the context this attribute. You can retrive it from your service using static method Mono.subscriberContext(). For example you can get traceId in this way
Mono.subscriberContext()
.flaMap(ctx -> {
.....
var traceId = ctx.getOrDefault("traceId", null);
.....
)
Sleuth 3.0 provides automatic instrumentation for WebFlux, which means, if you do nothing, you will always get the current span of the thread which subscribes to your Mono or Flux. If you wish to overwrite this, for instance because you are batching a lot of requests and want a unique trace for every transaction, all you have to do is to manipulate the Context of your operator chain.
private Mono<Data> performRequest() {
var span = tracer.spanBuilder().setNoParent().start(); // generate a completely new trace
// note: you can also just generate a new span if you want
return Mono.defer(() -> callRealService())
.contextWrite(Context.of(TraceContext.class, span.context());
}
When following this approach, make sure to import org.springframework.cloud.sleuth.Tracer and not the one by brave, because they use different types and Reactor will drop your Mono with an ugly error message if they don't align correctly (unfortunately, since the Context ist just a plain old Map<Object, Object>, you won't get a compiler error).
As i understand the decode() method of the feign ErrorDecoder will be called when a request responds with a status code != 2xx. Through debugging my tests i found out that the decode() method of my CustomErrorDecoder is not invoked on e.g. 504 or 404. I tried two ways to configure it:
Either include it as a Bean in the client configuration:
#Bean
public CustomErrorDecoder customErrorDecoder() {
return new CustomErrorDecoder();
}
or write it into the application configuration :
feign:
client:
config:
myCustomRestClientName:
retryer: com.a.b.some.package.CustomRetryer
errorDecoder: com.a.b.some.package.CustomErrorDecoder
Both ways don't invoke the ErrorDecoder. What am I doing wrong? The Bean is beeing instantiated and my CustomErrorDecoder looks like this:
#Component
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String s, Response response) {
Exception exception = defaultErrorDecoder.decode(s, response);
if (exception instanceof RetryableException) {
return exception;
}
if (response.status() == 504) {
// throwing new RetryableException to retry 504s
}
return exception;
}
}
Update:
I have created a minimal reproducible example in this git repo. Please look at the commit history to find 3 ways that I tried.
The problem is that your feign client uses feign.Response as the return type:
import feign.Param;
import feign.RequestLine;
import feign.Response;
public interface TestEngineRestClient {
#RequestLine(value = "GET /{uuid}")
Response getReport(#Param("uuid") String uuid);
}
In this case, Feign delegates its handling to the developer - e.g., you can retrieve HTTP status and a response body and do some stuff with it.
If interested, you can look at the source code of feign.SynchronousMethodHandler, executeAndDecode section.
To fix this, replace Response.class with the desired type in case of the correct response with status code = 2xx (probably some DTO class). I made a PR where I've changed it to String for simplicity.
I have a spring integration flow defined in the flow DSL syntax. One of my handlers is a Webflux.outboundGateway. When the remote URI is not accessible, an exception is thrown and sent to the "errorChannel". I'm trying to have the flow to retry, but so far with no success (the call is never retried). Here is what my configuration looks like:
#Bean
public IntegrationFlow retriableFlow() {
return IntegrationFlows
.from(...)
.handle(
WebFlux.outboundGateway(m ->
UriComponentsBuilder.fromUriString(remoteGateway + "/foo/bar")
.build()
.toUri(), webClient)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.replyPayloadToFlux(true), e -> e.advice(retryAdvice())
)
// [ ... ]
.get();
}
#Bean
public Advice retryAdvice() {
RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice();
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy retryPolicy = new ExponentialBackOffPolicy();
retryPolicy.setInitialInterval(1000);
retryPolicy.setMaxInterval(20000);
retryTemplate.setBackOffPolicy(retryPolicy);
advice.setRetryTemplate(retryTemplate);
return advice;
}
Should I be using something different than the RequestHandlerRetryAdvice? If so, what should it be?
Webflux is, by definition, async, which means the Mono (reply) is satisfied asynchronously when the request completes/fails, not on the calling thread. Hence the advice won't help because the "send" part of the request is always successful.
You would have to perform retries via a flow on the error channel (assigned somewhere near the start of the flow). With, perhaps, some header indicating how many times you have retried.
The ErrorMessage has properties failedMessage and cause; you can resend the failedMessage.
You could turn off async so the calling thread blocks, but that really defeats the whole purpose of using WebFlux.
I have two spring boot services.
A and B.
From A service, I am calling B Service(Using Feign Client).
I have one request interceptor which adds custom headers to the request before sending it.
This is my interceptor:
public class HeaderInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
try {
Object encrypyedData = RequestContextHolder.currentRequestAttributes().getAttribute(""headerName", 0);
if(encrypyedData != null) {
template.header("headerName", encrypyedData.toString());
}else {
System.out.println("Encrypted Data is NULL");
}
} catch(Exception e) {
System.out.println("ankit === "+e.getMessage());
}
}
}
But when I am running this code, I am getting exception : No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
I have also tried adding this in Service A
#Bean public RequestContextListener requestContextListener(){
return new RequestContextListener();
}
I have added this in Main Application File(file annotated with #SpringBootApplication).
Still the same issue. What am i missing?
Probably you're running your spring cloud feign on Hystrix.
Hystrix's default isolation mode is thread, so actual HTTP request will be called on the different thread that is managed by hystrix. And HeaderInterceptor also run on that thread.
But, you are accessing RequestContextHolder and it is using ThreadLocal. That is probably the reason for your error.
Try to use one of the the below properties on A service.
It will disable hystrix for feign.
feign:
hystrix:
enabled: false
Or, you can just change isolation mode of your hystrix like below.
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
If it works, you need to choose the way that you apply to. Just disable hystrix or change isolation strategy or just passing attribute via another way.