1. Introduction
In the previous article, we focused on the RESTEasy server-side implementation of JAX-RS 2.0.
JAX-RS 2.0 introduces a new client API so that you can make HTTP requests to your remote RESTful web services. Jersey, Apache CXF, Restlet, and RESTEasy are only a subset of the most popular implementations.
In this article, we’ll explore how to consume the REST API by sending requests with a RESTEasy API.
2. Project Setup
Add in your pom.xml the following dependencies:
<properties>
<resteasy.version>4.7.2.Final</resteasy.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
...
</dependencies>
3. Client-Side Code
The client implementation is quite small, being made up of 3 main classes:
- Client
- WebTarget
- Response
The Client interface is a builder of WebTarget instances.
WebTarget represents a distinct URL or URL template from which you can build more sub-resource WebTargets or invoke requests.
There are really two ways to create a Client:
- The standard way is using the org.jboss.resteasy.client.ClientRequest
- RESTeasy Proxy Framework: by using the ResteasyClientBuilder class
We will focus on the RESTEasy Proxy Framework here.
Instead of using JAX-RS annotations to map an incoming request to your RESTFul Web Service method, the client framework builds an HTTP request that it uses to invoke on a remote RESTful Web Service.
So let’s start writing a Java interface and using JAX-RS annotations on the methods and on the interface.
3.1. The ServicesClient Interface
@Path("/movies")
public interface ServicesInterface {
@GET
@Path("/getinfo")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
Movie movieByImdbId(@QueryParam("imdbId") String imdbId);
@POST
@Path("/addmovie")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
Response addMovie(Movie movie);
@PUT
@Path("/updatemovie")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
Response updateMovie(Movie movie);
@DELETE
@Path("/deletemovie")
Response deleteMovie(@QueryParam("imdbId") String imdbId);
}
3.2. The Movie Class
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "movie", propOrder = { "imdbId", "title" })
public class Movie {
protected String imdbId;
protected String title;
// getters and setters
}
3.3. The Request Creation
We’ll now generate a proxy client that we can use to consume the API:
String transformerImdbId = "tt0418279";
Movie transformerMovie = new Movie("tt0418279", "Transformer 2");
UriBuilder FULL_PATH = UriBuilder.fromPath("http://127.0.0.1:8082/resteasy/rest");
ResteasyClient client = (ResteasyClient)ClientBuilder.newClient();
ResteasyWebTarget target = client.target(FULL_PATH);
ServicesInterface proxy = target.proxy(ServicesInterface.class);
// POST
Response moviesResponse = proxy.addMovie(transformerMovie);
System.out.println("HTTP code: " + moviesResponse.getStatus());
moviesResponse.close();
// GET
Movie movies = proxy.movieByImdbId(transformerImdbId);
// PUT
transformerMovie.setTitle("Transformer 4");
moviesResponse = proxy.updateMovie(transformerMovie);
moviesResponse.close();
// DELETE
moviesResponse = proxy.deleteMovie(batmanMovie.getImdbId());
moviesResponse.close();
Note that the RESTEasy client API is based on the Apache HttpClient.
Also note that, after each operation, we’ll need to close the response before we can perform a new operation. This is necessary because, by default, the client only has a single HTTP connection available.
Finally, note how we’re working with the DTOs directly – we’re not dealing with the marshal/unmarshal logic to and from JSON or XML; that happens behind the scenes using JAXB or Jackson since the Movie class was properly annotated*.*
3.4. The Request Creation With Connection Pool
One note from the previous example was that we only had a single connection available. If – for example, we try to do:
Response batmanResponse = proxy.addMovie(batmanMovie);
Response transformerResponse = proxy.addMovie(transformerMovie);
without invoke close() on batmanResponse – an exception will be thrown when the second line is executed:
java.lang.IllegalStateException:
Invalid use of BasicClientConnManager: connection still allocated.
Make sure to release the connection before allocating another one.
Again – this simply happens because the default HttpClient used by RESTEasy is org.apache.http.impl.conn.SingleClientConnManager – which of course only makes a single connection available.
Now – to work around that limitation – the RestEasyClient instance must be created differently (with a connection pool):
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
cm.setMaxTotal(200); // Increase max total connection to 200
cm.setDefaultMaxPerRoute(20); // Increase default max connection per route to 20
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient);
ResteasyClient client = ((ResteasyClientBuilder) ClientBuilder.newBuilder()).httpEngine(engine).build();
ResteasyWebTarget target = client.target(FULL_PATH);
ServicesInterface proxy = target.proxy(ServicesInterface.class);
Now we can benefit from a proper connection pool and can have multiple requests running through our client without necessarily having to release the connection each time.
4. Conclusion
In this quick tutorial, we introduced the RESTEasy Proxy Framework and we built a super simple client API with it.
The framework gives us a few more helper methods to configure a client and can be defined as the mirror opposite of the JAX-RS server-side specifications.
The example used in this article is available as a sample project on GitHub.