I'm attempting to use org.springframework.retry.annotation.Retryable with an expectation that if a connection reset or timeout happens, the execution can re-try the call again. But so far I'm not getting the retry setup to work properly. The timeout error looks like this this error:
javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request:
java.net.SocketTimeoutException: Read timed out
Here is what I have for the retry:
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import java.util.function.Supplier;
#Service
public interface RetryService {
#Retryable(maxAttempts = 4,
backoff = #Backoff(delay = 1000))
<T> T run(Supplier<T> supplier);
}
and retry implementing class:
import com.service.RetryService;
import org.springframework.stereotype.Service;
import java.util.function.Supplier;
#Service
public class RetryImpl implements RetryService {
#Override
public <T> T run(Supplier<T> supplier) {
return supplier.get();
}
}
I use it in the following way:
retryService.run(()->
myClient.cancel(String id)
);
and MyClient.java has the following implementation for the cancel call and caching:
public Response cancel(String id) {
String externalUrl = "https://other.external.service/rest/cancel?id="+id;
WebTarget target = myClient.target(externalUrl);
return target.request()
.accept(MediaType.APPLICATION_JSON)
.cacheControl(cacheControl())
.buildPut(null)
.invoke();
}
private static CacheControl cacheControl(){
CacheControl cacheControl = new CacheControl();
cacheControl.setMustRevalidate(true);
cacheControl.setNoStore(true);
cacheControl.setMaxAge(0);
cacheControl.setNoCache(true);
return cacheControl;
}
The externalUrl var above is an endpoint that's a void method - it returns nothing.
My hope is that when timeout happens, the retry implementation would fire up and call the external service another time. My setup works in Postman as long as timeout does NOT occur. But if I let it sit for about 30 min, and try again, then I get the timeout error as above. And if I issue another call via Postman right after the timeout, i do get a successful execution. So the retry setup is not working, seems like something falls asleep.
I've tried a dazzling number of variations to make it work, but no luck so far. When timeout happens, i see no logs in Kibana in the external service, while getting the the error above on my end.
I'm hoping there is something obvious that I'm not seeing here, or maybe there is a better way to do this. Any help is very much appreciated.
You need to set #EnableRetry on one of your #Configuration classes.
See the README: https://github.com/spring-projects/spring-retry
Related
I'm using mysql for my db, spring for my backend and angular for my frontend. my frontend is throwing this weird bug when its routed proper: click here to see it
as you can see, the path at the end is %7Bid%7D (looks like {id} from the backend)
the http error code is always one of 3: 400,400 or 500
the backend looks okay and I've only really ever gotten this error code:
2022-02-04 15:30:31.465 WARN 15200 --- [nio-8081-exec-7] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'; nested exception is java.lang.NumberFormatException: For input string: "{id}"]
here is the controller in question(a get request):
#CrossOrigin
#RestController
#RequestMapping(path = "/api/patient")
public class PatientPortalController {
#Autowired
private PatientPortalRepo patientPortalRepo;
#PostMapping("/patientportal")
public PatientPortal createPatientPortal(#RequestBody PatientPortal patientportal) {
return patientPortalRepo.save(patientportal);
}
#GetMapping("/patientportal/{id}")
public ResponseEntity<PatientPortal> getpatientPortal(#PathVariable Long id){
PatientPortal patientportal = patientPortalRepo.findByPatientPortalId(id);
if(patientportal.getId()>0 && patientportal!=null)
return new ResponseEntity<PatientPortal>(patientportal, HttpStatus.OK);
return new ResponseEntity<PatientPortal>(patientportal, HttpStatus.BAD_REQUEST);
}}
Some things worth of note that I've tried with the backend
Tried changing response entity to of type long and returning id, tried refactoring the controller numerous times, tried changing decorators/paths around, 20x checked the types are correct, checked if any types other than the id are throwing it, checked if I had any security implemented that was denying access, checked if adding a onetoone would get it to pop up on the front end. It works fine on the backend(returns a list of what I'd assume is patientportal object) but I am either routing incorrectly, there is some security I'm missing, there is some type error, or there is some logic errors. I think however the issue lies in the front end.
here's the code where I call the front end method hard coded a value to test:
this.patientloginservice.loginPatient(this.patient).subscribe(data=>(this.route.navigate(['api/patient/patientportal/1'])),error=>console.log('error'));
and here is where that code is serviced:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http'
import { Observable } from 'rxjs'
import { PatientPortal } from './patientportal';
#Injectable({
providedIn: 'root'
})
export class PatientService {
private baseURL = "http://localhost:8081/api/patient/patientportal";
constructor(private httpClient: HttpClient) { }
getPatientPortalList(): Observable<PatientPortal[]> {
return this.httpClient.get<PatientPortal[]>(`${this.baseURL}`);
}
createPatientPortal(patientportal: PatientPortal): Observable<Object>{
return this.httpClient.post<Object>(`${this.baseURL}`, patientportal);
}
getPatientPortalById(id: number): Observable<PatientPortal>{
return this.httpClient.get<PatientPortal>(`${this.baseURL}/{id}`);
}
updatePatientPortal(id: number, patientportal: PatientPortal): Observable<Object>{
return this.httpClient.put(`${this.baseURL}/{id}`, patientportal);
}
deletePatientPortal(id: number): Observable<Object>{
return this.httpClient.delete(`${this.baseURL}/{id}`);
}
}
any help will be much appreciated, thank you. again like I said the route routes correctly as far as I can tell, but the rendered table does not fill data and it throws that error. I am using a login that is to redirect/route to a patient's details.
You're using Template literals incorrectly.
Instead of just {id} it should be ${id} just like what you did with ${this.baseUrl}
Hope that solves your issue.
I have updated the Micronaut application from 2.5.XX to 3.0.0. As per the Micronaut documentation, the project reactor is the recommended reactive library to use for reactive programming.
#Controller("/product")
public class ProductController implements IProductOperation {
#Override
public Flux<List> freeTextSearch(String text) {
return iProductManager.findFreeText(text).onErrorResume(throwable -> {
return Flux.error(new GlobalException(throwable));
});
}
}
public interface IProductOperation {
#Get(value = "/search/{text}")
Flux<?> freeTextSearch(#NotBlank String text);
}
When I CURL the end point curl -X 'GET' 'http://localhost:8081/product/search/ffff' -H 'accept: application/json' it goes to the infinite.
Since I have an error on the system so return Flux.error(new GlobalException(throwable)); should return the 500 internal system error, however, it goes to the infinite
I have integrated rabbitMQ and rabbitMQ producer is shutdown. iProductManager.findFreeText(text) throws an exception since the rabbitMQ producer is not running. Instead of going infinite, it should throw an exception and global error handling should get called. But is is not working as expected
public class GlobalException extends RuntimeException{
public GlobalException(Throwable throwable){super(throwable);}
}
This method never get called on error.
#Produces
#Singleton
#Requires(classes = {GlobalException.class, ExceptionHandler.class})
public class GlobalExceptionHandler implements ExceptionHandler<GlobalException, HttpResponse> {
private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);
#Override
public HttpResponse handle(HttpRequest request, GlobalException exception) {
LOG.error(exception.getLocalizedMessage());
LOG.error(exception.getCause().getMessage());
Arrays.stream(exception.getStackTrace()).forEach(item -> LOG.error(item.toString()));
return HttpResponse.serverError(exception.getLocalizedMessage());
}
}
Logs
22:40:02.151 [default-nioEventLoopGroup-1-3] INFO reactor.Flux.OnErrorResume.1 - onSubscribe(FluxOnErrorResume.ResumeSubscriber)
22:40:02.176 [default-nioEventLoopGroup-1-3] INFO reactor.Flux.OnErrorResume.1 - request(1)
I think you're using the wrong reactor Flux operator:
onErrorResume switches to a different Flux in case of errors; you can consider that as a "fallback Flux". In your case, the fallback throws itself an error - this could explain the infinite loop.
onErrorMap should do what you're looking for: mapping an exception to another that can be used for the HTTP response
If you'd like to wrap all exceptions from that Flux, you could do:
return iProductManager.findFreeText(text)
.onErrorMap(throwable -> new GlobalException(throwable));
Note that other onErrorMap methods allow you to have more fine-grained behavior.
I am using SpringBoot version 1.5.9.
I can’t understand why my Fallback class doesn’t work out.
Maybe I'm doing something wrong?
My Feign client:
#FeignClient(
name = "prices",
url = "${prices.url}",
configuration = MyFeignConfig.class,
fallbackFactory = FallbackClass.class
)
public interface PricesFeignClient {
#GetMapping("/{userId}")
PriceModel get(
#PathVariable("userId") String userId
);
}
Here is the fallback class:
#Component
public class FallbackClass implements FallbackFactory<PricesFeignClient> {
#Override
public PricesFeignClient create(Throwable cause) {
return new PricesFeignClient() {
#Override
public PriceModel get(String userId) {
System.out.println("LALALA");
return null;
}
};
}
}
In theory, my fallback method should work out if my Feign client returns an error.
Here in the Feign client in the files in prices.url I specified the wrong URL (simulated the situation that my remote service to which I am making a call is unavailable). Knowing my Feign client should return with an error and the Fallback class should be called in which in the console I should receive the message: "LALALA".
This message is not in the console: my Fallback class is not being called. Instead, I get an error stating that the requested resource was not found.
Please tell me what could be the problem? Can I make a mistake somewhere?
The thing is that now I'm trying to get my Fallback class to work. And then I want to call another Fagnet class in the Fallback class with a different URL so that it works out if my main service is unavailable.
Tell me, please. thanks
I had to add to dependencies this for it to work (also don't forget to insert feign.hystrix.enabled: true as was said in the comments)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
Using mock to launch a salesforce streaming route as shown here fails for the following route:
from("salesforce:AccountUpdateTopic?notifyForFields=ALL¬ifyForOperations=ALL")
.tracing().convertBodyTo(String.class).to("file:D:/tmp/")
.to("mock:output")
.log("SObject ID: ${body}");
in
package org.apache.camel.component.salesforce;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.component.salesforce.internal.OperationName;
import org.junit.Test;
public class StreamingApiIntegrationTest extends AbstractSalesforceTestBase {
#Test
public void testSubscribeAndReceive() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:AccountUpdateTopic");
mock.start();
Thread.sleep(10000);
mock.stop();
}
#Override
protected RouteBuilder doCreateRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
// test topic subscription
from("salesforce:AccountUpdateTopic?notifyForFields=ALL¬ifyForOperations=ALL").tracing().convertBodyTo(String.class).to("file:D:/tmp/").to("mock:output").log("SObject ID: ${body}");
}
};
}
}
Running this test does not start the route (updates are not fetched from Salesforce and stored in /tmp/).
Can mock run a route and wait for updates from Salesforce? Is there a shorter example that allows for testing salesforce routes without making use of spring?
You misunderstood the Camel Mock component. Mocks are not starting anything. They are just endpoints who record and assert the messages they receive.
To trigger a Camel route you have to send a message to it. You can do this easily using a ProducerTemplate.
It is this line from the example you mention that does exactly that.
CreateSObjectResult result = template().requestBody(
"direct:upsertSObject", merchandise, CreateSObjectResult.class);
template is the ProducerTemplate and requestBody the method to send a message to the endpoint direct:upsertSObject and wait for a response. See the Javadocs of ProducerTemplate for the various existing signatures.
I'm trying to create a custom http action (https://playframework.com/documentation/2.5.x/JavaActionsComposition) to log request and response bodies with Play 2.5.0 Java. This is what I've got so far:
public class Log extends play.mvc.Action.Simple {
public CompletionStage<Result> call(Http.Context ctx) {
CompletionStage<Result> response = delegate.call(ctx);
//request body is fine
System.out.println(ctx.request().body().asText())
//how to get response body string here while also not sabotaging http response flow of the framework?
//my guess is it should be somehow possible to access it below?
response.thenApply( r -> {
//???
return null;
});
return response;
}
}
Logging is often considered a cross-cutting feature. In such cases the preferred way to do this in Play is to use Filters:
The filter API is intended for cross cutting concerns that are applied indiscriminately to all routes. For example, here are some common use cases for filters:
Logging/metrics collection
GZIP encoding
Security headers
This works for me:
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import javax.inject.Inject;
import akka.stream.*;
import play.Logger;
import play.mvc.*;
public class LoggingFilter extends Filter {
Materializer mat;
#Inject
public LoggingFilter(Materializer mat) {
super(mat);
this.mat = mat;
}
#Override
public CompletionStage<Result> apply(
Function<Http.RequestHeader, CompletionStage<Result>> nextFilter,
Http.RequestHeader requestHeader) {
long startTime = System.currentTimeMillis();
return nextFilter.apply(requestHeader).thenApply(result -> {
long endTime = System.currentTimeMillis();
long requestTime = endTime - startTime;
Logger.info("{} {} took {}ms and returned {}",
requestHeader.method(), requestHeader.uri(), requestTime, result.status());
akka.util.ByteString body = play.core.j.JavaResultExtractor.getBody(result, 10000l, mat);
Logger.info(body.decodeString("UTF-8"));
return result.withHeader("Request-Time", "" + requestTime);
});
}
}
What is it doing?
First this creates a new Filter which can be used along with other filters you may have. In order to get the body of the response we actually use the nextFilter - once we have the response we can then get the body.
As of Play 2.5 Akka Streams are the weapon of choice. This means that once you use the JavaResultExtractor, you will get a ByteString, which you then have to decode in order to get the real string underneath.
Please keep in mind that there should be no problem in copying this logic in the Action that you are creating. I just chose the option with Filter because of the reason stated at the top of my post.