Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Wiki Markup
{span:style=font-size:2em;font-weight:bold} JAX-RS : Client API {span}

{toc}

h1. Proxy-based API

With the proxy-based API, one can reuse on the client side the interfaces or even the resource classes which have already been designed for processing the HTTP requests on the server side (note that a cglib-nodeps dependency need to be available on the classpath for proxies created from concrete classes). When reused on the client side, they simply act as the remote proxies.

[JAXRSClientFactory|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java] is a utility class which wraps [JAXRSClientFactoryBean|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java]. JAXRSClientFactory has a number of utility methods but JAXRSClientFactoryBean can be used directly when needed.

For example, given these class definitions :

{code:java}
@Path("/bookstore")
public interface BookStore {
   @GET
   Books getAllBooks();
   
   @Path("{id}")
   BookResource getBookSubresource(@PathParam("id") long id) throws NoBookFoundException;
}

public class BookStoreImpl implements BookStore {
   public Books getAllBooks() {}
   
   public Book getBookSubresource(long id) throws NoBookFoundException {}
}

public interface BookResource {
   @GET
   Book getDescription();
}

public class BookResourceImpl implements BookResource {
   @GET
   Book getDescription() {}
}

{code}

the following client code retrieves a Book with id '1' and a collection of books: 

{code:java}
BookStore store = JAXRSClientFactory.create("http://bookstore.com", BookStore.class);
// (1) remote GET call to http://bookstore.com/bookstore
Books books = store.getAllBooks();
// (2) no remote call
BookResource subresource = store.getBookSubresource(1);
// {3} remote GET call to http://bookstore.com/bookstore/1
Book b = subresource.getDescription();
{code}     

When proxies are created, initially or when subresource methods are invoked, the current URI is updated with corresponding \@Path, \@PathParam, \@QueryParam or @MatrixParam values, while \@HttpHeader and \@CookieParam values contribute to the current set of HTTP headers. Same happens before the remote invocation is done. 

It is important to understand that strictly speaking there is no direct relationship between a given method on the client side and the same one on the server side. The job of the proxy is to construct a correct URI according to a given class and method specifications - it may or may not be the same method on the corresponding server class that will be invoked (provided of course that it is a JAX-RS annotated server resource class - but it may not be the case !). More often than not, you will see a method foo() invoked on a server resource class whenever the same method is invoked on the corresponding remote proxy - but in the presence of \@Path annotations with arbitrary regular expressions is is not guaranteed - never mind, the most important things is that a proxy will produce a correct URI and it will be matched as *expected* by a server class.   

MessageBodyReaders and MessageBodyWriters are used to process request or response bodies, same way as on the server side. More specifically. method body writers are invoked whenever a remote method parameter is assumed to be a request body (that is, it has no JAX-RS annotations attached) or when a form submission is emulated with the help of either \@FormParams or JAX-RS MultivaluedMap. 

You can make multiple remote invocations on the same proxy (initial or subresource), the current URI and headers are updated properly. 

If you would like to proxify concrete classes such as BookStoreImpl for example (say you can not extract interfaces), then drop a cglib-nodeps.jar on a classpath. Such classes must have a default constructor. All the methods which have nothing to do with JAX-RS will simply be ignored on the client side and marked as unsupported.

h2. Customizing proxies 

Proxies end up implementing not only the interface requested at the proxy creation time but also a [Client|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/Client.java] interface. In many cases one does not need to explicitly specify commonly used HTTP headers such as Content-Type or Accept as this information will likely be available from \@Consumes or \@Produces annotations. At the same time you may to explicitly set either of these headers, or indeed some other header. You can use a simple [WebClient|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java] utility method for converting a proxy to a base client :

{code:java}
BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);
WebClient.client(proxy).accept("text/xml");
// continue using the proxy    
{code}

You can also check a current set of headers, current and base URIs and a client Response.

h2. Converting proxies to Web Clients and vice versa

Using proxies is just one way how you can consume a service. Proxies hide away the details of how URIs are being composed while HTTP-centric WebClients provide for an explicit URI creation. Both proxies and http clients rely on the same base information such as headers and the current URI so at any moment of time you can create a WebClient instance out of the existing proxy :

{code:java}
BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);
WebClient client = WebClient.create(proxy);
// continue using the http client    
{code}

At any moment of time you can convert an http client into a proxy too :

{code:java}
BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);
WebClient client = WebClient.create(proxy);
BookStore proxy = JAXRSClientFactory.fromClient(client, BookStore.class);
{code}

