I have a simple controller:
#RestController
public class SimpleController() {
public String get() {
if (System.nanoTime() % 2 == 0)
throw new IllegalArgumentException("oops");
return "ok"
}
}
Controller can throw simple exception, so i wrote controller advisor for it handling:
#ExceptionHandler(IllegalArgumentException.class)
#ResponseBody
public ResponseEntity<String> rejection(Rejection ex) {
return new ResponseEntity<>("bad", HttpStatus.CONFLICT);
}
Now i want to make get method async. But i don't know the best way for handling exception.
I tried:
public CompletableFuture<String> get() {
CompletableFuture.supplyAsync(
() -> {
if (System.nanoTime() % 2 == 0)
throw new IllegalArgumentException("oops");
return "ok";
}).exceptionally(thr -> {
//what should i do?
if (thr instanceof IllegalArgumentException)
throw ((IllegalArgumentException) t);
if (thr.getCause() instanceof IllegalArgumentException)
throw ((IllegalArgumentException) t.getCause());
return null;
}
}
But controller advisor still does not catch the exception.
Also i tried to return ResponseEntity("message", HttpStatuc.CONFLICT); in exceptionally block.
But in tests i still have MvcResult.getResponse().getStatus() == 200.
Any other idea?
Maybe it's a wrong way at all?
UPDATE
I don't know why, but it don't catch exceptions:
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
#Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
System.out.println();
}
};
And even if it work, how to set http status to response?
Try to return a ResponseEntity instance:
return new ResponseEntity<>("bad", HttpStatus.CONFLICT);
out of exceptionally method.
Sorry guys, the problem not in code.
I just wrote bad tests.
Here the link for explaining:
https://sdqali.in/blog/2015/11/24/testing-async-responses-using-mockmvc/
Just use:
MvcResult result = mockMvc.perform(...).andReturn();
result = mockMvc.perform(asyncDispatch(result)).andReturn();
before you check the status or result response.
Related
I have implemented the error handling in a filter that looks like this:
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
URI url = request.url();
HttpMethod method = request.method();
return next.exchange(request).flatMap(response -> {
if (response.statusCode().isError()) {
return response.bodyToMono(String.class).flatMap(responseBody -> {
Optional<Exception> exception = errorResponseHandler.handleError(method, response.statusCode(), url, responseBody);
if (exception.isPresent()) {
return Mono.error(exception.get());
} else {
// fallback
return Mono.error(new UnsupportedOperationException("The fallback functionality is still missing"));
}
});
} else {
return Mono.just(response);
}
});
}
This should work fine in the case where the response comes with a body as then the response.bodyToMono(String.class).flatMap(...) is executed. However when the body is empty nothing happens, but what I want is to also deal with the error. It is my understanding that I would do this something like this;
response.bodyToMono(String.class).flatMap(...)
.switchIfEmpty(Mono.error(new UnsupportedOperationException("The body was empty")));
This does not work as the expected type to be returned is Mono instead of Mono.
How can I achieve the handling of errors with and without response body, which is needed to construct to correct exception?
This question brought me onto the right track:
The switchIfEmpty invocation has to come before the flatMap. As there is no body, flatMap is not executed and neither is anything after, therefore the switchIfEmpty has to come first:
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
URI url = request.url();
HttpMethod method = request.method();
return next.exchange(request).flatMap(response -> {
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.switchIfEmpty(Mono.error(new UnsupportedOperationException("The body was empty")));
.flatMap(responseBody -> {
Optional<Exception> exception = errorResponseHandler.handleError(method, response.statusCode(), url, responseBody);
if (exception.isPresent()) {
return Mono.error(exception.get());
} else {
// fallback
return Mono.error(new UnsupportedOperationException("The fallback functionality is still missing"));
}
});
} else {
return Mono.just(response);
}
});
}
So i have following controller
Controller("/test")
public class MyController {
#Get("/anno")
#MyAnnotation(value="my annotation value") // <---- i want this value
public Object testAnnotation(HttpRequest<?> request){
return "Hello world";
}
}
I'm trying to implement custom filter on micronauts http server.
#Filter("/**")
public class MyFilter implements HttpServerFilter {
#Override
public Publisher<? extends HttpResponse<?>> doFilter(HttpRequest<?> request, FilterChain chain) {
// HERE
// how to get the MyAnnotation value from the handling method for the request ?
return chain.proceed(request);
}
}
How to get my custom annotation inside the filter ?
Thank you.
You would need AOP, Micronauts supports it. But you get it from a MethodInterceptor, not HttpFilter. Here is the code I modified based on what I wrote for tracing, in Kotlin, it would be very similar in Java:
#Singleton
#InterceptorBean(MyAnnotation::class)
class MyAnnotationInterceptor : MethodInterceptor<Any, Any> {
override fun intercept(context: MethodInvocationContext<Any, Any>): Any? {
val myAnnotation: AnnotationValue<MyAnnotation> = context.getAnnotation(MyAnnotation::class.java)!!
val value = myAnnotation.get("value", String::class.java).orElse(null)
val className = context.declaringType.simpleName
val methodName = context.methodName
val operationName = "$className.$methodName"
val interceptedMethod = InterceptedMethod.of(context)
try {
when (interceptedMethod.resultType()) {
SYNCHRONOUS -> {
try {
return context.proceed()
} catch (e: Exception) {
throw e
} finally {
}
}
COMPLETION_STAGE -> {
try {
var completionStage = interceptedMethod.interceptResultAsCompletionStage()
return interceptedMethod.handleResult(completionStage)
} catch (e: Exception) {
logError(newSpan, e)
throw e
}
}
else -> return interceptedMethod.unsupported()
}
} catch (e: Exception) {
return interceptedMethod.handleException<RuntimeException>(e)
}
}
}
val value = myAnnotation.get("value", String::class.java).orElse(null) is where you get the value.
We use the above code to extract the tracing sampling rate and it works well for us. Note that your annotation will need "around" AOP annotation:
#Retention(RUNTIME)
#Target(CLASS, FILE, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
#Around
annotation class MyAnnotation(
val value: String = "",
)
I use Monos with ResponseEntitys in my Webflux controllers in order to manipulate headers and other response info. For example:
#GetMapping("/{userId}")
fun getOneUser(#PathVariable userId: UserId): Mono<ResponseEntity<UserDto>> {
return repository.findById(userId)
.map(User::asDto)
.map { ResponseEntity.ok(it) }
.defaultIfEmpty(ResponseEntity.notFound().build())
}
#GetMapping
fun getAllUsers(): Flux<UserDto> {
return repository.findAllActive().map(User::asDto)
}
both works fine but there are cases where it is required to have ResponseEntity in conjunction with Flux as well. What should the response type be? Is it correct to use ResponseEntity<Flux<T>>?
For example:
#GetMapping("/{userId}/options")
fun getAllUserOptions(#PathVariable userId: UserId): ??? {
return repository.findById(userId)
.flatMapIterable{ it.options }
.map { OptionDto.from(it) }
// if findById -> empty Mono then:
// return ResponseEntity.notFound().build() ?
// else:
// return the result of `.map { OptionDto.from(it) }` ?
}
The behaviour I'd like to achieve here is that getAllUserOptions returns 404 if repository.findById(userId) is an empty Mono, otherwise return user.options as Flux.
Update:
repository here is ReactiveCrudRepository
Use switchIfEmpty to throw an exception in case the user doesn't exist:
return repository
.findById(userId)
.switchIfEmpty(Mono.error(NotFoundException("User not found")))
.flatMapIterable{ it.options }
.map { OptionDto.from(it) }
Then with an exception handler translate it to a 404 response.
You can use by returning Mono with ResponseEntity
like this
public Mono<ResponseEntity<?>> oneRawImage(
#PathVariable String filename) {
// tag::try-catch[]
return imageService.findOneImage(filename)
.map(resource -> {
try {
return ResponseEntity.ok()
.contentLength(resource.contentLength())
.body(new InputStreamResource(
resource.getInputStream()));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body("Couldn't find " + filename +
" => " + e.getMessage());
}
});
}
I have also example like this
public ResponseEntity<Mono<?>> newLive(#Valid #RequestBody Life life) {
Mono<Life> savedLive = liveRepository.save(life);
if (savedLive != null) {
return new ResponseEntity<>(savedLive, HttpStatus.CREATED);
}
return new ResponseEntity<>(Mono.just(new Life()), HttpStatus.I_AM_A_TEAPOT);
}
I dislike functional programming in the REST controllers.
Here is an example ReactiveController .
works for me, let me know if you have a trouble
#PostMapping(value = "/bizagi/sendmsg")
public Mono<ResponseEntity<?>> sendMessageToQueue(#RequestBody BizagiPost bizagiPost) {
Mono<BodyReturn> retorno = useCase.saveMsg(bizagiPost);
Map<String, Object> response = new HashMap<>();
return retorno.map(t ->
{
if (t.getStatusCode().equals("200")) {
response.put("message", t.getReazon());
return new ResponseEntity(t, HttpStatus.OK);
} else {
response.put("message", t.getReazon());
return new ResponseEntity(t, HttpStatus.BAD_REQUEST);
}
});
}
I am trying to handle Exception in spring boot Application using #ControllerAdvice. I don't want separate methods for each types of exception. I want to handle all types of exception using only one method with main class as #ExceptionHandler(Exception.class)
I tried like below it's handling exception properly but problem is that I also want to set different type of status code for different type of exception.
here I am getting 500 for every type of exception.
can any one tell me how to set different status code for different type of exception?
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptionMethod(Exception ex,WebRequest requset) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
exceptionMessageObj.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
exceptionMessageObj.setMessage(ex.getLocalizedMessage());
exceptionMessageObj.setError(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
return new ResponseEntity<>(exceptionMessageObj, new HttpHeaders(),HttpStatus.INTERNAL_SERVER_ERROR);
}
}
There's a different Spring approach that you can also do. Note that it doesn't work with native Java exceptions (as you need to add an annotation to the Exception class definition), which may or may not be acceptable to you.
Define custom exceptions for the status codes you want to surface (or reuse existing ones from your current business logic).
Add #ResponseStatus to the top of each of these exceptions.
In your controllers, only throw these exceptions.
This way, you don't need to do any type checking of the exceptions. You don't even need to define your own #ControllerAdvice. Spring will handle surfacing the correct HTTP status code. If you do choose to still implement your #ControllerAdvice with this method, you can use the annotation to grab the correct status code with:
import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation
HttpStatus resolveAnnotatedResponseStatus(Exception exception) {
ResponseStatus annotation = findMergedAnnotation(exception.getClass(), ResponseStatus.class);
if (annotation != null) {
return annotation.value();
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}
(Annotation resolution method originally posted here)
You may check what instance is your exception of and return accordingly:
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity < Object > handleAllExceptionMethod(Exception ex, WebRequest requset) {
if (ex instanceof MyCustomException1) {
// Return error code 500
} else {
// return error code 404
}
}
You can set different status code as per type of Exception.
HttpStatus statuscode = HttpStatus.INTERNAL_SERVER_ERROR;
if(ex instanceof ExceptionClass1){
statuscode = HttpStatus.BAD_REQUEST;
}else if(ex instanceof ExceptionClass2){
statuscode = HttpStatus.FORBIDDEN;
}
I'd question your needs first. Why do you want to have all exception handling in one place?
It's in the contrary with the other need - to have response code configurable.
you have two options:
create a nasty if/else switch containing your exception instanceofs
don't handle everything in one method
I prefer the latter one and if u're afraid that your code will be duplicated - please take a look below:
#RestControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(YourException1.class)
public ResponseEntity<ExceptionMessage> handleYourException1(YourException1 ex, WebRequest requset) {
return commonHandler(HttpStatus.INTERNAL_SERVER_ERROR, ex, requset);
}
public ResponseEntity<ExceptionMessage> commonHandler(HttpStatus status, Exception ex, WebRequest requset) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
exceptionMessageObj.setStatus(status.value());
exceptionMessageObj.setMessage(ex.getLocalizedMessage());
exceptionMessageObj.setError(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
return new ResponseEntity<>(exceptionMessageObj, status);
}
}
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleAllExceptions(Exception e) {
log.error("IOException: " + e.getMessage());
final Map<String, String> message = new HashMap<>();
message.put("message", "Something went wrong...");
// My custom IOException handler :)
if (e instanceof IOException) {
return ResponseEntity.unprocessableEntity().body(message);
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
You can use switch statement and create multiple cases:
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> exceptionHandler(Exception ex) {
ErrorResponse errorResponse = new ErrorResponse(new DescriptionInErrorResponse());
errorResponse.getError().setText(ex.getMessage());
HttpStatus httpStatus = HttpStatus.UNPROCESSABLE_ENTITY;
if (ex instanceof CustomException) {
switch (((CustomException) ex).getCode()) {
case BAD_REQUEST:
errorResponse.getError().setCode(ErrorCode.BAD_REQUEST.getName());
httpStatus = HttpStatus.BAD_REQUEST;
break;
case NOT_FOUND:
errorResponse.getError().setCode(ErrorCode.NOT_FOUND.getName());
httpStatus = HttpStatus.NOT_FOUND;
break;
case METHOD_ARGUMENT_NOT_VALID:
errorResponse.getError().setCode(ErrorCode.METHOD_ARGUMENT_NOT_VALID.getName());
httpStatus = HttpStatus.BAD_REQUEST;
break;
default:
errorResponse.getError().setCode(ErrorCode.UNKNOWN.getName());
httpStatus = HttpStatus.UNPROCESSABLE_ENTITY;
break;
}
} else if (ex instanceof ConstraintViolationException
|| ex instanceof javax.validation.ConstraintViolationException) {
errorResponse.getError().setCode(ErrorCode.BAD_REQUEST.getName());
httpStatus = HttpStatus.BAD_REQUEST;
} else if (ex instanceof MethodArgumentNotValidException) {
errorResponse.getError().setCode(ErrorCode.METHOD_ARGUMENT_NOT_VALID.getName());
httpStatus = HttpStatus.BAD_REQUEST;
} else if (ex instanceof DataIntegrityViolationException) {
errorResponse.getError().setCode(ErrorCode.INTERNAL_SERVER_ERROR.getName());
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
} else if (ex instanceof HttpMessageNotReadableException) {
errorResponse.getError().setCode(ErrorCode.BAD_REQUEST.getName());
httpStatus = HttpStatus.BAD_REQUEST;
} else if (ex instanceof ServletRequestBindingException){
errorResponse.getError().setCode(ErrorCode.BAD_REQUEST.getName());
httpStatus = HttpStatus.BAD_REQUEST;
}
else {
log.error(ex.getMessage(), ex);
errorResponse.getError().setCode(ErrorCode.INTERNAL_SERVER_ERROR.getName());
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
}
return ResponseEntity.status(httpStatus).body(errorResponse);
}
I am new to Spring Boot, and I am trying to test a connection using HTTP OPTIONS.
My design is that I have a Service class that contains the logics for the testing. I also have an API Controller class that implements the method from Service.
My currently understanding is that the controller can be used to respond back different HTTP statuses using exceptions.
This is the method I wrote inside the controller for this purpose:
#PostMapping(path = "/test")
public ResponseEntity<Void> testConnection(#RequestBody URL url) {
try {
ControllerService.testConnection(url);
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
} catch (CredentialsException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
} catch (URLException | URISyntaxException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
} catch (UnknownException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
The way exceptions are triggered and the method testConnection() are inside the service class:
public static void testConnection(URL url)
throws URISyntaxException, CredentialsException, URLException, UnknownException {
String authHeaderValue = "Basic " + Base64.getEncoder().encodeToString(("user" + ':' + "password").getBytes());
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Authorization", authHeaderValue);
RestTemplate rest = new RestTemplate();
final ResponseEntity<Object> optionsResponse = rest.exchange(url.toURI(), HttpMethod.OPTIONS, new HttpEntity<>(requestHeaders), Object.class);
int code = optionsResponse.getStatusCodeValue();
if (code == 403) {
throw new InvalidCredentialsException();
} else if (code == 404) {
throw new InvalidURLException();
} else if (code == 500) {
throw new UnknownErrorException();
} else if (code == 200){
String message = "Test connection successful";
LOGGER.info(message);
}
}
I have created those custom exception classes.
Is this the proper way to trigger the right HTTP response inside the controller method or does Spring Boot has some other design? If so, is my list of exceptions comprehensive enough or do I need to add more to the testConnection() method in the service class?
You can write ExceptionHandler for each of the Exception type, so you don't have to repeat the code or use try/ catch block at all. Just let your testConnection and other methods to throw the exception.
import org.springframework.web.bind.annotation.ExceptionHandler;
#ExceptionHandler(CredentialsException.class)
public void credentialsExceptionHandler(CredentialsException e, HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.FORBIDDEN.value(), e.getMessage());
}
There are different ways to define and use the ExceptionHandler method. But conceptually same.