Search This Blog

Saturday, February 27, 2010

REST Client Frameworks - Your options

In previous blogs, I have discussed how the different frameworks for REST work, client and server side. In particular, I have investigated different JAX RS vendors such as Jersey, RESTEasy and Restlet JAX-RS and an implementation that is not JAX-RS as well, i.e., Core Restlet whose API pre-dated the JAX RS specification. I am currently looking at different JAX RS vendor implements for client side support. The JAX RS specification does not include a client side API and different JAX RS vendors have proceeded to create their own API's for the same. The framework one selects for the client and service ends do not have to be the same. For example, one could have a RESTful service running using Restlet while having a client developed in RESTEasy that consumes the same. In most cases, one will typically be satisfied with using the same client framework as one is using for the service development in order to be consistent and potentially be able to re-use artifacts in both areas if the framework permits the same.
That said, I have been looking at the following vendors for the Client side API and implementation as my needs require me to.
  • Jersey - A JAX RS reference implementation from Oracle/Sun.
  • RESTEasy - A JAX RS reference implementation by JBoss.
  • Restlet - A pioneer of sorts that has support for Client Side REST calls
  • CXF - An apache project that is a merger of XFire and Celtix. Provides a JAX RS implementation and JAX WS as well.
  • Apache Wink - A JAX RS implementation that has a client and server side api that has had a 1.0 release just recently
  • Spring 3.0 - Spring provides a RestTemplate, quite like the JmsTemplate and HibernateTemplate in its support of REST clients.
  • Apache HTTPClient or Http Components - Direct use of HTTP API - Down and dirty.
As an evaluator, I would look for the following things in a Client framework:
  • User Base - Documentation, Support, Community, Release cycles, Age of solution
  • Easy of API for RESTful operations - URL Parameter substitution, HTTP verbs, Proxy support
  • Dryness of the API - Certain frameworks allow for components such as contracts developed for the service to be re-used in the client
  • Http Connection Control - Ability to do connection pooling, connection reaping and controlling lower level HTTP connection settings such as connection time out if required
  • Safety of the API - It is possible to "leak" connections. This occurs when connections are not restored properly to the pool.
  • Support for different Provider types - JAXB, JSON, Atom, Yaml and others while providing hookins to introduce new provider types.
  • Interception of a requests - For security and ability to add additional information, for example to HTTP headers
  • Deployment footprint - number of dependencies the library brings.
  • Error Response Handling - Ease of handling alternate response types apart from standard expected responses, for example how the framework supports exception throwing.
  • Performance - Efficiency of Provided Converters such as JAXB, JSON, Memory foot print etc
The above list represent things that one would look for while evaluating a solution. What I aim to do with this blog is provide a maven project that utilizes these client framework thus allowing evaluators to investigate the different solutions. I will put forth my opinions as well.
When dealing with connections, it is often desirable to keep alive Http Connections. The Apache Http Client has for sometime now provided a library that facilitates connection keep alive, connection reaping and safe release of connections. It is almost the defacto underlying Http Client library for Http operations. The standard Http support for the core jdk is limited in its offering. Apache Http Client recently underwent a re-haul of their architecture to clean out areas of their base while providing performance enhancements. Using Http Client directly has some limitations though where one would need to build converters for standard media types like JAXB, JSON, Atom etc. RESTful client libraries like RESTEasy, Jersey, Apache Wink and Restlet have a RESTful API layer on top of Apache Http Client that eases the development of RESTful clients.
I am working with a Maven project that has a service that uses Jersey while having clients from different frameworks consume the same. The code is NOT doing any benchmarking of any sort, it can however be used to if required. What the code instead does is demonstrate the different clients and their API's. It also demonstrates the use of Proxies where applicable.
The service developed is very similar to the ones I have used in previous blogs where a client can obtain product information and then perform CRUD operations on Orders. Subsequently, from the clients perspective, there are two clients. Order Clients and Product Clients. Some of the client frameworks being discussed have the concept of annotation driven proxies that allow for easy development; for those frameworks, I have provided proxy clients in the examples as well.


