Spring ExceptionHandlerController for catching errors is not working - java

I am trying to create a controller so that when a user goes to a non-existing URL, he/she will be mapped to a custom error page "error.jsp".
Currently, my Exception Handler Controller looks as followed:
#ControllerAdvice
public class ExceptionHandlerController {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlerController.class);
#ExceptionHandler(value = {Exception.class, RuntimeException.class})
public String defaultErrorHandler(Exception e) {
logger.error("Unhandled exception: ", e);
return "error";
}
#ExceptionHandler(NoHandlerFoundException.class)
public String handle(Exception e) {
logger.error("No handler found!", e);
return "error";
}
}
However, when I run my web app and visit a nonexisting URL, I get redirected to the default browser page saying '404 this page cannot be page.
Does anyone have any thoughts or suggestions as to why this is not working?

From the javaDoc of NoHandlerFoundException
By default when the DispatcherServlet can't find a handler for a
request it sends a 404 response. However if its property
"throwExceptionIfNoHandlerFound" is set to true this exception is
raised and may be handled with a configured HandlerExceptionResolver.
To solve this, You need to make sure you did these 2 things.
Creating SimpleMappingExceptionResolver and registering as a bean
#Bean
HandlerExceptionResolver customExceptionResolver () {
SimpleMappingExceptionResolver s = new SimpleMappingExceptionResolver();
Properties p = new Properties();
//mapping spring internal error NoHandlerFoundException to a view name.
p.setProperty(NoHandlerFoundException.class.getName(), "error-page");
s.setExceptionMappings(p);
//uncomment following line if we want to send code other than default 200
//s.addStatusCode("error-page", HttpStatus.NOT_FOUND.value());
//This resolver will be processed before default ones
s.setOrder(Ordered.HIGHEST_PRECEDENCE);
return s;
}
Set setThrowExceptionIfNoHandlerFound as true in your dispatcherServlet.
p
public class AppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
....
......
#Override
protected FrameworkServlet createDispatcherServlet (WebApplicationContext wac) {
DispatcherServlet ds = new DispatcherServlet(wac);
//setting this flag to true will throw NoHandlerFoundException instead of 404 page
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
}
Refer complete example here.

Related

What's the best option/alternative to treat exceptions in spring boot?

