JAX-RS Client

Goals

Concepts

Library

Dependencies

Preview

try {
  final Pen pigpen = ClientBuilder.newClient()
      .target(UriBuilder.fromUri("https://example.com/farm/").path(PensResource.class, "getPen"))
      .request()
      .accept(MediaType.APPLICATION_JSON_TYPE)
      .get(Pen.class);
  //TODO do something with the pigpen
} catch(final NotFoundException notFoundException) {
  //TODO handle the missing pigpen
}

Lesson

JAX-RS is very convenient for specifying a RESTful API and implementing RESTful services. But the framework is not only for the server side; JAX-RS also provides a comprehensive API for writing clients that can access RESTful services. The JAX-RS client API is included with the dependency javax.ws.rs:javax.ws.rs-api, which also contains the server API. As with the server API, you will need to choose a JAX-RS client API implementation for your program to work. You should write your code to the API itself, though, independent of the implementation you use.

The JAX-RS client API is contained in javax.ws.rs.client package, and has three central concepts:

client
Manages connections to the server.
web target
Defines a RESTful endpoint and its parameters.
invocation
Encapsulates a reusable client request to a particular web target.

Client

The term “client” can have different levels of meaning. When used in describing client/server communication, it can refer to the program that program that sends requests to the server. In JAX-RS, the javax.ws.rs.client.Client interface encapsulates the entire client-side infrastructure for client communication—in other words, it represents the part of your program that actually performs the “client” functionality. Some of the responsibilities of JAX-RS Client include:

Clients are created using the javax.ws.rs.client.ClientBuilder class. The easiest way to create a client is to call ClientBuilder.newClient(). However most commonly programs will need to create a ClientBuilder using ClientBuilder.newBuilder(), configure the builder, and then call ClientBuilder.build() to create the actual Client instance. ClientBuilder comes with a fluent interface, allowing you to chain configuration method calls.

Configuring a JAX-RS Client.
final Client client = ClientBuilder.newBuilder()
    .register(PlainTextLinesMessageBodyWriter.class)
    .build();

WebTarget

The main access to RESTful API endpoints is represented by instances of the javax.ws.rs.client.WebTarget interface. A Client instance acts as a factory for a WebTarget most commonly using the Client.target(URI uri) method. There is also a String version Client.target(String uri), allowing you to call client.target("https://example.com/farm/pens/") to retrieve all the pens from the example endpoint in the JAX-RS lesson.

But the power of WebTarget lies in its ability to maintain a template for resolving path parameters, as well as adding query parameters or matrix parameters on the fly. You learned in the previous JAX-RS lesson that the server API allows you to define a path parameter by placing some variable name between curly brackets { and }. WebTarget recognizes the same template format for URIs. For example, you can call client.target("https://example.com/farm/pens/{penId}") to create a template WebTarget instance; later, before using the WebTarget, you can use one or more of its fluent builder methods to create another WebTarget instance with the parameters replaced. Here are some of the common fluent customization methods of a WebTarget:

matrixParam(String name, Object... values)
Creates a new WebTarget instance with the given matrix parameters added to the last URI segment.
queryParam(String name, Object... values)
Creates a new WebTarget instance with the given query parameter added.
resolveTemplate(String name, Object value)
Encodes the indicated value and uses it to replaces the indicated template parameter, returning a new WebTarget with the result. There exist other variations of this method that allow substitution of mltiple variables, as well as more control over whether and how the value is encoded; see the WebTarget API documentation for more details.
Replacing parameters in a JAX-RS WebTarget URI template.
final WebTarget pigpen5AnimalsEndpoint = client.target("https://example.com/farm/pens/{penId}/animals")
    .resolveTemplate("penId", "pigpen")
    .queryParam("limit", 5);  //only return five animals in the pigpen

Invocation

After defining an endpoint to be used by the client, you will need to invoke the web target. The javax.ws.rs.client.Invocation interface provides all the information needed to actually make the HTTP request. To configure the Invocation, you will use a javax.ws.rs.client.Invocation.Builder, which you can retrieve for a specific web target using WebTarget.request().

Configuring an Invocation

An Invocation instance is essentially an HTTP request ready to be invoked, containing all the necessary configuration for headers and other HTTP information. Here are some of the useful fluent configuration methods of an Invocation.Builder:

