JBoss.orgCommunity Documentation

Chapter 44. Client Framework

44.1. Abstract Responses
44.2. Sharing an interface between client and server
44.3. Client Error Handling
44.4. Manual ClientRequest API
44.5. Spring integration on client side
44.6. Transport Layer

The Resteasy Client Framework is the mirror opposite of the JAX-RS server-side specification. Instead of using JAX-RS annotations to map an incoming request to your RESTFul Web Service method, the client framework builds an HTTP request that it uses to invoke on a remote RESTful Web Service. This remote service does not have to be a JAX-RS service and can be any web resource that accepts HTTP requests.

Resteasy has a client proxy framework that allows you to use JAX-RS annotations to invoke on a remote HTTP resource. The way it works is that you write a Java interface and use JAX-RS annotations on methods and the interface. For example:

public interface SimpleClient
{
   @GET
   @Path("basic")
   @Produces("text/plain")
   String getBasic();

   @PUT
   @Path("basic")
   @Consumes("text/plain")
   void putBasic(String body);

   @GET
   @Path("queryParam")
   @Produces("text/plain")
   String getQueryParam(@QueryParam("param")String param);

   @GET
   @Path("matrixParam")
   @Produces("text/plain")
   String getMatrixParam(@MatrixParam("param")String param);

   @GET
   @Path("uriParam/{param}")
   @Produces("text/plain")
   int getUriParam(@PathParam("param")int param);
}

Resteasy has a simple API based on Apache HttpClient. You generate a proxy then you can invoke methods on the proxy. The invoked method gets translated to an HTTP request based on how you annotated the method and posted to the server. Here's how you would set this up:

            import org.jboss.resteasy.client.ProxyFactory;
            ...
            // this initialization only needs to be done once per VM
            RegisterBuiltin.register(ResteasyProviderFactory.getInstance());


            SimpleClient client = ProxyFactory.create(SimpleClient.class, "http://localhost:8081");
            client.putBasic("hello world");
        

Please see the ProxyFactory javadoc for more options. For instance, you may want to fine tune the HttpClient configuration.

@CookieParam works the mirror opposite of its server-side counterpart and creates a cookie header to send to the server. You do not need to use @CookieParam if you allocate your own javax.ws.rs.core.Cookie object and pass it as a parameter to a client proxy method. The client framework understands that you are passing a cookie to the server so no extra metadata is needed.

The client framework can use the same providers available on the server. You must manually register them through the ResteasyProviderFactory singleton using the addMessageBodyReader() and addMessageBodyWriter() methods.

        ResteasyProviderFactory.getInstance().addMessageBodyReader(MyReader.class);
    

The framework also supports the JAX-RS locator pattern, but on the client side. So, if you have a method annotated only with @Path, that proxy method will return a new proxy of the interface returned by that method.

Sometimes you are interested not only in the response body of a client request, but also either the response code and/or response headers. The Client-Proxy framework has two ways to get at this information

You may return a javax.ws.rs.core.Response.Status enumeration from your method calls:

@Path("/")
public interface MyProxy {
   @POST
   Response.Status updateSite(MyPojo pojo);
}
            

Internally, after invoking on the server, the client proxy internals will convert the HTTP response code into a Response.Status enum.

If you are interested in everything, you can get it with the org.jboss.resteasy.spi.ClientResponse interface:

/**
 * Response extension for the RESTEasy client framework. Use this, or Response
 * in your client proxy interface method return type declarations if you want
 * access to the response entity as well as status and header information.
 *
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public abstract class ClientResponse<T> extends Response
{
   /**
    * This method returns the same exact map as Response.getMetadata() except as a map of strings 
    * rather than objects.
    *
    * @return
    */
   public abstract MultivaluedMap<String, String> getHeaders();

   public abstract Response.Status getResponseStatus();

   /**
    * Unmarshal the target entity from the response OutputStream.  You must have type information
    * set via <T> otherwise, this will not work.
    * <p/>
    * This method actually does the reading on the OutputStream.  It will only do the read once.  
    * Afterwards, it will cache the result and return the cached result.
    *
    * @return
    */
   public abstract T getEntity();

   /**
    * Extract the response body with the provided type information
    * <p/>
    * This method actually does the reading on the OutputStream.  It will only do the read once.  
    * Afterwards, it will cache the result and return the cached result.
    *
    * @param type
    * @param genericType
    * @param <T2>
    * @return
    */
   public abstract <T2> T2 getEntity(Class<T2> type, Type genericType);

   /**
    * Extract the response body with the provided type information.  GenericType is a trick used to
    * pass in generic type information to the resteasy runtime.
    * <p/>
    * For example:
    * <pre>
    * List<String> list = response.getEntity(new GenericType<List<String>() {});
    * <p/>
    * <p/>
    * This method actually does the reading on the OutputStream.  It will only do the read once.  Afterwards, it will
    * cache the result and return the cached result.
    *
    * @param type
    * @param <T2>
    * @return
    */
   public abstract <T2> T2 getEntity(GenericType<T2> type);
}

        

