[Note: This is one in a series of posts about JAX-RS. You may wish to start at the beginning]
Jersey, the reference implementation of the JAX-RS specification, is a framework that makes it very easy to implement RESTful web services. But there is more to Jersey than just the server side. In this post I will spend a little time exploring the Jersey client library.
Depending upon how you design your RESTful service, you may or may not want to have separate URLs for separate representations of the same resource. This presents a problem when trying to test with a browser. There is no way to tell popular browsers that you want a text/html representation or an image/jpeg representation. The browser has a list of preferred media types, but none that I'm aware of allow you to customize this (either in general or for a particular request). Even more importantly, we need to be able to build solid unit tests for our services. The Jersey client framework provides a good solution to this problem. It is designed to allow developers of RESTful web services to write good unit tests, but is more general purpose than that. It can also be used to write RESTful client applications.
There is an excellent tutorial on the Jersey client API, which you should download and read if you plan to use it. But I will give you a taste of the API here.
The Jersey client API is very clean, and requires a minimum of fuss to use. As an example, let's write a unit test which tests our web service serving up information about fighter planes. First, we write the code to set up and tear down our service implementation. This code is the same as that in our Main class before, just split up between the setup and tear-down methods.
public class Test3b {
private SelectorThread selector;
@org.junit.Before
public void createService() throws IOException {
MapinitParams = new HashMap ();
initParams.put("com.sun.jersey.config.property.packages", "net.gilstraps.server");
selector = GrizzlyWebContainerFactory.create("http://localhost:9998/", initParams);
}
@org.junit.After
public void DestroyService() {
selector.stopEndpoint();
selector = null;
}
The only difference in this case is we don't read from standard in to shut down the service, since we always want to shut it down at the end of the unit test.
Now, we can test that the text/html representation returned by invoking the service is what we expect. First, we do a bit of hoop jumping to read in a copy of the HTML we expect to receive:
@org.junit.Test
public void testF22Html() {
try {
File expected = new File("f22.html");
long fileSize = expected.length();
if (fileSize > Integer.MAX_VALUE) {
throw new IllegalArgumentException("File it larger than a StringWriter can hold");
}
int size = (int) fileSize;
BufferedReader r = new BufferedReader(new FileReader(expected), size);
char[] chars = new char[size];
int readChars = r.read(chars);
if (readChars != size) {
throw new RuntimeException("Failed to read all chars of the expected result html file");
}
final String expectedText = new String(chars);
At this point, the variable expectedText contains what we should receive back from our request. Making the request is quite straightforward. First, we create a JAX-RS client:
Client client = new Client();
These clients are 'heavyweight' objects (taking time to create and using significant resources). As such, in a production client we would want to create this client once and use it many times. For the sake of independent unit tests, we will go ahead and create a Client object for each test.
Next, we specify the resource we want to retrieve using a WebResource object:
WebResource f22 = client.resource("http://localhost:9998/planes/f22/f22.html");
And then we ask the client to retrieve the resource for us, specifying that we want a text/html representation (MediaType.TEXT_HTML_TYPE) and specifying that we want to get back a ClientResponse object:
ClientResponse response =
f22.accept(MediaType.TEXT_HTML_TYPE).get(ClientResponse.class);
This code is using the builder pattern, where we build up our request through a chain of method calls. In this case, the chain is only two calls long. First, we call the accept method to specify the media types we will accept in the response (text/html), then we call the get method to actually retrieve the resource. It is possible to chain together more calls to specify other characteristics of either the request or the expected response (for more information, see the whitepaper on the Jersey client API).
Now that we have gotten a representation of the resource in the form of an HTTP response, we can then retrieve the HTML entity contained within that response as a string:
String returnedHTML = response.getEntity(String.class);And finally, since this is a unit test, we make sure that what we got back matches what we expected:
assertEquals("Expected and actual HTML don't match"
expectedText, returnedHTML);The ClientResponse object is useful if you want to look at other characteristics of the response, such as the returned headers. In this case, we could just as well have asked for the string from the WebResource directly. To do so, we would replace these two lines:
ClientResponse response =
f22.accept(MediaType.TEXT_HTML_TYPE).get(ClientResponse.class);
String returnedHTML = response.getEntity(String.class);With this one:
String returnedHTML = asHTML.accept(MediaType.TEXT_HTML_TYPE).get(String.class);Testing for retrieval of the JPEG representation of an image is almost identical. The only differences are that we read in the image file as an array of bytes, ask for the response entity as an array of bytes, and compare the two as arrays of bytes. Here is the entire test method:
@org.junit.Test
public void testF22JPEG() {
try {
// Read in our expected result
File imageFile = new File("f22.jpg");
long fileSize = imageFile.length();
if (fileSize > Integer.MAX_VALUE) {
throw new IllegalArgumentException("File it larger than a StringWriter can hold");
}
int size = (int) fileSize;
byte[] expectedBytes = new byte[size];
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(imageFile), size);
int bytesRead = bis.read(expectedBytes);
assertEquals(size, bytesRead);
// Request the representation
Client client = new Client();
WebResource f22 = client.resource("http://localhost:9998/planes/f22/f22.jpg");
ClientResponse response = f22.accept(MediaType.WILDCARD).get(ClientResponse.class);
MultivaluedMapheaders = response.getHeaders();
for ( String key : headers.keySet() ) {
System.out.println( key + "=" + headers.get(key) );
}
byte[] returnedBytes = new byte[0];
returnedBytes = response.getEntity(returnedBytes.getClass());
// Compare the two sets of bytes to make sure they match
assertEquals(size, returnedBytes.length);
assertTrue(Arrays.equals(expectedBytes,returnedBytes));
}
catch (FileNotFoundException e) {
AssertionError ae = new AssertionError("File containing expected HTML not found!");
ae.initCause(e);
throw ae;
}
catch (IOException e) {
AssertionError ae = new AssertionError("Problems reading expected text!");
ae.initCause(e);
throw ae;
}
}Just like the core of Jersey, the client library enables clear, short code that clearly expresses what the developer is trying to do. This is a marked departure from many other web frameworks and APIs. It is refreshing to be able to write web services clients in such a terse, yet clear style.[Note: I plan to continue working with Jersey and continue posting about it. But I'm going to take a few weeks to look into some other topics first. I'll make sure to update the list of JAX-RS and Jersey posts as I add more]