JAX-RS Client API - REST and the JAX-RS Standard - RESTful Java with JAX-RS 2.0 (2013)

RESTful Java with JAX-RS 2.0 (2013)

Part I. REST and the JAX-RS Standard

Chapter 8. JAX-RS Client API

One huge gaping hole in the first version of the JAX-RS specification was the lack of a client API. You could slog through the very difficult-to-use java.net.URL set of classes to invoke on remote RESTful services. Or you could use something like Apache HTTP Client, which is not JAX-RS aware, so you would have to do marshalling and unmarshalling of Java objects manually. Finally, you could opt to use one of the proprietary client APIs of one of the many JAX-RS implementations out there. This would, of course, lock you into that vendor’s implementation. JAX-RS 2.0 fixed this problem by introducing a new HTTP client API.

Client Introduction

Before I dive into the Client API, let’s look at a simple code example that illustrates the basics of the API:

Client client = ClientBuilder.newClient();

WebTarget target = client.target("http://commerce.com/customers");

Response response = target.post(Entity.xml(new Customer("Bill", "Burke)));

response.close();

Customer customer = target.queryParam("name", "Bill Burke")

.request()

.get(Customer.class);

client.close();

This example invokes GET and POST requests on a target URL to create and view a Customer object that is represented by XML over the wire. Let’s now pull this code apart and examine each of its components in detail.

Bootstrapping with ClientBuilder

The javax.ws.rs.client.Client interface is the main entry point into the JAX-RS Client API. Client instances manage client socket connections and are pretty heavyweight. Instances of this interface should be reused wherever possible, as it can be quite expensive to create and destroy these objects. Client objects are created with the ClientBuilder class:

package javax.ws.rs.client;

import java.net.URL;

import java.security.KeyStore;

import javax.ws.rs.core.Configurable;

import javax.ws.rs.core.Configuration;

import javax.net.ssl.HostnameVerifier;

import javax.net.ssl.SSLContext;

public abstract class ClientBuilder implements Configurable<ClientBuilder> {

public static Client newClient() {...}

public static Client newClient(final Configuration configuration) {...}

public static ClientBuilder newBuilder() {...}

public abstract ClientBuilder sslContext(final SSLContext sslContext);

public abstract ClientBuilder keyStore(final KeyStore keyStore,

final char[] password);

public ClientBuilder keyStore(final KeyStore keyStore,

final String password) {}

public abstract ClientBuilder trustStore(final KeyStore trustStore);

public abstract ClientBuilder

hostnameVerifier(final HostnameVerifier verifier);

public abstract Client build();

}

The easiest way to create a Client is to call ClientBuilder.newClient(). It instantiates a preinitialized Client that you can use right away. To fine-tune the construction of your Client interfaces, the newBuilder() method creates a ClientBuilder instance that allows you to register components and set configuration properties. It inherits these capabilities by implementing the Configurable interface:

package javax.ws.rs.core;

public interface Configurable<C extends Configurable> {

public C property(String name, Object value);

public C register(Class<?> componentClass);

public C register(Object component);

...

}

The ClientBuilder class also has methods to configure SSL. We’ll cover this in detail in Chapter 15. Let’s take a look at using ClientBuilder:

Client client = ClientBuilder.newBuilder()

.property("connection.timeout", 100)

.sslContext(sslContext)

.register(JacksonJsonProvider.class)

.build();

We create a ClientBuilder instance by calling the static method ClientBuilder.newBuilder(). We then set a proprietary, JAX-RS implementation–specific configuration property that controls socket connection timeouts. Next we specify the sslContext we want to use to manageHTTPS connections. The RESTful services we’re going to interact with are primarily JSON, so we register() an @Provider that knows how to marshal Java objects to and from JSON. Finally, we call build() to create the Client instance.

WARNING

Always remember to close() your Client objects. Client objects often pool connections for performance reasons. If you do not close them, you are leaking valuable system resources. While most JAX-RS implementations implement a finalize() method for Client, it is not a good idea to rely on the garbage collector to clean up poorly written code.

Client and WebTarget

Now that we have a Client, there’s a bunch of stuff we can do with this object. Like ClientBuilder, the Client interface implements Configurable. This allows you to change configuration and register components for the Client on the fly as your application executes. The most important purpose of Client, though, is to create WebTarget instances:

package javax.ws.rs.client.Client;

public interface Client extends Configurable<Client> {

public void close();

public WebTarget target(String uri);

public WebTarget target(URI uri);

public WebTarget target(UriBuilder uriBuilder);

public WebTarget target(Link link);

...

}

The WebTarget interface represents a specific URI you want to invoke on. Through the Client interface, you can create a WebTarget using one of the target() methods:

package javax.ws.rs.client.Client;

public interface WebTarget extends Configurable<WebTarget> {

public URI getUri();

public UriBuilder getUriBuilder();

public WebTarget path(String path);

public WebTarget resolveTemplate(String name, Object value);

public WebTarget resolveTemplate(String name, Object value,

boolean encodeSlashInPath);

public WebTarget resolveTemplateFromEncoded(String name, Object value);

public WebTarget resolveTemplates(Map<String, Object> templateValues);

public WebTarget resolveTemplates(Map<String, Object> templateValues,

boolean encodeSlashInPath);

public WebTarget resolveTemplatesFromEncoded(

Map<String, Object> templateValues);

public WebTarget matrixParam(String name, Object... values);

public WebTarget queryParam(String name, Object... values);

...

}

WebTarget has additional methods to extend the URI you originally constructed it with. You can add path segments or query parameters by invoking path() and queryParam(). If the WebTarget represents a URI template, the resolveTemplate() methods can fill in those variables:

WebTarget target = client.target("http://commerce.com/customers/{id}")

.resolveTemplate("id", "123")

.queryParam("verbose", true);

In this example, we initialized a WebTarget with a URI template string. The resolveTemplate() method fills in the id expression, and we add another query parameter. If you take a look at the UriBuilder class, you’ll see that WebTarget pretty much mirrors it. Instead of building URIs, though, WebTarget is building instances of WebTargets that you can use to invoke HTTP requests.

Building and Invoking Requests

Once you have a WebTarget that represents the exact URI you want to invoke on, you can begin building and invoking HTTP requests through one of its request() methods:

public interface WebTarget extends Configurable<WebTarget> {

...

public Invocation.Builder request();

public Invocation.Builder request(String... acceptedResponseTypes);

public Invocation.Builder request(MediaType... acceptedResponseTypes);

}

The Invocation.Builder interface hierarchy is a bit convoluted, so I’ll explain how to build requests using examples and code fragments:

package javax.ws.rs.client;

public interface Invocation {

...

public interface Builder extends SyncInvoker, Configurable<Builder> {

...

public Builder accept(String... types);

public Builder accept(MediaType... types

public Builder acceptLanguage(Locale... locales);

public Builder acceptLanguage(String... locales);

public Builder acceptEncoding(String... encodings);

public Builder cookie(Cookie cookie);

public Builder cookie(String name, String value);

public Builder cacheControl(CacheControl cacheControl);

public Builder header(String name, Object value);

public Builder headers(MultivaluedMap<String, Object> headers);

}

}

Invocation.Builder has a bunch of methods that allow you to set different types of request headers. The various acceptXXX() methods are for content negotiation (see Chapter 9). The cookie() methods allow you to set HTTP cookies you want to return to the server. And then there are the more generic header() and headers() methods that cover the more esoteric HTTP headers and any custom ones your application might have.

After setting the headers the request requires, you can then invoke a specific HTTP method to get back a response from the server. GET requests have two flavors:

<T> T get(Class<T> responseType);

<T> T get(GenericType<T> responseType);

Response get();

The first two generic get() methods will convert successful HTTP requests to specific Java types. Let’s look at these in action:

Customer customer = client.target("http://commerce.com/customers/123")

.accept("application/json")

.get(Customer.class);

List<Customer> customer = client.target("http://commerce.com/customers")

.accept("application/xml")

.get(new GenericType<List<Customer>>() {});

In the first request we want JSON from the server, so we set the Accept header with the accept() method. We want the JAX-RS client to grab this JSON from the server and convert it to a Customer Java type using one of the registered MessageBodyReader components.

The second request is a little more complicated. We have a special MessageBodyReader that knows how to convert XML into List<Customer>. The reader is very sensitive to the generic type of the Java object, so it uses the javax.ws.rs.core.GenericType class to obtain information about the type. GenericType is a sneaky trick that bypasses Java type erasure to obtain generic type information at runtime. To use it, you create an anonymous inner class that implements GenericType and fill in the Java generic type you want to pass information about to the template parameter. I know this is a little weird, but there’s no other way around the Java type system.

TIP

WebTarget has additional request() methods whose parameters take one or more String or MediaType parameters. These parameters are media types you want to include in an Accept header. I think it makes the code more readable if you use the Invocation.Builder.accept() method instead. But this generally is a matter of personal preference.

There’s also a get() method that returns a Response object. This is the same Response class that is used on the server side. This gives you more fine-grained control of the HTTP response on the client side. Here’s an example:

import javax.ws.rs.core.Response;

Response response = client.target("http://commerce.com/customers/123")

.accept("application/json")

.get();

try {

if (response.getStatus() == 200) {

Customer customer = response.readEntity(Customer.class);

}

} finally {

response.close();

}

In this example, we invoke an HTTP GET to obtain a Response object. We check that the status is OK and if so, extract a Customer object from the returned JSON document by invoking Response.readEntity(). The readEntity() method matches up the requested Java type and the response content with an appropriate MessageBodyReader. This method can be invoked only once unless you buffer the response with the bufferEntity() method. For example:

Response response = client.target("http://commerce.com/customers/123")

.accept("application/json")

.get();

try {

if (response.getStatus() == 200) {

response.bufferEntity();

Customer customer = response.readEntity(Customer.class);

Map rawJson = response.readEntity(Map.class);

}

} finally {

response.close();

}

In this example, the call to bufferEntity() allows us to extract the HTTP response content into different Java types, the first type being a Customer and the second a java.util.Map that represents raw JSON data. If we didn’t buffer the entity, the second readEntity() call would result in an IllegalStateException.

WARNING

Always remember to close() your Response objects. Response objects reference open socket streams. If you do not close them, you are leaking system resources. While most JAX-RS implementations implement a finalize() method for Response, it is not a good idea to rely on the garbage collector to clean up poorly written code. The default behavior of the RESTEasy JAX-RS implementation actually only lets you have one open Response per Client instance. This forces you to write responsible client code.

So far we haven’t discussed PUT and POST requests that submit a representation to the server. These types of requests have similar method styles to GET but also specify an entity parameter:

<T> T put(Entity<?> entity, Class<T> responseType);

<T> T put(Entity<?> entity, GenericType<T> responseType);

<T> T post(Entity<?> entity, Class<T> responseType);

<T> T post(Entity<?> entity, GenericType<T> responseType);

Response post(Entity<?> entity);

Response put(Entity<?> entity);

The Entity class encapsulates the Java object we want to send with the POST or GET request:

package javax.ws.rs.client;

public final class Entity<T> {

public Variant getVariant() {}

public MediaType getMediaType() {

public String getEncoding() {

public Locale getLanguage() {

public T getEntity() {

public Annotation[] getAnnotations() { }

...

}

The Entity class does not have a public constructor. You instead have to invoke one of the static convenience methods to instantiate one:

package javax.ws.rs.client;

import javax.ws.rs.core.Form;

public final class Entity<T> {

public static <T> Entity<T> xml(final T entity) { }

public static <T> Entity<T> json(final T entity) { }

public static Entity<Form> form(final Form form) { }

...

}

The xml() method takes a Java object as a parameter. It sets the MediaType to application/xml. The json() method acts similarly, except with JSON. The form() method deals with form parameters and application/x-www-form-urlencoded, and requires using the Form type. There’s a few other helper methods, but for brevity we won’t cover them here.

Let’s look at two different examples that use the POST create pattern to create two different customer resources on the server. One will use JSON, while the other will use form parameters:

Customer customer = new Customer("Bill", "Burke");

Response response = client.target("http://commerce.com/customers")

.request().

.post(Entity.json(customer));

response.close();

Here we pass in an Entity instance to the post() method using the Entity.json() method. This method will automatically set the Content-Type header to application/json.

To submit form parameters, we must use the Form class:

package javax.ws.rs.core;

public class Form {

public Form() { }

public Form(final String parameterName, final String parameterValue) { }

public Form(final MultivaluedMap<String, String> store) { }

public Form param(final String name, final String value) { }

public MultivaluedMap<String, String> asMap() { }

}

This class represents application/x-www-form-urlencoded in a request. Here’s an example of it in use:

Form form = new Form().param("first", "Bill")

.param("last", "Burke);

response = client.target("http://commerce.com/customers")

.request().

.post(Entity.form(form));

response.close();

Invocation

The previous examples are how you’re going to typically interact with the Client API. JAX-RS has an additional invocation model that is slightly different. You can create full Invocation objects that represent the entire HTTP request without invoking it. There’s a few additional methods on Invocation.Builder that help you do this:

public interface Invocation {

...

public interface Builder extends SyncInvoker, Configurable<Builder> {

Invocation build(String method);

Invocation build(String method, Entity<?> entity);

Invocation buildGet();

Invocation buildDelete();

Invocation buildPost(Entity<?> entity);

Invocation buildPut(Entity<?> entity);

...

}

}

The buildXXX() methods fill in the HTTP method you want to use and finish up building the request by returning an Invocation instance. You can then execute the HTTP request by calling one of the invoke() methods:

package javax.ws.rs.client;

public interface Invocation {

public Response invoke();

public <T> T invoke(Class<T> responseType);

public <T> T invoke(GenericType<T> responseType);

...

}

So what is the use of this invocation style? For one, the same Invocation object can be used for multiple requests. Just prebuild your Invocation instances and reuse them as needed. Also, since invoke() is a generic method, you could queue up Invocation instances or use them with the execute pattern. Let’s see an example:

Invocation generateReport = client.target("http://commerce.com/orders/report")

.queryParam("start", "now - 5 minutes")

.queryParam("end", "now")

.request()

.accept("application/json")

.buildGet();

while (true) {

Report report = generateReport.invoke(Report.class);

renderReport(report);

Thread.sleep(300000);

}

The example code prebuilds a GET Invocation that will fetch a JSON report summary of orders made in the last five minutes. We then loop every five minutes and reexecute the invocation. Sure, this example is a little bit contrived, but I think you get the idea.

Exception Handling

One thing we didn’t discuss is what happens if an error occurs when you use an invocation style that automatically unmarshalls the response. Consider this example:

Customer customer = client.target("http://commerce.com/customers/123")

.accept("application/json")

.get(Customer.class);

In this scenario, the client framework converts any HTTP error code into one of the exception hierarchy exceptions discussed in Exception Hierarchy. You can then catch these exceptions in your code to handle them appropriately:

try {

Customer customer = client.target("http://commerce.com/customers/123")

.accept("application/json")

.get(Customer.class);

} catch (NotAcceptableException notAcceptable) {

...

} catch (NotFoundException notFound) {

...

}

If the server responds with an HTTP error code not covered by a specific JAX-RS exception, then a general-purpose exception is thrown. ClientErrorException covers any error code in the 400s. ServerErrorException covers any error code in the 500s.

This invocation style will not automatically handle server redirects—that is, when the server sends one of the HTTP 3xx redirection response codes. Instead, the JAX-RS Client API throws a RedirectionException from which you can obtain the Location URL to do the redirect yourself. For example:

WebTarget target = client.target("http://commerce.com/customers/123");

boolean redirected = false;

Customer customer = null;

do {

try {

customer = target.accept("application/json")

.get(Customer.class);

} catch (RedirectionException redirect) {

if (redirected) throw redirect;

redirected = true;

target = client.target(redirect.getLocation());

}

} while (customer == null);

In this example, we loop if we receive a redirect from the server. The code makes sure that we allow only one redirect by checking a flag in the catch block. We change the WebTarget in the catch block to the Location header provided in the server’s response. You might want to massage this code a little bit to handle other error conditions, but hopefully you get the concepts I’m trying to get across.

Configuration Scopes

If you look at the declarations of ClientBuilder, Client, WebTarget, Invocation, and Invocation.Builder, you’ll notice that they all implement the Configurable interface. Each one of these interfaces has its own scope of properties and registered components that it inherits from wherever it was created from. You can also override or add registered components or properties for each one of these components. For example:

Client client = ClientBuilder.newBuilder()

.property("authentication.mode", "Basic")

.property("username", "bburke")

.property("password", "geheim")

.build();

WebTarget target1 = client.target("http://facebook.com");

WebTarget target2 = client.target("http://google.com")

.property("username", "wburke")

.register(JacksonJsonProvider.class);

If you viewed the properties of target1 you’d find the same properties as those defined on the client instances, as WebTargets inherit their configuration from the Client or WebTarget they were created from. The target2 variable overrides the username property and registers a provider specifically for target2. So, target2’s configuration properties and registered components will be a little bit different than target1’s. This way of configuration scoping makes it much easier for you to share initialization code so you can avoid creating a lot of extra objects you don’t need and reduce the amount of code you have to write.

Wrapping Up

In this chapter, we learned about the JAX-RS Client API. Client interfaces manage global configuration and the underlying socket connections you’ll use to communicate with a service. WebTarget represents a URI that you can build and invoke HTTP requests from. If you want to see the Client API in action, check out the numerous examples in Part II. They all use the Client API in some form or another.