I have a Spring MVC application which uses FreeMarker as View technology (But maybe the view technology doesn't really matter for my question). I need to intercept all exceptions which may get thrown during a request.
I have implemented a HandlerExceptionResolver but this resolver is only executed when the exception occurs within a controller. But when a controller returns a ModelAndView and the exception occurs while rendering the view (Because a variable was not found or something like this) then the exception resolver is not called and instead I get a stack trace in the browser window.
I also tried using an exception handler method within the controller which returns the view and annotated it with #ExceptionHandler but this also doesn't work (Most likely again because the exception is not thrown in the controller but in the view).
So is there some Spring mechanism where I can register an exception handler which captures view errors?
A word upfront: if you just need a "static" error page without much logic and model preparation, it should suffice to put a <error-page>-Tag in your web.xml (see below for an example).
Otherwise, there might be better ways to do this, but this works for us:
We use a servlet <filter> in the web.xml that catches all Exceptions and calls our custom ErrorHandler, the same we use inside the Spring HandlerExceptionResolver.
<filter>
<filter-name>errorHandlerFilter</filter-name>
<filter-class>org.example.filter.ErrorHandlerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>errorHandlerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The implementation looks essentially like this:
public class ErrorHandlerFilter implements Filter {
ErrorHandler errorHandler;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(request, response);
} catch (Exception ex) {
// call ErrorHandler and dispatch to error jsp
String errorMessage = errorHandler.handle(request, response, ex);
request.setAttribute("errorMessage", errorMessage);
request.getRequestDispatcher("/WEB-INF/jsp/error/dispatch-error.jsp").forward(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
errorHandler = (ErrorHandler) WebApplicationContextUtils
.getRequiredWebApplicationContext(filterConfig.getServletContext())
.getBean("defaultErrorHandler");
}
// ...
}
I believe this should work pretty much the same for FreeMarker templates. Of course if your error view throws an error, you're more or less out of options.
To also catch errors like 404 and prepare the model for it, we use a filter that is mapped to the ERROR dispatcher:
<filter>
<filter-name>errorDispatcherFilter</filter-name>
<filter-class>org.example.filter.ErrorDispatcherFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>errorDispatcherFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/error/dispatch-error.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/jsp/error/dispatch-error.jsp</location>
</error-page>
The doFilter-Implementation looks like this:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
// handle code(s)
final int code = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (code == 404) {
final String uri = (String) request.getAttribute("javax.servlet.error.request_uri");
request.setAttribute("errorMessage", "The requested page '" + uri + "' could not be found.");
}
// notify chain
filterChain.doFilter(servletRequest, servletResponse);
}
You could extends the DispatcherServlet.
In your web.xml replace the generic DispatcherServlet for your own class.
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>com.controller.generic.DispatcherServletHandler</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
Later create your own class DispatcherServletHandler and extends from DispatcherServlet:
public class DispatcherServletHandler extends DispatcherServlet {
private static final String ERROR = "error";
private static final String VIEW_ERROR_PAGE = "/WEB-INF/views/error/view-error.jsp";
#Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
try{
super.doService(request, response);
} catch(Exception ex) {
request.setAttribute(ERROR, ex);
request.getRequestDispatcher(VIEW_ERROR_PAGE).forward(request, response);
}
}
}
And in that page we only have to show a message to the user.
Not sure if my solution works with the problem you're having. Ill just post the way i catch my exceptions to ensure no stack trace is show inside the browser:
I made an AbstractController class with a method that will handle a specific conflict like this:
public class AbstractController {
#ResponseStatus(HttpStatus.CONFLICT)
#ExceptionHandler({OptimisticLockingFailureException.class})
#ResponseBody
public void handleConflict() {
//Do something extra if you want
}
}
This way whenever an exception occurs the user will see a default HTTPResponse status. (eg. 404 Not Found etc..)
I extend this class on all my controller classes to make sure errors are redirected to the AbstractController. This way I don't need to use ExceptionHandler on a specific controller but I can add the globally to all my controllers. (by extending the AbstractController class).
Edit:
After another go on your question, I noticed you're getting errors in your view. Not sure if this way will catch that error..
Hope this helps!!
Related
I've done my java web app in Java EE with HttpServlet and JSP. I normally map my Servlet like this:
#WebServlet(urlPatterns = "/main")
public class MainServlet extends HttpServlet{
I do my servlet stuff and would like to pass data to JSP file like this:
RequestDispatcher dispatcher = req.getRequestDispatcher("/main.jsp");
dispatcher.forward(req, resp);
The main.jsp is in my web app folder (I use IntelliJ Idea).
The question is, I've initially made my UI with Vaadin 8. Using following:
#Theme("mytheme")
#CDIUI("users")
#SuppressWarnings("serial")
public class Vaadin extends UI
and then override init.
Now I would like to add a single HttpServlet and override doGet and then call the dispatcher to forward data to jsp. Here's the problem adding Vaadin somehow broke the path to tsp, as jsp does not display, instead a standard vaadin
Request was not handled by any registered handler.
appears, I know the servlet was mapped properly as the servlet starts and does work, what does not work is the running the JSP file.
Can anyone advise?
It seems that you need to implement yet a WebFilter to process JSP. Your dispatcher forwards the request but it is then a request that is again handled by some filter by Vaadin I guess. Also I am not sure if you need any servlet and/or dispatcher (not sure what your actual use is).
Anyway, with WebFilter it is possible to intercept this processing. Check the following example
#WebFilter(filterName="jspFilter", urlPatterns="*")
public class JspFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
HttpServletRequest hreq = (HttpServletRequest)request;
String path = hreq.getPathTranslated();
if(path.toLowerCase().endsWith(".jsp")) {
try(Writer writer = response.getWriter();
Reader reader = new FileReader(path) ) {
processJsp(reader, writer);
return;
} catch (Exception e) { /* TODO: handle exception */};
}
} catch (Exception e) { /* TODO: handle exception */};
chain.doFilter(request, response); // forward to filter chain by default
}
#Override public void destroy() {}
#Override public void init(FilterConfig filterConfig)
throws ServletException {}
}
This filter checks all request. If URI (here checked from translated/absolute path) is ending with .jsp it is processed with processJsp(reader, writer) that you might want to implement to do the forwarding to JSP parser or so.
I am learning JSP and Servlets. Consider the following code inside the doPost method of a Servlet which forwards a HTTP request to a JSP -
RequestDispatcher view = request.getRequestDispatcher("/MyWebApp/MvcView.jsp");
I wonder what will happen if someone wants this servlet to forward the request to another jsp instead of the one above ? Does one have to change this code manually every time in their application ? How can one free oneself of all this manual work ?
One simple solution is to set the url as a parameter for your servlet:
<servlet>
<servlet-name>YourServlet</servlet-name>
<servlet-class>com.you.YourServlet</servlet-class>
<init-param>
<param-name>url</param-name>
<param-value>/MyWebApp/MvcView.jsp</param-value>
</init-param>
</servlet>
and the in the servlet:
public class YourServlet {
protected String url = null;
public void init(ServletConfig servletConfig) throws ServletException {
this.url = servletConfig.getInitParameter("url");
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
RequestDispatcher view = request.getRequestDispatcher(url);
}
}
then there is no need to recompile servlet code to chage the url.
I'm using Tomcat 7 to serve some JAXRS services.
I also want to get a few static web pages to be served by the same application, using default servlet. This is how I define the mapping :
public void contextInitialized(ServletContextEvent sce) {
sce.getServletContext().getServletRegistrations().get("default").addMapping("/backoffice/*");
}
My problem is that the only way to access those static files is to use http://myserver.com/backoffice/index.html. I would like to access them just with http://myserver.com/backoffice
I do not define any mapping in web.xml file, just my main JAXRS application.
I've tried using welcome file list this way :
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
I did not find any workaround on this problem, and the way I define the mapping to default servlet is the only one I found working.
Thanks for help.
I can only think of two possibilities.
Define a servlet mapping in the web.xml to the html file or
Create a servlet, annotate it with #WebServlet and then in the doGet() method dispatch/redirect to the html file.
You could dynamically register the servlet if you prefered.
What I ended with :
In my ServletContextListener, I added :
public void contextInitialized(ServletContextEvent sce) {
String name = "backoffice-filter";
sce.getServletContext().addFilter(name, new StaticRedirectionFilter(basePath, targetPath));
sce.getServletContext().getFilterRegistrations().get(name).addMappingForUrlPatterns(null, false, pathDepart);
sce.getServletContext().getServletRegistrations().get("default").addMapping("/backoffice/*");
}
The class StaticRedirectionFilter :
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String requestURI = request.getServletPath();
if (requestURI.equals(basePath)) {
HttpServletResponse response = (HttpServletResponse) res;
response.sendRedirect(request.getContextPath() + targetPath);
}
else {
chain.doFilter(req, res);
}
}
As Alex mentionned it, I could have done it with an annotation #WebFilter("/backoffice") abose the StaticRedirectionFilter class, but using the mapping in context seems better for reusability.
I also think it works before Servlet 3, even if I didn't try it.
I use Tomcat 7 and Lo4j for all my server logs and GWT for client (only AJAX calls).
All my unhandled exceptions get logged in my catalina.log.
Now I want to catch all exceptions and add some of the user's specific Tomcat SessionData.
There are several ways:
Try catch over all servlets (there must be a better solution).
http://tomcat.apache.org/tomcat-7.0-doc/aio.html: I would have to change my connecter, and I don't know if I could use the Tomcat Session within the Event Handler (EventType.ERROR).
Better way?
What would be the best way to achieve this?
Out of what I understood from your question, you can try to use at least one of two ways:
Basic Logging Servlet
If you have access to the source code of all of your servlets, you can make a little refactoring using a basic super-servlet that is responsible for the logging/whatever of every request working transparently with AJAX, no error forwards directives, and no global exception handlers. Suppose you use service(ServletRequest,ServletResponse) as the servlet entry point (but you can do the following for every do*() method either), then you can create an abstract super-servlet and simply inherit your servlets from it.
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>stackoverflow.Servlet1</servlet-class>
</servlet>
<servlet>
<servlet-name>servlet2</servlet-name>
<servlet-class>stackoverflow.Servlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet1</servlet-name>
<url-pattern>servlet1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>servlet2</servlet-name>
<url-pattern>servlet2</url-pattern>
</servlet-mapping>
public abstract class BasicServlet extends HttpServlet {
/**
* Won't let it be {#code abstract} - we don't want to force any sub-servlet to implement this method.
*/
protected void doService(ServletRequest request, ServletResponse response) {
}
#Override
public final void service(ServletRequest request, ServletResponse response) {
try {
doService(request, response);
} catch ( Throwable ex ) {
err.println(ex.getMessage());
}
}
}
public final class Servlet1 extends BasicServlet {
#Override
protected void doService(ServletRequest request, ServletResponse response) {
out.println("I'm servlet #1");
}
}
public final class Servlet2 extends BasicServlet {
#Override
protected void doService(ServletRequest request, ServletResponse response) {
out.println("I'm servlet #2");
}
}
An advantage of this method is that you do not need to configure anything else than changing your servlet classes and not depend on the external configuration or context. The disadvantage is that you always must extend BasicServlet.
Filter
I didn't actually test it right now, for more information please see http://docs.oracle.com/javaee/6/api/javax/servlet/Filter.html . Filters allow to intercept each request (we use such a filter implementation for our JSPs while debugging and writing the exceptions into the common log file). The disadvantage is that it's not guaranteed that the filter can cover every exception/case, for example if any filter preceded your own filter.
<filter>
<filter-name>exceptionLoggingFilter</filter-name>
<filter-class>stackoverflow.ExceptionLoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionLoggingFilter</filter-name>
<url-pattern>*</url-pattern> <!-- we will process every request -->
</filter-mapping>
public final class ExceptionLoggingFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {
try {
filterChain.doFilter(request, response);
} catch ( Throwable ex ) {
err.println(ex);
}
}
#Override
public void destroy() {
}
}
Hope this helps.
Just Overriding the GWT function doUnexpectedFailure worked.
#Override
protected void doUnexpectedFailure(Throwable t) {
ServerLog.error(t.getMessage(), t);
super.doUnexpectedFailure(t);
}
1) You can define error page for your webapp, like this:
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error</location>
</error-page>
Then you can bind another servlet at /error and handle the exception there.
2) You can setUncaughtExceptionHandler for every HTTP connector thread. You could use this technique with servlet filter which would contain reference to the current HttpRequest (say, via a thread local). This won't work with async I/O by the way.
I am researching and experimenting with a ThreadLocal variable in my Java Web Application. I am using the ThreadLocal variable to store a username (collected from the session) before a request, and then removing it after the request. I have done this by calling a static utility method in a ServletFilter. The reason I do not simply retrieve the username from the session is because I have inherited a system with long-running processes that sometimes take longer to run than the session timeout would allow. My idea is to grab the username before the request is processed and store it in a ThreadLocal variable, thus giving me access to the username throughout the duration of the request even if it takes longer than 15 minutes.
My question is:
Are there any security/performance concerns with this design, and if so, what would be a better solution? Even if there aren't any security and/or performance issues, better ideas are welcome. Snippets from my solution are shown below:
Here is my utility class that would be called in my filter and anywhere that I would need the username.
public abstract class UserUtil {
private static final ThreadLocal<String> threadUser = new ThreadLocal<String>();
public static String getUserId(){
return threadUser.get();
}
public static void setUserId(String userId){
threadUser.set(userId);
}
public static void removeUserId(){
threadUser.remove();
}
}
Here is my servlet filter used to set the username before the request (and clear it via the finally block after the request).
public class UserFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest request = (HttpServletRequest) servletRequest;
UserBean userBean = (UserBean) ((HttpServletRequest) servletRequest).getSession().getAttribute("userBean");
UserUtil.setUserId(userBean.getUserId());
filterChain.doFilter(servletRequest, servletResponse);
} finally{
UserUtil.removeUserId();
}
}
}
Here's my web.xml configuration:
<!--web.xml-->
<web-app>
...
...
...
<filter>
<filter-name>UserFilter</filter-name>
<filter-class>filter.UserFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>UserFilter</filter-name>
<url-pattern>*.jsf</url-pattern>
</filter-mapping>
Any ideas are much appreciated :)
This is actually a fairly common approach for attaching security information to the thread of execution (or any other execution related information).
It is used in Java EE servers internally and by 3rd party code/utils like Spring as well.
It will work just fine.