h2. Handling exceptions

There is a couple of ways you can handle remote exceptions with proxies.
One approach is to register a [ResponseExceptionMapper|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ResponseExceptionMapper.java] as a provider either from Spring using a jaxrs:client or using a corresponding JAXRSClientFactory utility method. This way you can map remote error codes to expected checked exceptions or runtime exceptions if needed.
 
If no ResponseExceptionMapper is available when a remote invocation failed then an org.apache.cxf.jaxrs.client.ServerWebApplicationException (which is an instance of JAX-RS WebApplication) will be thrown. At this point of time you can check the actual Response and proceed from there :

{code:java}
BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);
try {
    proxy.getBook();
} catch(ServerWebApplicationException ex) {
  Response r = ex.getResponse();
  String message = ex.getMessage();
}
{code}

org.apache.cxf.jaxrs.client.ClientWebApplicationException will be thrown if the exception has occurred for one of two reasons : 
- the remote invocation succeeded but no proper MessageBodyReader has been found on the client side; in this case the Response object representing the result of the invocation will still be available
- the remote invocation has failed for whatever reasons on the client side, example, no MessageBodyWriter is available.

h2. Configuring proxies in Spring

When creating a proxy with JAXRSClientFactory, you can pass a Spring configuration location as one of the arguments. Or you can create a default bus using a spring configuration and all proxies will pick it up :

{code:java}
SpringBusFactory bf = new SpringBusFactory();
Bus bus = bf.createBus("org/apache/cxf/systest/jaxrs/security/jaxrs-https.xml");
BusFactory.setDefaultBus(bus);
// BookStore proxy will get the configuration from Spring
BookStore proxy = JAXRSClientFactory.create("http://books", BookStore.class);
{code} 

h2. Injecting proxies

For injecting proxies via a spring context, use the jaxrs:client element like:
{code:xml}
  <jaxrs:client id="restClient"
         address="http://localhost:${testutil.ports.BookServerRestSoap}/test/services/rest"
         serviceClass="org.apache.cxf.systest.jaxrs.BookStoreJaxrsJaxws"
         inheritHeaders="true">
         <jaxrs:headers>
             <entry key="Accept" value="text/xml"/>
         </jaxrs:headers>
  </jaxrs:client>  
{code}

