Alternative Java Clients - 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 16. Alternative Java Clients

While JAX-RS 2.0 added client support, there are other Java clients you can use to interact with web services if you do not have JAX-RS 2.0 available in your environment.

java.net.URL

Like most programming languages, Java has a built-in HTTP client library. It’s nothing fancy, but it’s good enough to perform most of the basic functions you need. The API is built around two classes, java.net.URL and java.net.HttpURLConnection. The URL class is just a Java representation of a URL. Here are some of the pertinent constructors and methods:

public class URL {
 
   public URL(java.lang.String s)
            throws java.net.MalformedURLException {}
 
   public java.net.URLConnection
            openConnection() throws java.io.IOException {}
...
}

From a URL, you can create an HttpURLConnection that allows you to invoke specific requests. Here’s an example of doing a simple GET request:

URL url = new URL("http://example.com/customers/1");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/xml");
 
if (connection.getResponseCode() != 200) {
  throw new RuntimeException("Operation failed: "
                              + connection.getResponseCode());
}
 
System.out.println("Content-Type: " + connection.getContentType());
 
BufferedReader reader = new BufferedReader(new
              InputStreamReader(connection.getInputStream()));
 
String line = reader.readLine();
while (line != null) {
   System.out.println(line);
   line = reader.readLine();
}
connection.disconnect();

In this example, we instantiate a URL instance and then open a connection using the URL.openConnection() method. This method returns a generic URLConnection type, so we need to typecast it to an HttpURLConnection. Once we have a connection, we set the HTTP method we are invoking by calling HttpURLConnection.setMethod(). We want XML from the server, so we call the setRequestProperty() method to set the Accept header. We get the response code and Content-Type by calling getResponseCode() and getContentType(), respectively. The getInputStream() method allows us to read the content sent from the server using the Java streaming API. We finish up by calling disconnect().

Sending content to the server via a PUT or POST is a little different. Here’s an example of that:

URL url = new URL("http://example.com/customers");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setInstanceFollowRedirects(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/xml");
OutputStream os = connection.getOutputStream();
os.write("<customer id='333'/>".getBytes());
os.flush();
if (connection.getResponseCode() != HttpURLConnection.HTTP_CREATED) {
   throw new RuntimeException("Failed to create customer");
}
System.out.println("Location: " + connection.getHeaderField("Location"));
connection.disconnect();

In this example, we create a customer by using POST. We’re expecting a response of 201, “Created,” as well as a Location header in the response that points to the URL of our newly created customer. We need to call HttpURLConnection.setDoOutput(true). This allows us to write a body for the request. By default, HttpURLConnection will automatically follow redirects. We want to look at our Location header, so we call setInstanceFollowRedirects(false) to disable this feature. We then call setRequestMethod() to tell the connection we’re making a POST request. The setRequestProperty() method is called to set the Content-Type of our request. We then get a java.io.OutputStream to write out the data and the Location response header by calling getHeaderField(). Finally, we call disconnect() to clean up our connection.

Caching

By default, HttpURLConnection will cache results based on the caching response headers discussed in Chapter 11. You must invoke HttpURLConnection.setUseCaches(false) to turn off this feature.

Authentication

The HttpURLConnection class supports Basic, Digest, and Client Certificate Authentication. Basic and Digest Authentication use the java.net.Authenticator API. Here’s an example:

Authenticator.setDefault(new Authenticator() {
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication ("username, "password".toCharArray());
    }
});

The setDefault() method is a static method of Authenticator. You pass in an Authenticator instance that overrides the class’s getPasswordAuthentication() method. You return a java.net.PasswordAuthentication object that encapsulates the username and password to access your server. When you do HttpURLConnection invocations, authentication will automatically be set up for you using either Basic or Digest, depending on what the server requires.

The weirdest part of the API is that it is driven by the static method setDefault(). The problem with this is that your Authenticator is set VM-wide. So, doing authenticated requests in multiple threads to different servers is a bit problematic with the basic example just shown. You can address this by using java.lang.ThreadLocal variables to store username and passwords:

public class MultiThreadedAuthenticator extends Authenticator {
 
   private static ThreadLocal<String> username = new ThreadLocal<String>();
   private static ThreadLocal<String> password = new ThreadLocal<String>();
 
   public static void setThreadUsername(String user) {
      username.set(user);
   }
 
   public static void setThreadPassword(String pwd) {
      password.set(pwd);
   }
 
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication (username.get(),
                                           password.get().toCharArray());
    }
}

The ThreadLocal class is a standard class that comes with the JDK. When you call set() on it, the value will be stored and associated with the calling thread. Each thread can have its own value. ThreadLocal.get() returns the thread’s current stored value. So, using this class would look like this:

