Chapter 49. JAX-RS 2.0 Client API
Chapter 49. JAX-RS 2.0 Client API Abstract JAX-RS 2.0 defines a full-featured client API which can be used for making REST invocations or any HTTP client invocations. This includes a fluent API (to simplify building up requests), a framework for parsing messages (based on a type of plug-in known as an entity provider ), and support for asynchronous invocations on the client side. 49.1. Introduction to the JAX-RS 2.0 Client API Overview JAX-RS 2.0 defines a fluent API for JAX-RS clients, which enables you to build up a HTTP request step-by-step and then invoke the request using the appropriate HTTP verb (GET, POST, PUT, or DELETE). Note It is also possible to define a JAX-RS client in Blueprint XML or Spring XML (using the jaxrs:client element). For details of this approach, see Section 18.2, "Configuring JAX-RS Client Endpoints" . Dependencies To use the JAX-RS 2.0 client API in your application, you must add the following Maven dependency to your project's pom.xml file: If you plan to use the asynchronous invocation feature (see Section 49.6, "Asynchronous Processing on the Client" ), you also need the following Maven dependency: Client API package The JAX-RS 2.0 client interfaces and classes are located in the following Java package: When developing JAX-RS 2.0 Java clients, you also typically need to access classes from the core package: Example of a simple client request The following code fragment shows a simple example, where the JAX-RS 2.0 client API is used to make an invocation on the http://example.org/bookstore JAX-RS service, invoking with the GET HTTP method: Fluent API The JAX-RS 2.0 client API is designed as a fluent API (sometimes called a Domain Specific Language). In the fluent API, a chain of Java methods is invoked in a single statement, in such a way that the Java methods look like the commands from a simple language. In JAX-RS 2.0, the fluent API is used to build and invoke a REST request. Steps to make a REST invocation Using the JAX-RS 2.0 client API, a client invocation is built and invoked in a series of steps, as follows: Bootstrap the client. Configure the target. Build and make the invocation. Parse the response. Bootstrap the client The first step is to bootstrap the client, by creating a javax.ws.rs.client.Client object. This Client instance is a relatively heavyweight object, which represents the stack of technologies required to support a JAX-RS client (possibly including, interceptors and additional CXF features). Ideally, you should re-use client objects when you can, instead of creating new ones. To create a new Client object, invoke a static method on the ClientBuilder class, as follows: Configure the target By configuring the target, you effectively define the URI that will be used for the REST invocation. The following example shows how you can define a base URI, base , and then add additional path segments to the base URI, using the path(String) method: Build and make the invocation This is really two steps rolled up into one: firstly, you build up the HTTP request (including headers, accepted media types, and so on); and secondly, you invoke the relevant HTTP method (optionally providing a request message body, if one is required). For example, to create and invoke a request that accepts the application/xml media type: Parse the response Finally, you need to parse the respose, resp , obtained in the step. Usually, the response is returned in the form of a javax.ws.rs.core.Response object, which encapsulates HTTP headers, along with other HTTP metadata, and the HTTP message body (if any). If you want to access the returned HTTP message in String format, you can easily do so by invoking the readEntity method with a String.class argument, as follows: You can always access the message body of a response as a String , by specifying String.class as the argument to readEntity . For more general transformations or conversions of the message body, you can provide an entity provider to perform the conversion. For more details, see Section 49.4, "Parsing Requests and Responses" . 49.2. Building the Client Target Overview After creating the initial Client instance, the step is to build up the request URI. The WebTarget builder class enables you to configure all aspects of the URI, including the URI path and query parameters. WebTarget builder class The javax.ws.rs.client.WebTarget builder class provides the part of the fluent API that enables you to build up the REST URI for the request. Create the client target To create a WebTarget instance, invoke one of the target methods on a javax.ws.rs.client.Client instance. For example: Base path and path segments You can specify the complete path all in one go, using the target method; or you can specify a base path, and then add path segments piece by piece, using a combination of the target method and the path methods. The advantage of combining a base path with path segments is that you can easily re-use the base path WebTarget object for multiple invocations on slightly different targets. For example: URI template parameters The syntax of the target path also supports URI template parameters. That is, a path segment can be initialized with a template parameter, { param } , which subsequently gets resolved to a specify value. For example: Where the resolveTemplate method replaces the path segment, {id} , with the value 123 . Define query parameters Query parameters can be appended to the URI path, where the beginning of the query parameters is marked by a single ? character. This mechanism enables you to set a series of name/value pairs, using the syntax: ? name1 = value1 & name2 = value2 &... A WebTarget instance enables you to define query parameters using the queryParam method, as follows: Define matrix parameters Matrix parameters are somewhat similar to query parameters, but are not as widely supported and use a different syntax. To define a matrix parameter on a WebTarget instance, invoke the matrixParam(String, Object) method. 49.3. Building the Client Invocation Overview After building the target URI, using the WebTarget builder class, the step is to configure the other aspects of the request-such as HTTP headers, cookies, and so on-using the Invocation.Builder class. The final step in building the invocation is to invoke the appropriate HTTP verb (GET, POST, PUT, or DELETE) and provide a message body, if required. Invocation.Builder class The javax.ws.rs.client.Invocation.Builder builder class provides the part of the fluent API that enables you to build up the contents of the HTTP message and to invoke a HTTP method. Create the invocation builder To create an Invocation.Builder instance, invoke one of the request methods on a javax.ws.rs.client.WebTarget instance. For example: Define HTTP headers You can add a HTTP header to the request message using the header method, as follows: Define cookies You can add a cookie to the request message using the cookie method, as follows: Define properties You can set a property in the context of this request using the property method, as follows: Define accepted media types, languages, or encodings You can define accepted media types, languages, or encodings, as follows: Invoke HTTP method The process of building a REST invocation is terminated by invoking a HTTP method, which performs the HTTP invocation. The following methods (inherited from the javax.ws.rs.client.SyncInvoker base class) can be invoked: If the specific HTTP verb you want to invoke is not on this list, you can use the generic method method to invoke any HTTP method. Typed responses All of the HTTP invocation methods are provided with an untyped variant and a typed variant (which takes an extra argument). If you invoke a request using the default get() method (taking no arguments), a javax.ws.rs.core.Response object is returned from the invocation. For example: It is also possible, however, to ask for the response to be returned as a specific type, using the get(Class<T>) method. For example, to invoke a request and ask for the response to be returned as a BookInfo object: In order for this to work, however, you must register a suitable entity provider with the Client instance, which is capable of mapping the response format, application/xml , to the requested type. For more details about entity providers, see Section 49.4, "Parsing Requests and Responses" . Specifying the outgoing message in post or put For HTTP methods that include a message body in the request (such as POST or PUT), you must specify the message body as the first argument of the method. The message body must be specified as a javax.ws.rs.client.Entity object, where the Entity encapsulates the message contents and its associated media type. For example, to invoke a POST method, where the message contents are provided as a String type: If necessary, the Entity.entity() constructor method will automatically map the supplied message instance to the specified media type, using the registered entity providers. It is always possible to specify the message body as a simple String type. Delayed invocation Instead of invoking the HTTP request right away (for example, by invoking the get() method), you have the option of creating an javax.ws.rs.client.Invocation object, which can be invoked at a later time. The Invocation object encapsulates all of the details of the pending invocation, including the HTTP method. The following methods can be used to build an Invocation object: For example, to create a GET Invocation object and invoke it at a later time, you can use code like the following: Asynchronous invocation The JAX-RS 2.0 client API supports asynchronous invocations on the client side. To make an asynchronous invocation, simply invoke the async() method in the chain of methods following request() . For example: When you make an asynchronous invocation, the returned value is a java.util.concurrent.Future object. For more details about asynchronous invocations, see Section 49.6, "Asynchronous Processing on the Client" . 49.4. Parsing Requests and Responses Overview An essential aspect of making HTTP invocations is that the client must be able to parse the outgoing request messages and the incoming responses. In JAX-RS 2.0, the key concept is the Entity class, which represents a raw message tagged with a media type. In order to parse the raw message, you can register multiple entity providers , which have the capability to convert media types to and from particular Java types. In other words, in the context of JAX-RS 2.0, an Entity is the representation of a raw message and an entity provider is the plug-in that provides the capability to parse the raw message (based on the media type). Entities An Entity is a message body augmented by metadata (media type, language, and encoding). An Entity instance holds the message in a raw format and is associated with a specific media type. To convert the contents of an Entity object to a Java object you require an entity provider , which is capable of mapping the given media type to the required Java type. Variants A javax.ws.rs.core.Variant object encapsulates the metadata associated with an Entity , as follows: Media type, Language, Encoding. Effectively, you can think of an Entity as consisting of the HTTP message contents, augmented by Variant metadata. Entity providers An entity provider is a class that provides the capability of mapping between a media type and a Java type. Effectively, you can think of an entity provider as a class that provides the ability to parse messages of a particular media type (or possibly of multiple media types). There are two different varieties of entity provider: MessageBodyReader Provides the capability of mapping from media type(s) to a Java type. MessageBodyWriter Provides the capability of mapping from a Java type to a media type. Standard entity providers Entity providers for the following Java and media type combinations are provided as standard: byte[] All media types ( */* ). java.lang.String All media types ( */* ). java.io.InputStream All media types ( */* ). java.io.Reader All media types ( */* ). java.io.File All media types ( */* ). javax.activation.DataSource All media types ( */* ). javax.xml.transform.Source XML types ( text/xml , application/xml , and media types of the form application/*+xml ). javax.xml.bind.JAXBElement and application-supplied JAXB classes XML types ( text/xml , application/xml , and media types of the form application/*+xml ). MultivaluedMap<String,String> Form content ( application/x-www-form-urlencoded ). StreamingOutput All media types ( */* ), MessageBodyWriter only. java.lang.Boolean , java.lang.Character , java.lang.Number Only for text/plain . Corresponding primitive types supported through boxing/unboxing conversion. Response object The default return type is the javax.ws.rs.core.Response type, which represents an untyped response. The Response object provides access to the complete HTTP response, including the message body, HTTP status, HTTP headers, media type, and so on. Accessing the response status You can access the response status, either through the getStatus method (which returns the HTTP status code): Or though the getStatusInfo method, which also provides a description string: Accessing the returned headers You can access the HTTP headers using any of the following methods: For example, if you know that the Response has a Date header, you could access it as follows: Accessing the returned cookies You can access any new cookies set on the Response using the getCookies method, as follows: Accessing the returned message content You can access the returned message content by invoking one of the readEntity methods on the Response object. The readEntity method automatically invokes the available entity providers to convert the message to the requested type (specified as the first argument of readEntity ). For example, to access the message content as a String type: Collection return value If you need to access the returned message as a Java generic type-for example, as a List or Collection type-you can specify the request message type using the javax.ws.rs.core.GenericType<T> construction. For example: 49.5. Configuring the Client Endpoint Overview It is possible to augment the functionality of the base javax.ws.rs.client.Client object by registering and configuring features and providers. Example The following example shows a client configured to have a logging feature, a custom entity provider, and to set the prettyLogging property to true : Configurable API for registering objects The Client class supports the Configurable API for registering objects, which provides several variants of the register method. In most cases, you would register either a class or an object instance, as shown in the following examples: For more details about the register variants, see the reference documentation for Configurable . What can you configure on the client? You can configure the following aspects of a client endpoint: Features Providers Properties Filters Interceptors Features A javax.ws.rs.core.Feature is effectively a plug-in that adds an extra feature or functionality to a JAX-RS client. Often, a feature installs one or more interceptors in order to provide the required functionality. Providers A provider is a particular kind of client plug-in that provides a mapping capability. The JAX-RS 2.0 specification defines the following kinds of provider: Entity providers An entity provider provides the capability of mapping between a specific media type a Java type. For more details, see Section 49.4, "Parsing Requests and Responses" . Exception mapping providers An exception mapping provider maps a checked runtime exception to an instance of Response . Context providers A context provider is used on the server side, to supply context to resource classes and other service providers. Filters A JAX-RS 2.0 filter is a plug-in that gives you access to the URI, headers, and miscellaneous context data at various points (extension points) of the message processing pipeline. For details, see Chapter 61, JAX-RS 2.0 Filters and Interceptors . Interceptors A JAX-RS 2.0 interceptor is a plug-in that gives you access to the message body of a request or response as it is being read or written. For details, see Chapter 61, JAX-RS 2.0 Filters and Interceptors . Properties By setting one or more properties on the client, you can customize the configuration of a registered feature or a registered provider. Other configurable types It is possible, not only to configure a javax.ws.rs.client.Client (and javax.ws.rs.client.ClientBuilder ) object, but also a WebTarget object. When you change the configuration of a WebTarget object, the underlying client configuration is deep copied to give the new WebTarget configuration. Hence, it is possible to change the configuration of the WebTarget object without changing the configuration of the original Client object. 49.6. Asynchronous Processing on the Client Overview JAX-RS 2.0 supports asynchronous processing of invocations on the client side. Two different styles of asynchronous processing are supported: either using a java.util.concurrent.Future<V> return value; or by registering an invocation callback. Asynchronous invocation with Future<V> return value Using the Future<V> approach to asynchronous processing, you can invoke a client request asynchronously, as follows: You can use a similar approach for typed responses. For example, to get a response of type, BookInfo : Asynchronous invocation with invocation callback Instead of accessing the return value using a Future<V> object, you can define an invocation callback (using javax.ws.rs.client.InvocationCallback<RESPONSE> ), as follows: You can use a similar approach for typed responses:
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-client</artifactId> <version>3.3.6.fuse-7_13_0-00015-redhat-00001</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-hc</artifactId> <version>3.3.6.fuse-7_13_0-00015-redhat-00001</version> </dependency> javax.ws.rs.client javax.ws.rs.core // Java import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; import javax.ws.rs.core.Response; Client client = ClientBuilder.newClient(); Response res = client.target("http://example.org/bookstore/books/123") .request("application/xml").get(); // Java import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; Client client = ClientBuilder.newClient(); // Java import javax.ws.rs.client.WebTarget; WebTarget base = client.target("http://example.org/bookstore/"); WebTarget books = base.path("books").path("{id}"); // Java import javax.ws.rs.core.Response; Response resp = books.resolveTemplate("id", "123").request("application/xml").get(); // Java String msg = resp.readEntity(String.class); // Java import javax.ws.rs.client.WebTarget; WebTarget base = client.target("http://example.org/bookstore/"); // Java import javax.ws.rs.client.WebTarget; WebTarget base = client.target("http://example.org/bookstore/"); WebTarget headers = base.path("bookheaders"); // Now make some invocations on the 'headers' target WebTarget collections = base.path("collections"); // Now make some invocations on the 'collections' target // Java import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Response; WebTarget base = client.target("http://example.org/bookstore/"); WebTarget books = base.path("books").path("{id}"); Response resp = books.resolveTemplate("id", "123").request("application/xml").get(); // Java WebTarget target = client.target("http://example.org/bookstore/") .queryParam("userId","Agamemnon") .queryParam("lang","gr"); // Java import javax.ws.rs.client.WebTarget; import javax.ws.rs.client.Invocation.Builder; WebTarget books = client.target("http://example.org/bookstore/books/123"); Invocation.Builder invbuilder = books.request(); Invocation.Builder invheader = invbuilder.header("From", "fionn@example.org"); Invocation.Builder invcookie = invbuilder.cookie("myrestclient", "123xyz"); Invocation.Builder invproperty = invbuilder.property("Name", "Value"); Invocation.Builder invmedia = invbuilder.accept("application/xml") .acceptLanguage("en-US") .acceptEncoding("gzip"); get post delete put head trace options Response res = client.target("http://example.org/bookstore/books/123") .request("application/xml").get(); BookInfo res = client.target("http://example.org/bookstore/books/123") .request("application/xml").get(BookInfo.class); import javax.ws.rs.client.Entity; Response res = client.target("http://example.org/bookstore/registerbook") .request("application/xml") .put(Entity.entity("Red Hat Install Guide", "text/plain")); buildGet buildPost buildDelete buildPut build import javax.ws.rs.client.Invocation; import javax.ws.rs.core.Response; Invocation getBookInfo = client.target("http://example.org/bookstore/books/123") .request("application/xml").buildGet(); // Later on, in some other part of the application: Response = getBookInfo.invoke(); Future<Response> res = client.target("http://example.org/bookstore/books/123") .request("application/xml") .async() .get(); int status = resp.getStatus(); String statusReason = resp.getStatusInfo().getReasonPhrase(); MultivaluedMap<String,Object> getHeaders() MultivaluedMap<String,String> getStringHeaders() String getHeaderString(String name) String dateAsString = resp.getHeaderString("Date"); import javax.ws.rs.core.NewCookie; java.util.Map<String,NewCookie> cookieMap = resp.getCookies(); java.util.Collection<NewCookie> cookieCollection = cookieMap.values(); String messageBody = resp.readEntity(String.class); import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; import javax.ws.rs.core.GenericType; import java.util.List; GenericType<List<String>> stringListType = new GenericType<List<String>>() {}; Client client = ClientBuilder.newClient(); List<String> bookNames = client.target("http://example.org/bookstore/booknames") .request("text/plain") .get(stringListType); // Java import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; import org.apache.cxf.feature.LoggingFeature; Client client = ClientBuilder.newClient(); client.register(LoggingFeature.class) .register(MyCustomEntityProvider.class) .property("LoggingFeature.prettyLogging","true"); client.register(LoggingFeature.class) client.register(new LoggingFeature()) // Java import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; import java.util.concurrent.Future; import javax.ws.rs.core.Response; Client client = ClientBuilder.newClient(); Future<Response> futureResp = client.target("http://example.org/bookstore/books/123") .request("application/xml") .async() .get(); // At a later time, check (and wait) for the response: Response resp = futureResp.get(); Client client = ClientBuilder.newClient(); Future<BookInfo> futureResp = client.target("http://example.org/bookstore/books/123") .request("application/xml") .async() .get(BookInfo.class); // At a later time, check (and wait) for the response: BookInfo resp = futureResp.get(); // Java import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; import java.util.concurrent.Future; import javax.ws.rs.core.Response; import javax.ws.rs.client.InvocationCallback; Client client = ClientBuilder.newClient(); Future<Response> futureResp = client.target("http://example.org/bookstore/books/123") .request("application/xml") .async() .get( new InvocationCallback<Response>() { @Override public void completed(final Response resp) { // Do something when invocation is complete } @Override public void failed(final Throwable throwable) { throwable.printStackTrace(); } }); // Java import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; import java.util.concurrent.Future; import javax.ws.rs.core.Response; import javax.ws.rs.client.InvocationCallback; Client client = ClientBuilder.newClient(); Future<BookInfo> futureResp = client.target("http://example.org/bookstore/books/123") .request("application/xml") .async() .get( new InvocationCallback<BookInfo>() { @Override public void completed(final BookInfo resp) { // Do something when invocation is complete } @Override public void failed(final Throwable throwable) { throwable.printStackTrace(); } });