Spring Webflux WebExceptionHandler not being Triggered by OptimisticLockingFailure - spring-boot

I have a GlobalExceptionHandler to catch all exceptions thrown and return a correct return code.
Everything works except when an OptimisticLockingFailureException happens and I have no clue why this is.
GlobalExceptionHandler:
#Component
#Order(-2)
class GlobalExceptionHandler : WebExceptionHandler {
override fun handle(exchange: ServerWebExchange, throwable: Throwable): Mono<Void> = handleException(throwable)
.flatMap { it.writeTo(exchange, HandlerStrategiesResponseContext(HandlerStrategies.withDefaults())) }
.flatMap { Mono.empty<Void>() }
private fun handleException(ex: Throwable): Mono<ServerResponse> = when (ex) {
is EntityNotFoundException -> notFound(ex.message)
is InvalidRequestException,
is ReferenceKeyNotKnown,
is InvalidContractTypeException -> badRequest(ex.message)
is DuplicateKeyException,
is TransactionException,
is OptimisticLockingFailureException -> conflict(ex.message)
else -> errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex.message)
}
private class HandlerStrategiesResponseContext(val strategies: HandlerStrategies) : ServerResponse.Context {
override fun viewResolvers(): MutableList<ViewResolver> = strategies.viewResolvers()
override fun messageWriters(): MutableList<HttpMessageWriter<*>> = strategies.messageWriters()
}
}
the notFound, badRequest and errorResponse functions return a Mono with the correct statuscode and message.
Anyone that can help me out and point me in the right direction on why this isn't being triggered by the OptimistickLockingException?
Thx!

So the reason the exceptionhandler wasn't triggered was that we still had a .onErrorContinue step in our handler which logged the exception and nothing else. After moving the logging into the GlobalExceptionHandler and removing the onErrorContinue the GlobalExceptionHandler was working as expected

Related

How to globally handle errors thrown from WebFilter in Spring WebFlux?

How to intercept and handle errors globally in WebFlux when they are being thrown from WebFilter chain?
It is clear how to handle errors thrown from controllers: #ControllerAdvice and #ExceptionHandler help great.
This approach does not work when an exception is thrown from WebFilter components.
In the following configuration GET /first and GET /second responses intentionally induce exceptions thrown. Although #ExceptionHandler methods handleFirst, handleSecond are similar, the handleSecond is never called. I suppose that is because MyWebFilter does not let a ServerWebExchange go to the stage where GlobalErrorHandlers methods could be applied.
Response for GET /first:
HTTP 500 "hello first" // expected
HTTP 500 "hello first" // actual
Response for GET /second:
HTTP 404 "hello second" // expected
HTTP 500 {"path": "/second", "status": 500, "error": "Internal Server Error" } // actual
#RestController
class MyController {
#GetMapping("/first")
String first(){
throw new FirstException("hello first");
}
}
#Component
class MyWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange swe, WebFilterChain wfc) {
var path = swe.getRequest().getURI().getPath();
if (path.contains("second")){
throw new SecondException("hello second")
}
}
}
#ControllerAdvice
class GlobalErrorHandlers {
#ExceptionHandler(FirstException::class)
ResponseEntity<String> handleFirst(FirstException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.message)
}
#ExceptionHandler(SecondException::class)
ResponseEntity<String> handleSecond(SecondException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.message)
}
}
Three steps are required to get full control over all exceptions thrown from application endpoints handling code:
Implement org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler
Annotate with #ControllerAdvice (or just #Component)
Set #Priority less than 1 to let the custom handler run before the default one (WebFluxResponseStatusExceptionHandler)
The tricky part is where we get an instance implementing
ServerResponse.Context for passing to
ServerResponse.writeTo(exchange, context). I did not find the final
answer, and comments are welcome. In the internal Spring code they always create a new instance of context for each writeTo invocation,
although in all cases (I've manged to find) the context instance is immutable.
That is why I ended up using the same ResponseContextInstance for all responses.
At the moment no problems detected with this approach.
#ControllerAdvice
#Priority(0) /* should go before WebFluxResponseStatusExceptionHandler */
class CustomWebExceptionHandler : ErrorWebExceptionHandler {
private val log = logger(CustomWebExceptionHandler::class)
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
log.error("handled ${ex.javaClass.simpleName}", ex)
val sr = when (ex) {
is FirstException -> handleFirst(ex)
is SecondException -> handleSecond(ex)
else -> defaultException(ex)
}
return sr.flatMap { it.writeTo(exchange, ResponseContextInstance) }.then()
}
private fun handleFirst(ex: FirstException): Mono<ServerResponse> {
return ServerResponse
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.bodyValue("first")
}
private fun handleSecond(ex: SecondException): Mono<ServerResponse> {
return ServerResponse.status(HttpStatus.BAD_REQUEST).bodyValue("second")
}
private object ResponseContextInstance : ServerResponse.Context {
val strategies: HandlerStrategies = HandlerStrategies.withDefaults()
override fun messageWriters(): List<HttpMessageWriter<*>> {
return strategies.messageWriters()
}
override fun viewResolvers(): List<ViewResolver> {
return strategies.viewResolvers()
}
}
}

