I am running a Spring 4 web mvc project:
Issue:
My controlleradvice for 404 exception handler is not working. However, if I comment the "addResourceHandlers" method in WebConfig class, it will work. (I can't remove that as it resolves my static resources)_
This is my web config:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/*
* Resource handler for static resources
*/
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}
And this is my 404 exception handler:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(NoHandlerFoundException.class)
public String handle404(Exception e) {
return "error/404";
}
}
If your webapp is using web.xml it's very simple - just add the following (assuming usage of InternalResourceViewResolver with prefix pointing at your WEB-INF view folder and suffix .jsp). You can have multiple error-page elements of other error codes too.
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
If you are not using web.xml it's a bit more complicated.
If you want to catch the NoHandlerFound exception you first have to tell Spring to throw it via setting a flag in the DispatcherServlet directly.
To do so, in the class that you are extending AbstractAnnotationConfigDispatcherServletInitializer override the onStartup method to expose the DispatcherServlet definition and add manually the needed flag:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
//...
WebApplicationContext context = getContext();
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
//we did all this to set the below flag
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet",dispatcherServlet );
//..
}
Then your existing code within ExceptionController should work and intercept the exception
Related
We are Attempting to Migrate Our Existing Spring WebServices App to SpringBoot and ran into an issue for which we seek your advice.
We have a Base Service Servlet that disables the GET on the port that the App is deployed on for Security reasons this servlet returns 501 Unimplemented Response as follows:
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.warn("GET request received!");
response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED);
}
public abstract class BaseServiceServlet extends HttpServlet {
...
}
public class ServiceServlet extends BaseServiceServlet {
...
}
public class ServletInitializer extends SpringBootServletInitializer implements ServletContextInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
ServletRegistration.Dynamic application = container
.addServlet("ServiceServlet", new ServiceServlet());
application.setLoadOnStartup(2);
application.addMapping("/*");
}
}
Previously we had an old-fashioned HealthCheck JSP that we implemented. With the move to SpringBoot we are now using the SpringBoot Actuator.
But we find that if we set the Actuator health monitor port to the same one as the App when we try to monitor the health we get the 501 Unimplemented response.
Config is as follows:
# Spring-Boot Embedded Tomcat Config
server.port=8080
server.connection-timeout=10s
server.ssl.enabled=false
## Springboot based health monitor
management.endpoints.enabled-by-default=true
management.endpoint.health.enabled=true
management.endpoint.loggers.enabled=true
management.endpoints.web.cors.allowed-methods=GET
management.endpoint.beans.cache.time-to-live=20s
management.endpoints.web.base-path=/manage
management.server.port=8080
management.security.enabled=false
One way we could get around this problem is if we changed the actuator health check port to something else that Works.
Question:
How can we set the Actuator port to be the same as the App and make the actuator health check url which is something like http://localhost:8080/manage/health not return 501 Unimplemented from the Base Service Servlet ?
We can register DispatcherServlet with health endpoint in mapping before the ServiceServlet.
#SpringBootApplication
public class ServletInitializer extends SpringBootServletInitializer implements ServletContextInitializer {
#Autowired
private DispatcherServlet dispatcherServlet;
#Override
public void onStartup(ServletContext container) throws ServletException {
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", dispatcherServlet);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/manage", "/manage/health");
ServletRegistration.Dynamic application = container
.addServlet("ServiceServlet", new ServiceServlet());
application.setLoadOnStartup(2);
application.addMapping("/*");
}
}
I have Spring Boot application with REST API mapped on /api. I need to define additional servlet on /. I want all request that match /api was handled by REST API and all others requests by the servlet. How to do this?
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#RestController
#RequestMapping("/api")
public class ApiController {
#GetMapping
public String get() {
return "api";
}
}
#Bean
public ServletRegistrationBean customServletBean() {
return new ServletRegistrationBean<>(new HttpServlet() {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().println("custom");
}
}, "/*");
}
}
In code above I want something like this:
curl http://localhost:8080/api/
> api⏎
curl http://localhost:8080/custom/
> custom
I have tried with filter to redirect requests, but all requests go to custom servlet:
#Bean
public FilterRegistrationBean apiResolverFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter((req, response, chain) -> {
HttpServletRequest request = (HttpServletRequest) req;
String path = request.getRequestURI().substring(request.getContextPath().length());
if (path.startsWith("/api/")) {
request.getRequestDispatcher(path).forward(request, response);
} else {
chain.doFilter(request, response);
}
});
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
This project is available on github: https://github.com/mariuszs/nestedweb
When mapping a servlet to the root path you will override the mapping for the DispatcherServlet which, by default, is mapped to /.
There are basically 3 solutions you could try
Map the DispatcherServlet to /api and modify the mappings in your controllers
Use a ServletForwardingController to forward the request to the configured but unmapped Servlet
Use a ServletWrappingController to wrap a Servlet instance
Number 2 and 3 are almost the same, with this difference that with option 3 Spring also manages the Servlet instance whereas with option 2, the Servlet container manages the Servlet.
Mapping DispatcherServlet to /api
Option 1 can be an option if all of your controllers are mapped under /api, if they aren't this isn't an option. In your application.properties you would set the spring.mvc.servlet.path to /api. Then you would configure your other Servlet like you did in your question.
Use a ServletForwardingController
Spring provides a ServletForwardingController which will lookup a Servlet in the ServletContext given the name of the servlet and forward the request to it. You will still have to register the Servlet but prevent it from being mapped.
Next you would need a SimpleUrlHandlerMapping to map the URLs to this controller or set it as the default handler (basically a catch all).
#Bean
public ServletForwardingController forwarder() {
ServletForwardingController controller = new ServletForwardingController();
controller.setServletName("my-servlet");
return controller;
}
#Bean
public CustomServlet customServlet() {
return new CustomServlet();
}
#Bean
public ServletRegistrationBean customServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(customServlet(), false);
registration.setServletName("customServlet");
return registration;
}
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setDefaultHandler(forwarder());
mapping.setOrder(LOWEST_PRECEDENCE - 2);
return mapping;
}
Use a ServletWrappingController
Spring provides a ServletWrappingController which will internally create and configure a Servlet instance. It acts as an adapter from/to the Servlet to a Spring Controller. You don't have to register the CustomServlet in this case and is thus slightly easier to configure the then ServletForwardingController.
Next you would need a SimpleUrlHandlerMapping to map the URLs to this controller or set it as the default handler (basically a catch all).
#Bean
public ServletWrappingController wrapper() {
ServletWrappingController controller = new ServletWrappingController ();
controller.setServletName("my-servlet");
controller.setServletClass(CustomerServlet.class);
return controller;
}
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setDefaultHandler(wrapper());
mapping.setOrder(LOWEST_PRECEDENCE - 2);
return mapping;
}
Depending on your architecture and url structure you might want to go for option 1 or option 3.
I'm trying to handle 404 error using an #ControllerAdvice in a Spring MVC application totally configured using Java configuration.
Here you have my conf:
public class WebAppInitializer implements WebApplicationInitializer
{
#Override
public void onStartup(ServletContext container)
{
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
dispatcherServlet.register(WebMvcConfig.class);
dispatcherServlet.setServletContext(container);
dispatcherServlet.refresh();
CookieHelper cookie = (CookieHelper) dispatcherServlet.getBean("cookie");
final Gson gson = (Gson) dispatcherServlet.getBean("gson");
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
dispatcher.addMapping("/");
dispatcher.setLoadOnStartup(1);
dispatcher.setInitParameter("throwExceptionIfNoHandlerFound", "true");
FilterRegistration.Dynamic filter = container.addFilter("BaseFilter", new BaseFilter(cookie, gson));
filter.setInitParameter("forceEncoding", "true");
filter.addMappingForUrlPatterns(null, true, "/coolers/*");
filter.addMappingForUrlPatterns(null, true, "/hothouses/*");
filter.addMappingForUrlPatterns(null, true, "/lang/*");
filter.addMappingForUrlPatterns(null, true, "/organizations/*");
filter.addMappingForUrlPatterns(null, true, "/reworks/*");
filter.addMappingForUrlPatterns(null, true, "/select/*");
filter.addMappingForUrlPatterns(null, true, "/volumes/*");
}
}
and my GlobalExceptionHandlerController:
#ControllerAdvice
public class GlobalExceptionHandlerController
{
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public String handle() {
System.out.println("test test test test");
return "error/index";
}
}
NoHandlerFoundException not firing?
I had the same issue, got it resolved. Below given steps to solve the same.
Create a class GlobalExceptionHandler annotated with #ControllerAdvice
#ControllerAdvice
public class GlobalExceptionHandler
{
#ExceptionHandler(NoHandlerFoundException.class)
public String handleNotFoundError(Exception ex)
{
return "redirect:/yourCustom404page";
}
}
By default, when a page/resource does not exist the servlet container will render a default 404 page. If you want a custom 404 response then you need to tell DispatcherServlet to throw the exception if no handler is found. We can do this by setting the throwExceptionIfNoHandlerFound servlet initialization parameter to true
a. If spring-mvc java based configuration is
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
...
#Override
protected DispatcherServlet createDispatcherServlet(WebApplicationContext servletAppContext)
{
final DispatcherServlet servlet = (DispatcherServlet) super.createDispatcherServlet(servletAppContext);
servlet.setThrowExceptionIfNoHandlerFound(true);
return servlet;
}
}
b. if spring-mvc xml based configuration, initialize your dispatcher servlet like this
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
c. if spring-boot
spring.resources.add-mappings=false in your application.properties or yaml file.
Hope it helps
I have simple Spring MVC application where I want to hande 404 Not found exceptions in my Advice Controller class
Configuration:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class, SecurityConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new SessionListener());
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encodingFilter", new CharacterEncodingFilter());
encodingFilter.setInitParameter("encoding", "UTF-8");
encodingFilter.setInitParameter("forceEncoding", "true");
encodingFilter.addMappingForUrlPatterns(null, true, "/*");
}
}
Controller:
#Controller
#RequestMapping("/")
public class HomeController {
#RequestMapping(value = "/error/", method = RequestMethod.GET)
public String error(){return "error";}
}
ControllerAdvice:
#ControllerAdvice
public class AdviceController {
#ExceptionHandler(MyOwnException.class)
#ResponseStatus(value= HttpStatus.BAD_REQUEST)
public String checkoutException(CheckoutException e, HttpServletRequest httpServletRequest) {
return "error";
}
}
I can catch my own exceptions when I manually throw MyOwnException but I can't get how to catch NoHandlerFound exception. I need to send 404 error code and appropriate error.jsp page when there is no controller method to handle request
If your webapp is using web.xml it's very simple - just add the following (assuming usage of InternalResourceViewResolver with prefix pointing at your WEB-INF view folder and suffix .jsp). You can have multiple error-page elements of other error codes too.
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
If you are not using web.xml it's more complicated and you'll have to define and register your own ExceptionResolver. Take a look at this spring.io blog article for details on how to do this.
(Edit after comment)
If you want to catch the NoHandlerFound exception you first have to tell Spring to throw it via setting a flag in the DispatcherServlet directly. To do so, in your AppInitializer class add the DispatcherServlet definition on top of what you are currently doing to add the flag:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new SessionListener());
//BEGIN OF NEW CODE
WebApplicationContext context = getContext();
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
//we did all this to set the below flag
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet",dispatcherServlet );
//END OF NEW CODE
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encodingFilter", new CharacterEncodingFilter());
encodingFilter.setInitParameter("encoding", "UTF-8");
encodingFilter.setInitParameter("forceEncoding", "true");
encodingFilter.addMappingForUrlPatterns(null, true, "/*");
}
Then you can catch the NoHandlerFound exception directly in your AdviceController:
#ControllerAdvice
public class AdviceController {
//..
#ExceptionHandler(NoHandlerFoundException.class)
public String dealWithNoHandlerFoundException(CheckoutException e, HttpServletRequest httpServletRequest) {
return "error";
}
}
I would like to display custom 404 error page if a user enters a request path that isn't handled by any controller.
Suppose that I have only one controller and it handles the following request paths: path1, path2 and path3. How can I display a custom 404 error page if a user enters path4? Currently, Tomcat displays its own 404 error page. I would like to use annotations and not implement this task by configuring any xml files.
My current approach doesn't achieve this requirement.It might be possible to configure any filters, but I am not aware of such approach. Thus, I will appreciate any ideas about it.
Here is my code:
public class WebAppContextInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext();
annotationConfigWebApplicationContext.register(WebContextConfiguration.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(annotationConfigWebApplicationContext);
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("SpringDispatcher",dispatcherServlet);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
servletContext.setInitParameter("spring.profiles.active","demo");
}
}
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public String handleConflict() {
return "404_error_page";
}
}