ÃֽŠ°Ô½Ã±Û(JAVA)
2018.10.18 / 15:35

A First RESTful Example

hanulbit
Ãßõ ¼ö 244

A First RESTful Example

As befits a first example, the implementation is simple but sufficient to highlight key aspects of a RESTful web service. The implementation consists of a JSP (Java Server Pages) script and two backend JavaBeans that the JSP script uses to get the data returned to the client (see Figure 1-6). The data is composed of sage corporate predictions. Here is a sample:

Decentralized 24/7 hub will target robust web-readiness.
Synergistic disintermediate policy will expedite backend experiences.
Universal fault-tolerant architecture will synthesize bleeding-edge channels.
The organization of the predictions web service

Figure 1-6. The organization of the predictions web service

There is an Ant script (see An Ant script for service deployment) that automates the deployment of this and other service examples. Here is a summary of the service parts and how the Ant script puts the parts together:

  1. The service consists of a JSP script together with two POJO (JavaBean) classes. The classes provide backend support for the JSP script. There is also a small configuration file, web.xml, that allows the URI to be shortened from:

    /predictions/predictions.jsp

    to:

    /predictions/
  2. The Ant script compiles the .java files and then packages the JSP script, the compiled .class files, and—only for convenience—the .java files into a JAR file with a .war extension (hereafter, a WAR file).
  3. The WAR file is copied to the Tomcat webapps subdirectory, which thereby deploys the service. The section The Tomcat web server goes into the details of Tomcat installation and management.

In the predictions service, each prediction has an associated human predictor. The RESTful resource is thus a list of predictor names (e.g., Hollis McCullough) and their predictions (Hollis is responsible for the third prediction shown above). The resource name or URI is /predictions/, and the only allowable HTTP verb is GET, which corresponds to read among the CRUD operations. If the HTTP request is correct, the RESTful service returns an XML representation of the predictor/prediction list; otherwise, the service returns the appropriate HTTP status code (e.g., 404 for ¡°Not Found¡± if the URI is incorrect or 405 for ¡°Method Not Allowed¡± if the verb is not GET). Example 1-5shows a slice of the XML payload returned upon a successful request.

Example 1-5. The XML response from the predictions service

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0" class="java.beans.XMLDecoder">
 <array class="predictions.Prediction" length="32">
  <void index="0">
   <object class="predictions.Prediction">
    <void property="what">
     <string>
       Managed holistic contingency will grow killer action-items.
     </string>
    </void>
    <void property="who">
     <string>Cornelius Tillman</string>
    </void>
   </object>
  </void>
  ...
  <void index="31">
   <object class="predictions.Prediction">
    <void property="what">
     <string>
       Versatile tangible application will maximize rich ebusiness.
     </string>
    </void>
    <void property="who">
     <string>Hiram Gulgowski</string>
    </void>
   </object>
  </void>
 </array>
</java>

How the Predictions Web Service Works

When the predictions service is deployed to a web server such as Tomcat, the server translates the JSP script predictions.jsp (see Example 1-6) into a servlet instance. For now, this technical detail is overlooked because it is convenient to talk about the JSP script itself as the target of a request.

Example 1-6. The JSP script predictions.jsp

<jsp:useBean id    = "preds"                            1
             type  = "predictions.Predictions"
             class = "predictions.Predictions">
  <% // Check the HTTP verb: if it's anything but GET,
     // return 405 (Method Not Allowed).
     String verb = request.getMethod();
     if (!verb.equalsIgnoreCase("GET")) {
       response.sendError(response.SC_METHOD_NOT_ALLOWED,
                          "Only GET requests are allowed.");
     }
     // If it's a GET request, return the predictions.
     else {
       preds.setServletContext(application);            2
       out.println(preds.getPredictions());
     }
  %>
</jsp:useBean>

As requests come to the JSP script, the script first checks the request¡¯s HTTP method. If the method is GET, an XML representation of the predictions is returned to the requester. If the verb is not GET, the script returns an error message together with the HTTP status code. The relevant code follows:

String verb = request.getMethod();
if (!verb.equalsIgnoreCase("GET")) {
  response.sendError(response.SC_METHOD_NOT_ALLOWED,
                     "Only GET requests are allowed.");
}

JSP scripts have implicit object references such as requestresponse, and out; each of these is a field or a parameter in the servlet code into which the web server, such as Tomcat or Jetty, translates the JSP script. A JSP script can make the same calls as an HttpServlet.

On a successful request, the JSP script returns a list of predictions and their predictors, a list available from the backend JavaBean Predictions. The JSP code is pretty straightforward:

out.println(preds.getPredictions());

The object reference out, available in every JSP script, refers to an output stream through which the JSP script can communicate with the client. In this example, the object reference preds (line 1) refers to the backend JavaBean that maintains the collection of predictions; the getPredictions method in the backend bean converts the Java list of Predictions into an XML document.

The backend code consists of two POJO classes, Prediction (see Example 1-7) and Predictions (see Example 1-8). The Prediction class is quite simple, consisting of two properties: who (line 1) is the person making the prediction and what (line 2) is the prediction.

Example 1-7. The backend predictions.Prediction class

package predictions;
import java.io.Serializable;

public class Prediction implements Serializable {
    private String who;   // person
    private String what;  // his/her prediction
    public Prediction() { }
    public void setWho(String who) { this.who = who; }      1
    public String getWho() { return this.who; }
    public void setWhat(String what) { this.what = what; }  2
    public String getWhat() { return this.what; }
}

The Predictions class does the grunt work. For example, its populate method (line 3) reads the prediction data from a text file, predictions.db, encapsulated in the deployed WAR file, and the toXML method serializes a Java List<Prediction> into an XML document, which is sent back to the client. If there were problems reading or formatting the data, the predictions service would return null to the client.

Example 1-8. The backend predictions.Predictions class

package predictions;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.beans.XMLEncoder; // simple and effective
import javax.servlet.ServletContext;

public class Predictions {
    private int n = 32;
    private Prediction[ ] predictions;
    private ServletContext sctx;
    public Predictions() { }
    public void setServletContext(ServletContext sctx) { this.sctx = sctx; } 1
    public ServletContext getServletContext() { return this.sctx; }
    // getPredictions returns an XML representation of
    // the Predictions array
    public void setPredictions(String ps) { } // no-op
    public String getPredictions() {                                         2
        // Has the ServletContext been set?
        if (getServletContext() == null) return null;
        // Has the data been read already?
        if (predictions == null) populate();
        // Convert the Predictions array into an XML document
        return toXML();
    }
    private void populate() {                                                3
        String filename = "/WEB-INF/data/predictions.db";
        InputStream in = sctx.getResourceAsStream(filename);
        // Read the data into the array of Predictions.
        if (in != null) {
            try {
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader reader = new BufferedReader(isr);
                predictions = new Prediction[n];
                int i = 0;
                String record = null;
                while ((record = reader.readLine()) != null) {
                    String[] parts = record.split("!");
                    Prediction p = new Prediction();
                    p.setWho(parts[0]);
                    p.setWhat(parts[1]);

                    predictions[i++] = p;
                }
            }
            catch (IOException e) { }
        }
    }
    private String toXML() {                                              4
        String xml = null;
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            XMLEncoder encoder = new XMLEncoder(out);
            encoder.writeObject(predictions); // serialize to XML
            encoder.close();
            xml = out.toString(); // stringify
        }
        catch(Exception e) { }
        return xml;
    }
}

On a GET request, the JSP script invokes the method setServletContext (line 1 in the bean, line 2 in the JSP script) with an argument, the implicit object reference named application. The backend bean needs access to the servlet context in order to read data from a text file embedded in the deployed WAR file. The ServletContext is a data structure through which a servlet/JSP script can interact explicitly with the servlet container. The call to the setServletContext method sets up the subsequent call to the getPredictionsmethod (line 2), which returns the XML representation shown in Example 1-5. Here is the getPredictions method without the comments:

public String getPredictions() {
   if (getServletContext() == null) return null;
   if (predictions == null) populate(); 1
   return toXML();
}

The method populate (line 1 immediately above) reads the data. The predictions reference in the code segment above refers to the Map in which Prediction references are values. If the JSP script fails to set the servlet context, there is no way for the back-end Predictions bean to provide the requested data. The reason is that the populate method requires the servlet context (the reference is sctx, line 1, in the code below) in order to access the data:

private void populate() {
   String filename = "/WEB-INF/data/predictions.db";
   InputStream in = sctx.getResourceAsStream(filename); 1
   ...
}

If the servlet context has been set but the predictions reference is null, then the data must be read from the predictions.db file into the Map that makes the data available to clients. Each entry in the Map is a Prediction, which again is a pair: who predicts what. Finally, the toXML method serializes the Java predictions into an XML document using the Java utility class XMLEncoder (line 1):

private String toXML() {
   String xml = null;
   try {
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      XMLEncoder encoder = new XMLEncoder(out);                 1
      encoder.writeObject(predictions); // serialize to XML
      encoder.close();
      xml = out.toString(); // stringify
   }
   catch(Exception e) { }
   return xml;                                                  2
}