Constructor parameter is null when referred to in an overrided method

My code is written in Kotlin. I have a config class defined in a file along 2 more classes as below:
#Configuration
class MultipartConfig(private val multipartProperties: MultipartProperties) {
#Bean
fun multipartResolver(): StandardServletMultipartResolver {
val multipartResolver = MultipartResolver(multipartProperties)
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily)
return multipartResolver
}
}
class MultipartResolver(private val multipartProperties: MultipartProperties) :
StandardServletMultipartResolver() {
override fun resolveMultipart(request: HttpServletRequest): MultipartHttpServletRequest {
return MultipartHttpServletRequest(multipartProperties, request)
}
}
class MultipartHttpServletRequest(
private val multipartProperties: MultipartProperties, request: HttpServletRequest
) : StandardMultipartHttpServletRequest(request, multipartProperties.isResolveLazily) {
override fun handleParseFailure(ex: Throwable) {
val msg = ex.message
if (msg != null && msg.contains("size") && msg.contains("exceed")) {
throw MaxUploadSizeExceededException(multipartProperties.maxFileSize.toMegabytes(), ex)
}
throw MultipartException("Failed to parse multipart servlet request", ex)
}
}
When I debug this code, in the class MultipartHttpServletRequest, constructor property multipartProperties is NOT null but the same property in the throw MaxUploadSizeExceededException(multipartProperties.maxFileSize.toMegabytes(), ex) is ALWAYS null. I cannot understand why this is happening.
Could someone please explain why this is happening?
I'm just answering my own question for clarity. I figured out the issue. It was related to the constructor of that class StandardMultipartHttpServletRequest. Below is the code of the constrcutor.
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {
super(request);
if (!lazyParsing) {
parseRequest(request);
}
}
Now, inside of parseRequest there is a catch block (for brevity I'm not posting the whole code of the method)
catch (Throwable ex) {
handleParseFailure(ex);
}
When the constructor of the super class is throwing exception the child's constructor will not get a chance to initialize.

anyone can explain how does this code send message to specified user

#Bean
public WebSocketHandler webSocketHandler() {
TopicProcessor<String> messageProcessor = this.messageProcessor();
Flux<String> messages = messageProcessor.replay(0).autoConnect();
Flux<String> outputMessages = Flux.from(messages);
return (session) -> {
System.out.println(session);
session.receive().map(WebSocketMessage::getPayloadAsText).subscribe(messageProcessor::onNext, (e) -> {
e.printStackTrace();
});
return session.getHandshakeInfo().getPrincipal().flatMap((p) -> {
session.getAttributes().put("username", p.getName());
return session.send(outputMessages.filter((payload) -> this.filterUser(session, payload))
.map((payload) -> this.generateMessage(session, payload)));
}).switchIfEmpty(Mono.defer(() -> {
return Mono.error(new BadCredentialsException("Bad Credentials."));
})).then();
};
}
I am trying to build a online chating system with webflux,and have found a example through github.as a beginner in reactor development,I am confused about how does this code send a message to single user.
this is the way i think of in springmvc
put all the active websocketsession into map
check every message if the field username in message equals the username stored in session,use this session send msg
private static Map clients = new ConcurrentHashMap();
public void sendMessageTo(String message, String ToUserName) throws IOException {
for (WebSocket item : clients.values()) {
if (item.username.equals(ToUserName) ) {
item.session.sendText(message);
break;
}
}
}
can you explain how does the code in the webflux code above works?
i know all the messages are stored in the outputMessages and subcribed.
when a new message be emitted,how does it find the correct session ?
My guess is that the WebSocketHandler is an interface containing only one method handle WebSocketHandler
which in turn i believe makes it a FunctionalInterface that can be used as a lambda.
(session) -> { ... }
So when a session is established with a client, and the client sends a websocket event. The server will look for the WebSocketHandler and populate it with the session from the client that sent the event.
If you find this confusing you can just implement the interface.
class ExampleHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession session) {
Mono<Void> input = session.receive()
.doOnNext(message -> {
// ...
})
.concatMap(message -> {
// ...
})
.then();
Flux<String> source = ... ;
Mono<Void> output = session.send(source.map(session::textMessage));
return Mono.zip(input, output).then();
}
}
#Bean
public WebSocketHandler webSocketHandler() {
return new ExampleHandler();
}

