users@jersey.java.net

Re: [Jersey] Idea for new contribution -- a pattern for handling HTTP errors as Java exceptions?

From: Jeff Schmidt <jas_at_535consulting.com>
Date: Thu, 23 Oct 2008 22:12:28 -0600

Hi Craig:

This is uncanny. First the multipart request solution, and now
this. :) I was just trying to figure out how to code something up
myself. For my perspective though, I'm trying to keep business logic
out of the JAX-RS resource classes, and confine it to some number
services the resources will invoke to get the job done. The idea is
for JAXB annotation classes and method parameters to define the
service interface, with no declared, checked exceptions. For example:

public interface SessionService {

     public void logout(String applicationName);
     public void keepAlive(String applicationName);
}

I don't want the service interfaces or implementations to be tied in
anyway to JAX-RS/Jersey. I also don't want any service interface to be
dependent on business classes. My goals in doing this are to make it
easy to plug in different service implementations so I can better
exercise the resource classes in my IDE and not be tied into a huge
web application. I also want to make it so these same services can be
invoked by the Spring Web Services based SOAP endpoints.

So the resource class portion for the logout() method can be something
like:

     @GET
     @Path("/logout")
     public Response logout(
             @DefaultValue(ApiConstants.DEF_APPLICATION_NAME)
             @QueryParam(ApiConstants.QUERY_PARAM_APPLICATION_NAME)
String applicationName) {

         _sessionService.logout(applicationName);

         //TODO: return entity like SOAP LogoutEndPoint?
         return Response.ok().build();
     }

Note this is a work in progress, and yes the APIs are session
oriented. :)

So, an important aspect is exception handling. I don't want to add
'throws Exception' to each service method and force the resource to
even address the existence of checked exceptions. I'd rather use
runtime exceptions which can be thrown by the service method and
handled by the JAX-RS ExceptionMapper implementations. I'm trying to
keep these exception classes JAX-RS/Jersey agnostic so that they can
be used by the Spring WS exception mapper as well for SOAP fault
processing.

I too wish to provide more than just an HTTP status code. Returning
400 (Bad Request) might be helpful if the client provided bugus input
parameters, but I would like to do more.

Please see additional comments below.

Cheers,

Jeff

On Oct 23, 2008, at 6:22 PM, Craig McClanahan wrote:

> In addition to the jersey-multipart stuff I contributed for creating
> and consuming MIME multipart messages (see the "jersey-multipart"
> module in the "contribs" directory), one of the other things I've
> been doing in my "day job" work is defining a common "best
> practices" approach to use for error handling in RESTful web
> services. We all know that JAX-RS (and therefore Jersey) lets us
> create and consume HTTP messages with any needed status code, but
> that still doesn't address two concerns:
>
> * Ensuring that every developer on the team uses the same HTTP
> status code
> for the same "error" scenario, across multiple services (so that the
> service APIs
> all look like they actually came from the same company :-).

For this aspect I was going to encourage developers to throw
exceptions. For example, some fragments of the ServiceException:

public class ServiceException extends RuntimeException {

     final protected int _responseStatusCode;
     protected FaultInfo _faultInfo = null;

     public ServiceException(String message, int responseStatusCode) {
         super(message);
         _responseStatusCode = responseStatusCode;
     }

     public ServiceException(String message, int responseStatusCode,
FaultInfo faultInfo) {
         this(message, responseStatusCode);
         _faultInfo = faultInfo;
     }

     ...

     public int getResponseStatusCode() {
         return _responseStatusCode;
     }

     public FaultInfo getFaultInfo() {
         return _faultInfo;
     }
}

Then, a couple sub-class fragments:

public class InputValidationException extends ServiceException {

     public static final int RESPONSE_STATUS_CODE =
HttpURLConnection.HTTP_BAD_REQUEST;

     public InputValidationException(String message) {
         super(message, RESPONSE_STATUS_CODE);
     }