All the clients of the example, implement one of the following two interfaces,
OrdersClientIF

public interface OrdersClientIF { 
public static final String ORDERS_RESOURCE = "/orders";
 /**
  * Create an order.
  */
  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException;

  /**
   * Update an existing order
   */
  public void update(OrderDto dto) throws OrderNotFoundException, OrderValidationException,    OrderException;

  /**
   * Retreive an Order
   */
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException;

  /** 
   * Deletes an order
   */
  public void delete(Long orderId) throws OrderException;
}

ProductsClientIF

public interface ProductsClientIF {
public static final String PRODUCTS_RESOURCE = "/products";
  /** 
   * @return A set of Products
   */
  public Set<ProductDto> getProducts();
}

1. Apache CXF:
Apache CXF is a full fledged JAX RS implementation with a client side api as well. It is a framework for JAX-RS and JAX-WS. A client API is provided in three forms, proxy based, HTTP-Centric and XML-centric. Apache Cxf however does not use HTTP Components or Apache HTTP Client. More information on their Client API can be viewed at http://cxf.apache.org/docs/jax-rs.html#JAX-RS-ClientAPI

a. Proxy Based:

public class ApacheCxfProxiedOrdersClient implements OrdersClientIF {
  /**
   * Proxy Definition
   */
  private static interface OrderCxfIF {
    @GET
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE + "/{id}")
    public OrderDto get(@PathParam("id") String id);

    @POST
    @Produces(MediaType.APPLICATION_XML)
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE)
    public OrderDto create(OrderDto dto);

    @PUT
    @Produces(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE + "/{id}")
    public void update(@PathParam("id") String id, OrderDto dto);

    @DELETE
    @Path(ORDERS_RESOURCE + "/{id}")
    public void delete(@PathParam("id") String id);
}
....

public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
  try {
    return JAXRSClientFactory.create(baseUri, OrderCxfIF.class).create(dto);
  }
  catch (WebApplicationException e) {
    if (e.getResponse().getStatus() == Status.BAD_REQUEST.getStatusCode()) {
      throw new OrderValidationException(e.getMessage());
    }
    throw new OrderException(e.getMessage());
  }
}
...
@Override
public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
  try {
    return JAXRSClientFactory.create(baseUri, OrderCxfIF.class).get(String.valueOf(orderId));
  }
  catch (WebApplicationException e) {
    if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {
      throw new OrderNotFoundException(e.getMessage());  
    }
    throw new OrderException(e.getMessage());
  }
}
..
}
With the Proxy based API one can re-use server side artifacts for the client side as well. The API looks pretty straight forward to use and if requiring more control, one can also utilize the WebClient for more detailed operations such as setting header or content type. For handling exceptions, the Cxf site suggests the using the ResponseExceptionMapper. I however could not get the same to be registered and working. The documentation on the same appeared sparse. If one does not define a ResponseExceptionMapper, then when a failure occurs, a WebApplicationException is thrown. One can utilize the same to re-throw appropriate exceptions and consume alternate return types.

b. HTTP-Centric:

public class ApacheCxfOrdersClient implements OrdersClientIF {
....
  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    try {
       return WebClient.create(baseUri).path(ORDERS_RESOURCE).accept(MediaType.APPLICATION_XML)
         .invoke(HttpMethod.POST, dto, OrderDto.class);
    }
    catch (WebApplicationException e) {
      if (e.getResponse().getStatus() == Status.BAD_REQUEST.getStatusCode()) {
        throw new OrderValidationException(e.getMessage());
      } 
      throw new OrderException(e.getMessage());
    }
  }
....
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    try {
      return   WebClient.create(baseUri).path(ORDERS_RESOURCE).path(String.valueOf(orderId)).accept(
         MediaType.APPLICATION_XML).invoke(HttpMethod.GET, null, OrderDto.class); 
    }
    catch (WebApplicationException e) {
      if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException(e.getMessage());
      }
      throw new OrderException(e.getMessage());
    }
  }
}

