Tuesday 15 April 2014

Exception Handling and AJAX in a Java Servlet Container

The Servlet Spec provides a simple way to implement error pages for different types of exceptions that might be thrown by your application with the <error-page> element in your web.xml. However, just like container managed security, you need to make some small changes if you want to handle AJAX requests differently.

Once again this technique relies on your being able to differentiate AJAX requests from full page requests. In my app I have taken a conscious decision to make sure that any requests that are made asynchronously use a URL containing '/ajax/'. This simplifies life quite a lot, but if you don't want to do this you could use a query parameter such as ?ajax=true on all AJAX requests.

The simplest use-case is that you want to catch all exceptions thrown at runtime and forward the user to an error page if they are making a normal http request, and return a small snippet of error message if they are making use of some asynchronous functionality.

In this case you can put this in your web.xml:

  <error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/error</location>
  </error-page>


Then to handle the errors you would create a small servlet like this:

@WebServlet("/error")
public class ErrorServlet extends HttpServlet {
 @Override
 public void service(
  HttpServletRequest request, 
  HttpServletResponse response
 )throws ServletException {
  String uri = (String) request
    .getAttribute(
      RequestDispatcher.ERROR_REQUEST_URI
    );
  if (uri != null && uri.contains("/ajax/")) {
   response.setStatus(
    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
   request.getServletContext()
    .getRequestDispatcher("/error.jsp")
    .forward(request, response);
  } else {
   request.getServletContext()
    .getRequestDispatcher("/error-page.jsp")
    .forward(request, response);
  }
 }

}

In a nutshell this code will forward to a jsp called error-page.jsp if the request is a normal http request. In the case of an AJAX request it returns an HTTP status code 500 and forwards to a different jsp that generates snippet of html with the error message, in case the JavaScript on the client wishes to display a message.

Of course you may want to handle different types of errors differently, which can be done using the configuration in web.xml to specify another Servlet. I handle validation errors from forms submitted via AJAX separately but in a similar way to this, putting the name of the fields and the messages in an object to be serialised to JSON, allowing the client to handle highlighting error fields and displaying messages.


Monday 7 April 2014

Container managed security and AJAX

A problem which seems to happen a lot to people using Java form-based security is that if a user gets logged out (eg their session expires), any request they make is forwarded to the login jsp. This means that if the request was made using AJAX it will cause problems because the client will have to handle getting the login page as a response.

My solution here is to use my login page jsp to check whether the request is coming asynchronously and handle it appropriately. I use the URL to differentiate since all my asyncronous servlets are mapped to a URL  with '/ajax/' in the path:

<%@ include file="/taglibs.jsp" %>
<%--Handle AJAX URLs separately--%>
<c:choose>
   <c:when test="${fn:contains(requestScope['javax.servlet.forward.request_uri'],'/ajax/')}">
       <c:set target="${pageContext.response}" property="status" value="401"/>
   </c:when>
   <c:otherwise>
       <%--Return the login page for non-AJAX URLs--%>
       <!DOCTYPE html>
       <html>
           <%--main login page html goes here--%>
       </html>
    </c:otherwise>
</c:choose>

Once the login jsp detects that the response should be asyncronous, it simply sets the http status code of the response to 401 (no permission) which my code on the client handles separately. 
On the client, when 401 is received, the user is notified that they have been logged out, then the page is reloaded, causing the container to redirect them to the login page:

if (xmlHTTP!=null && (xmlHTTP.readyState==4 || xmlHTTP.readyState=="complete")){
   if(xmlHTTP.status==200 || xmlHTTP.status==304){
       //handle correct response here

   }else if(xmlHTTP.status==401){//authentication required
       alert("You've been logged out please press OK to go to the login page.");
       window.location.reload();
   }else{

    //handle other error codes
   }
}

This has the advantage that all once the user logs in, they will be redirected to the original URL they were viewing at the point their session expired. It also means you don't have to write your own login servlet to handle this situation.