accept(MediaType... mediaTypes)
Sets the Accept header for the HTTP request. There is a companion method that accepts String media types.
acceptEncoding(String... encodings)
Sets the Accept-Encoding header for the HTTP request.
acceptLanguage(Locale... locales)
Sets the Accept-Language header for the HTTP request. There is a companion method that accepts String language tags.
build(String method)
Builds the Invocation, configuring it to invoke the named HTTP method such as GET. Convenience methods are provided for common HTTP methods, such as buildGet().
cookie(Cookie cookie)
Adds a cookie to be set by the request. There is a companion method that accepts a String name and value.
header(String name, Object value)
Sets an arbitrary header of the HTTP request. Many of the above header-specific methods are convenience versions of this method.

Invoking an HTTP Request

An Invocation only encapsulates the information necessary to make the HTTP request. To actually invoke the request, call Invocation.invoke(), which when finished will return an instance of javax.ws.rs.core.Response. This is the same Response type used by the JAX-RS server API you already studied.

From the Response you can determine the HTTP response code using Response.getStatus(). You can use methods such as Response.getLocation() for example to determine the Location header (for redirect responses), or Response.getHeaderString(String name) to get an arbitrary header by name. Response.getMediaType() will return the type of content returned (specified in the Content-Type header), or null if there is no response content.

Invoking an HTTP request using an Invocation.
final Invocation getPigpenAnimalsInvocation = pigpenAnimalsEndpoint.request()
    .accept(MediaType.TEXT_XML_TYPE)
    .buildGet();
final Response response = getPigpenAnimalsInvocation.invoke();
try {
  if(response.getStatus() == Response.Status.OK.getStatusCode()) {
      //TODO process the returned XML
  } else {
    //TODO handle the error
  }
} finally {
  response.close();
}

As a shortcut, the javax.ws.rs.client.SyncInvoker interface allows you to skip the explicit creation of an intermediate Invocation object, and invoke the request directly from the Invocation.Builder, which implements that interface. SyncInvoker provides a plethora of invocation methods such as SyncInvoker.get() for performing an HTTP GET request, and SyncInvoker.method(String name) for invoking some arbitrary HTTP method. These methods and similar ones return the same Response as you would get from calling Invocation.invoke(), allowing the above example to be rewritten in a more compact form.

Invoking an HTTP request directly using a SyncInvoker method of Invocation.Builder.
final Response response = pigpenAnimalsEndpoint.request()
    .accept(MediaType.TEXT_XML_TYPE)
    .get();
try {
  if(response.getStatus() == Response.Status.OK.getStatusCode()) {
      //TODO process the returned XML
  } else {
    //TODO handle the error
  }
} finally {
  response.close();
}

Response Content

To process the returned information, you can request to read an entity from the response using Response.readEntity(Class<T> entityType), specifying the type of response you expect.

Raw Bytes

At the lowest level of processing, you can request a java.io.InputStream which will allow you to read the raw bytes returned by the response. As usual you must close the stream when you are finished with it.

Reading the raw bytes of a Response.
final Response response = penAnimalsEndpoint.request()
    .accept(MediaType.TEXT_XML_TYPE)
    .get();
try {
  if(response.getStatus() == Response.Status.OK.getStatusCode()) {
    try(final InputStream inputStream = new BufferedInputStream(response.readEntity(InputStream.class))) {
      //TODO parse the XML bytes; don't forget to check the charset
    }
  } else {
    //TODO handle the error
  }
} finally {
  response.close();
}
Strings

Most of the time you do not want to handle parsing an input stream manually when processing a response. JAX-RS can take care of producing many entity types automatically. If the response content type can be interpreted as text (such as text/xml or more obviously text/plain), simply call response.readEntity(String.class). JAX-RS will take care converting the bytes based upon the charset, and return a string with the contents.

Unmarshaling Entities

The preferred higher-level approach is to let JAX-RS completely take care of object marshaling. When you used JAX-RS on the server, you learned that you could automatically marshal returned objects by registering a javax.ws.rs.ext.MessageBodyWriter<T> for the appropriate type. The same approach works on the client side; if you have an appropriate javax.ws.rs.ext.MessageBodyReader<T> registered for the FooBar type, you can call response.readEntity(FooBar.class) and JAX-RS will automatically unmarshal the content for you and return an instance of FooBar. There is no need to close any input stream; JAX-RS handles this for you.