     public InputValidationException(String message, FaultInfo
faultInfo) {
         super(message, RESPONSE_STATUS_CODE, faultInfo);
     }

     ...
}

public class NotFoundException extends ServiceException {

     public static final int RESPONSE_STATUS_CODE =
HttpURLConnection.HTTP_NOT_FOUND;

     public NotFoundException(String message) {
         super(message, RESPONSE_STATUS_CODE);
     }

     public NotFoundException(String message, FaultInfo faultInfo) {
         super(message, RESPONSE_STATUS_CODE, faultInfo);
     }

     ...
}

Thus, service and resource classes that throw this set of exceptions
will use the same HTTP status codes. I'm using the HttpURLConnection
constants here so that the exception classes are not JAX-RS/Jersey
dependent.

> * Passing error messages in a consistent format that the client can
> rely on, and
> parse to good effect. For example, if your API offers a POST URI to
> create a new
> resource, but several of the input fields in the entity have
> incorrect or missing values,
> it would be nice to have a data structure that lets me include zero
> or more error
> messages, optionally tagged to the corresponding field names (so
> that a client side
> UI can present them nicely).

I would like to return some entity to help out the client. My thought
was to provide the entity as a class instance added to the exception
constructor (FaultInfo). The code that detects the problem can
populate this class to better assist the client. I would like for
this entity to be marshaled in the same manner a proper response would
be by JAX-RS, using @Produces/_at_Consumed to return the right format.
Starting with something from the SOAP side of things, I defined this
class:

@XmlRootElement(name = "Error")
@XmlType(name = "ErrorType")
@XmlAccessorType(XmlAccessType.FIELD)
public class FaultInfo {

        @XmlElement(name = "Code")
        protected ErrorCode _errorCode;
        @XmlElement(name = "Message")
        protected String _message;
        @XmlElement(name = "Resource")
        protected String _resource;
        @XmlElement(name = "DevInfo")
        protected String _devInfo;

        public FaultInfo() {
        }

        public FaultInfo(ErrorCode errorCode) {
                _errorCode = errorCode;
                _message = errorCode.getMessage();
        }

        public FaultInfo(ErrorCode errorCode, String resource) {
                this(errorCode);
                _resource = resource;
        }

        public FaultInfo(ErrorCode errorCode, String resource, String
devInfo) {
                this(errorCode, resource);
                _devInfo = devInfo;
        }

        public ErrorCode getErrorCode() {
                return _errorCode;
        }

        public String getResource() {
                return _resource;
        }
}

Note that devInfo is 'developer info', which provides more detail than
what would be returned in production. I would like consistency in the
error message, and even some kind of error code that would be easier
to handle by the client. So, the ErroCode class is an enumeration like:

@XmlEnum
public enum ErrorCode {

        @XmlEnumValue("InvalidUser")
        INVALID_USER("Invalid Username/password"),

        @XmlEnumValue("AuthorizationError")
        AUTHORIZATION_ERROR("User not authorized to use this webservice"),

        @XmlEnumValue("InternalError")
        INTERNAL_ERROR("Internal Server Error - Please try again later"),
        
        @XmlEnumValue("InvalidSourceType")
        INVALID_SOURCE_TYPE("Invalid Source Type"),
        
        @XmlEnumValue("InvalidSourceId")
        INVALID_SOURCE_ID("Invalid Source Id"),

         @XmlEnumValue("InvalidTokenType")
        INVALID_TOKEN_TYPE("Invalid token type"),

         @XmlEnumValue("InvalidTokenValue")
        INVALID_TOKEN_VALUE("Invalid token value"),

         ;

        final private String _message;

        ErrorCode(String message) {
                _message = message;
        }

        public String getMessage() {
                return _message;
        }
}

And then, in one of the service class methods, when an input
validation fails:

         final TokenTypes tokenType =