The XML document from the toXML method (line 2) becomes the body of the HTTP response to the client.

Although the XML from the predictions service is generated using the XMLEncoder class, Java does provide other ways to generate XML—but perhaps none quite as simple as XMLEncoder. The Prediction objects must be serializable in order to be encoded as XML using the XMLEncoder; hence, the Prediction class implements the empty (or markerSerializable interface and defines the get/set methods for the properties who (the predictor) and what (the prediction). The Prediction properties are serialized into XML elements in the response document.

The predictions service can be deployed under the Tomcat web server using a provided Ant script (with % as the command-line prompt):

% ant -Dwar.name=predictions deploy

The deployed WAR file would be predictions.war in this case. The next section (see The Tomcat web server) elaborates on the Apache Tomcat server and explains how to install and use this server. The section An Ant script for service deployment clarifies the Ant script, which is packaged with the book¡¯s code examples. The deployed WAR file predictions.war includes a standard web deployment document, web.xml, so that the URI can be shortened to /predictions/.

The Tomcat web server

Apache Tomcat is a commercial-grade yet lightweight web server implemented in Java. Tomcat has various subsystems for administration, security, logging, and troubleshooting, but its central subsystem is Catalina, a container that executes servlets, including JSP and other scripts (e.g., JSF scripts) that Tomcat automatically translates into servlets. Tomcat is the popular name for the web server, and Catalina is the official name for the servlet container that comes with Tomcat.

Tomcat also includes a web console, tutorials, and sample code. This section focuses on installing Tomcat and on basic post-installation tasks such as starting and stopping the web server. The current version is 7.x, which requires Java SE 6 or higher. Earlier Tomcat versions are still available.

There are different ways to download Tomcat, including as a ZIP file. Tomcat can be installed in any directory. For convenience, let TOMCAT_HOME be the install directory. The directory TOMCAT_HOME/bin has startup and shutdown scripts for Unixy and Windows systems. For instance, the startup script is startup.sh for Unix and startup.bat for Windows. Tomcat is written in Java but does not ship with the Java runtime; instead, Tomcat uses the Java runtime on the host system. To that end, the environment variable JAVA_HOME should be set to the Java install directory (e.g., to/usr/local/java7D:\java7, and the like).

In summary, the key commands (with comments introduced with two semicolons) are:

% startup.sh   ;; or startup.bat on Windows to start Tomcat
% shutdown.sh  ;; or shutdown.bat on Windows to stop Tomcat

The commands can be given at a command-line prompt. On startup, a message similar to:

Using CATALINA_BASE:   /home/mkalin/tomcat7
Using CATALINA_HOME:   /home/mkalin/tomcat7
Using CATALINA_TMPDIR: /home/mkalin/tomcat7/temp
Using JRE_HOME:        /usr/local/java
Using CLASSPATH:       /home/mkalin/tomcat7/bin/bootstrap.jar

appears.

Under TOMCAT_HOME there is directory named logs, which contains various logfiles, and several other directories, some of which will be clarified later. A important directory for now is TOMCAT_HOME/webapps, which holds JAR files with a .war extension (hence the name WAR file). Subdirectories under TOMCAT_HOME/webapps can be added as needed. Deploying a web service under Tomcat is the same as deploying a website: a WAR file containing the site or the service is copied to the webapps directory, and a website or web service is undeployed by removing its WAR file.

Tomcat maintains various logfiles in TOMCAT_HOME/logs, one of which is especially convenient for ad hoc debugging. In standard configuration, Tomcat redirects output to System.err and System.out to logs/catalina.out. Accordingly, if a servlet executes:

System.err.println("Goodbye, cruel world!");

the farewell message will appear in the catalina.out logfile.

Apache Tomcat is not the only game in town. There is the related TomEE web server, basically Tomcat with support for Java EE beyond servlets. Another popular Java-centric web server is Jetty. The sample services in this book can be deployed, as is, with either Tomcat or Jetty; the next chapter has a sidebar on how to install and run Jetty.

An Ant script for service deployment

The first sample web service is published with a web server such as Tomcat or Jetty. The ZIP file with my code examples includes an Ant script to ease the task of deployment. The Ant utility, written in Java, is available on all platforms. My script requires Ant 1.6 or higher.

To begin, let the current working directory (cwd) be any directory on the local filesystem. The cwd holds the Ant script build.xml:

cwd: build.xml

The cwd has a subdirectory named src that holds the web service¡¯s artifacts. Suppose, for example, that a web service includes a JSP script, a backend JavaBean, the standard Tomcat or Jetty deployment file web.xml, and a JAR file that holds a JSON library. Here is a depiction:

cwd: build.xml
 |
src: products.jsp, json.jar, web.xml

Suppose, further, that the backend JavaBean has the fully qualified name:

acme.Products

The file structure is now:

cwd: build.xml
 |
src: products.jsp, json.jar, web.xml
 |
acme: Products.java

Finally, assume that the src directory also holds the datafile new_products.db. From the cwd command line, the command:

% ant -Dwar.name=products deploy

does the following:

  • Creates the directory cwd/build, which holds copies of files in directory srcand any subdirectories
  • Compiles any .java files, in this case acme.Products.java
  • Builds the WAR file, whose major contents are:

    WEB-INF/web.xml
    WEB-INF/classes/acme/Products.class
    WEB-INF/data/new_products.db
    WEB-INF/lib/json.jar
    acme/Products.java
    products.jsp

In the constructed WAR file:

  • Any .xml file winds up in WEB-INF.
  • Any .jar file winds up in WEB-INF/lib.
  • Any .db file winds up in WEB-INF/data.
  • Any .java or .class file winds up in its package/subdirectory.
  • Other files, such as .jsp files, wind up in the WAR file¡¯s top level.

For convenience, the Ant script includes, in the WAR file, Java source (.java) and compiled (.class) files. In production, the source files would be omitted.

Finally, the Ant script copies the constructed WAR file to TOMCAT_HOME/webapps and thereby deploys the web service. The script also leaves a copy of the WAR file in cwd.

The command:

% ant

displays the three most useful commands. The command:

% ant clean

removes any .war files from the cwd and deletes the build directory. The command:

% ant compile

compiles any .java files but does not build or deploy a WAR file. The command:

% ant -Dwar.name=predictions deploy

first cleans and compiles; then the command builds and deploys the WAR file predictions.war.

The Ant file build.xml has extensive documentation and explains, in particular, what needs to be done to customize this file for your environment. Only one line in the file needs to be edited. Although the Ant script is targeted at Tomcat deployment, the WAR files that the script produces can be deployed as is to Jetty as well. As noted earlier, Chapter 2 goes into the details of installing and running Jetty.

A Client Against the Predictions Web Service

Later examples introduce RESTful clients in Java and other languages; for now, either a browser or a utility such as curl (see The curl Utility) is good enough. On a successful curl request to the service:

% curl -v http://localhost:8080/predictions/

the response includes not only the XML shown earlier in Example 1-5 but also a trace (thanks to the -v flag) of the HTTP request and response messages. The HTTP request is:

GET /predictions/ HTTP/1.1
User-Agent: curl/7.19.7
Host: localhost:8080
Accept: */*

The HTTP response start line and headers are:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=96C78773C190884EDE76C714728164EC; Path=/test1/;
Content-Type: text/html;charset=ISO-8859-1
Transfer-Encoding: chunked

Recall that an HTTP GET message has no body; hence, the entire message is the start line and the headers. The response shows the session identifier (a 128-bit statistically unique number, in hex, that Tomcat generates) in the header. In the JSP script, the session identifier could be disabled, as it is not needed; for now, the goal is brevity and simplicity in the code.

If a POST request were sent to the RESTful predictions service:

% curl --request POST --data "foo=bar" http://localhost:8080/predictions/

the request message header becomes:

POST /predictions/ HTTP/1.1
User-Agent: curl/7.19.7
Host: localhost:8080
Accept: */*
Content-Length: 7
Content-Type: application/x-www-form-urlencoded

The response header is now:

HTTP/1.1 405 Method Not Allowed
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=34A013CDC5A9F9F8995A28E30CF31332; Path=/test1/;
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 1037

The error message:

Only GET requests are allowed.

is in an HTML document that makes up the response message¡¯s body. Tomcat generates an HTML response because my code does not (but could) stipulate a format other than HTML, the default Tomcat format for a Tomcat response.

This first example illustrates how a JSP script is readily adapted to support web services in addition to websites. The next section goes into more detail on servlets and JSP scripts. In summary, the predictions web service is implemented as a JSP script with the two backend JavaBeans in support. This first example highlights key aspects of a REST-style service:

  • The service provides access to resource under the URI /predictions/.
  • The service filters access on the HTTP request verb. In this example, only GET requests are successful; any other type of request generates a bad method error.
  • The service responds with an XML payload, which the consumer now must process in some appropriate way.

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