For example you can use the same Jackson library from the previous lessons to automatically unmarshal an instance of the Pen class using content returned as JSON.

Reading a Pen entity from a Response using e.g. Jackson.
final Response response = client.target("https://example.com/farm/pens/pigpen")
    .request()
    .accept(MediaType.APPLICATION_JSON_TYPE)
    .get();
try {
  if(response.getStatus() == Response.Status.OK.getStatusCode()) {
    final Pen pigpen = response.readEntity(Pen.class);
    //TODO do something with the pigpen
  } else {
    //TODO handle the error
  }
} finally {
  response.close();
}
Bypassing Response with Entities

By specifying a response type during invocation, you can bypass altogether the explicit creation and processing of the Response object. The Invocation.invoke(Class<T> responseType) method will invoke the HTTP method check the Response for an error condition. If the request failed, JAX-RS will throw the appropriate javax.ws.rs.WebApplicationException subtype, for example javax.ws.rs.NotFoundException, indicating the problem. This is the same WebApplicationException type used by the JAX-RS server API you already studied. If the request was successful, JAX-RS will automatically read, unmarshal, and return the entity. With this approach there is no need to worry about the intermediate Response object!

Reading a Pen entity directly, bypassing Response.
final Invocation getPigpenInvocation = client.target("https://example.com/farm/pens/pigpen")
    .request()
    .accept(MediaType.APPLICATION_JSON_TYPE)
    .buildGet();
try {
  final Pen pigpen = getPigpenInvocation.invoke(Pen.class);
  //TODO do something with the pigpen
} catch(final NotFoundException notFoundException) {
  //TODO handle the missing pigpen
}

In fact a series of SyncInvoker methods such as SyncInvoker.get(Class<T> responseType) allow you to bypass, not only the intermediate Response, but also the intermediate Invocation itself. Calling one of these methods (including SyncInvoker.method(String name, Class<T> responseType) for arbitrary HTTP methods) on the Invocation.Builder as you did above results in a very compact and readable invocation, with automatic unmarshaling.

Reading a Pen entity directly, bypassing both Invocation and Response.
try {
  final Pen pigpen = client.target("https://example.com/farm/pens/pigpen")
      .request()
      .accept(MediaType.APPLICATION_JSON_TYPE)
      .get(Pen.class);
  //TODO do something with the pigpen
} catch(final NotFoundException notFoundException) {
  //TODO handle the missing pigpen
}

Request Content

For several HTTP methods such as PUT or POST you may need to send resource representations in addition to receiving them. Resource representations to send are wrapped with the javax.ws.rs.client.Entity<T> class, which encapsulates the object itself along with other information such as the media type to use. The Entity class comes with several static factory methods, such as Entity.entity(T entity, MediaType mediaType).

Once you have an entity, you can send it in the request using variations of the methods you've seen above, depending on which form of invocation you are using.

Storing a cattle yard Pen entity using HTTP PUT.
final Pen cattleYard = new Pen("cattleyard", "Cattle Yard", 10, 20, 50);
final Response response = client.target("https://example.com/farm/pens/cattleyard")
      .request()
      .put(Entity.json(cattleYard));
try {
  //TODO do something with the response if needed
} finally {
  response.close();
}

Configuration

For everything to work smoothly you'll likely need to configure JAX-RS. You may need to provide authentication credentials for the HTTP connection, and you will want to register providers for marshaling custom types. Such configuration is performed using methods of the javax.ws.rs.core.Configurable<C extends Configurable> interface. Several of the JAX-RS client components implement Configurable:

This means that you can perform configuration at the place most convenient. For example you may want to configure HTTP authentication information at the client level, but install different providers for marshaling based upon which web target you are using. Or you may simply configure the client with all the necessary settings for the entire application—you can still provide overriding properties and/or registrations at the other levels. Here are some of the most common Configurable methods:

property(String name, Object value)
Sets a configuration property, updating an existing property if it has been set already.
register(Class<?> componentClass)
Registers a class of a provider, such as a MessageBodyReader<T>, to be instantiated when needed.
register(Object component)
Registers a component that has already been instantiated.
Configuring a JAX-RS Client.
final Client client = ClientBuilder.newBuilder()
    .property("javax.xml.ws.client.connectionTimeout", 120000)
    .register(PlainTextLinesMessageBodyWriter.class)
    .build();