All the getEntity() methods are deferred until you invoke them. In other words, the response OutputStream is not read until you call one of these methods. The empty parameter getEntity() method can only be used if you have templated the ClientResponse within your method declaration. Resteasy uses this generic type information to know what type to unmarshal the raw OutputStream into. The other two getEntity() methods that take parameters, allow you to specify which Object types you want to marshal the response into. These methods allow you to dynamically extract whatever types you want at runtime. Here's an example:

@Path("/")
public interface LibraryService {

   @GET
   @Produces("application/xml")
   ClientResponse<LibraryPojo> getAllBooks();
}

We need to include the LibraryPojo in ClientResponse's generic declaration so that the client proxy framework knows how to unmarshal the HTTP response body.

It is generally possible to share an interface between the client and server. In this scenario, you just have your JAX-RS services implement an annotated interface and then reuse that same interface to create client proxies to invoke on the client-side. One caveat to this is when your JAX-RS methods return a Response object. The problem on the client is that the client does not have any type information with a raw Response return type declaration. There are two ways of getting around this. The first is to use the @ClientResponseType annotation.

import org.jboss.resteasy.annotations.ClientResponseType;
import javax.ws.rs.core.Response;

@Path("/")
public interface MyInterface {

   @GET
   @ClientResponseType(String.class)
   @Produces("text/plain")
   public Response get();
}

This approach isn't always good enough. The problem is that some MessageBodyReaders and Writers need generic type information in order to match and service a request.

@Path("/")
public interface MyInterface {

   @GET
   @Produces("application/xml")
   public Response getMyListOfJAXBObjects();
}

In this case, your client code can cast the returned Response object to a ClientResponse and use one of the typed getEntity() methods.

MyInterface proxy = ProxyFactory.create(MyInterface.class, "http://localhost:8081");
ClientResponse response = (ClientResponse)proxy.getMyListOfJAXBObjects();
List<MyJaxbClass> list = response.getEntity(new GenericType<List<MyJaxbClass>>());

If you are using the Client Framework and your proxy methods return something other than a ClientResponse, then the default client error handling comes into play. Any response code that is greater tha 399 will automatically cause a org.jboss.resteasy.client.ClientResponseFailure exception

   @GET
   ClientResponse<String> get() // will throw an exception if you call getEntity()

   @GET
   MyObject get(); // will throw a ClientResponseFailure on response code > 399

In cases where Client Proxy methods do not return Response or ClientResponse, it may be not be desirable for the Client Proxy Framework to throw generic ClientResponseFailure exceptions. In these scenarios, where more fine-grained control of thrown Exceptions is required, the ClientErrorInterceptor API may be used.

public static T getClientService(final Class clazz, final String serverUri)
{
	ResteasyProviderFactory pf = ResteasyProviderFactory.getInstance();
	pf.addClientErrorInterceptor(new DataExceptionInterceptor());

	System.out.println("Generating REST service for: " + clazz.getName());
	return ProxyFactory.create(clazz, serverUri);
}

ClientErrorInterceptor provides a hook into the proxy ClientResponse request lifecycle. If a Client Proxy method is called, resulting in a client exception, and the proxy return type is not Response or ClientResponse, registered interceptors will be given a chance to process the response manually, or throw a new exception. If all interceptors successfully return, RestEasy will re-throw the original encountered exception. Note, however, that the response input stream may need to be reset before additional reads will succeed.

public class ExampleInterceptor implements ClientErrorInterceptor
{
	public void handle(ClientResponse response) throws RuntimeException
	{
		try
		{
			BaseClientResponse r = (BaseClientResponse) response;
			InputStream stream = r.getStreamFactory().getInputStream();
			stream.reset();
			
			String data = response.getEntity(String.class);
		
		
			if(FORBIDDEN.equals(response.getResponseStatus()))
			{
				throw new MyCustomException("This exception will be thrown "
					+ "instead of the ClientResponseFailure");
			}
		
		}
		catch (IOException e)
		{
			//...
		}
		// If we got here, and this method returns successfully, 
		// RESTEasy will throw the original ClientResponseFailure
	}
}

Resteasy has a manual API for invoking requests: org.jboss.resteasy.client.ClientRequest See the Javadoc for the full capabilities of this class. Here is a simple example:


   ClientRequest request = new ClientRequest("http://localhost:8080/some/path");
   request.header("custom-header", "value");

   // We're posting XML and a JAXB object
   request.body("application/xml", someJaxb);

   // we're expecting a String back
   ClientResponse<String> response = request.post(String.class);
   
   if (response.getStatus() == 200) // OK!
   {
      String str = response.getEntity();
   }

When using spring you can generate a REST client proxy from an interface with the help of org.jboss.resteasy.client.spring.RestClientProxyFactoryBean.

<bean id="echoClient" class="org.jboss.resteasy.client.spring.RestClientProxyFactoryBean"
    p:serviceInterface="a.b.c.Echo" p:baseUri="http://server.far.far.away:8080/echo" />