With the HTTP Centric client, one uses WebClient instances. As in the case of the Proxy, one can catch the WebApplicationException in the case of failure responses for Exception management.

2. RESTEasy:

RESTEasy is a JBoss project that is a fully certified JAX-RS implementation. The client side framework supports the Proxy style model and a HTTP centric model as well. The client side framework utilizes Apache HTTP Client 3.X and has support for 4.X as well thus one has the ability to easily control connection parameters and pooling. The last time I read about the framework, both Http Client versions are supported but the HttpClient 4 has not been validated as well as the 3 version. The same might have changed since then. It is very easy to set one or the other though. Another nice feature is that an interface can be shared between the client and server. Requests can also nicely be intercepted via an implementation of org.jboss.resteasy.spi.interception.ClientExecutionInterceptor to add addition information to the header etc. More information on their Client API can be viewed from the web site http://www.jboss.org/file-access/default/members/resteasy/freezone/docs/1.2.GA/userguide/html/RESTEasy_Client_Framework.html

a. Proxy Based:

public class ResteasyProxiedOrdersClient implements OrdersClientIF {
  private static interface RestEasyIF {
    @GET
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE + "/{id}")
    public OrderDto get(@PathParam("id") Long id);

    @POST
    @Produces(MediaType.APPLICATION_XML)
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE)
    public OrderDto create(OrderDto order);

    @PUT
    @Produces(MediaType.APPLICATION_XML)
    @Consumes(MediaType.APPLICATION_XML)
    @Path(ORDERS_RESOURCE + "/{id}")
    public void update(@PathParam("id") Long id, OrderDto dto);

    @DELETE
    @Path(ORDERS_RESOURCE + "/{id}")
    public void delete(@PathParam("id") Long orderId);
  }

  static {
    RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
    // Execution interceptor registration
    ResteasyProviderFactory.getInstance().registerProvider(ExecutionInterceptor.class);
  }

  private final ClientExecutor clientExecutor;
  private final RestEasyIF delegate;

  public ResteasyProxiedOrdersClient(String baseUri) {
  ...
    clientExecutor = new ApacheHttpClient4Executor(helper.getHttpClient());
    delegate = ProxyFactory.create(RestEasyIF.class, baseUri, clientExecutor);
  }

  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    try {
      return delegate.create(dto);
    }
    catch (ClientResponseFailure failure) {
      if (failure.getResponse().getStatus() == Status.BAD_REQUEST.getStatusCode()) {
        throw new OrderValidationException(failure.getMessage());
      }
      throw new OrderException(failure.getMessage());
    }
  }

  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    try {
      return delegate.get(orderId);
    }
    catch (ClientResponseFailure e) {
      if (e.getResponse().getStatus() == Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException("Order Not found");
    }
    throw new OrderException(e.getMessage());
  }
 }
}

One of the gripes that I had while working with the version of the RESTEasy Proxy is I could not understand why I had to provide an @Consumes annotation on the interface for an update operation that returns "void" and is a PUT HTTP method. The Cxf client did not have that requirement and I feel it redundant to have to specify the same.
Upon failure of an invocation, a ClientResponseFailure is thrown which can then be interrogated to throw any custom exception you desire. I must admit, one thing I have not tested is whether on not, upon receiving a ClientResponseFailure if the response body is not read, will the underlying HttpClient connection be safely released by RESTEasy proxy code for re-use or is there a potential to leak a connection? This is worth investigating if looking at the same.

b. HTTP Centric or Manual Client Request API:

public class ResteasyOrdersClient implements OrdersClientIF {
  ...
  private final ClientExecutor clientExecutor;

  static {
    RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
    ResteasyProviderFactory.getInstance().registerProvider(ExecutionInterceptor.class);
  }