See this [bean|http://svn.apache.org/repos/asf/cxf/trunk/systests/jaxrs/src/test/resources/jaxrs_soap_rest/WEB-INF/beans.xml] for a full example how jaxrs:client can be used to inject a proxy 

h2. Limitations

Proxy methods can not have \@Context method parameters and subresource methods returning Objects can not be invoked - perhaps it is actually not too bad at all - please inject contexts as field or bean properties and have subresource methods returning typed classes : interfaces, abstract classes or concrete implementations. 

When a proxy method returning JAX-RS Response is invoked, the returned Response.getEntity() will return a response InputStream by default. Starting from CXF 2.4.0-SNAPSHOT one can register an org.apache.cxf.jaxrs.client.ResponseReader provider and cast the Response.getEntity() to more specific application classes :

{code:java}
ResponseReader reader = new ResponseReader();
reader.setEntityClass(Book.class);
        
BookStore bs = JAXRSClientFactory.create("http://localhost:8080/books", BookStore.class,
                                         Collections.singletonList(reader));
Response r1 = bs.getBook("123");
Book book = (Book)r1.getEntity();

reader.setEntityClass(Author.class);
Response r2 = bs.getBookAuthor("123");
Author book = (Author)r2.getEntity();
{code}

h2. Working with user models

Proxies can be created with the external user model being applied to a proxy class, for example :

{code:java}
JAXRSClientFactory.createFromModel("http://books", BookNoAnnotations.class, "classpath:/resources/model.xml", null);
{code}

BookNoAnnotations is either an interface or concrete class with no JAX-RS annotations. Both client proxies and server endpoints can 'turn' it into a RESTful resource by applying an external user model.

h1. HTTP-centric clients

HTTP centric clients are [WebClient|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java] instances which also implement the [Client|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/Client.java] interface. In addition to setting various Client request properties, you can also make an explicit HTTP invocation with an HTTP verb being the name of a given operation :

{code:java}
WebClient client = WebClient.create("http://books");
Book book = client.path("bookstore/books").accept("text/xml").get(Book.class);
{code}

You can choose to get an explicit JAX-RS Response instead and check the response code, headers or entity body if any :

{code:java}
WebClient client = WebClient.create("http://books");
client.path("bookstore/books");
client.type("text/xml").accept("text/xml")
Response r = client.post(new Book());
InputStream is = (InputStream)r.getEntity();
Book b = getFromInputStreamUsingJaxb(is);
{code}

org.apache.cxf.jaxrs.client.ResponseReader can be registered to make it possible to cast Response.getEntity() to specific types.

WebClient lets you get back to a base URI or to a previous path segment and move forward, it can be handy for getting a number of individual entries from a service with ids embedded in path segments :

{code:java}
WebClient client = WebClient.create("http://books");
List<Book> books = getBooks(client, 1L, 2L, 3L)

private List<Book> getBooks(WebClient client, Long ...ids) {
   List<Book> books = new ArrayList<Book>(); 
   for (Long id : ids) {
       books.add(client.path(id).get(Book.class));
       client.back(); 
   } 
   return books;
}
{code}

The above code will send requests like "GET http://books/1", "GET http://books/2", etc. 

If therequestthe request URI can be parameterized then you may want to use the following code :

{code:java}
Book book = WebClient.create("http://books").path("{year}/{id}", 2010, 123).get(Book.class);
// as opposed to
// WebClient.create("http://books").path(2010).path(123).get(Book.class);
{code}


When reusing the same WebClient instance for multiple invocations, one may want to reset its state with the help of the reset() method, for example, when the Accept header value needs to be changed and the current URI needs to be reset to the baseURI (as an alternative to a back(true) call). The resetQuery() method may be used to reset the query values only. Both options are available for proxies too.

h2. Working with explicit collections

Example :

{code:java}

Collection<? extends Book> books = WebClient.getCollection(Book.class);
Collection<? extends Book> books = WebClient.postAndGetCollection(new ArrayList<Book>(), Book.class);

{code}

h2. Handling exceptions


You can handle remote exceptions by either explicitly getting a Response object as shown above and handling error statuses as needed or you can catch either ServerWebApplicationException or ClientWebApplicationException exceptions, the same way it can be done with proxies. 

h2. Configuring HTTP clients in Spring

Like proxies, HTTP clients can be created using a number of WebClient static utility methods: you can pass a location to a Spring configuration bean if needed or you can set up a default bus as shown above. For example :
{code:xml}
<bean id="myJsonProvider" 
class="org.apache.cxf.jaxrs.provider.JSONProvider" > 
        <property name="supportUnwrapped" value="true" /> 
        <property name="wrapperName" value="nodeName" /> 
    </bean> 

<util:list id="webClientProviders"> 
    <ref bean="myJsonProvider"/> 
</util:list> 

<bean id="myWebClient" class="org.apache.cxf.jaxrs.client.WebClient" 
factory-method="create"> 
        <constructor-arg type="java.lang.String" 
value="http://some.base.url.that.responds/" /> 
        <constructor-arg ref="webClientProviders" /> 
</bean> 
{code} 

h1. XML-centric clients

XML-centric clients are WebClients using an [XMLSource|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/xml/XMLSource.java] utility class. XMLSource has a number of methods facilitating the retrieval of JAXB beans, individual properties or links with the help of XPath expressions. For example :

{code:java}
WebClient wc = WebClient.create("http://aggregated/data");
XMLSource source = wc.get(XMLSource.class);
source.setBuffering(true);
Book b1 = source.getNode("/books/book[position() = 1]", Book.class);
Book b2 = source.getNode("/books/book[position() = 2]", Book.class);
{code}

Note that an XMLSource instance can be set to buffer the input stream thus allowing for executing multiple XPath queries.
XMlSource can also help with getting the URIs representing the links or XML instances as Strings.

h1. Thread Safety

Proxies and web clients (clients) are not thread safe by default. In some cases this can be a limitation, especially when clients are injected; synchronizing on them can cause performance sideeffects. 

One way to 'make' clients thread-safe is to use WebClient.fromClient(Client) for web clients or JAXRSClientFactoryBean.fromClient() factory methods which copy all the original configuration properties and can be used to create new client instances per every request.

A single client doing multiple invocations without changing the current URI or headers is thread-safe. The only limitation in this case applies to proxies, in that they can not get "outofband" headers without synchronizing, ex :
{code:java}
// get some response headers passed to us 'outofband', which is not thread-safe for a plain proxy : 
String bookHeader = WebClient.toClient(injectedBookStoreProxy).getHeaders().getFirst("BookHeader"); 
{code}  

Final option is to use a 'threadSafe' boolean property when creating proxies or web clients (either from Spring or programmatically), see this [test|http://svn.apache.org/repos/asf/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java] for more details. Thread-safe clients created this way keep their state in a thread-local storage. 

If a number of incoming threads is limited then one option is just do nothing, while the other option is to reset the thread local state :

{code:java}
try { 
   webClient.path("bar") 
   webClient.header("bar", baz); 
   webClient.invoke(...); 
} finally { 
   // if proxy : WebClient.client(proxy).reset(); 
   webClient.reset(); 
} 
{code}

Yet another option is to use JAXRSClientFactoryBean and a 'secondsToKeepState' property for creating thread-safe clients - this will instruct clients to clean-up the thread-local state periodically.
 

h1. Configuring Clients at Runtime

Proxy and http-centric clients are typically created by JAXRSClientFactory or WebClient factory methods but [JAXRSClientFactoryBean|http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java] can also be used for pre-configuring clients, before they are created.

Sometimes, you may want to configure a client instance after it is been created. For example, one may want to configure HTTPConduit programmatically, as opposed to setting its properties using Spring. ClientConfiguration represents a client-specific configuration state and can be accessed like this :

{code:java}
Book proxy = JAXRSClientFactory.create("http://books", Book.class);
ClientConfiguration config = WebClient.getConfig(proxy);
HTTPConduit conduit1 = (HTTPConduit)config.getConduit();

WebClient webclient = WebClient.create("http://books");
HTTPConduit conduit2 = (HTTPConduit)WebClient.getConfig(webclient).getConduit();
{code}


h1. Creating clients programmatically with no Spring dependencies

Example :

{code:java}
JAXRSClientFactoryBean sf = new JAXRSClientFactoryBean();
sf.setResourceClass(CustomerService.class);
sf.setAddress("http://localhost:9000/");
BindingFactoryManager manager = sf.getBus().getExtension(BindingFactoryManager.class);
JAXRSBindingFactory factory = new JAXRSBindingFactory();
factory.setBus(sf.getBus());
manager.registerBindingFactory(JAXRSBindingFactory.JAXRS_BINDING_ID, factory);
CustomerService service = sf.create(CustomerService.class);
WebClient wc = sf.createWebClient();
{code} 


h1. Configuring HTTP Conduit from Spring

There's a number of ways to configure HTTPConduits for proxies and WebClients.

It is possible to have an HTTPConduit configuration which will apply to all clients using different request URIs or only to those with using a specific URI. For example :

{code:xml}
<http:conduit name="http://books:9095/bookstore.*"/> 
{code}

This configuration will affect all proxies and WebClients which have requestURIs starting from 'http://books:9095/bookstore'. Note the trailing '.*' suffix in the name of the http:conduit element.

Please see [this configuration file|http://svn.apache.org/repos/asf/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/security/jaxrs-https-url.xml] for more examples.

Alternatively you can just do :

{code:xml}
<http:conduit name="*.http-conduit"/> 
{code}

This configuration will affect all the clients, irrespectively of which URIs the deal with.

If you work with proxies then you can have the proxy-specific configuration using the expanded QName notation:

{code:xml}
<http:conduit name="{http://foo.bar}BookService.http-conduit"/> 
{code}

In this example, 'foo.bar' is a reverse package name of the BookService proxy class.

Similarly, for WebClients you can do :

{code:xml}
<http:conduit name="{http://localhost:8080}WebClient.http-conduit"/> 
{code}

In this example, 'http://localhost:8080' is the base service URI.

Please see [this configuration file|http://svn.apache.org/repos/asf/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/security/jaxrs-https.xml] for more examples.

Also see [this wiki page|http://cwiki.apache.org/CXF20DOC/client-http-transport-including-ssl-support.html] on how to configure HTTPConduits.

h1. Clients and Authentication

Proxies and HTTP-centric clients can have a correct HTTP Authorization header set up explicitly :

{code:java}

// proxies
WebClient.client(proxy).header("Authorization", "Basic " + toBase64("user:password"));

// web clients
webClient.header("Authorization", "Basic " + toBase64("user:password"));

{code}

or by providing a username and password pair at the client creation time, for example :
{code:java}
BookStore proxy = JAXRSClientFactory.create("http:books", BookStore.class, "username", "password", "classpath:/config/https.xml");

WebClient client = WebClient.create("http:books", "username", "password", "classpath:/config/https.xml");
{code}

When injecting clients from Spring, one can add 'username' and 'password' values as attributes to jaxrs:client elements or add them to WebClient factory create methods.