Authenticator.setDefault(new MultiThreadedAuthenticator());
 
MultiThreadedAuthenticator.setThreadUsername("bill");
MultiThreadedAuthenticator.setThreadPassword("geheim");

Client Certificate Authentication

Client Certificate Authentication is a little different. First, you must generate a client certificate using the keytool command-line utility that comes with the JDK:

$ <JAVA_HOME>/bin/keytool -genkey -alias client-alias -keyalg RSA
-keypass changeit -storepass changeit -keystore keystore.jks

Next, you must export the certificate into a file so it can be imported into a truststore:

$ <JAVA_HOME>/bin/keytool -export -alias client-alias
-storepass changeit -file client.cer -keystore keystore.jks

Finally, you create a truststore and import the created client certificate:

$ <JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias client-alias -file client.cer
-keystore cacerts.jks
-keypass changeit -storepass changeit

Now that you have a truststore, use it to create a javax.net.ssl.SSLSocketFactory within your client code:

import javax.net.ssl.SSLContext;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLSocketFactory;
import java.security.SecureRandom;
import java.security.KeyStore;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.File;
 
public class MyClient {
 
   public static SSLSocketFactory
             getFactory( File pKeyFile, String pKeyPassword )
                                                   throws Exception {
     KeyManagerFactory keyManagerFactory =
                           KeyManagerFactory.getInstance("SunX509");
     KeyStore keyStore = KeyStore.getInstance("PKCS12");
 
     InputStream keyInput = new FileInputStream(pKeyFile);
     keyStore.load(keyInput, pKeyPassword.toCharArray());
     keyInput.close();
 
     keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());
 
     SSLContext context = SSLContext.getInstance("TLS");
     context.init(keyManagerFactory.getKeyManagers(), null
                    , new SecureRandom());
 
     return context.getSocketFactory();
   }

This code loads the truststore into memory and creates an SSLSocketFactory. The factory can then be registered with a java.net.ssl.HttpsURLConnection:

   public static void main(String args[]) throws Exception {
      URL url = new URL("https://someurl");
      HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
      con.setSSLSocketFactory(getFactory(new File("cacerts.jks"),
                                "changeit"));
   }
}

You may then make invocations to the URL, and the client certificate will be used for authentication.

Advantages and Disadvantages

The biggest advantage of using the java.net package as a RESTful client is that it is built in to the JDK. You don’t need to download and install a different client framework.

There are a few disadvantages to the java.net API. First, it is not JAX-RS–aware. You will have to do your own stream processing and will not be able to take advantage of any of the MessageBodyReaders and MessageBodyWriters that come with your JAX-RS implementation.

Second, the framework does not do preemptive authentication for Basic or Digest Authentication. This means that HttpURLConnection will first try to invoke a request without any authentication headers set. If the server requires authentication, the initial request will fail with a 401, “Unauthorized,” response code. The HttpURLConnection implementation then looks at the WWW-Authenticate header to see whether Basic or Digest Authentication should be used and retries the request. This can have an impact on the performance of your system because each authenticated request will actually be two requests between the client and server.

Third, the framework can’t do something as simple as form parameters. All you have to work with are java.io.OutputStream and java.io.InputStream to perform your input and output.

Finally, the framework only allows you to invoke the HTTP methods GET, POST, DELETE, PUT, TRACE, OPTIONS, and HEAD. If you try to invoke any HTTP method other than those, an exception is thrown and your invocation will abort. In general, this is not that important unless you want to invoke newer HTTP methods like those defined in the WebDAV specification.

Apache HttpClient

The Apache foundation has written a nice, extensible, HTTP client library called HttpClient.[20] It is currently on version 4.x as of the writing of this book. Although it is not JAX-RS–aware, it does have facilities for preemptive authentication and APIs for dealing with a few different media types like forms and multipart. Some of its other features are a full interceptor model, automatic cookie handling between requests, and pluggable authentication. Let’s look at a simple example:

import org.apache.http.*;
import org.apache.http.client.*;
 
public class MyClient {
 
  public static void main(String[] args) throws Exception {
 
      DefaultHttpClient client = new DefaultHttpClient();
      HttpGet get = new HttpGet("http://example.com/customers/1");
      get.addHeader("accept", "application/xml");
 
      HttpResponse response = client.execute(get);
      if (response.getStatusLine().getStatusCode() != 200) {
         throw new RuntimeException("Operation failed: " +
                   response.getStatusLine().getStatusCode());
      }
 
      System.out.println("Content-Type: " +
           response.getEntity().getContentType().getValue());
 
      BufferedReader reader = new BufferedReader(new
               InputStreamReader(response.getEntity()
                                         .getInputStream()));
 
      String line = reader.readLine();
      while (line != null) {
         System.out.println(line);
         line = reader.readLine();
      }
      client.getConnectionManager().shutdown();
   }
}

