Request asynchronous processing does not work - spring

I would like to make my controller (Spring MVC) handle request in parallel. I use #Callable below and it does not work, because next request is handled since first one finished (returns view).
#RequestMapping(method = RequestMethod.GET)
public Callable<String> helloWorld(final Model model) throws InterruptedException {
return new Callable<String>() {
#Override
public String call() throws Exception {
Thread.sleep(5000);
return "helloWorld";
}
};
}
Do I need any special code?

The documentation for Spring MVC states the following about the Callable<?> return type for handler methods
A Callable can be returned when the application wants to produce
the return value asynchronously in a thread managed by Spring MVC.
Spring will take the returned Callable instance, pass it to an ExecutorService (actually does more processing then than that, but for simplicity) and execute it in a different Thread then your initial request. After waiting 5 seconds, it will return the view associated with the view name "helloWorld".

Related

Spring Feign client call enters exception block when it should stay in try block

Need some small help about Spring Feign client. So here is the situation,
I have 2 Spring boot services. Let’s say Service A and Service B. I have configured my Service A with Feign client through which I call the Service B method.
So here is the code for my Service A,
My FeignCleint config interface,
#FeignClient(name = "FeignClient", url = "http://localhost:8081/ServiceB/hello")
public interface FeignApi {
#RequestMapping(method = RequestMethod.GET)
ResponseEntity<?> hello();
}
And my rest controller that uses above feign config to call the Service B method,
#RestController
public class ApiController {
#Autowired
private FeignApi feignApi;
#RequestMapping(value = "/callServiceB")
public ResponseEntity<?> companyInfo() {
ResponseEntity<?> response = new ResponseEntity("OK Response", HttpStatus.OK);
try {
response = feignApi.hello();
// Code for some other things related to application.
return response;
} catch (Exception ex) {
System.out.println("Service A Exception block reached.");
return new ResponseEntity(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
}
below is my controller for Service B,
#RestController
public class MyController {
#GetMapping("/hello")
public String hello() throws Exception {
if (true) {
throw new Exception("Service B Exception...");
}
return "Hello World";
}
}
And my Controller advice to handle the exception that I am manually throwing,
#ControllerAdvice
public class MyControllerAdvice {
#ExceptionHandler
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<?> handleException(Exception exception, Model model) {
return new ResponseEntity<>("Caused due to : " + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Now my flow is like below,
As you can see, I am calling service B from service A using feign client. My service B is throwing an exception manually which I am catching using the controller advice and sending the exception details as an ResponseEntity back to the calling service A. So that Service A can process the details and move forward based on that.
The problem is when I hit the call from Service A using
http://localhost:8080/feign/callServiceB
The service B fails as expected. Now what I expect is that the Service A should receive the response back in form of the ResponseEntity. But what really happens is that the flow enters the exception block instead of staying in the try block. I can see this line printed,
"Service A Exception block reached."
This is what I don't understand. If I have managed the service B exception using controller advice and sent back the response to service A in form of ResponseEntity, then how come the flow of service A enters catch block. I expect it to stay in try block only as I want to process further based on the data.
Any idea, how can I get around this thing? Or is this how it will behave even when I am using controller advice to manage exceptions? What should be the expected behavior in this case?
Please advice.
By default Feign throws FeignException for any error situation.
Make use of fallback mechanism to handle failures.
#FeignClient(name = "FeignClient", url = "http://localhost:8081/ServiceB/hello", fallback= FeignApiFallback.class)
public interface FeignApi {
#RequestMapping(method = RequestMethod.GET)
ResponseEntity<?> hello();
}
#Component
class FeignApiFallback implements FeignApi {
#Override
public ResponseEntity<?> hello() {
//do more logic here
return ResponseEntity.ok().build();
}
}
make sure you add below property to wrap methods in hystrix commands in recent releases
feign.hystrix.enabled=true
Any status other than 200, feign client will consider it as an exception and you are setting HttpStatus.INTERNAL_SERVER_ERROR in your controller advice.
You can use custom ErrorDecoder
refer https://github.com/OpenFeign/feign/wiki/Custom-error-handling

Spring controller advice does not correctly handle a CompletableFuture completed exceptionally

I am using Spring Boot 1.5, and I have a controller that executes asynchronously, returning a CompletableFuture<User>.
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private final UserService service;
#GetMapping("/{id}/address")
public CompletableFuture<Address> getAddress(#PathVariable String id) {
return service.findById(id).thenApply(User::getAddress);
}
}
The method UserService.findById can throw a UserNotFoundException. So, I develop dedicated controller advice.
#ControllerAdvice(assignableTypes = UserController .class)
public class UserExceptionAdvice {
#ExceptionHandler(UserNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
public String handleUserNotFoundException(UserNotFoundException ex) {
return ex.getMessage();
}
}
The problem is that tests are not passing returning an HTTP 500 status and not a 404 status in case of an unknown user request to the controller.
What's going on?
The problem is due to how a completed exceptionally CompletableFuture handles the exception in subsequent stages.
As stated in the CompletableFuture javadoc
[..] if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause. [..]
In my case, the thenApply method creates a new instance of CompletionStage that wraps with a CompletionException the original UserNotFoundException :(
Sadly, the controller advice does not perform any unwrapping operation. Zalando developers also found this problem: Async CompletableFuture append errors
So, it seems to be not a good idea to use CompletableFuture and controller advice to implement asynchronous controllers in Spring.
A partial solution is to remap a CompletableFuture<T> to a DeferredResult<T>. In this blog, an implementation of a possible Adapter was given.
public class DeferredResults {
private DeferredResults() {}
public static <T> DeferredResult<T> from(final CompletableFuture<T> future) {
final DeferredResult<T> deferred = new DeferredResult<>();
future.thenAccept(deferred::setResult);
future.exceptionally(ex -> {
if (ex instanceof CompletionException) {
deferred.setErrorResult(ex.getCause());
} else {
deferred.setErrorResult(ex);
}
return null;
});
return deferred;
}
}
So, my original controller would change to the following.
#GetMapping("/{id}/address")
public DeferredResult<Address> getAddress(#PathVariable String id) {
return DeferredResults.from(service.findById(id).thenApply(User::getAddress));
}
I cannot understand why Spring natively supports CompletableFuture as return values of a controller, but it does not handle correctly in controller advice classes.
Hope it helps.
For those of you who still run into trouble with this : even though Spring correctly unwraps the ExecutionException, it doesn't work if you have a handler for the type "Exception", which gets chosen to handle ExecutionException, and not the handler for the underlying cause.
The solution : create a second ControllerAdvice with the "Exception" handler, and put #Order(Ordered.HIGHEST_PRECEDENCE) on your regular handler. That way, your regular handler will go first, and your second ControllerAdvice will act as a catch all.

Can I get Spring Validation errors in prehandle

I currently have something similar to this in all of my endpoints in my spring app.
if(bindingResult.hasErrors()){
return new ResponseEntity<>(BAD_REQUEST);
}
I would like to move this to a http interceptor so that I only need it in one place. However, I cannot figure out how to get all of the errors from the binding result in preHandle.
How would I get validation errors in preHandle, or some other time before it starts the actual route?
One way to achieve what I think you're looking for is to not include BindingResult as a method parameter. Given no BindingResult is included as a method argument Spring will throw a BindException exception. You can define an ExceptionHandler, generally I've placed these within a #ControllerAdvice, to handle the exception as needed. Below is some sample code
Controller
#PostMapping
public SomeReturnObject someMethod(#Valid SomeCommand command) {
// logic - no longer contains checks for binding result errors
}
As part of ControllerAdvice
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ApplicationControllerAdvice {
....
#ExceptionHandler(BindException.class)
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
protected SomeResponse handleBindException(BindException ex) {
// handle exception
}
}

Testing controller get return value

How do I get the return value from this test? My controller returns #Responsebody Meal
#Test
// #Transactional
public void testPost() {
Profile profile = ProfileUtil.getProfile();
profileService.save(profile);
String requestUri = "/user/" + profile.getId() + "/meals";
request.setMethod("POST");
request.setRequestURI(requestUri);
Object handler;
try {
handler = handlerMapping.getHandler(request).getHandler();
handlerAdapter.handle(request, response, handler);
Assert.assertEquals(200, response.getStatus());
//I would like to have the controller return value here! :)
} catch (Exception e) {
}
}
You have two choices:
call the controller directly instead of going through the handler mapping/servlet layer. This way you'll simply get the Meal object and you can run assertions on it
parse the response from response object. In your case Meal object was taken by Spring MVC and marshalled, probably to XML or JSON. The original object is now lost, you can only retrieve the XML/JSON string, parse it and run assertions on it (e.g. using XPath).
Both approaches have their advantages. Prefer the first one if you want to test the controller itself. The latter is more complex but lets you test marshalling.

Why doesn't Spring MVC throw an error when you POST to a controller action that accepts HTTP GET?

I just noticed a weird problem as I've been testing my application. I was accidentally POSTing to a method that accepts HTTP GET (It was a typo - I'm a little tired), but the weird thing is that Spring was executing a GET action anyway - it wasn't throwing an error.
Here is the mapping for my GET action that I was POSTing to instead:
#RequestMapping(value = "/partialMapping/{partialMappingId}/edit", method = RequestMethod.GET)
public ModelAndView edit(#PathVariable long partialMappingId) {
return new ModelAndView(view("edit"), "partialMapping",
partialMappingService.findPartialMapping(partialMappingId));
}
What I would have expected was for Spring to say, "There is no action called /partialMapping/{partialMappingId}/edit for HTTP POST".
Instead... if you use the HandlerAdapter and pass it "POST" and "/partialMapping/1/edit", it runs my index action instead ("/partialMapping"). It doesn't throw an error. Why?
Is this a bug in spring, or is this desired behaviour? It's not a big deal when it comes to production code, but it surely makes debugging problems harder.
Here is the code I am using to execute a controller action in my tests:
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response) {
try {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
// if you want to override any injected attributes do it here
final HandlerInterceptor[] interceptors =
handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
return handlerAdapter.handle(request, response, controller);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
I found this code per another answer to a question on this site.
I believe your test code mimics the dispatch step that tries to find a matching Controller method signature after the URL and HTTP method have resolved. In other words, you are not testing your controller at the right level if you want to test the HTTP message bindings. For that kind of testing you would probably want to deploy to a server (perhaps embedded Jetty inside your test) and use RestTemplate to call it. That's what I do anyway.
If you annotate with Spring MVC annotations as below
#RequestMapping(method = RequestMethod.GET it should work.

Resources