Spring Boot Web app - Redirect controller - spring

I need to have a controller (or other component) that handles all 404 errors and smartly redirects to the correct page (this is based on a table src/ target). I have found a few questions about handling thrown exceptions FROM controllers so I have made the following:
#ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Throwable.class)
#ResponseBody
ResponseEntity<String> handleControllerException(HttpServletRequest req, Throwable ex) {
String slug = req.getRequestURI(); // this is the URL that was not found
URI location=null;
try {
// lookup based on slug ...
location = new URI("http://cnn.com"); // let's say I want to redirect to cnn
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(location);
return new ResponseEntity<String>("Page has permanently moved", responseHeaders, HttpStatus.PERMANENT_REDIRECT);
}
}
I have not made any other configuration changes
The two issues with this are:
It only catches exceptions thrown by my other controllers and not 404 errors
It catches ALL types of exceptions and not just 404
Any ideas on how to implement a kind of "catch-all"?

This is probably not the best Spring boot solution, but maybe a similar idea can be extrapolated from it and applied to your application. I had a similar problem when working with Spring 3.x, and here's the solution I came up with.
My scenario was that I had multiple servlets running under the same application context. One of those handled a web interface, and another one handled API calls. I wanted the servlet handling the API calls to handle 404s differently than the UI servlet.
I created a new class which extended DispatcherServlet, and overridden the noHandlerFound method. I wired my new class in my web.xml in the <servlet>tags instead of the default DispatcherServlet class I had there before.
<servlet>
<servlet-name>api-servlet</servlet-name>
<servlet-class>path.to.your.new.DispatcherServletClass</servlet-class>
...
</servlet>

Related

Spring #ControllerAdvice/ #ExceptionHandler not working

My Spring Boot Service will do a job and exit after success with 0 (there is no restcontroller), but i want it aslo to exit on every exception so i added #ControllerAdvice on a class and put this method:
#ControllerAdvice
#RequiredArgsConstructor
#Slf4j
public class ImportInekData {
final InekService inekService;
final ImportDataService dataService;
public void doTheJob(){
log.info("Fetching new list from Inek.");
UpdatedList updatedList = inekService.getUpdatedList();
List<Standort> toBeUpdated = updatedList.getToBeUpdated();
List<String> toBeDeleted = updatedList.getToBeDeleted();
log.info("List fetched with " + toBeUpdated.size() + " valid entries to be updated and " + toBeDeleted.size() + " entries marked for deletion. ");
log.info("Pushing to DB...");
dataService.importAll(toBeUpdated);
}
#EventListener
public void onStart(ContextStartedEvent start){
log.info("Application started.");
doTheJob();
log.info("Import finished.");
SpringApplication.exit(start.getApplicationContext(), () -> 0);
}
#ExceptionHandler(value = Exception.class)
public String outOnException(Exception e){
log.error("Exception occurred see logs. Stopping..");
SpringApplication.exit(context, () -> -1);
return "dying";
}
}
All is working fine but when i throw an IllegalArgumentException the #ExceptionHandler method is not called. First i had a void method with no parameter and then i began trying with String return and at least one parameter - that is not needed.
How get this working? Is there a better way for my case to react on every exception?
Controller Advices in spring is a mechanism intended to properly handle the Exceptions at the level of spring MVC.
Spring MVC in a nutshell is a web framework, and as such, it assumes that you have some kind of web endpoint that is called by the end user or maybe frontend. This endpoint is an "entry-point" to your backend code that can have services, query the database, and so forth. If during this backend flow the exception is thrown in general you don't want that the web endpoint will return 500 internal server error, so spring provides tooling for convenient mapping of these exceptions: translating them to json with a "good-looking" message, with correct HTTP code, and so forth.
If you don't have any controllers, then the whole concept of controller advices is not applicable in your flow, so there is no point in using it...
Now the real question is what exactly do you want to achieve with this exception handling?
If Application context cannot start usually spring boot application will be closed gracefully...
If you want to close the application programmatically, make sure you've read this thread

Spring Boot will serve static resources or my 404 redirection Handler, but not both