Network communication between the client and server is handled in Resteasy, by default, by HttpClient (4.x) from the Apache HttpComponents project. In general, the interface between the Resteasy Client Framework and the network is found in an implementation of org.jboss.resteasy.client.ClientExecutor, and org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor, which uses HttpClient (4.x), is the default implementation. Resteasy also ships with the following client executors, all found in the org.jboss.resteasy.client.core.executors package:

The choice of a default executor may be overriden by calling ClientRequest.setDefaultExecutorClass():

ClientRequest.setDefaultExecutorClass("org.bluemonkeydiamond.MyClientExecutor");
     

and a client executor may be passed to a specific ClientRequest:

ClientExecutor executor = new MyClientExecutor();
ClientRequest request = new ClientRequest("http://localhost:8081/customer", executor);
     

or to a specific proxy:

ClientExecutor executor = new MyClientExecutor();
SimpleClient client = ProxyFactory.create(SimpleClient.class, "http://localhost:8081/customer", executor);
     

Resteasy and HttpClient make reasonable default decisions so that it is possible to use the client framework without ever referencing HttpClient, but for some applications it may be necessary to drill down into the HttpClient details. ApacheHttpClient4Executor can be supplied with an instance of org.apache.http.client.HttpClient and an instance of org.apache.http.protocol.HttpContext, which can carry additional configuration details into the HttpClient layer. For example, authentication may be configured as follows:

// Configure HttpClient to authenticate preemptively
// by prepopulating the authentication data cache.
 
// 1. Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
 
// 2. Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put("com.bluemonkeydiamond.sippycups", basicAuth);
 
// 3. Add AuthCache to the execution context
BasicHttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
 
// 4. Create client executor and proxy
httpClient = new DefaultHttpClient();
ApacheHttpClient4Executor executor = new ApacheHttpClient4Executor(httpClient, localContext);
client = ProxyFactory.create(BookStoreService.class, url, executor);
     

One default decision made by HttpClient and adopted by Resteasy is the use of org.apache.http.impl.conn.SingleClientConnManager, which manages a single socket at any given time and which supports the use case in which one or more invocations are made serially from a single thread. For multithreaded applications, SingleClientConnManager may be replaced by org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager:

ClientConnectionManager cm = new ThreadSafeClientConnManager();
HttpClient httpClient = new DefaultHttpClient(cm);
ClientExecutor executor = new ApacheHttpClient4Executor(httpClient);
client = ProxyFactory.create(BookStoreService.class, url, executor);
     

For more information about HttpClient (4.x), see the documentation at http://hc.apache.org/httpcomponents-client-ga/tutorial/html/.

Note. It is important to understand the difference between "releasing" a connection and "closing" a connection. Releasing a connection makes it available for reuse. Closing a connection frees its resources and makes it unusable.

SingleClientConnManager manages a single socket, which it allocates to at most a single invocation at any given time. Before that socket can be reused, it has to be released from its current use, which can occur in one of two ways. If an execution of a ClientRequest or a call on a proxy returns a class other than ClientResponse, then Resteasy will take care of releasing the connection. For example, in the fragments

ClientRequest request = new ClientRequest("http://localhost:8081/customer/123");
String answer = request.getTarget(String.class);
     

or

RegistryStats stats = ProxyFactory.create(RegistryStats.class, "http://localhost:8081/customer/123");
RegistryData data = stats.get();
     

Resteasy will release the connection under the covers. The only counterexample is the case in which the response is an instance of InputStream, which must be closed explicitly.

On the other hand, if the result of an invocation is an instance of ClientResponse, then one of two additional steps must be taken to release the connection. If some version of the overloaded method ClientResponse.getEntity() is called, then Resteasy will release the connection (unless the entity is an instance of InputStream). If the entity is ignored, then the connection must be released explicitly:

ClientRequest request = new ClientRequest("http://localhost:8081:/customer/123"));
ClientResponse<?> response = request.get();
System.out.println(response.getStatus());
response.releaseConnection();
response = request.delete("123");
System.out.println(response.getStatus());
response.releaseConnection();
     

Again, releasing a connection only makes it available for another use. It does not normally close the socket.

On the other hand, ApacheHttpClient4Executor.finalize() will close any open sockets, but only if it created the HttpClient it has been using. If an HttpClient has been passed into the ApacheHttpClient4Executor, then the user is responsible for closing the connections:

HttpClient httpClient = new DefaultHttpClient();
ApacheHttpClient4Executor executor = new ApacheHttpClient4Executor(httpClient);
ClientRequest request = new ClientRequest("http://localhost:8081/customer"), executor);
...
httpClient.getConnectionManager().shutdown();
     

Note that if ApacheHttpClient4Executor has created its own instance of HttpClient, it is not necessary to wait for finalize() to close open sockets. The ClientExecutor interface has a close() method for this purpose:

ClientRequest request = new ClientRequest("http://localhost:8081/customer/123"));
ClientResponse<Customer> response = request.get(Customer.class);
response.releaseConnection();
request.delete();
request.getExecutor().close();
     

The call to ClientResponse.releaseConnection() makes the underlying connection available for the delete() invocation. The call to ClientRequest.getExecutor().close() closes the underlying connection.