O'Reilly logo

Java Web Services: Up and Running by Martin Kalin

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Java Clients Against Real-World RESTful Services

There are many RESTful services available from well-known players such as Yahoo!, Amazon, and eBay, although controversy continues around the issue of what counts as a truly RESTful service. This section provides sample clients against some of these commercial REST-style services.

The Yahoo! News Service

Here HTTP verb is a client against Yahoo!’s RESTful service that summarizes the current news on a specified topic. The request is an HTTP GET with a query string:

import java.net.URI;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.Dispatch;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMResult;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.ws.handler.MessageContext;
import org.w3c.dom.NodeList;
import yahoo.NSResolver;

// A client against the Yahoo! RESTful news summary service.
class YahooClient {
    public static void main(String[ ] args) throws Exception {
        if (args.length < 1) {
            System.err.println("YahooClient <your AppID>");
            return;
        }
        String app_id = "appid=" + args[0];
        
        // Create a name for a service port.
        URI ns_URI = new URI("urn:yahoo:yn");
        QName serviceName = new QName("yahoo", ns_URI.toString());
        QName portName = new QName("yahoo_port", ns_URI.toString());

        // Now create a service proxy
        Service s = Service.create(serviceName);

        String qs = app_id + "&type=all&results=10&" +
                    "sort=date&language=en&query=quantum mechanics";

        // Endpoint address
        URI address = new URI("http",                  // HTTP scheme
                              null,                    // user info
                              "api.search.yahoo.com",  // host
                              80,                      // port
                              "/NewsSearchService/V1/newsSearch", // path
                              qs,                      // query string
                              null);                   // fragment

        // Add the appropriate port
        s.addPort(portName, HTTPBinding.HTTP_BINDING, address.toString());

        // From the service, generate a Dispatcher
        Dispatch<Source> d =
            s.createDispatch(portName, Source.class, Service.Mode.PAYLOAD);
        Map<String, Object> request_context = d.getRequestContext();
        request_context.put(MessageContext.HTTP_REQUEST_METHOD, "GET");

        // Invoke
        Source result = d.invoke(null);
        DOMResult dom_result = new DOMResult();
        Transformer trans = TransformerFactory.newInstance().newTransformer();
        trans.transform(result, dom_result);

        XPathFactory xpf = XPathFactory.newInstance();
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(new NSResolver("yn", ns_URI.toString()));
        NodeList resultList = (NodeList)
            xp.evaluate("/yn:ResultSet/yn:Result",
                        dom_result.getNode(),
                        XPathConstants.NODESET);

        int len = resultList.getLength();
        for (int i = 1; i <= len; i++) {
            String title = 
                xp.evaluate("/yn:ResultSet/yn:Result(" + i + ")/yn:Title",
                            dom_result.getNode());
            String click =
                xp.evaluate("/yn:ResultSet/yn:Result(" + i + ")/yn:ClickUrl",
                            dom_result.getNode());
            System.out.printf("(%d) %s (%s)\n", i, title, click);
        }
    }
}   