In Apache HttpClient 4.x, the org.apache.http.impl.client.DefaultHttpClient class is responsible for managing HTTP connections. It handles the default authentication settings, and pools and manages persistent HTTP connections (keepalive) and any other default configuration settings. It is also responsible for executing requests. The org.apache.http.client.methods.HttpGet class is used to build an actual HTTP GET request. You initialize it with a URL and set any request headers you want using the HttpGet.addHeader() method. There are similar classes in this package for doing POST, PUT, and DELETE invocations. Once you have built your request, you execute it by calling DefaultHttpClient.execute(), passing in the request you built. This returns an org.apache.http.HttpResponse object. To get the response code from this object, execute HttpResponse.getStatusLine().getStatusCode(). The HttpResponse.getEntity() method returns an org.apache.http.HttpEntity object, which represents the message body of the response. From it, you can get the Content-Type by executing HttpEntity.getContentType() as well as a java.io.InputStream so you can read the response. When you are done invoking requests, you clean up your connections by calling HttpClient.getConnectionManager().shutdown().

To push data to the server via a POST or PUT operation, you need to encapsulate your data within an instance of the org.apache.http.HttpEntity interface. The framework has some simple prebuilt ones for sending strings, forms, byte arrays, and input streams. Let’s look at sending some XML.

In this example, we want to create a customer in a RESTful customer database. The API works by POSTing an XML representation of the new customer to a specific URI. A successful response is 201, “Created.” Also, a Location response header is returned that points to the newly created customer:

import org.apache.http.*;
import org.apache.http.client.*;
import org.apache.impl.client.*;
 
public class MyClient {
 
  public static void main(String[] args) throws Exception {
 
      DefaultHttpClient client = new DefaultHttpClient();
      HttpPost post = new HttpPost("http://example.com/customers");
      StringEntity entity = new StringEntity("<customer id='333'/>");
      entity.setContentType("application/xml");
      post.setEntity(entity);
      HttpClientParams.setRedirection(post.getParams(), false);
      HttpResponse response = client.execute(post);
      if (response.getStatusLine().getStatusCode() != 201) {
         throw new RuntimeException("Operation failed: " +
                   response.getStatusLine().getStatusCode());
      }
 
      String location = response.getLastHeader("Location")
                                 .getValue();
 
      System.out.println("Object created at: " + location);
      System.out.println("Content-Type: " +
           response.getEntity().getContentType().getValue());
 
      BufferedReader reader = new BufferedReader(new
           InputStreamReader(response.getEntity().getContent()));
 
      String line = reader.readLine();
      while (line != null) {
         System.out.println(line);
         line = reader.readLine();
      }
      client.getConnectionManager().shutdown();
   }
}

We create an org.apache.http.entity.StringEntity to encapsulate the XML we want to send across the wire. We set its Content-Type by calling StringEntity.setContentType(). We add the entity to the request by calling HttpPost.setEntity(). Since we are expecting a redirection header with our response and we do not want to be automatically redirected, we must configure the request to not do automatic redirects. We do this by calling HttpClientParams.setRedirection(). We execute the request the same way we did with our GET example. We get the Location header by calling HttpResponse.getLastHeader().

Authentication

The Apache HttpClient 4.x supports Basic, Digest, and Client Certificate Authentication. Basic and Digest Authentication are done through the DefaultHttpClient.getCredentialsProvider().setCredentials() method. Here’s an example:

DefaultHttpClient client = new DefaultHttpClient();
client.getCredentialsProvider().setCredentials(
    new AuthScope("example.com", 443),
    new UsernamePasswordCredentials("bill", "geheim");
);

The org.apache.http.auth.AuthScope class defines the server and port that you want to associate with a username and password. The org.apache.http.auth.UsernamePasswordCredentials class encapsulates the username and password into an object. You can callsetCredentials() for every domain you need to communicate with securely.

Apache HttpClient, by default, does not do preemptive authentication for the Basic and Digest protocols, but does support it. Since the code to do this is a bit verbose, we won’t cover it in this book.

Client Certificate authentication

Apache HttpClient also supports Client Certificate Authentication. As with HttpsURLConnection, you have to load in a KeyStore that contains your client certificates. The section java.net.URL describes how to do this. You initialize anorg.apache.http.conn.ssl.SSLSocketFactory with a loaded KeyStore and associate it with the DefaultHttpClient. Here is an example of doing this:

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
 
import org.apache.http.*;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.*;
import org.apache.http.conn.scheme.*;
import org.apache.http.conn.ssl.*;
import org.apache.http.impl.client.DefaultHttpClient;
 
public class MyClient {
 