  public ResteasyOrdersClient(String baseUri) {
    ..
    helper = new HttpClientFourHelper();
    clientExecutor = new ApacheHttpClient4Executor(helper.getHttpClient());
  }

  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    ClientResponse response = null;
    try {
      ClientRequest request = new ClientRequest(ORDERS_URI, clientExecutor);
      response = request.body(MediaType.APPLICATION_XML, dto).post();

      if (response.getStatus() 
            == javax.ws.rs.core.Response.Status.BAD_REQUEST.getStatusCode())  {
        throw new OrderValidationException(response.getEntity(String.class));
      }
      return response.getEntity(OrderDto.class);
    }
    catch (OrderValidationException e) {
      throw e;
    }
    catch (Exception e) {
      throw new OrderException(e.getMessage());
    }
    finally {
      // Safe release of connection/stream
     if (response != null) {
       response.releaseConnection();
     }
    }
  }
  ...
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    ClientRequest request = new ClientRequest(ORDERS_URI + "/{id}",
     clientExecutor).pathParameter("id", orderId);
    ClientResponse response = null;
    try {
      response = request.accept(MediaType.APPLICATION_XML).get();
      if (response.getStatus() 
           == javax.ws.rs.core.Response.Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException("Order Not found");
      }
      return response.getEntity(OrderDto.class);
    }
    catch (OrderNotFoundException e) {
      throw e;
    }
    catch (Exception e) {
      throw new OrderException(e.getMessage());
    }
    finally {
      if (response != null) {
        response.releaseConnection();
      }
    }
 }
....
}

With the manual API, one has considerable control on the request object such as setting headers etc. One difference to note that unlike in the case of the Proxy Client, in the event of a failure response, a ClientResponseFailure exception is not thrown. One would need to explicitly work with the ClientResponse object to discern the same and throw any custom exceptions desired. With the manual client, one has the ability to explicitly release the down stream connection to prevent leaks.

3. Restlet:

Restlet is one of the earliest frameworks for RESTful services ever developed if not the earliest. It has a very mature API and implementation and provides multiple ways of working with RESTful services. There is the core API which pre-dates the JAX-RS specification while also having a JAX-RS implementation. They have a mature client API that with their upcoming 2.0 release will utilize HttpClient 4.0. The control over the HTTP Client is a bit hard to get to as the API tends to hide the same. However, their API does allow for most HTTP Client control one would imagine. Again, there are two ways in which one can work with the Restlet client framework, either using Proxy Client or via direct HTTP centric API. Restlet documentation can be viewed at http://www.restlet.org/documentation/2.0/tutorial

a. Proxy Based:

public class RestletProxiedOrdersClient implements OrdersClientIF {
  // Proxy interface
  public static interface OrdersResource {
    @Get
    public OrderDto getOrder();

    @Post
    public OrderDto create(OrderDto dto);

    @Put
    public void update(OrderDto dto);

    @Delete
    public void delete();
  }
  ....

  public OrderDto create(OrderDto orderDto) 
    throws OrderValidationException, OrderException {  
    try {
      ClientResource cr = new ClientResource(ORDERS_URI);
      OrdersResource res = cr.wrap(OrdersResource.class);

      OrderDto result = res.create(orderDto);
      return result;
    }
    catch (ResourceException e) {
      if (e.getStatus().equals(Status.CLIENT_ERROR_BAD_REQUEST)) {
        throw new OrderValidationException(e.getMessage());
      }
      throw new OrderException("Unexpected Error:" + e.getStatus());
    }
  }
  ...
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    ClientResource cr = new ClientResource(ORDERS_URI + "/" + orderId);
    OrdersResource res = cr.wrap(OrdersResource.class);
    try {
      return res.getOrder();
    }
    catch (ResourceException e) {
      if (e.getStatus().equals(Status.CLIENT_ERROR_NOT_FOUND)) {
        throw new OrderNotFoundException(e.getMessage());
      }
      throw new OrderException("Unexpected Error:" + e.getStatus());
   }
 }
..
}

Exception management is handled by catching a ResourceException and throwing any custom exception desired. One area that requires further investigation is how to safely release a HTTP Connection when using HTTP Client as the underlying transport and ClientResource with the Proxy. Again, this is an area one would need to dig into if control of HTTP Client parameters is required along with safe release of pooled connections. For more information on the same look at http://n2.nabble.com/Client-Timeout-on-ClientResource-post-method-td3690842.html.