Returning proper value from #AfterThrowing

I am new to String, SpringBoot.
Can we suppress thrown exception in a method annotated with #AfterThrowing?
I mean when an exception is thrown, it will suppress that and will return a default value on behalf of the invoking method?
Say, I have a controller -
#RestController
public class MyRestController implements IRestController{
#Override
#GetMapping("hello-throw")
public String mustThrowException(#RequestParam(value = "name")final String name) throws RuntimeException {
System.out.println("---> mustThrowException");
if("Bakasur".equals(name)) {
throw new RuntimeException("You are not welcome here!");
}
return name + " : Welcome to the club!!!";
}
}
I have created a #AspectJ, as follows -
#Aspect
#Component
public class MyAspect {
#Pointcut("execution(* com.crsardar.handson.java.springboot.controller.IRestController.*(..))")
public void executionPointcut(){
}
#AfterThrowing(pointcut="executionPointcut()",
throwing="th")
public String afterThrowing(JoinPoint joinPoint, Throwable th){
System.out.println("\n\n\tMyAspect : afterThrowing \n\n");
return "Exception handeled on behalf of you!";
}
}
If I run this & hit a ULR like - http://localhost:8080/hello-throw?name=Bakasur
I will get RuntimeException, but, I want to return a default message like - Exception handeled on behalf of you!, can we do it using #AfterThrowing?
I know it can be done using #Around, but around will be called on every hit of the url, that I do not want
What you want to do is Exception Handling on the controller. You don't need to build it yourself, Spring already supports you with some annotations like #ExceptionHandler and #ControllerAdvice. Best would be to follow this example: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc#using-controlleradvice-classes
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
#ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
#ExceptionHandler(value = Exception.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with #ResponseStatus rethrow it and let
// the framework handle it - like the OrderNotFoundException example
// at the start of this post.
// AnnotationUtils is a Spring Framework utility class.
if (AnnotationUtils.findAnnotation
(e.getClass(), ResponseStatus.class) != null)
throw e;
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
You should use the fully qualified name of the class before method's name when you're referring to a pointcut. So, you should change #AfterThrowing something like this.
#AfterThrowing(pointcut="packageName.MyAspect.executionPointcut()",
throwing="th")
Please note that packageName is full package name of MyAspect.

Spring 5 Reactive - WebExceptionHandler is not getting called