   public final static void main(String[] args) throws Exception {
      DefaultHttpClient client = new DefaultHttpClient();
 
      KeyStore trustStore  = KeyStore.getInstance(
                                 KeyStore.getDefaultType());
      FileInputStream instream = new FileInputStream(
                                     new File("my.keystore"));
      try {
          trustStore.load(instream, "changeit".toCharArray());
      } finally {
          instream.close();
      }
 
      SSLSocketFactory socketFactory =
                                     new SSLSocketFactory(trustStore);
      Scheme scheme = new Scheme("https", socketFactory, 443);
      client.getConnectionManager()
             .getSchemeRegistry().register(scheme);
 
      HttpGet httpget = new HttpGet("https://localhost/");
 
      ... proceed with the invocation ...
   }
}

Advantages and Disadvantages

Apache HttpClient is a more complete solution and is better designed than java.net.HttpURLConnection. Although you have to download it separately from the JDK, I highly recommend you take a look at it. It has none of the disadvantages of HttpURLConnection, except that it is not JAX-RS–aware. Many JAX-RS implementations, including RESTEasy, allow you to use Apache HttpClient as the underlying HTTP client engine, so you can get the best of both worlds.

RESTEasy Client Proxies

The RESTEasy Client Proxy Framework is a different way of writing RESTful Java clients. The idea of the framework is to reuse the JAX-RS annotations on the client side. When you write JAX-RS services, you are using the specification’s annotations to turn an HTTP invocation into a Java method call. The RESTEasy Client Proxy Framework flips this around to instead use the annotations to turn a method call into an HTTP request.

You start off by writing a Java interface with methods annotated with JAX-RS annotations. For example, let’s define a RESTful client interface to the customer service application we have talked about over and over again throughout this book:

@Path("/customers")
public interface CustomerResource {
 
   @GET
   @Produces("application/xml")
   @Path("{id}")
   public Customer getCustomer(@PathParam("id") int id);
 
   @POST
   @Consumes("application/xml")
   public Response createCustomer(Customer customer);
 
   @PUT
   @Consumes("application/xml")
   @Path("{id}")
   public void updateCustomer(@PathParam("id") int id, Customer cust);
}

This interface looks exactly like the interface a JAX-RS service might implement. Through RESTEasy, we can turn this interface into a Java object that can invoke HTTP requests. To do this, we use the org.jboss.resteasy.client.jaxrs.ResteasyWebTarget interface:

Client client = ClientFactory.newClient();
WebTarget target = client.target("http://example.com/base/uri");
ResteasyWebTarget target = (ResteasyWebTarget)target;
 
CustomerResource customerProxy = target.proxy(CustomerResource.class);

If you are using RESTEasy as your JAX-RS implementation, all you have to do is typecast an instance of WebTarget to ResteasyWebTarget. You can then invoke the ResteasyWebTarget.proxy() method. This method returns an instance of the CustomerResource interface that you can invoke on. Here’s the proxy in use:

// Create a customer
Customer newCust = new Customer();
newCust.setName("bill");
Response response = customerProxy.createCustomer(newCust);
 
// Get a customer
Customer cust = customerProxy.getCustomer(333);
 
// Update a customer
cust.setName("burke");
customerProxy.updateCustomer(333, cust);

When you invoke one of the methods of the returned CustomerResource proxy, it converts the Java method call into an HTTP request to the server using the metadata defined in the annotations applied to the CustomerResource interface. For example, the getCustomer() invocation in the example code knows that it must do a GET request on the http://example.com/customers/333 URI, because it has introspected the values of the @Path, @GET, and @PathParam annotations on the method. It knows that it should be getting back XML from the @Produces annotation. It also knows that it should unmarshal it using a JAXB MessageBodyReader, because the getCustomer() method returns a JAXB annotated class.

Advantages and Disadvantages

A nice side effect of writing Java clients with this proxy framework is that you can use the Java interface for Java clients and JAX-RS services. With one Java interface, you also have a nice, clear way of documenting how to interact with your RESTful Java service. As you can see from the example code, it also cuts down on a lot of boilerplate code. The disadvantage, of course, is that this framework, while open source, is proprietary.

Wrapping Up

In this chapter, you learned three alternative ways to write RESTful clients in Java using the JDK’s java.net.HttpURLConnection class, Apache HttpClient, and the RESTEasy Client Proxy Framework. All three have their merits as alternatives to the JAX-RS 2.0 Client API.


[20] For more information, see http://hc.apache.org.





All materials on the site are licensed Creative Commons Attribution-Sharealike 3.0 Unported CC BY-SA 3.0 & GNU Free Documentation License (GFDL)

If you are the copyright holder of any material contained on our site and intend to remove it, please contact our site administrator for approval.

© 2016-2026 All site design rights belong to S.Y.A.