b. HTTP Centric or Manual Client Request API:

public class RestletOrdersClient implements OrdersClientIF {
  private final Client client;
  ....
  public RestletOrdersClient(String baseUri) {
    client = new Client(new Context(), Protocol.HTTP);
    ..
  }

  public OrderDto create(OrderDto orderDto) throws OrderValidationException, OrderException {
    Response response = null;

    try {
      response = client.post(ORDERS_URI, new JaxbRepresentation<OrderDto>(orderDto));

      if (response.getStatus().isSuccess()) {
        return new JaxbRepresentation<OrderDto>(response.getEntity(), OrderDto.class).getObject();
      }
      else if (response.getStatus().equals(Status.CLIENT_ERROR_BAD_REQUEST)) {
        throw new OrderValidationException("Error validating order");
      }
      else {
        throw new OrderException("Error processing order:" + response.getStatus());
      }
    }
    catch (OrderValidationException e) {
      throw e;
    }
    catch (IOException e) {
      throw new OrderException("Unexpected:" + e);
    }
    finally {
      // Explicit safe release of response
      if (response != null) {
        response.release();
      }
    }
  }

  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    Response response = null;

    try {
      response = client.get(ORDERS_URI + "/" + orderId);
      if (response.getStatus().isSuccess()) {
        return new JaxbRepresentation<OrderDto>(response.getEntity(), OrderDto.class).getObject();
      }
      else if (response.getStatus().equals(Status.CLIENT_ERROR_NOT_FOUND)) {
        throw new OrderNotFoundException("Order Not Found");
      }
        throw new OrderException("Unknown error processing order:" + response.getStatus());
    }
    catch (IOException e) {
      throw new OrderException(e.getMessage());
    }
    finally {
      if (response != null) {
        response.release();
      }
    }
  }
}

The direct Client API is very straight forward as well. With the safe release of Http Connections accounted for and control of the HTTP Client parameters, the Restlet client is a very powerful proven client side implementation.

4. Apache Wink:

Apache Wink is a complete implementation of the JAX-RS specification while providing a client side API to communicate with RESTful services. The framework is relatively new with 1.0-incubating version available at the time of this blog. Apache Wink allows you to work with Http Client and thus control all the lower level operations easily. Unlike the above mentioned frameworks, there is currently no Proxy based client support for Apache Wink. That said, their client API flows very well and is easy to understand and use. An implementation of their ClientHandler interface allows one to easily intercept requests for custom header or security while also providing an avenue to throw custom exceptions based of alternate failure responses. Documentation on the Apache Wink client can be viewed at http://incubator.apache.org/wink/1.0/html/6%20Apache%20Wink%20Client.html

public class ApacheWinkOrdersClient implements OrdersClientIF {
  private final RestClient restClient;
  ...
  public ApacheWinkOrdersClient(String baseUri) {
    ClientConfig config = new ApacheHttpClientConfig(helper.getHttpClient());
    // Exception handler can also be used as an intercepting filter
    config.handlers(new ExceptionHandler());
    restClient = new RestClient(config);   
  }

  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    try {
      return restClient.resource(UriBuilder.fromUri(baseUri)
            .path(ORDERS_RESOURCE).build()).contentType(MediaType.APPLICATION_XML)
            .accept(MediaType.APPLICATION_XML).post(OrderDto.class, dto);
    }
    catch (ClientRuntimeException e) {
      if (e.getCause() instanceof OrderValidationException) {
         throw ((OrderValidationException) e.getCause());
      }
      else if (e.getCause() instanceof OrderException) {
         throw ((OrderException) e.getCause());
      }
      throw e;
    }   
  }
  ...
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    try {
      return restClient.resource(UriBuilder.fromUri(baseUri)
        .path(ORDERS_RESOURCE).path("{id}").build(orderId)).accept(MediaType.APPLICATION_XML)
        .get(OrderDto.class);
    }
    catch (ClientRuntimeException e) {
      if (e.getCause() instanceof OrderNotFoundException) {
        throw ((OrderNotFoundException) e.getCause());
      }
     else if (e.getCause() instanceof OrderException) {
        throw ((OrderException) e.getCause());
     }
     throw e;
   }   
  }
  ....
  private static final class ExceptionHandler implements ClientHandler {
    public ClientResponse handle(ClientRequest request, HandlerContext context) throws Exception {
      // Filter for example for standard headers
      request.getHeaders().add("foo", "bar");

      ClientResponse cr = context.doChain(request);
      if (cr.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException(cr.getMessage());
      }
      else if (cr.getStatusCode() == Status.BAD_REQUEST.getStatusCode()) {
        throw new OrderValidationException(cr.getMessage());
      }
      else if (cr.getStatusCode() == Status.SERVICE_UNAVAILABLE.getStatusCode()) {
        throw new OrderException(cr.getMessage());
      }
      return cr;
   }
 }
}