Testing with Restito

RESTful client code needs to be tested like any other code, but the need for a server to connect to presents some difficulties. It would be nice to test the functionality of the REST client, independent of the real server—to test the REST client as a unit, without invoking the entire server back-end implementation, which would bring in code that itself needs to be tested. The more isolated the unit tests, the better.

You've already learned about test doubles, and how they can “mock” a dependency to return values necessary just for testing. The Restito testing framework for REST clients uses the same concept to essentially mock the entire server layer. In other words, Restito creates a fake server that you can configure, using a fluent API, to respond to your client requests with prepared data as if it were the real server you would use in production.

Restito Server

You'll need a com.xebialabs.restito.server.StubServer to run during each unit test of your RESTful client. This is best done using the JUnit @Before and @After annotations you learned about for hooking into the unit test life cycle. You can start the server using StubServer.start(), and stop it using StubServer.stop().

Setting up a Restito StubServer in a unit test.
public class PensResourceTargetTest {

  private StubServer server;

  @Before
  public void startServer() {
    server = new StubServer.run();
  }

  …

  @After
  public void stopServer() {
    server.stop();
  }

Once the server is started, you can find out the server's port using StubServer.getPort().

Stubbing Endpoints

Now that the mock server is in place, you'll need to stub out some RESTful endpoints. The first step is to define the endpoints, but not in a declarative fashion as you used in JAX-RS on the server. Rather Restito uses an fluent, imperative approach, essentially saying, When a particular HTTP request is made at a particular path with certain parameters, then….

To start stubbing, call the static factory method StubHttp.whenHttp(StubServer server) to get a stubbed version of the server, com.xebialabs.restito.builder.stub.StubHttp. This class allows you to check for specific conditions such as incoming HTTP requests using StubHttp.match(Condition... conditions). Here are some common conditions, each a subclass of com.xebialabs.restito.semantics.Condition, returned via fluent factory methods of the same Condition class:

get(String uri)
Matches when the HTTP method GET was called on the URI path. Similar methods are available for DELETE, PATCH, POST, and PUT.
matchesUri(Pattern p)
Matches a URI pattern.
method(Method m)
Matches arbitrary HTTP methods. Currently the HTTP method must be identified using a class from the underlying Glassfish server Restito uses. See Restito Issue #22.
not(Condition c)
Negates another condition.
parameter(String key, String... parameterValues)
Matches a URI with the given parameter(s).
uri(String uri)
Matches an exact URI path.
withHeader(String key, String value)
Matches an HTTP request with the given header set to the given value. There is a similar method withHeader(String key) that simply matches the presence of a header, without regard to its value.

Providing one or more conditions will return a com.xebialabs.restito.builder.stub.StubWithCondition which allows you to specify what actions to perform in response to the request. You provide the actions using StubWithCondition.then(Applicable... actions). The actions you provide are usually implementations of the com.xebialabs.restito.semantics.Action class, and can be retrieved using fluent factory methods of the Action class itself. Here are some of the actions you'll use most often:

bytesContent(byte[] content)
Provides actual bytes to return in the content.
charset(Charset charset)
Sets the charset of the response. A similar method charset(String charset) exists which takes a string. You must set the charset before specifying content to return.
contentType(String contentType)
Indicates the content type of the response.
header(String key, String value)
Sets a response header name and value.
noContent()
Sets the HTTP response code to 204 (No Content).
noop()
Does nothing.
ok()
Sets the HTTP response code to 200 (OK). There is an equivalent method success(), but ok() should usually be used instead as it makes clearer which HTTP response is indicated.
resourceContent(URL resourceUrl)
Returns content from the given URL. Restito will attempt to determine the content type based upon the resource extension; currently .xml and .json are supported. The convenience method resourceContent(URL resourceUrl, Charset charset) allows you to indicate the content and the charset at the same time, without needing to call charset(Charset charset) separately. Be careful with the similar methods resourceContent(String resourcePath) and resourceContent(String resourcePath, String charset); these take string resource paths, but use the classloader of the Action class, which may not be appropriate for loading your resources.
status(HttpStatus status)
Allows an arbitrary HTTP status to be returned. Currently the HTTP status must be identified using a class from the underlying Glassfish server Restito uses.
stringContent(String content)
Returns the provided response content as text. As of Restito 0.9.2 there is a severe string encoding bug that uses the default system charset to determing the string content. Avoid this method for now and call charset(charset) followed by bytesContent(string.getBytes(charset)). See Restito Issue #66.
unauthorized()
Sets the HTTP response code to 401 (Unauthorized). A companion method unauthorized(String realm) is available if you need to specify the authentication realm.

Thus you might store a premade representation of the pigpen in a JSON resource file, in the same package as the Pen interface.

Test representation of a pigpen in JSON resource src/test/resources/com/example/farm/pigpen.json.
{
  "id": "pigpen",
  "name": "Pig Pen",
  "length": 40,
  "width": 30,
  "capacity": 100
}

Now you can set up a Restito mock server to return the pigpen representation when the appropriate endpoint is accessed by a client.

Configuring a mock endpoint to return a representation of a pigpen.
public class PensResourceTargetTest {