This client application expects, as a command-line argument, the user’s application identifier. The news service is free but requires this identifier. (Signup is available at http://www.yahoo.com.) In this example, the client requests a maximum of 10 results on the topic of quantum gravity. Here is a segment of the raw XML that the Yahoo! service returns:

<?xml version="1.0" encoding="UTF-8"?>
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="urn:yahoo:yn"
   xsi:schemaLocation="urn:yahoo:yn
    http://api.search.yahoo.com/NewsSearchService/V1/NewsSearchResponse.xsd"
   totalResultsAvailable="9" totalResultsReturned="9"
   firstResultPosition="1">
<Result>
  <Title>Cosmic Log: Private space age turns 4</Title>
   <Summary>Science editor Alan Boyle's Weblog: Four years after the first 
            private-sector spaceship crossed the 62-mile mark, some space-age 
            dreams have been slow to rise while others have paid off.
   </Summary>
   <Url>
     http://cosmiclog.msnbc.msn.com/archive/2008/06/20/1158681.aspx
   </Url>
   <ClickUrl>
     http://cosmiclog.msnbc.msn.com/archive/2008/06/20/1158681.aspx
   </ClickUrl>
   <NewsSource>MSNBC</NewsSource>
   <NewsSourceUrl>http://www.msnbc.msn.com/</NewsSourceUrl>
   <Language>en</Language>
    <PublishDate>1213998337</PublishDate>
    <ModificationDate>1213998338</ModificationDate>
</Result>
...
</ResultSet>

Here is the parsed output that the YahooClient produces:

(1) Cosmic Log: Private space age turns 4 (http://cosmiclog.msnbc.msn.com/...
(2) Neutrino experiment shortcuts from novel to real world...
(3) There Will Be No Armageddon (http://www.spacedaily.com/reports/...
(4) TSX Venture Exchange Closing Summary for June 19, 2008 (http://biz.yahoo.com...
(5) Silver Shorts Reported (http://news.goldseek.com/GoldSeeker/1213848000.php)
(6) There will be no Armageddon (http://en.rian.ru/analysis/20080618/...
(7) New Lunar Prototype Vehicles Tested (Gallery)...
(8) World's Largest Quantum Bell Test Spans Three Swiss Towns...
(9) Creating science (http://www.michigandaily.com/news/2008/06/16/...

The client uses a Dispatch object to issue the request and an XPath object to search the DOM result for selected elements. The output above includes the Summary and the ClickURL elements from the raw XML. As quantum gravity is not a hot news topic, there were only 9 results from a request for 10.

The Yahoo! example underscores that clients of RESTful services assume the burden of processing the response document, which is typically XML, in some way that is appropriate to the application. Although there generally is an XML Schema that specifies how the raw XML is formatted, there is no service contract comparable to the WSDL used in SOAP-based services.

The Amazon E-Commerce Service: REST Style

Yahoo! exposes only RESTful web services, but Amazon provides its web services in two ways, as SOAP-based and as REST-style. The AmazonClientREST application that follows issues a read request against the Amazon E-Commerce service for books about the Fibonacci numbers. The client uses a Dispatch object and an HTTP GET request with a query string that specifies the details of the request:

import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.Dispatch;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.ws.handler.MessageContext;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import ch04.dispatch.NSResolver;

class AmazonClientREST {
    private final static String uri =
        "http://webservices.amazon.com/AWSECommerceService/2005-03-23";

    public static void main(String[ ] args) throws Exception {
        if (args.length < 1) {
            System.err.println("Usage: AmazonClientREST <access key>");
            return;
        }
        new AmazonClientREST().item_search(args[0].trim());
    }

    private void item_search(String access_key) {
        QName service_name = new QName("awsREST", uri);
        QName port = new QName("awsPort", uri);

        String base_url = "http://ecs.amazonaws.com/onca/xml";
        String qs = "?Service=AWSECommerceService&" +
            "Version=2005-03-23&" +
            "Operation=ItemSearch&" +
            "ContentType=text%2Fxml&" +
            "AWSAccessKeyId=" +  access_key + "&" +
            "SearchIndex=Books&" +
            "Keywords=Fibonacci";
        String endpoint = base_url + qs;

        // Now create a service proxy dispatcher.
        Service service = Service.create(service_name);
        service.addPort(port, HTTPBinding.HTTP_BINDING, endpoint);
        Dispatch<Source> dispatch =
            service.createDispatch(port, Source.class, Service.Mode.PAYLOAD);

        // Set HTTP verb.
        Map<String, Object> request_context = dispatch.getRequestContext();
        request_context.put(MessageContext.HTTP_REQUEST_METHOD, "GET");

        Source result = dispatch.invoke(null); // null payload: GET request
        display_result(result);
    }

    private void display_result(Source result) {
        DOMResult dom_result = new DOMResult();
        try {
            Transformer trans = TransformerFactory.newInstance().newTransformer();
            trans.transform(result, dom_result);
            XPathFactory xpf = XPathFactory.newInstance();
            XPath xp = xpf.newXPath();
            xp.setNamespaceContext(new NSResolver("aws", uri));

            NodeList authors = (NodeList)
                xp.evaluate("//aws:ItemAttributes/aws:Author",
                            dom_result.getNode(),
                            XPathConstants.NODESET);

            NodeList titles = (NodeList)
                xp.evaluate("//aws:ItemAttributes/aws:Title",
                            dom_result.getNode(),
                            XPathConstants.NODESET);

            int len = authors.getLength();
            for (int i = 0; i < len; i++) {
                Node author = authors.item(i);
                Node title  = titles.item(i);
                if (author != null && title != null) {
                    String a_name = author.getFirstChild().getNodeValue();
                    String t_name = title.getFirstChild().getNodeValue();
                    System.out.printf("%s: %s\n", a_name, t_name);
                }
            }
        }
        catch(TransformerConfigurationException e) { System.err.println(e); }
        catch(TransformerException e) { System.err.println(e); }
        catch(XPathExpressionException e) { System.err.println(e); }
    }
}      

The response document is now raw XML rather than a SOAP envelope. However, the raw XML conforms to the very same XML Schema document that is used in the E-Commerce WSDL contract for the SOAP-based version of the service. In effect, then, the only difference is that the raw XML is wrapped in a SOAP envelope in the case of the SOAP-based service, but is simply the payload of the HTTP response in the case of the REST-style service. Here is a segment of the raw XML:

<?xml version="1.0" encoding="UTF-8"?>
<ItemSearchResponse 
    xmlns="http://webservices.amazon.com/AWSECommerceService/2005-03-23">
   ...
  <ItemSearchRequest>
    <Keywords>Fibonacci</Keywords>
    <SearchIndex>Books</SearchIndex>
  </ItemSearchRequest>
  ...
  <TotalResults>177</TotalResults>
  <TotalPages>18</TotalPages>
  ...
  <Items>
    <Item>
      <ItemAttributes>
        <Author>Carolyn Boroden</Author>
        <Manufacturer>McGraw-Hill</Manufacturer>
        <ProductGroup>Book</ProductGroup>
        <Title>Fibonacci Trading: How to Master Time and Price Advantage</Title>
      </ItemAttributes>
    </Item>
  ...
  </Items>   
</ItemSearchResponse>      

For variety, the AmazonClientREST parses the raw XML in a slightly different way than in earlier examples. In particular, the client uses XPath to get separate lists of authors and book titles:

NodeList authors = (NodeList) xp.evaluate("//aws:ItemAttributes/aws:Author",
                                dom_result.getNode(), XPathConstants.NODESET);
NodeList titles = (NodeList)  xp.evaluate("//aws:ItemAttributes/aws:Title",
                                dom_result.getNode(), XPathConstants.NODESET);

and then loops through the lists to extract the author and the title using the DOM API. Here is the loop:

int len = authors.getLength();
for (int i = 0; i < len; i++) {
   Node author = authors.item(i);
   Node title  = titles.item(i);
   if (author != null && title != null) {
     String a_name = author.getFirstChild().getNodeValue();
     String t_name = title.getFirstChild().getNodeValue();
     System.out.printf("%s: %s\n", a_name, t_name);
   }
}

that produced, on a sample run, the following output:

Carolyn Boroden: Fibonacci Trading: How to Master Time and Price Advantage
Kimberly Elam: Geometry of Design: Studies in Proportion and Composition
Alfred S. Posamentier: The Fabulous Fibonacci Numbers
Ingmar Lehmann: Math for Mystics: From the Fibonacci sequence to Luna's Labyrinth...
Renna Shesso: Breakthrough Strategies for Predicting any Market...
Jeff Greenblatt: Wild Fibonacci: Nature's Secret Code Revealed
Joy N. Hulme: Fibonacci Analysis (Bloomberg Market Essentials: Technical Analysis)
Constance Brown: Fibonacci Fun: Fascinating Activities With Intriguing Numbers
Trudi Hammel Garland: Fibonacci Applications and Strategies for Traders
Robert Fischer: New Frontiers in Fibonacci Trading: Charting Techniques,...

The Amazon Simple Storage Service, known as Amazon S3, is a pay-for service also accessible through a SOAP-based or a RESTful client. As the name indicates, the service allows users to store and retrieve individual data objects, each of up to 5G in size. S3 is often cited as a fine example of a useful web service with a very simple interface.

The RESTful Tumblr Service

Perhaps the Tumblr service is best known for the associated term tumblelog or tlog, a variation on the traditional blog that emphasizes short text entries with associated multimedia such as photos, music, and film. It is common for tumblelogs to be artistic endeavors. The service is free but the full set of RESTful operations requires a user account, which can be set up at http://www.tumblr.com.

The TumblrClient that follows uses an HttpURLConnection to send a GET and a POST request against the Tumblr RESTful service. In this case, the HttpURLConnection is a better choice than Dispatch because a POST request to Tumblr does not contain an XML document but rather the standard key/value pairs. Here is the client code:

import java.net.URL;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import java.io.ByteArrayInputStream;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

class TumblrClient {
    public static void main(String[ ] args) {
        if (args.length < 2) {
            System.err.println("Usage: TumblrClient <email> <passwd>");
            return;
        }
        new TumblrClient().tumble(args[0], args[1]);
    }

    private void tumble(String email, String password) {
        try {
            HttpURLConnection conn = null;

            // GET request.
            String url = "http://mgk-cdm.tumblr.com/api/read";
            conn = get_connection(url, "GET");
            conn.setRequestProperty("accept", "text/xml");
            conn.connect();
            String xml = get_response(conn);
            if (xml.length() > 0) {
                System.out.println("Raw XML:\n" + xml);
                parse(xml, "\nSki photo captions:", "//photo-caption");
            }

            // POST request
            url = "http://www.tumblr.com/api/write";
            conn = get_connection(url, "POST");
            String title = "Summer thoughts up north";
            String body = "Craigieburn Ski Area, NZ";
            String payload =
                URLEncoder.encode("email", "UTF-8") + "=" +
                URLEncoder.encode(email, "UTF-8") + "&" +
                URLEncoder.encode("password", "UTF-8") + "=" +
                URLEncoder.encode(password, "UTF-8") + "&" +
                URLEncoder.encode("type", "UTF-8") + "=" +
                URLEncoder.encode("regular", "UTF-8") + "&" +
                URLEncoder.encode("title", "UTF-8") + "=" +
                URLEncoder.encode(title, "UTF-8") + "&" +
                URLEncoder.encode("body", "UTF-8") + "=" +
                URLEncoder.encode(body, "UTF-8");
            DataOutputStream out = new DataOutputStream(conn.getOutputStream());
            out.writeBytes(payload);
            out.flush();
            String response = get_response(conn);
            System.out.println("Confirmation code: " + response);
        }
        catch(IOException e) { System.err.println(e); }
        catch(NullPointerException e) { System.err.println(e); }
    }

    private HttpURLConnection get_connection(String url_s, String verb) {
        HttpURLConnection conn = null;
        try {
            URL url = new URL(url_s);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(verb);
            conn.setDoInput(true);
            conn.setDoOutput(true);
        }
        catch(MalformedURLException e) { System.err.println(e); }
        catch(IOException e) { System.err.println(e); }
        return conn;
    }

    private String get_response(HttpURLConnection conn) {
        String xml = "";
        try {
            BufferedReader reader =
                new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String next = null;
            while ((next = reader.readLine()) != null) xml += next;
        }
        catch(IOException e) { System.err.println(e); }
        return xml;
    }

    private void parse(String xml, String msg, String pattern) {
        StreamSource source =
            new StreamSource(new ByteArrayInputStream(xml.getBytes()));
        DOMResult dom_result = new DOMResult();
        System.out.println(msg);
        try {
            Transformer trans = TransformerFactory.newInstance().newTransformer();
            trans.transform(source, dom_result);
            XPathFactory xpf = XPathFactory.newInstance();
            XPath xp = xpf.newXPath();
            NodeList list = (NodeList)
                xp.evaluate(pattern, dom_result.getNode(), XPathConstants.NODESET);
            int len = list.getLength();
            for (int i = 0; i < len; i++) {
                Node node = list.item(i);
                if (node != null) 
                  System.out.println(node.getFirstChild().getNodeValue());
            }
        }
        catch(TransformerConfigurationException e) { System.err.println(e); }
        catch(TransformerException e) { System.err.println(e); }
        catch(XPathExpressionException e) { System.err.println(e); }
    }
}      

The URL for the GET request is:

http://mgk-cdm.tumblr.com/api/read

which is the URL for my Tumblr account’s site with api/read appended. The request returns all of my public (that is, unsecured) postings. Here is part of the raw XML returned as the response:

<?xml version="1.0" encoding="UTF-8"?>
<tumblr version="1.0">
  <tumblelog name="mgk-cdm" timezone="US/Eastern" title="Untitled"/>
  <posts start="0" total="5">
    <post id="40130991" url="http://mgk-cdm.tumblr.com/post/40130991" type="photo"
         date-gmt="2008-06-28 03:09:29 GMT" date="Fri, 27 Jun 2008 23:09:29"
         unix-timestamp="1214622569">
     <photo-caption>Trying the new skis, working better than I am.</photo-caption>
     <photo-url max-width="500">
       http://media.tumblr.com/awK1GiaTRar6p46p6Xy13mBH_500.jpg
     </photo-url>
    </post>
    ...
    <post id="40006745" url="http://mgk-cdm.tumblr.com/post/40006745" type="regular"
          date-gmt="2008-06-27 04:12:53 GMT" date="Fri, 27 Jun 2008 00:12:53"
          unix-timestamp="1214539973">
      <regular-title>Weathering the weather</regular-title>
      <regular-body>miserable, need to get fully wet or not at all</regular-body>
    </post>
    ...
    <post id="40006638" url="http://mgk-cdm.tumblr.com/post/40006638" type="regular"
          date-gmt="2008-06-27 04:11:34 GMT" date="Fri, 27 Jun 2008 00:11:34"
          unix-timestamp="1214539894">
      <regular-title>tumblr. API</regular-title>
      <regular-body>Very restful</regular-body>
    </post>
  </posts>
</tumblr>      

The raw XML has a very simple structure, dispensing even with namespaces. The TumblrClient uses XPath to extract a list of the photo captions:

Ski photo captions:
Trying the new skis, working better than I am.
Very tough day on the trails; deep snow, too deep for skating.
Long haul up, fun going down.

The client then sends a POST request, which adds a new entry in my Tumblr posts. The URL now changes to the main Tumblr site, http://www.tumblr.com, with /api/write appended. My email and password must be included in the POST request’s payload, which the following code segment handles:

String payload =
   URLEncoder.encode("email", "UTF-8") + "=" +
   URLEncoder.encode(email, "UTF-8") + "&" +
   URLEncoder.encode("password", "UTF-8") + "=" +
   URLEncoder.encode(password, "UTF-8") + "&" +
   URLEncoder.encode("type", "UTF-8") + "=" +
   URLEncoder.encode("regular", "UTF-8") + "&" +
   URLEncoder.encode("title", "UTF-8") + "=" +
   URLEncoder.encode(title, "UTF-8") + "&" +
   URLEncoder.encode("body", "UTF-8") + "=" +
   URLEncoder.encode(body, "UTF-8");
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.writeBytes(payload);
out.flush();

The documentation for the Tumblr API is a single page. The API supports the CRUD read and create operations through the /api/read and the /api/write suffixes. As usual, a read operation is done through a GET request, and a create operation is done through a POST request. Tumblr does support some variation. For example, the suffix /api/read/json causes the response to be JSON (JavaScript Object Notation) instead of XML. An HTTP POST to the Tumblr site can be used to upload images, audio, and film in addition to text postings, and multimedia may be uploaded as either unencoded bytes or as standard URL-encoded payloads in the POST request’s body.

The simplicity of the Tumblr API encourages the building of graphical interfaces and plugins that, in turn, allow Tumblr to interact easily with other sites such as Facebook. The Tumblr API is a fine example of how much can be done with so little.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required