I have a Spring Boot app bundled with React, that I am wanting to re-direct all requests that aren't found as static resources back to the index (so React can handle the single-page routing).
Currently, I can serve the static resources or do the 404 redirection, but not both.
Does anyone see any issues with my config?
Static resources are in my Jar at:
classes/static/index.html (etc)
Spring Boot application.yaml config
spring:
main:
allow-bean-definition-overriding: true
mvc:
throw-exception-if-no-handler-found: true
static-path-pattern: /**
resources:
static-locations: classpath:/static
NotFoundResourceHandler.java:
#ControllerAdvice
public class NotFoundHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<String> renderDefaultPage() {
try {
InputStream inputStream = new ClassPathResource("/static/index.html").getInputStream();
String body = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(body);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("There was an error completing the action.");
}
}
}
The above configuration will serve the static resources fine. I've also been able to get it to use the 404 Handler (when I was serving the resources from /, and some other configurations) - but in that case it was using that for every resource, including the JS/CSS/Media files).
Thanks for any help you can provide!
Update:
It's now resolved. I now understand that it's not working because it is mapping every request (/) to the static handler, so that is handling all 404's. I got it to work by mapping the static handler to a specific path (/subdirectory/) and the 404 handler should be thrown for any requests outside of that /subdirectory path (/brokenpath should cause the 404 handler to fire) - which I then set the 404 handler to redirect to /subdirectory/index.html.
Update: It's now resolved. I now understand that it's not working because it is mapping every request (/) to the static handler, so that is handling all 404's. I got it to work by mapping the static handler to a specific path (/subdirectory/) and the 404 handler should be thrown for any requests outside of that /subdirectory path (/brokenpath should cause the 404 handler to fire) - which I then set the 404 handler to redirect to /subdirectory/index.html.

How to make Spring 3.2 Async Servlet-Filter defined in Java Config..without having web.xml

Using latest and greatest Spring 3.2.3, Tomcat 7.0.42, Servlet 3.0 container, Java 7. We need to do JSONP for our response, so we implement that by doing a Servlet Filter just like this:
http://jpgmr.wordpress.com/2010/07/28/tutorial-implementing-a-servlet-filter-for-jsonp-callback-with-springs-delegatingfilterproxy/
but without the web.xml currently..we are using Java Annotation Config.
In our #Controller, we are returning a DeferredResult<String>, and then in our #Service, which is called by our #Controller, we have the #Async annotation. The reason we went the #Async route (not the AysncContext route) was because this is a "fire and forget" type async operation. But I do need to return some JSON that says the operation has begun to the requestor.
#RequestMapping(method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public DeferredResult<String> testing(#RequestParam(value="submit", required=true) String param)
{
final DeferredResult<String> result = new DeferredResult<String>();
service.doIt(param, result);
return result;
}
And in our #Service
#Async
public DeferredResult<String> doIt(String param2, DeferredResult<String> result)
{
log.info("Calling Async doIt Method");
result.setResult("{hello:there}");
}
I'm stuck...I'm trying to figure out if there is a way add async-supported tag for a Servlet Filter in a Java Config? I base that on seeing this presentation (slide 28):
http://rstoyanchev.github.io/spring-mvc-32-update/#28
Some other info:
Have #EnableAsync on the Configuration, and we are using the AbstractAnnotationConfigDispatcherServletInitializer to define our application, but I can't seem to figure out how to tell that to use have async filters (just using the getServletFilters() to return our JsonpFilter).
Basically what is happening is that the JSONP filter is "wrapping" nothing...but I do see the Filter called a second time, after the result has been set in the TaskExecutor thread..but at that point the response has already gone out...so, I see the filter "get Data" twice..once right when the #Controller method exits, and then a second time right after the DeferredResult.setResult() has been set.
We also tried this as well on our Filter class:
#WebFilter(urlPatterns = "/*", asyncSupported = true)
but that to did not work (even looked like it even made 2 separate filters according to the logs..but, could I be wrong there about that).
We could switch to a Callable if we need to..that's not a big deal. I did see that there was some differences between DeferredResult and Callable with regards to what threads Spring knows about.
If the answer is that you need to use web.xml, any really good way to do a Java Config with web.xml hybrid?
EDIT 1:
Based on reading a few different resources I found a couple of things:
From: Spring Aync Preview Blog
Servlet Filters
*All Spring Framework Servlet filter implementations have been modified as necessary to work in asynchronous request processing. As
for any other filters, some will work — typically those that do
pre-processing, and others will need to be modified — typically those
that do post-processing at the end of a request. Such filters will
need to recognize when the initial Servlet container thread is being
exited, making way for another thread to continue processing, and when
they are invoked as part of an asynchronous dispatch to complete
processing.**
From the Spring MVC Ref. docs, I actually RTFM'd:
The sequence of events for async request processing with a DeferredResult is the same in principle except it's up to the
application to produce the asynchronous result from some thread: (1)
Controller returns a DeferredResult and saves it in some in-memory
queue or list where it can be accessed, (2) Spring MVC starts async
processing, (3) the DispatcherServlet and all configured Filter's exit
the request processing thread but the response remains open, (4) the
application sets theDeferredResult from some thread and Spring MVC
dispatches the request back to the Servlet container, (5) the
DispatcherServlet is invoked again and processing resumes with the
asynchronously produced result.
So, basically, i knew/know that the separate thread would be calling the filter...that's not what has been hanging me up..it was how to figure out if the filter should modify the response...and you can't look at the size of the data, because a 0 bytes could be a correct "answer."
So, I now only write if the DispatchType = ASYNC
Not really sure this is correct to do long term..but, it does seem to fix the problem.
Any other suggestions/ideas?
It seems if you're extending AbstractAnnotationConfigDispatcherServletInitializer, you can override this method and set async support:
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setInitParameter("dispatchOptionsRequest", "true");
registration.setAsyncSupported(true);
}
You just need to set the asyncSupport flag on each servlet instance like below:
#Override
public void onStartup(final ServletContext servletContext)
throws ServletException {
final AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
root.setServletContext(servletContext);
root.scan("com.my.site");
root.refresh();
final Dynamic servlet = servletContext.addServlet("cf-validator",
new DispatcherServlet(root));
servlet.setLoadOnStartup(1);
servlet.addMapping("/api/*");
servlet.setAsyncSupported(true);
}
I solved this by adding #Async to the controller method. My guess is that doing that takes care of the filters behind the scenes.
You can set async-supported as true in the servlet definition inside your web.xml file:
<servlet>
<servlet-name>my-servlet</servlet-name>
<async-supported>true</async-supported>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

Spring Asynchronous Processing Does not Return To View

I'm using the Spring MVC asyncronous processing but the controller does not return a view on web browser.
#RequestMapping(value = "/generateGM", method = RequestMethod.POST)
public Callable<ModelAndView> generateGMReport(#RequestParam("countryCode") int countryCode, ModelAndView mv) {
Callable<ModelAndView> c = new GenericCallable(countryCode, reportDao, mv);
return c;
}
#Override
public ModelAndView call() throws Exception {
List<CostReport> gmList = reportDao.generateGrossMarginReport(countryCode);
mv.setViewName("gmReport");
mv.addObject("gmList", gmList);
return mv;
}
I had tried to modify the code to return Callable but it still does not return to the specified view name.
I'm using JBoss 7.1 as.
There is warning during deployment :
WARN [org.jboss.as.ee] (MSC service thread 1-7)
JBAS011006: Not installing optional component
org.springframework.web.context.request.async.StandardServletAsyncWebRequest
due to exception: org.jboss.as.server.deployment.DeploymentUnitProcessingException:
JBAS011054:
Could not find default constructor for class
org.springframework.web.context.request.async.StandardServletAsyncWebRequest
Reason: Perhaps sitemesh cannot set the response object from Spring MVC framework (AsynContext).
What is the reason ?
Please help.
Thanks.
Since the Sitemesh filter does some post-processing at the end of a request, it needs to support the Servlet 3 async request feature in order for this to work. When the initial Servlet container thread exits and the response remains open. If the Sitemesh filter is unaware of this, it will attempt to complete processing to early.
I am not an expect on sitemesh. But it's a servlet also so they follow the "chain of command" pattern which means it's possible it fail to transfer the correct url you need. can you post you config for async spring and sitemesh config in web.xml
It may be helpful. Return as a String instead of ModelAndView.

Exception handler for REST controller in spring

I want to handle exceptions so the URL information is automatically shown to the client. Is there an easy way to do this?
<bean id="outboundExceptionAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
<!-- what property to set here? -->
</bean>
You have two choices:
Spring Reference 15.9.1 HandlerExceptionResolver
Spring HandlerExceptionResolvers ease the pain of unexpected
exceptions that occur while your request is handled by a controller
that matched the request. HandlerExceptionResolvers somewhat resemble
the exception mappings you can define in the web application
descriptor web.xml. However, they provide a more flexible way to
handle exceptions. They provide information about which handler was
executing when the exception was thrown. Furthermore, a programmatic
way of handling exceptions gives you more options for responding
appropriately before the request is forwarded to another URL (the same
end result as when you use the servlet specific exception mappings).
The HandlerExceptionResolver has one method, containing everything you need:
HandlerExceptionResolver.resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex)
Or if you need different handlers for different controllers: Spring Reference Chapter 15.9.2 #ExceptionHandler
#ExceptionHandler(IOException.class)
public String handleIOException(IOException ex, HttpServletRequest request) {
return "every thing you asked for: " + request;
}
Short question short answer
I'm doing the following trick:
#ExceptionHandler(Exception.class)
public ModelAndView handleMyException(Exception exception) {
ModelAndView mv = new ModelAndView("redirect:errorMessage?error="+exception.getMessage());
return mv;
}
#RequestMapping(value="/errorMessage", method=RequestMethod.GET)
#Responsebody
public String handleMyExceptionOnRedirect(#RequestParamter("error") String error) {
return error;
}
Works flawless.

Resources