5. Jersey:

Jersey is sun's implementation of the JAX-RS specification. Jersey like other frameworks provides for a client side framework as well. Jersey supports Apache HTTP Client via a totally separate implementation and artifact. Currently the support exists for HttpClient 3.X, whether 4.X will be incorporated is up in the air. One can choose to write custom code to do the same if Http Client 4.X is the direction one wishes to employ. Jersey does not have the concept of Proxy clients currently. However, their API flows very well with their standard client. Their use of Filters on the client side enables easy interception of requests for customization while providing safe release of any connections used. Information on jersey and their client API can be found at https://jersey.dev.java.net/
public class JerseyOrdersClient implements OrdersClientIF {
  ....
  public JerseyOrdersClient(String baseUri) {
    ..
    ApacheHttpClientHandler handler = new ApacheHttpClientHandler(helper.getHttpClient());
    client = new ApacheHttpClient(handler);
    // Filter allows for intercepting request
    client.addFilter(new RequestFilter());
  }
  ...
  public OrderDto create(OrderDto dto) throws OrderValidationException, OrderException {
    ClientResponse response = null;
    try {
      response = client.resource(baseUri).path(ORDERS_RESOURCE).entity(dto,
        MediaType.APPLICATION_XML).post(ClientResponse.class);
      throwExceptionIfNecessary(response);
      return response.getEntity(OrderDto.class);
    }
    finally {
      if (response != null) {
        response.close();
      }
    }
  }
  ...
  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    ClientResponse response = null;
    try {
      response = client.resource(baseUri).path(ORDERS_RESOURCE)
       .path(String.valueOf(orderId)).accept(MediaType.APPLICATION_XML)
       .get(ClientResponse.class);

      if (response.getStatus() == Status.OK.getStatusCode()) {
        return response.getEntity(OrderDto.class);
      }
      else if (response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
        throw new OrderNotFoundException(response.getEntity(String.class));
      }
      else if (response.getStatus() == Status.SERVICE_UNAVAILABLE.getStatusCode()) {
        throw new OrderException(response.getEntity(String.class));
      }
      throw new OrderException("Unexpected");
   }
   finally {
     if (response != null) {
       response.close();
     }
   }
 }
 ....
 private static final class RequestFilter extends ClientFilter {
   public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
      MultivaluedMap<String, Object> map = cr.getHeaders();
      map.add("foo", "bar");
      return getNext().handle(cr);
   }   
 }
}

6. Spring RestTemplate:

Aah, what do I say. My favorite framework in the whole wide world is now supporting REST with their 3.X release. Like the popular HibernateTemplate, JdbcTemplate we now have RestTemplate with Spring. Spring supports server side JAX-RS and a client API to consume the service with the RestTemplate. RestTemplate can be configured to work with HttpClient 3.X and thus have control over lower level HTTP parameters and pooling pretty easily. The RestTemplate like other thoughtful Spring implementations is based of callbacks where safe release of resources is important. The RestTemplate has simple methods for commonly used HTTP operations while providing the call back mechanism when one desires more control. I must however mention with great restraint that going the call back route is not an easy task and requires considerable customization. This becomes important if you wish to customize the header properties etc. Error handling is easily accomplished with an extention of ResponseErrorHandler. Further information on the RestTemplate can be viewed at the following location http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/remoting.html#rest-client-access