TokenTypes.getTypeFromName(tokenTypeStr);
         if (tokenType == null) {
             final String msg = String.format("tokenType of '%s' is
not valid", tokenTypeStr);
             throw new InputValidationException(
                     msg,
                     new FaultInfo(ErrorCode.INVALID_TOKEN_TYPE,
tokenTypeStr, msg));
         }

So, by throwing InputValidationException, HTTP response code 400 will
be used, and the FaultInfo entity is populated with some helpful
information. Finally, the exception mapper is defined as:

@Provider
public class ServiceExceptionMapper implements
         ExceptionMapper<com.mycompany.ws.service.ServiceException> {

     public Response toResponse(ServiceException ex) {
             return Response.status(ex.getResponseStatusCode()).
                     entity(ex.getFaultInfo()).
                     build();
     }
}

This all does seem a bit awkward, and as you can see only a single
fault is being reported.

> Does this sound like an interesting problem to anyone else?

Count me in!

> The solution being developed by our team started from the notion
> that Java developers are used to handling "exceptional" situations
> with ... "exceptions" :-). Back in the Jersey 0.8 or so time frame,
> JAX-RS added the concept of an ExceptionMapper, which basically acts
> like a MessageBodyWriter when the resource method itself throws an
> exception. So, maybe we can give the server side developer a set of
> well defined exception classes (and corresponding exception mappers)
> that the resource method can just throw, and let the library deal
> with setting the correct status code and an entity containing the
> right message infomation.
>
> On the server side, your resource method code might look something
> like this:
>
> @GET
> @Path("/customers/{id}")
> @Produces({"application/json","application/xml","text/xml"})
> public Customer find(@PathParam("id") String id) {
> // Finder method returns null if no such customer exists
> Customer cust = MyBusinessLogic.findCustomerById(id);
> if (cust != null) {
> return cust; // Use the provider for formatting this
> response
> } else {
> // NotFoundException is a RuntimeException so you do not
> need to
> // declare it in a throws clause, but you SHOULD javadoc it
> throw new NotFoundException("Customer '" + id + "' does
> not exist");
> }
> }
>
> The library would provide an exception mapper that enforces our
> "best practice" policy to use an HTTP 404 "Not Found" status for
> this condition, and package an appropriate message body containing
> the message text, in a standard XML or JSON format.

I'm very interested in seeing what you have in mind. I don't know if
you'll get it into Jersey to meet my timeframe, but I am interested in
learning about your approach and perhaps implementing something
similar for now, and using it directly in Jersey 1.1 or whenever
you've integrated it.

I can't comment too much on the client-side of things as I've much to
do on the server side. :)

Thanks!

>
>
> For extra credit, I write client SDKs for our APIs as well. Why
> can't we make it possible for the client developer to reason in
> exceptions just like the server side developer can? That's actually
> pretty straightforward, if you have a client side library that knows
> how to map HTTP error responses back into an exception again.
>
> Let's imagine a client side business logic class (based on jersey-
> client) that abstracts calls to the above service with a nice Java
> API. The code to call the server method described above might look
> like:
>
> public class CustomerService {
>
> WebResource service = ...; // See jersey-client examples for
> setting this up
>
> public Customer findCustomerById(String id) {
> try {
> return service.
> path("customers/" + id).
> accept("text/xml").
> get(Customer.class);
> } catch (UniformInterfaceException e) {
> throw ErrorsHelper.asException(e.getResponse());
> }
> }
>
> }
>
> where ErrorsHelper.asException analyzes the HTTP status of the
> response and synthesizes an appropriate exception (in our case,
> turning a 404 into a NotFoundException) and grabs the relevant
> information out of the response body (including the error message
> text).
>
> So, does this sort of thing sound like something you might use? It
> would be pretty straightforward to generalize what I've already got
> working to make it fit in with Jersey (that's mostly a matter of a
> Maven based build environment plus package name changes), if it
> sounds like this would be generally useful.
>
> Craig
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>



--
Jeff Schmidt