  …

  @Test
  public void testGetPigpen() {

    //stub the pigpen endpoint using Restito
    whenHttp(server).
        match(get("/farm/pens/pigpen"),
            withHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)).
        then(ok(),
            resourceContent(Pen.class.getResource("pigpen.json")));

    //get the pigpen using JAX-RS
    ClientBuilder.newClient()
      .target(URIBuilder.fromUri("http://localhost/farm/pens/pigpen").port(server.getPort()))
      .request()
      .accept(MediaType.APPLICATION_JSON_TYPE)
      .get(Pen.class);

    assertThat(pen.getId(), is("pigpen"));
    assertThat(pen.getName(), is("Pig Pen"));
    assertThat(pen.getLength(), is(40));
    …

  }

Verifying Calls

In addition to stubbing methods, Restito can act like a spy so that you can verify the correct endpoints were called. You start by retrieving an instance of com.xebialabs.restito.builder.verify.VerifyHttp by calling the static factory method VerifyHttp.verifyHttp(StubServer stubServer) with the stub server you just used. Then you verify that certain conditions were met based upon the number of times you expected those conditions to occur. This is accomplished by other methods of VerifyHttp, some of the most useful of which are shown below. Conditions are specified using the same Condition types you saw above.

atLeast(int t, Condition... conditions)
Checks that there certain conditions were met at least a specified number of times.
never(Condition... conditions)
Checks that certain conditions never happened.
times(int t, Condition... conditions)
Checks that certain conditions happened an exact number of times. The convenience verification once(Condition... conditions) checks that the conditions occurred only once.
Verifying that the server was actually called.
public class PensResourceTargetTest {

  …

  @Test
  public void testGetPigpen() {

    //TODO stub the pigpen endpoint using Restito

    //TODO get the pigpen using JAX-RS

    …

    verifyHttp(server).once(get("/farm/pens/pigpen"));

  }

Review

Summary

Gotchas

In the Real World

If you use the base JAX-RS client library, you may find that yourself duplicating code in different calls to REST endpoints. It might be useful to encapsulate JAX-RS client calls in some class related to each endpoint. It turns out that, if care is taken, you can implement the same interface you defined on the server for the endpoint, yet on the client. For example you could implement the same PensResource interface you created to define the endpoint for pens as a XXXResourceTarget on the client.

PensResource
Defines the RESTful endpoint in general.
PensResourceService
The server-side implementation of the pens endpoint.
PensResourceTarget
The client-side implementation of the pens endpoint.
Implementing a PensResourceTarget using JAX-RS client.
public class PensResourceTarget implements PensResource {

  private final Client client;

  private final URI baseUri;

  public PensResourceTarget(@Nonnull final Client client, @Nonnull final URI baseUri) {
    this.client = checkNotNull(client);
    this.baseUri = checkNotNull(baseUri);
  }

  …

  @Override
  public Pen getPen(final String penId) throws IOException, NotFoundException, WebApplicationException {
    return client.target(UriBuilder.fromUri(baseUri).path(getClass(), "getPen"))
      .resolveTemplate("pen", penId);
      .request()
      .accept(MediaType.APPLICATION_JSON_TYPE)
      .get(Pen.class);
  }

  …

}

Think About It

Self Evaluation

Task

Update your Booker command-line client application to fully support accessing library information from a remote computer using the RESTful API you designed and already implemented on the server.

See Also

References

Resources

Acknowledgments