public class SpringOrdersClient implements OrdersClientIF {
  private final RestTemplate template;
  ....
  public SpringOrdersClient(String baseUri) {
    ...
    ClientHttpRequestFactory requestFactory
       = new CommonsClientHttpRequestFactory(helper.getHttpClient());
    template = new RestTemplate(requestFactory);
    // Set Error handler
    template.setErrorHandler(new ErrorHandler());
  }

  public OrderDto create(OrderDto orderDto) throws OrderValidationException, OrderException {
    return template.postForObject(ORDERS_URI, orderDto, OrderDto.class);      
  }

  public OrderDto get(Long orderId) throws OrderNotFoundException, OrderException {
    return template.getForObject(ORDERS_URI + "/{id}", OrderDto.class,
        Collections.singletonMap("id", String.valueOf(orderId)));
  }
  ....
  private static final class ErrorHandler implements ResponseErrorHandler {
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
      if (response.getStatusCode().series().compareTo(Series.SUCCESSFUL) == 0) {
        return false;
      }
      return true;
    }

    public void handleError(ClientHttpResponse response) throws IOException {
       if (response.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
          throw new OrderNotFoundException("Order Not Found");
       } else if (response.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
          throw new OrderValidationException("Error validating order");
       } else {
          throw new OrderException("Unexpected error:" + response.getStatusText());
       }
    }  
  }
}

Running the Examples:

The example is available as JDK 6.X project that uses maven. An integration test is provided that uses each of the different mentioned clients to communicate the very same life cycle test for CRUD operations with the RESTful service. Once you have a maven environment, execute "mvn install" from the root level of the project to see the entire project build and execute the integration test where each of the clients are demonstrated. For those interested, you could use the clients to determine benchmarks, test for memory foot print, what have you.
Download the source from HERE.

Parting Thoughts:
I hope the above examples are of use to someone evaluating REST frameworks for their project and would like to witness each of them in action. I have been a REST fanatic ever since introduced to the architectural style and simply enjoy playing with frameworks and tools that present themselves. It would have been nice if during jsr 311 the expert body had defined a client side api as well. I beleive a future JSR is expected for a client API to JAX-RS.
One can details about each of the above mentioned projects and metrics about the same such as commiters, activity, maturing etc from Oh Loh. That said, I would like to share my 2c on the above frameworks. Note that these are my own 2c that I am pulling out of my b..t and is not in any way my employers views or thoughts on the matter.

Apache Cxf:
The one word that comes to mind when I view this framework is BLOAT. It seems to bring in so many dependencies that are of no use if ones goal is simply to work with REST. Their documentation on the REST clients API was not the best with broken links. It is my understanding that they do not support Apache HTTP Client and there is no drive to do the same. If a team is supporting JAX-WS and JAX-RS, maybe their framework works in synergy. You will notice that among all the frameworks mentioned above, I never bothered to support an example of CXF, the primary reason for the same is my disillusionment with their documentation and WIKI where I encountered broken links and partial information. I also ran into an issue where I needed to have HttpServletRequest on the client as a dependency.

RestEasy:
In RestEasy, you have me as a fan. I really like the effort put into the framework by its owner Bill Burke and his team. Their proxy solution is very enticing and will serve to address most RESTful consumers. I might be pipe dreaming here but never the less, I recall reading somewhere that the RESTEasy's implementation of the client library will be serving as the foundation of the client side equivalent of JSR 311. Active development, with support for ASYNC HTTP along with good documentation is what they bring to the table. It is an especially notable consideration to use RESTEasy if re-use across service and client for "contract" purposes is desired. One additional point of note is the philosphy that RestEasy employs regarding backward compatibilty. They seem very concious regarding the direction they employ with change to ensure backward compatibility.