I have tried all 3 solutions suggested in what is the right way to handle errors in spring-webflux, but WebExceptionHandler is not getting called. I am using Spring Boot 2.0.0.M7. Github repo here
#Configuration
class RoutesConfiguration {
#Autowired
private lateinit var testService: TestService
#Autowired
private lateinit var globalErrorHandler: GlobalErrorHandler
#Bean
fun routerFunction():
RouterFunction<ServerResponse> = router {
("/test").nest {
GET("/") {
ServerResponse.ok().body(testService.test())
}
}
}
}
#Component
class GlobalErrorHandler() : WebExceptionHandler {
companion object {
private val log = LoggerFactory.getLogger(GlobalErrorHandler::class.java)
}
override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
log.info("inside handle")
/* Handle different exceptions here */
when(ex!!) {
is ClientException -> exchange!!.response.statusCode = HttpStatus.BAD_REQUEST
is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
}
return Mono.empty()
}
}
UPDATE:
When I change Spring Boot version to 2.0.0.M2, the WebExceptionHandler is getting called. Do I need to do something for 2.0.0.M7?
SOLUTION:
As per Brian's suggestion, it worked as
#Bean
#Order(-2)
fun globalErrorHandler() = GlobalErrorHandler()
You can provide your own WebExceptionHandler, but you have to order it relatively to others, otherwise they might handle the error before yours get a chance to try.
the DefaultErrorWebExceptionHandler provided by Spring Boot for error handling (see reference documentation) is ordered at -1
the ResponseStatusExceptionHandler provided by Spring Framework is ordered at 0
So you can add #Order(-2) on your error handling component, to order it before the existing ones.
An error response should have standard payload info. This can be done by extending AbstractErrorWebExceptionHandler
ErrorResponse: Data Class
data class ErrorResponse(
val timestamp: String,
val path: String,
val status: Int,
val error: String,
val message: String
)
ServerResponseBuilder: 2 different methods to build an error response
default: handle standard errors
webClient: handle webClient exceptions (WebClientResponseException), not for this case
class ServerResponseBuilder(
private val request: ServerRequest,
private val status: HttpStatus) {
fun default(): Mono<ServerResponse> =
ServerResponse
.status(status)
.body(BodyInserters.fromObject(ErrorResponse(
Date().format(),
request.path(),
status.value(),
status.name,
status.reasonPhrase)))
fun webClient(e: WebClientResponseException): Mono<ServerResponse> =
ServerResponse
.status(status)
.body(BodyInserters.fromObject(ErrorResponse(
Date().format(),
request.path(),
e.statusCode.value(),
e.message.toString(),
e.responseBodyAsString)))
}
GlobalErrorHandlerConfiguration: Error handler
#Configuration
#Order(-2)
class GlobalErrorHandlerConfiguration #Autowired constructor(
errorAttributes: ErrorAttributes,
resourceProperties: ResourceProperties,
applicationContext: ApplicationContext,
viewResolversProvider: ObjectProvider<List<ViewResolver>>,
serverCodecConfigurer: ServerCodecConfigurer) :
AbstractErrorWebExceptionHandler(
errorAttributes,
resourceProperties,
applicationContext
) {
init {
setViewResolvers(viewResolversProvider.getIfAvailable { emptyList() })
setMessageWriters(serverCodecConfigurer.writers)
setMessageReaders(serverCodecConfigurer.readers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> =
RouterFunctions.route(RequestPredicates.all(), HandlerFunction<ServerResponse> { response(it, errorAttributes) })
private fun response(request: ServerRequest, errorAttributes: ErrorAttributes?): Mono<ServerResponse> =
ServerResponseBuilder(request, status(request, errorAttributes)).default()
private fun status(request: ServerRequest, errorAttributes: ErrorAttributes?) =
HttpStatus.valueOf(errorAttributesMap(request, errorAttributes)["status"] as Int)
private fun errorAttributesMap(request: ServerRequest, errorAttributes: ErrorAttributes?) =
errorAttributes!!.getErrorAttributes(request, false)
}

Resources