Sunday, 12 October 2014

REST and Error Messages

WebApplicationException

In Java REST you can throw a WebApplicationException, which indicates that something went wrong. You can add the HTTP Status to the exception, to indicate what went wrong.
throw new WebApplicationException(Response.Status.BAD_REQUEST);
But that amount of information is in most cases too little. Sure, a HTTP Status of 404 (Not Found) is quite clear, but you'd like some more information.

Luckily, I found out that we can add an entity in the Response and add the Response to the WebApplicationException.

In fact, it is the most convenient to just create your own subclass of WebApplicationException to handle this automatically.

Your own WebApplicationException

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author mr.bear
*/
public class MudWebException extends WebApplicationException
{
/**
* Create a new MudWebException.
*
* @param name the name of the player.
* @param message the message to send to the player.
* @param status the HTTP status code.
*/
public MudWebException(String name, String message, Response.Status status)
{
super(message, (new ErrorDetails(name, message)).getResponse(status));
}
/**
* Create a new MudWebException.
*
* @param name the name of the player.
* @param message the message to send to the player.
* @param status the HTTP status code.
* @param errormessage the error message to log in GlassFish (which can provide additional information too sensitive for the user).
*/
public MudWebException(String name, String message, String errormessage, Response.Status status)
{
super(errormessage, (new ErrorDetails(name, message)).getResponse(status));
}
/**
* Create a new MudWebException caused by a different exception.
*
* @param name the name of the player.
* @param message the message to send to the player.
* @param status the HTTP status code.
* @param e the underlying exception that was thrown.
*/
public MudWebException(String name, String message, Throwable e, Response.Status status)
{
super(e, (new ErrorDetails(name, message, e)).getResponse(status));
}
/**
* Create a new MudWebException caused by a different exception.
*
* @param name the name of the player.
* @param status the HTTP status code.
* @param e the underlying exception that was thrown.
*/
public MudWebException(String name, Throwable e, Response.Status status)
{
super(e, (new ErrorDetails(name, e)).getResponse(status));
}
/**
* Create a new MudWebException caused by a different exception.
*
* @param status the HTTP status code.
* @param e the underlying exception that was thrown.
*/
public MudWebException(Throwable e, Response.Status status)
{
super(e, (new ErrorDetails(e)).getResponse(status));
}
}

An Error Entity

The entity that gets translated into JSON and passed over the line, in my case called ErrorDetails, can provide all the information you need.
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Object that is sent to the client as a json string, upon throwing
* a MudWebException.
*
* @see MudWebException
* @author mr.bear
*/
@XmlRootElement
public class ErrorDetails
{
public Date timestamp;
public String errormessage;
public String stacktrace;
public String user;
public ErrorDetails()
{
// required for REST
}
ErrorDetails(String errormessage)
{
this.timestamp = new Date();
this.errormessage = errormessage;
}
ErrorDetails(String user, String errormessage)
{
this(errormessage);
this.user = user;
}
ErrorDetails(Throwable t)
{
this.timestamp = new Date();
this.errormessage = t.getMessage();
this.stacktrace = stackTraceToString(t);
}
ErrorDetails(String user, Throwable t)
{
this(t);
this.user = user;
}
ErrorDetails(String user, String message, Throwable t)
{
this(t);
this.user = user;
this.errormessage = message;
}
private String stackTraceToString(Throwable e)
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
return sw.toString();
}
public Response getResponse(Response.Status status)
{
return Response.status(status).entity(this).build();
}
}

The getResponse method is the one that creates the Response, which is used to provide the HTTP Status code and the error payload for the WebApplicationException.

The json generated looks like:

    timestamp="2014-09-21T21:34:01.831", 
    errormessage="sdsgsdgsgd was not found.", 
    stacktrace=””,
    user="sdsgsdgsgd"
}

JQuery

It just took a little effort to retrieve the ErrorDetail object from the Xqhr object in the JQuery Ajax call. This should be done during error processing, as shown in the JQuery Ajax call below.
$.ajax({
type: 'POST',
url: some_url // Which url should be handle the ajax request.
cache: false,
success: (function(data) {processor(data); }),
error: webError,
complete: (function() { if (window.console) console.log("complete"); }),
dataType: 'json', //define the type of data that is going to get back from the server
contentType: 'text/plain; charset=utf-8',
data: command
}); // end of ajax
view raw ajaxcalls.js hosted with ❤ by GitHub

The webError in the code above is a function to parse the JSON containing ErrorDetails object, provided below.
function webError(jqXHR, textStatus, errorThrown)
{
if (window.console)
{
console.log(jqXHR);
console.log(textStatus);
console.log(errorThrown);
}
try
{
var errorDetails = JSON.parse(jqXHR.responseText);
if (window.console) console.log(errorDetails);
} catch(e)
{
alert("An error occurred.");
if (window.console) console.log(e);
return;
}
if (errorDetails.stacktrace !== undefined)
{
var buffer = "Timestamp: " + errorDetails.timestamp + "<br/>";
buffer += "Errormessage: " + errorDetails.errormessage + "<br/>";
buffer += "Stacktrace: " + errorDetails.stacktrace + "<br/>";
buffer += "User: " + errorDetails.user + "<br/>";
buffer += "Browser CodeName: " + navigator.appCodeName + "<br/>";
buffer += "Browser Name: " + navigator.appName + "<br/>";
buffer += "Browser Version: " + navigator.appVersion + "<br/>";
buffer += "Cookies Enabled: " + navigator.cookieEnabled + "<br/>";
buffer += "Platform: " + navigator.platform + "<br/>";
buffer += "User-agent header: " + navigator.userAgent + "<br/>";
var $ = Karchan.$;
$("#warning").html(buffer);
}
alert(errorDetails.errormessage);
}
view raw weberror.js hosted with ❤ by GitHub

It parses the jqXHR.responseText, to be precise. And if a stacktrace is provided, the details are put into a HTML tag with id "warning". An 'alert' is always provided.

References

Whatever can go wrong … Error Handling and RESTful Services
http://djna.wordpress.com/2009/12/07/whatever-can-go-wrong-error-handling-and-restful-services/
RESTful API Design: what about errors?
https://blog.apigee.com/detail/restful_api_design_what_about_errors
How to get the jQuery $.ajax error response text?
http://stackoverflow.com/questions/1637019/how-to-get-the-jquery-ajax-error-response-text
Use jQuery to catch and display ASP.NET AJAX service errors
http://encosia.com/use-jquery-to-catch-and-display-aspnet-ajax-service-errors/

1 comment: