Skip to main content
Last updated June 15, 2011 15:30, by m_potociar
Feedicon  

Low-level Client API for JAX-RS 2.0

This page is a summary of the low-level client API proposal for JAX-RS 2.0. The quickest way to get up to speed with the proposed API is to read through the proposal below as well as to check out the source code and look at the API javadoc. To get the basic idea about the API classes and their interactions, please, have a look at the class diagram at the bottom of this page.

Umbrella issue JAX_RS_SPEC-50
See the umbrela issue in Jira for the list of outstanding sub-tasks and their statuses.

Unified HTTP Request/Response API

Existing core.Request and core.Response classes are heavily oriented on the end-user server side use cases. These classes don't provide the necessary support for client-side use cases nor for the filtering and invocation interception use cases. To support these use cases uniformly, the new core classes HttpRequest and HttpResponse were introduced. Rather than defining a specific client-side interfaces, the Client API proposal builds on these newly introduced core interfaces.

Bootstraping a Client instance

Client API is the most requested missing feature in JAX-RS. The goal of this proposal to focus on a low-level RESTful API using a fluent builder pattern with support for synchronous as well as asynchronous HTTP invocation processing. Additionally, the API proposes a bootstrapping mechanism for plugging in custom implementations and API extensions.

The main entry point for the API is the Client class. It contains factory methods for creating resource links and invocations. The Client instances are created using one of the static methods of the ClientFactory class:

// Default client instantiation using default configuration
Client defaultClient = ClientFactory.newClient(); 

// Default client instantiation using custom configuration
ClientConfiguration cfg = new DefaultClientConfiguration();
cfg.getFeatures().put("CUSTOM_FEATURE", true);
Client defaultConfiguredClient = ClientFactory.newClient(cfg);

The example above demonstrates the way of creating a Client instance using the default implementation discovered by the ClientFactory. It is also possible to request a particular custom Client implementation using a specific implementation of the javax.ws.rs.ext.ClientBuilderFactory and (optionally) a custom implementation of ClientConfiguration class, for example:

// Custom newClient instantiation using custom configuration
MyClientConfiguration myCfg = new MyClientConfiguration();
myCfg.enableCaching();
MyClient myConfiguredClient = ClientFactory.newClientBy(MyClientBuilderFactory.class).create(myCfg);

This mechanism is proposed to provide easier and standardised support for implementing custom extensions of the JAX-RS Client API.

Resource access and invocations

The primary role of the Client instance is to hold the associated client configuration and provide factory methods for instantiating pre-configured ResourceUri instances as well as directly generating client-side HTTP requests and invocations (represented by the universal core.HttpRequest and client-side client.Invocation classes).

Client client = ClientFactory.newClient();
ResourceUri customersUri = client.resourceUri("http://jaxrs.examples.org/jaxrsApplication/customers");
Invocation getInvocation = client.request("http://jaxrs.examples.org/jaxrsApplication/customers").get();
HttpRequest getRequest = getInvocation; // Invocation extends HttpRequest

The difference between an Invocation and a HttpRequest is that the HttpRequest interface provides the mutable interface to prepare and configure a HTTP request, while the client-side Invocation interface, which extends the HttpRequest interface Client instance is to hold the associated client configuration and provide factory methods for instantiating pre-configured ResourceUri instances as well as directly generating client-side HTTP requests and invocations (represented by the universal core.HttpRequest and client-side client.Invocation classes).

Client client = ClientFactory.newClient();
ResourceUri customersUri = client.resourceUri("http://jaxrs.examples.org/jaxrsApplication/customers");
Invocation getInvocation = client.request("http://jaxrs.examples.org/jaxrsApplication/customers").get();
HttpRequest getRequest = getInvocation; // Invocation extends HttpRequest

The difference between an Invocation and a HttpRequest is that the HttpRequest interface provides the mutable interface to prepare and configure a HTTP request, while the client-side Invocation interface, which extends the HttpRequest interface, adds additional HTTP request invocation methods to support programming styles based on the command pattern (e.g. batch request processing) as well as the fluent API focused on producing response objects as will be demonstrated later. Another significant advantage of decoupling the invocation logic from the reusable HttpRequest interface is that the resulting HttpRequest interface becomes much more suitable for a consumption by common JAX-RS filters and interceptors where an ability to perform client-side invocations on a filtered/intercepted request would only be confusing and might lead to unnecessary errors.

Even though the primary goal of the HttpRequest interface on the client side is to support filters and interceptors, the Client class API defines additional set of invocation methods for processing arbitrary HttpRequest instances. These invocations are using the configuration of the invoking Client instance, which effectively overrides any configuration set on the originating Client, ResourceUri or Invocation instance).

HttpRequest request = ...created in another scope...;  

ClientConfiguration cfg = new DefaultClientConfiguration();
cfg.getFeatures().put("CUSTOM_FEATURE", true);
Client client = ClientFactory.newClient(cfg);
client.invoke(request); // invoke using target client instance and its configuration

In the typical modern RPC programming models (e.g. JAX-WS) the server side artefacts are represented by a strongly typed client-side proxies that are fully replicating the server-side artefact interfaces. In RESTful API, the focus is more on the resource and its identification, since the invocation API is, by principle, reduced to a single uniform interface with a small set of well defined methods. This resource-centric programming model is also emphasised in the proposed API.

As briefly mentioned above, the JAX-RS Client API introduces a concept of a "glorified" resource URI in the form of a ResourceUri interface. It's core responsibility is to manage the identifier (URI) of a selected server-side resource (or group of resources) and the associated configuration while providing additional methods for deriving sub-resource URIs and generating targeted HTTP requests and invocations.

It was already demonstrated earlier that a ResourceUri is instantiated by a client using a resource URI or URI template. Subsequent sub-resource URI instances can be created using the approach similar to the one seen in the UriBuilder (path(...), pathParam(...), queryParam(...) methods):

/* ResourceUri( http://jaxrs.examples.org/jaxrsApplication/customers/ ) */
ResourceUri customers = ClientFactory.newClient().resourceUri("http://jaxrs.examples.org/jaxrsApplication/customers");  
/* ResourceUri( http://jaxrs.examples.org/jaxrsApplication/customers/{id}/ ) */
ResourceUri anyCustomer = customers.path("{id}");   
/* ResourceUri( http://jaxrs.examples.org/jaxrsApplication/customers/123/ ) */
ResourceUri customer123 = anyCustomer.pathParam("id", 123); 

Similarly to the Client instance, ResourceUri instances are typically used for creating Invocation (enhanced HttpRequest) instances:

Client client = ClientFactory.newClient();
ResourceUri customers = client.resourceUri("http://jaxrs.examples.org/jaxrsApplication/customers");
Invocation newCustomerRequest = customers.post();
newCustomerRequest.entity(new Customer("Marek")).type("application/xml"); // prepare the invocation
HttpResponse responseA = newCustomerRequest.invoke(); // invoking the Invocation

// Using WebDAV extensions:
ResourceUri indexFile = client.resourceUri("http://jaxrs.examples.org/jaxrsApplication/index.html");
HttpRequest copyRequest = indexFile.method("COPY") // Invocation cast down to a HttpRequest 
        .header("Destination", "http://jaxrs.examples.org/jaxrsApplication/backup/index.html")
        .header("Overwrite", "T");
HttpResponse responseB = client.invoke(copyRequest); // using client to invoke HttpRequest

The example above also demonstrates that once the HTTP invocation is prepared and configured, it can be invoked using one of the synchronous Invocation.invoke(...) or asynchronous Invocation.start(...) methods to produce the resulting HttpResponse. Similarly, a configured HttpRequest can be passed to one of the Client invocation method variants that are similarly designed to support both synchronous as well as asynchronous request invocations.

In the previous example, synchronous invocation interface was used. The example of asynchronous invocations looks as follows:

ResourceUri indexFile = ClientFactory.newClient().resourceUri("http://jaxrs.examples.org/jaxrsApplication/index.html");
Invocation copyRequest = indexFile.method("COPY")
        .header("Destination", "http://jaxrs.examples.org/jaxrsApplication/backup/index.html")
        .header("Overwrite", "T");

Future<ClientResponse> futureResponse = copyRequest.queue(); // start invocation using Future
// ...do something
if (!futureResponse.isDone()) {
    futureResponse.cancel(true);
}
//start invocation again using InvocationCallback
copyRequest.queue(new AbstractInvocationCallback<HttpResponse>() {
    @Override
    public void onComplete(Future<HttpResponse> future) { /* do something */ }
});

Note that the above code is somewhat verbose, primarily for the demonstration purposes, clearly distinguishing the various API classes. The client API is however designed to support fluent method invocation chaining, such as:

// Fluent API example of adding new customer
HttpResponse response = ClientFactory.newClient().request("http://jaxrs.examples.org/jaxrsApplication/customers").post()
    .entity(new Customer("Marek")).type("application/xml").invoke();
// Fluent API example of manipulating customer resources (using a resourceUri)
ResourceUri customer = ClientFactory.newClient().resourceUri("http://jaxrs.examples.org/jaxrsApplication/customers/{id}/");
Customer c = customer.get().pathParam("id", 123).invoke(Customer.class);
c.setId(456);
HttpResponse r = customer.put().pathParam("id", 456).entity(c).type("application/customer+xml").invoke();
assert t.getStatusCode() == 201;

Consuming large responses

It is often necessary to consume large amounts of data returned to the client from an invocation. To avoid an unreasonable impact on the amount of consumed system resources or performance degradation the API providers must make sure to consume response entities lazily, based on a user request. Additionally, since it is often required to consume response entities based on values of the response headers or timely availability of a response, support for the following use-cases of large HTTP response processing is proposed:

Process response headers & entity sequentially with a lazy-loaded or streamed entity
Most common use case. As part of this use case it must be possible for the API user to ask for the response entity in the form of an input stream

HttpResponse responseA = invocation.invoke();
if (responseA.getStatus() == "200") {
    // Entity loaded lazily
    Order order = response.getEntity(Order.class);
    ...
}

HttpResponse responseB = invocation.invoke();
if (responseB.getStatus() == "200") {
    // Entity streaming support
    InputStream input = response.getEntityInputStream();
    ...
    input.close();
}

Request an entity future
To be able to control code flow based on the response availability, API providers must support returning an entity Future

HttpResponse response = ....invoke();
if (response.getStatus() == "200") {
   // Does not block when returning a future
   Future<Order> f = response.getEntity(new GenericType<Future<Order>>() {});
   // Wait for a result until a timeout
   Order o = f.get([some time out]);
}

Configuring API components

The proposal introduces a unified generic Configurable<T> interface for the main API components, namely ClientConfiguration, Client, ResourceUri and Invocation. The Configurable interface defines fluent configuration mutator methods as well as common configuration accessor methods. There are 3 main configuration classes distinguished and supported by the API:

  1. Features are identified by a unique String name and indicate a bivalent information whether or not a particular feature is enabled. Presence of a feature in the set of feature indicates that the feature is enabled. OTOH the absence of the feature in the feature set indicates that the feature is disabled for that particular configurable instance.
  2. Properties are defined as a pair of String property name and Object property value. They can be used to further customize enabled features or some general implementation details.
  3. Providers are all classes or instances of classes that implement a specific JAX-RS provider interface and are annotated with a @Provider JAX-RS annotation. Additional providers can be registered for each configurable instance. Their functionality on the particular instance can be controlled using both features and properties. It is recommended that enabling and disabling of a provider is controlled via a feature rather than a property.

Whenever a new sub-component is created using a parent configurable instance, the existing (current) parent configuration is inherited by the newly created sub-component and detached from the parent configuration. This means that any subsequent changes in the configuration of either parent or child component must not have any visible impact on the configuration of the other component. IOW, updating a configuration of a parent component does not update the configuration of the existing child sub-components and vice versa.

Injection support

Injecting providers configured in client components

TODO

Injecting client component instances in JavaEE components

TODO

More examples...

For further examples please check the JAX-RS 2.0 Client API example usages.

Class Diagram

 
 
Close
loading
Please Confirm
Close