Restlet:
Restlet is an established and proven framework. One of the things I particularly like about Restlet is that their community is very active in offering assitance to those in need. If you post a question on their mailing list, you can almost be guaranteed a response as long as the question is within answerable parameters. They are very quality concious as well. One gripe that I do have have with Restlet is that they have chosen to break compatiblity between their 1.1.X series of releases when moving to their 2.0.X series without a transitionary phase.
The selling point of Restlet as a client API is their transparent API and simplicitly, coupled with their helpful community.

Apache Wink:
YAJAXRS (Yet another JAX-RS implementation) is my initial reaction when thinking of Apache Wink. However, when I look further, although comparitively immature in the space, they have a solid offering in WINK. If you are looking for a light weight JAX-RS implementation, look no further. Their client API utilizes Http Client 4.0 to effect and in my tests with the API, it found it really fast and performant. Their API is simple, transparent and effective. Their documentation is however sparse and I wonder regarding their longevity when compared with the big hitters such as jersey, restlet and resteasy.

jersey:
jersey, as I said in a previous blog, home town of the boss, rocks. What appeals to me is the simplicity of the API, the adoption, the community and decent documentation regarding the API. Their support for Http Client 3.X is present. I am certain they will support 4.X soon.

spring:
Its hard to say anything about spring without seeming biased in favor. Spring's RestTemplate is as solid as can be expected. Their standard call back based approach for safe release of resources works well even for the REST template. If one is using Spring-mvc and their REST support, very few reasons would drive me to consider an alternative framework for the client. One among them is definitely finer grained control, another is Http Client 4.X support. Documentation is sparse on RestTemplate as well. But one has a community to back up on. There might a bit of up front customizations, standard call back etc that an adopter might create but once done I feel that it would be a easy to work with the RestTemplate.
Clearly one has many choices in selecting a client side framework for RESTful HTTP. In most cases it probably makes sense to use the same framework for the service and client end. Then again, if you are only a consumer of a service you have multiple choices among those shown above as well as the option of using Apache HTTP Client or Components directly and bypassing the higher level frameworks. For some, integrating with the spring framework is important and all the above frameworks have means of integration points, both on the service and client sides. Support for Client Proxies is something one might want to consider as they tend to simplify the programming model. Further if Resource definitions can be shared among client server, that can be quite useful in being DRY (Don't repeat yourself) and provide means for contract definition. For those interested in performance and tuning of the HTTP Connections, using a framework that allows you to manage connection pooling and other parameters is definitely the way to go. One should also look at the maturity, user base, support, back ward compatibility support when making a selection. Are there other options apart from the above mentioned? In addition, any recommendations based of personal experience with the above mentioned client frameworks is always welcomed.

7 comments:

Anonymous said...

Really appreciate the effort taken to provide the trade offs

Looking forward to see these sort of articles.

Dave Stockmann said...

I see you're still using dozer. Thanks for the example - it's coming in handy.

Sanjay Acharya said...

@Dave
Yeah, dozer is still cool :-). Would be nice if they had annotation support that was really strong. I didn't like the dozer annotation project much.

Glad the example is of use

Lars Clausen said...

I see you just give the ID for the delete methods. I'm looking into doing optimistic locking in a REST context, and for create and update it's easy - the object can carry a lock token back to the server. I'm unsure what to do for delete, though: Add a path parameter (probably not), add a query parameter (possibly ok, a little ugly), or pass the entire entity (overkill). Any thoughts?

Sanjay Acharya said...

The ID for the delete is to identify the resource. Delete like PUT should be an idempotent operation. I am not certain I understand why you would need "optimistic-locking" for the delete operation. In other words, if you call delete on the resource from more than one thread, end result of both the operations should be deleting the resource. Even after deleting the resource, calling delete on the same resource is the same as when the resource was present. I hope I understood your question correctly?

Kelly Davis said...

For the Wink example, your implementation of ExceptionHandler will leave connections open.

Before you throw the custom exception, you need to call consumeContent() on the ClientResponse to avoid this.

Sanjay Acharya said...

Hi Kelly thanks for the input. In the ExceptionMapper for Wink, in the case of excepted exceptions, it is performing a clientResponse.getMessage(). Will not doing the same free the stream?