Right now i'm using this example of exception handling:
//get an object of type curse by id
//in the service file, this findCurseById() method throws a
//CursaNotFoundException
#GetMapping("/{id}")
public ResponseEntity<curse> getCursaById (#PathVariable("id") Long id) {
curse c = curseService.findCurseById(id);
return new ResponseEntity<>(c, HttpStatus.OK);
}
//so if not found, this will return the message of the error
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(CursaNotFoundException.class)
public String noCursaFound(CursaNotFoundException ex) {
return ex.getMessage();
}
and that's my exception
public class CursaNotFoundException extends RuntimeException {
public CursaNotFoundException(String s) {
super(s);
}
}
in future I want to use Angular as front-end, so I don't really know how I should treat the exceptions in the back-end. For this example let's say, should I redirect the page to a template.html page in the noCursaFound() method, or should I return something else? A json or something? I couldn't find anything helpful. Thanks
I would suggest keeping the error handling at the REST API level and not redirecting to another HTML page on the server side. Angular client application consumes the API response and redirects to template.html if needed.
Also, it would be better if the backend returns an ApiError when an exception occurs with a message and, optionally, an error code:
public class ApiError {
private String message;
private String code;
}
and handle the exceptions in a separate class, ExceptionHandler annotated with #ControllerAdvice:
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(value = CursaNotFoundException.class)
public ResponseEntity cursaNotFoundException(CursaNotFoundException cursaNotFoundException) {
ApiError error = new ApiError();
error.setMessase(cursaNotFoundException.getMessage());
error.setCode(cursaNotFoundException.getCode());
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(value = Exception.class)
public ResponseEntity<> genericException(Exception exception) {
ApiError error = new ApiError();
error.setMessase(exception.getMessage());
error.setCode("GENERIC_ERROR");
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

SAP Hybris: How to load app context in custom error page controller?

I'm using SAP Hybris 1811 on my local machine. I've got custom error page handler in web.xml
<error-page>
<exception-type>java.lang.NullPointerException</exception-type>
<location>/errors</location>
</error-page>
And a controller for handling this error (note it's not extending AbstractPageController, for reason read further)
#Controller
public class ErrorController {
#RequestMapping(value = "/errors", method = RequestMethod.GET)
public ModelAndView handleErrors(Model model, HttpServletRequest httpRequest) {
httpRequest.getLocale();
.... some code here
}
}
I need to get correct current locale of the app for displaying correct language in error page, but it's still getting only English, although it should be other language.
I tried to load i18nService and it's locale for example like this, but it's still "en":
SpringHelper.getSpringBean(httpRequest, "i18nService", DefaultI18NService.class, true).getCurrentLocale()
I thought the problem was because of the ErrorController doesn't extend AbstractPageController, but when I tried that, none of the error methods could be reached.
In the end I was able to get the correct locale like this:
Locale loc = ((Locale)((CommerceJaloSession)this.pageContext.getSession().getAttribute("jalosession")).getAttribute("locale"));
We have used controller advice concept of Spring MVC where you should be able to receive all the information. Also this way, the error would be caught in the storefront itself and you will have good control there.
#ControllerAdvice(basePackages =
{ "com.custom", "de.hybris.platform", "org.springframework" })
public class GlobalControllerExceptionHandler
{
private static final Logger LOG = Logger.getLogger(GlobalControllerExceptionHandler.class);
private static final String FORWARD_TO_ERROR_PAGE = ControllerConstants.FORWARD_STMT
+ ControllerConstants.ControllerMappings.Error.ErrorController;
#ExceptionHandler(Exception.class)
public String handleException(final Exception exception, final HttpServletRequest request)
{
LOG.error("Exception caught :: " + exception.getMessage(), exception);
request.setAttribute(ControllerConstants.ControllerMappings.Error.ExceptionAttributeName, exception);
return FORWARD_TO_ERROR_PAGE;
}
}

Changing error logging in Spring Boot/Netty

I have a Spring Boot application that is running Netty for a REST API.
I have a WebExceptionHandler to handle exceptions that may occur, which is working. It builds appropriate responses and sends them.
The problem is, it also still logs the exception as an error. I want to change this to log it as an info instead (because we have tracking and alerts that operate differently based on info or error). It even logs things like 404's as errors.
It seems like exceptionCaught in a ChannelInboundHandler would help, but exceptionCaught is deprecated. I can't find anything that doesn't use this method, and I can't find anything referring to what (if anything) has replaced it).
I also tried using an #ControllerAdvice or #RestControllerAdvice with an #ExceptionHandler, but that is never called.
What is the correct way to intercept and handle the logging of the exception?
A minimal example implementation of our current set-up looks like this:
#RestController
class MyController {
#RequestMapping(method = [GET], value = ["endpoint"], produces = [APPLICATION_JSON_VALUE])
fun myEndpoint(): Mono<MyResponse> = createResponse()
}
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
class MyWebExceptionHandler : WebExceptionHandler {
// This does get called and sends the appropriate response back, but an error is logged somewhere outside of our code.
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> =
createErrorResponse();
}
// Tried using both, or just one at a time, no difference.
// It does get created.
#ControllerAdvice
#RestControllerAdvice
class MyExceptionHandler {
// Never called
#ExceptionHandler(Exception::class)
fun handle(ex: Exception) {
log.error(ex.message)
}
}
More information on how you are implementing your Exception Handler would be helpful.
here's a simple implementation which i follow to convert the exceptions and log them.
#ControllerAdvice
public class DefaultExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionHandler.class);
private static String ERROR = "ERROR";
#ExceptionHandler(Exception.class)
ResponseEntity<Map<String, Map<String, String>>> exception(Exception e) {
Map<String,Map<String,String>> map = new HashMap<>(1);
Map<String,String> m = new HashMap<>(1);
m.put("message",e.getMessage());
map.put(ERROR, m);
LOG.info("some error " + e);
return new ResponseEntity<>(map, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Also don't forget to create a bean or add you ExceptionHandler class to the spring config.
#Bean
public DefaultExceptionHandler defaultExceptionHandler(){
return new DefaultExceptionHandler();
}

Vaadin missing SpringSecurityContext in StreamResource callback method

I got a simple StreamResource example where SpringSecurityContext mysteriously disappears when clicking on the download anchor. Basically when download anchor is clicked createInputStream method is called for download file creation but when this method is executed SecurityContext is null. Below a simplified example that reproduces the issue.
public class HomeView extends VerticalLayout {
public HomeView() {
Anchor anchor = new Anchor();
anchor.add("DOWNLOAD");
anchor.setHref(new StreamResource("file", () -> createInputStream()));
add(anchor);
// SecurityContext returns correct value and is not null.
System.err.println(SecurityContextHolder.getContext());
System.err.println("Thread name in constructor : " + Thread.currentThread().getName());
}
private InputStream createInputStream() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
outputStream.write("text".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
// SecurityContextHolder.getContext() returns null here. Why?
System.err.println(SecurityContextHolder.getContext());
System.err.println("Thread name in createInputStream() : " + Thread.currentThread().getName());
return new ByteArrayInputStream(outputStream.toByteArray());
}}
When this code is executed I get following messages.
org.springframework.security.core.context.SecurityContextImpl#db426455: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#db426455: Principal: org.springframework.security.core.userdetails.User#983d0d8b...Rest omitted
Thread name in constructor : http-nio-8080-exec-4
org.springframework.security.core.context.SecurityContextImpl#ffffffff: Null authentication
Thread name in createInputStream() : http-nio-8080-exec-9
But I found out that one way to fix the issue is to set the SecurityContext manually in the createInputStream method. Below an example.
public class HomeView extends VerticalLayout {
SecurityContext context;
public HomeView() {
Anchor anchor = new Anchor();
anchor.add("DOWNLOAD");
anchor.setHref(new StreamResource("file", () -> createInputStream()));
add(anchor);
// Save Context to a variable
context = SecurityContextHolder.getContext();
System.err.println(SecurityContextHolder.getContext());
}
private InputStream createInputStream() {
// Set SecurityContext before accessing it.
SecurityContextHolder.setContext(context);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
outputStream.write("text".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
// SecurityContextHolder.getContext() no longer returns null.
System.err.println(SecurityContextHolder.getContext());
return new ByteArrayInputStream(outputStream.toByteArray());
}}
In the end I got this question. Why is Spring SecurityContext lost in the first example is there a better way to fix this or am I stuck with the second example?
As a side note I realised that Vaadin's Upload component is having the same issue. SecurityContext is lost in addSucceededListener callback method.
I'm using Vaadin 13.0.1 and Spring Boot 2.1.3.
The issue was with Spring Security's WebSecurity configuration below an example which is a direct copy from Vaadin's Bakery app example.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
// Vaadin Flow static resources
"/VAADIN/**", // This was the problematic spot
//Rest of configuration omitted for simplicity
}
The issue was that dynamically created files via StreamResource or Upload Component are mapped to a url that has a following prefix /VAADIN/dynamic/resource/**. In the above configuration we tell Spring Security with /VAADIN/** to ignore all requests starting with /VAADIN/. This leads Spring Security to ignore all HttpServletRequest that point to dynamically created resource since Vaadin maps them with /VAADIN/dynamic/resource/** url prefix. When Spring Security ignores a HttpServletRequest it's SpringSecurityContext will be empty. See WebSecurity.ignoring() documentation.
The issue can be fixed by renaming /VAADIN/** to /VAADIN/static/**. This will prevent Spring Security from ignoring requests to dynamic resources and thus SpringSecurityContext will be available in StreamResource and Upload callback methods. Below a working example.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
// Vaadin Flow static resources
"/VAADIN/static/**",
//Rest of configuration omitted for simplicity
}

Minimum Spring Boot Code to log 404s

In my Spring Boot Application, I'm currently leveraging the resources/public/error/404.html custom error page to show (automagically) this 404 page errors on invalid URLS.
Is there an easy way to retain this auto functionality, and add a simple log message (with the invalid URL) for every such 404 ?
Ideally with as little code as possible I would want some like :
//Some code
LOGGER.warn("Invalid URL " + request.url);
//Some more code
You need to define a custom ErrorViewResolver:
#Component
public class MyErrorViewResolver implements ErrorViewResolver {
#Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
if (HttpStatus.NOT_FOUND == status) {
LoggerFactory.getLogger(this.getClass()).error("error 404 for url " + model.get("path"));
return new ModelAndView("error404", model);
}
else {
return new ModelAndView("error", model);
}
}
}
This MyErrorViewResolver will be automatically called in the BasicErrorController class.
For a 404 error, the view "error404" will be displayed.
For the other errors, the view "error" will be displayed.
Views must be in the "templates" folder (resources/templates/error404.html).
Add NotFoundException handler.
public class BaseController {
// Logger declaration
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResponse handleNotFoundError(HttpServletRequest req, NotFoundException exception) {
List<Error> errors = Lists.newArrayList();
errors.add(new Error(String.valueOf(exception.getCode()), exception.getMessage()));
log.error(exception);
return new ErrorResponse(errors);
}
}

Categories

Resources