JBoss.orgCommunity Documentation

Chapter 1. Client Framework

1.1. Fluent interface
1.2. Client proxies
1.3. Client side error handling
1.4. Maven considerations

The two principal client side classes in Resteasy 2 are ClientRequest and ClientResponse:

ClientRequest request = new ClientRequest("http://localhost:8081/test");
request.body("text/plain", "hello world");
ClientResponse<?> response = request.post();
String result = response.getEntity(String.class);
   

ClientRequest holds the target URL and entity, if any. ClientResponse holds the response entity, which can be extracted by the getEntity() method.

In JAX-RS 2.0, these classes are replaced by four classes that support a fluent call pattern: Client, WebTarget, Invocation.Builder, and Response:

Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8081/test");
Invocation.Builder builder = target.request();
Entity<String> entity = Entity.entity("hello world", "text/plain");
Response response = builder.post(entity);
String result = response.readEntity(String.class);
   

The invocation process begins with Client, whose primary responsibility is to create a WebTarget. Clients are somewhat expensive to build, so it often makes sense to reuse a Client to create multiple WebTargets.

Resteasy extends ClientBuilder and Client with methods that allow the registration of providers:

static class TestWriter implements MessageBodyWriter<String>
{
   @Override
   public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
   {
      return false;
   }

   @Override
   public long getSize(String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
   {
      return 0;
   }

   @Override
   public void writeTo(String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
         MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
         throws IOException, WebApplicationException
   {
      //
   }
}

ResteasyClientBuilder clientBuilder = new ResteasyClientBuilder();
Client client = clientBuilder.register(TestWriter.class).build();
   

All Clients created by that ResteasyClientBuilder, and all invocations on all WebTargets created by those Clients, will have TestWriter available.

WebTarget, as its name implies, constructs and holds a URL which targets a server side resource. It has various options for extending and manipulating URIs:

WebTarget target = client.target("http://localhost:8081/test/{index}");
WebTarget target1 = target.resolveTemplate("index", "1");
WebTarget target2 = target.resolveTemplate("index", "2");
   

Here, two new WebTargets are created from the original target, each with a different ending path segment. Query and matrix parameters can also be appended:

WebTarget target3 = target2.queryParam("x", "y");
   

Here, target3 targets "http://localhost:8081/test/2?x=y".

Resteasy also extends WebTarget with the ability to register providers:

Client client = ClientBuilder.newClient();
String url = "http://localhost:8081/test/{index}";
WebTarget target = client.target(url).register(TestWriter.class);
WebTarget target1 = target.resolveTemplate("index", "1");  
WebTarget target2 = target.resolveTemplate("index", "2");  
WebTarget target3 = target2.queryParam("x", "y");
   

Here, TestWriter is available to all invocations on target1, target2, and target3.

Invocation.Builder plays a role similar to the old ClientRequest:

Response response = builder.header("User-Agent", "Mozilla/5.0").get();
   

or

 
String s = builder.header("User-Agent", "Mozilla/5.0").get(String.class);
   

Finally, note that Response, unlike the old ClientResponse<T>, is not a generic type, so it is necessary to give a type when extracting a response entity:

String result = response.readEntity(String.class);
   

Note. Response.getEntity() still exists, but it plays a different role, which could easily lead to bugs. It is necessary to call readEntity() to extract the response entity. If getEntity() is called instead, it will return null.

Note. Unlike the old getEntity(), readEntity() is not idempotent. Once it is called, the response is closed, and subsequent calls will throw an IllegalStateException. This behavior can be circumvented by calling Response.bufferEntity() before calling readEntity(). I.e., this will work:

response.bufferEntity();
System.out.println(response.readEntity(String.class));
System.out.println(response.readEntity(String.class));
   

The client framework in Resteasy 2 included a facility for interacting with JAX-RS resources through client side POJOs, not unlike the Java RMI facility:

@Path("/test")
public static interface TestResource
{
   @GET
   @Produces("text/plain")
   public String test();
}

public void testProxy() throws Exception
{
   String url = "http://localhost:8081";
   TestResource pojo = ProxyFactory.create(TestResource.class, url);
   String result = pojo.test();
   

This technique avoids a lot of complications, but, perhaps because it is perceived as not being in the RESTful spirit, it is not part of the JAX-RS 2.0 client framework. It still exists in Resteasy 3, but in a re-worked form that fits into the official client framework. Now, a proxy is created by a call to ResteasyWebTarget.proxy():

Client client = ClientBuilder.newClient();
String url = "http://localhost:8081";
ResteasyWebTarget target = (ResteasyWebTarget) client.target(url);
TestResource pojo = target.proxy(TestResource.class);
String result = pojo.test();
   

Resteasy 2 had two facilities for handling errors on the client side.

An instance of an org.jboss.resteasy.client.core.ClientErrorInterceptor could be registered to handle exceptions thrown during a proxied call. Also, an instance of an org.jboss.resteasy.client.exception.mapper.ClientExceptionMapper could be registered to map exceptions thrown during a proxied call. A default ClientExceptionMapper was installed that mapped exceptions thrown by the HttpClient transport layer to Resteasy specific analogs. For example, an org.apache.http.client.ClientProtocolException would be mapped to an org.jboss.resteasy.client.exception.ResteasyClientProtocolException.

These two facilities do not exist in Resteasy 3. Instead, the JAX-RS 2.0 specification mandates the use of javax.ws.rs.ProcessingException and javax.ws.rs.client.ResponseProcessingException. In particular, exceptions thrown while processing a request should be mapped to a ProcessingException, and exceptions thrown while processing a response should be mapped to a ResponseProcessingException.

For example, the ProcessingException javadoc lists possible conditions leading to a ProcessingException:

  • failures in filter or interceptor chain execution
  • errors caused by missing message body readers or writers for the particular Java type and media type combinations
  • propagated java.io.IOExceptions thrown by javax.ws.rs.ext.MessageBodyReaders and javax.ws.rs.ext.MessageBodyWriters during entity serialization and de-serialization

Note that ProcessingException and ResponseProcessingException represent internal problems. If the client side receives a response with status codes 3xx, 4xx or 5xx, it will map the response to an instance of javax.ws.rs.WebApplicationException or one of its subclasses.

In Resteasy 2, the client framework lives in the resteasy-jaxrs module. In Resteasy 3, it has its own module, resteasy-client:

<dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-client</artifactId>
   <version>${project.version}</version>
</dependency>