The lab activity is described in the Activity section below.

Background

Web APIs

Web APIs are the simplest form of web services. Although there is no standard for Web APIs, they ususally:

  • send request data as query parameters (HTTP GET) or form parameters (HTTP POST)
  • return response data in the body of an HTTP response, encoded as XML or JSON (although it could be encoded in any format)

You can think of a web service as being, essentially, a procedure. You provide it some input parameters, it does some computation, and then returns a result.

Making HTTP requests in Java

Most flexible technique: use Apache HttpComponents. For complete details, read the tutorial.

Example: let's say that we have a web service available that determines whether or not an integer is a prime number. It is accessed using the URI /isPrime on the server. It takes a single parameter, called num, which specifies the integer to test. It returns a plain text response consisting of the text true or false. If the request was not specified properly, such as not specifying a num parameter, or specifying a num parameter that is not an integer, the service returns a response with the code 400 (Bad Request), and returns a plain text error message in the body of the message.

Here is code to use this web service:

Scanner keyboard = new Scanner(System.in);

// Get the host (server) the web service is running on
System.out.print("Host for web service: ");
String host = keyboard.nextLine();

// Get integer to check
System.out.println("Integer to check: ");
int num = keyboard.nextInt();

// Create an HttpClient
HttpClient client = new DefaultHttpClient();

// Create a URI to access the web service, encoding the
// number as a query parameter
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("num", String.valueOf(num)));
URI uri = URIUtils.createURI("http", host, -1, "/isPrime",
            URLEncodedUtils.format(params, "UTF-8"), null);

// Create an HttpGet object, which is the HTTP request
// we want to send
HttpGet request = new HttpGet(uri);

// Execute the request, getting back an HttpResponse object
// representing the server's response
HttpResponse response = client.execute(request);

// Copy the response body to a string
HttpEntity entity = response.getEntity();
InputStreamReader reader = new InputStreamReader(entity.getContent(), Charset.forName("UTF-8"));
StringWriter sw = new StringWriter();
IOUtils.copy(reader, sw);
String responseBody = sw.toString();

// The response's status code will indicate whether or not
// the request succeeded
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
        System.out.println("Error: " + responseBody);
} else {
        System.out.println(responseBody);
}

GET vs. POST

An HTTP GET request encodes its parameters in a query string, which is part of the URI (Uniform Request Identifier) describing the information the client wants from the server.

Query parameters are good for encoding small bits of information, but are not well-suited to larger chunks of information. An HTTP POST request encodes query parameters in the body of the HTTP request.

It is fairly easy to make POST requests using HttpComponents: just use an an HttpPost object for the request, and set its entity (body) to be a UrlEncodedFormEntity:

HttpPost request = new HttpPost("http://" + host + "/isPrime");
request.setEntity(new UrlEncodedFormEntity(params));

Note that the URI used to create the request no longer encodes the value of the num parameter.

XML

XML is the eXtensible Markup Language. It is a standard code encoding "semi-structured" data.

An XML document is a consisting of elements. An element is specified by a start tag and an end tag. Between the element's start and end tag are any number of child elements, interspersed with any number of chunks of text.

Example:

<Parent>
        Some text
        <Child1>Child 1's text</Child1>
        More text
        <Child2>Child 2's text</Child2>
</Parent>

Tags that have no child elements or text can be written in a compressed form which combines the start and end tag:

<CompactedElement/>

Each start tag may specify one or more attributes, which are name/value pairs encoding extra information about the element:

<Meal type="lunch">
        <Sandwich cheese="provolone" />
        <Fruit type="orange"/>
</Meal>

Example XML document:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<geonames>
        <totalResultsCount>1</totalResultsCount>
        <code>
                <postalcode>20500</postalcode>
                <name>Washington</name>
                <countryCode>US</countryCode>
                <lat>38.89461</lat>
                <lng>-77.03546</lng>
                <adminCode1>DC</adminCode1>
                <adminName1>District of Columbia</adminName1>
                <adminCode2>001</adminCode2>
                <adminName2>District of Columbia</adminName2>
                <adminCode3/>
                <adminName3/>
        </code>
</geonames>

This document is the result of a geocoding query on an address (1600 Pennsylvania Ave, zip code 20500). Note that it specifies a latitude and longitude for the address. Geocoding is useful for applications that need to do operations based on geographic locations.

Interpreting XML in a program

To make use of XML data, you need an XML parser, and a way of representing the XML data.

A very common way to represent XML data in a program is using a DOM tree. DOM means "Document Object Model", and represents each element and attribute in the XML document as a node in a tree.

By accessing nodes in the tree, the program can interpret the data in the XML document.

Example (using the dom4j library, see the dom4j quick start guide for details). Assume that entity is an HttpEntity from an HTTP response, and that its body contains the XML document shown above. We could use the following code to extract the latitude and longitude from the geocoding result:

SAXReader r = new SAXReader();
Document doc = r.read(entity.getContent());

Node latNode = doc.selectSingleNode("//geonames/code/lat");
if (latNode != null) {
        System.out.println("lat: " + latNode.getText());
}
Node lngNode = doc.selectSingleNode("//geonames/code/lng");
if (lngNode != null) {
        System.out.println("lng: " + lngNode.getText());
}

Note that we are using XPath expressions (e.g., "//geonames/code/lat") to describe the elements we want to retrieve from the DOM tree. In the specific XPath expressions above, we are listing the names of the elements that are traversed on the path to finding the text we are interested in.

When executed on the

Activity

Getting started: download CS496_Lab1.zip and import it into Eclipse.

Your task is to write a Java program which, given any two US addresses specified as street address and zip code, will print the distance in miles between them.

Example run (user input in bold):

First street address: 725 Grantley Rd
First zip code      : 17403
Second street address: 1600 Pennsylvania Ave
Second zip code      : 20500
Distance is 74.80 miles

Use the free geocoding API described here:

http://www.geonames.org/export/free-geocoding.html

You will need to use the following parameters:

  • postalcode - the zip code
  • placeName - the street address
  • country - should be set to "US"

The result is an XML-encoded document in the format shown above in the XML section.

You can find the (approximate) distance in miles between two points (lat1,lng1 and lat2,lng2) using the following formula:

double dist = 3956 * 2 * Math.asin(
                Math.sqrt(
                        Math.pow(Math.sin((lat1 - lat2)*Math.PI / 180 / 2), 2) +
                        Math.cos(lat1 * Math.PI / 180) * Math.cos(lat1 * Math.PI / 180) *
                        Math.pow(Math.sin((lng1 - lng2) * Math.PI / 180 / 2), 2)));