pretty_pretty_fish

导航

restful

How to Create a RESTful Web Service

REST is an architectural style for implementing web services over standard HTTP. You can learn more about REST from the following resources:

Building Web Services the REST Way

Introduction to REST Slide Deck

REST Tutorial

How to Create a REST Protocol

RESTful Web Services book

Second Generation Web Services

REST and the Real World

REST for the Rest of Us


Java API for RESTful Web Services

The JAX-RS (Java API for RESTful Web Services) specification presented in JSR 311 defines a standard way to deploy RESTful web services using annotated POJOs (plain old Java objects). For detailed information, you can read the JSR here. In this article, we'll use JAX-RS to create a simple sample application that allows you to add, retrieve, and delete books from a virtual library.

 

 

Design the REST API

The first thing to do when creating a new RESTful web service is to define an API that exposes service functionality to the web service client. REST is often called a Resource Oriented Architecture (ROA) since it is based on resources that are uniquely addressable via URIs (Uniform Resource Identifier). A REST API exposes the ability to perform a small set of operations on these resources. Essentially, there are four important elements within a REST service call that must be defined by the service author. These elements are as follows:

  • HTTP method
  • URI
  • Request body
  • Response

Let's take a closer look at each of these elements.

HTTP Method

The HTTP method indicates the type of action that should be taken on the resource specified by the URI. RESTful services typically use four methods that roughly equate the standard CRUD (create, read, update, delete) operations as follows:

HTTP MethodCRUD Operation
GET read
POST create a new server-identified resource (occasionally used for update)
PUT update an existing resource or create a new client-identified resource
DELETE delete

The GET method is used for calls that have no side effects on the server (any calls that have no side effects should always use GET). An example of this would be when retrieving information about a book. No transaction is taking place so the book can be retrieved any number of times without altering state on the server. A typical GET method call looks like this:

GET http://{server}/MyRestService/library/books/12345

Notice that the PUT and POST operations can both be used to create and update resources. This has been a cause of much confusion when creating REST services. Here is a hint from the bookRESTful Web Services to help you decide when to use each method:

"The difference between PUT and POST is this: the client uses PUT when it’s in charge of deciding which URI the new resource should have. The client uses POST when the server is in charge of deciding which URI the new resource should have."

Another way of stating this rule is to say that PUT should be used when creating a new URI and POST when calling an existing URI. So, you use PUT to create (as well as update) resources when the client controls the URI that references the resource. For example, the client can create a new book that is referenced by an ISBN number assigned by the client in this manner:

PUT http://{server}/MyRestService/library/books/12345

Notice that the client passed the ID (12345) by which this book will be referenced. This call creates a new book resource that can be accessed as shown in the GET method above. In this case, the PUT operation is appropriate because the client is responsible for specifying the URI that uniquely identifies the book. On the other hand, consider a service call where the client doesn't specify the URI:

POST http://{server}/MyRestService/library/books/12345/reviews

This call creates a review for the specified book. In a case like this, the server will return the URI by which the review can be referenced in the Location header of the HTTP response. This URI allows the client to retrieve the review using a URI created by the server. The GET call would look something like this:

GET http://{server}/MyRestService/library/books/12345/reviews/2937846292

In this case, the new review resource is known as a subordinate resource. A subordinate resource is a resource that only exists in relation to some parent resource. In other words, a review cannot exist on its own without being attached to a book. POST is usually used for creating subordinate resources. PUT and POST can both be used for updating resources but PUT replaces the entire resource while POST may update only a portion of it. For example, if the PUT request shown above is called a second time with a different book representation, the entire book with ID 12345 will be replaced with the new representation. In order to update just a portion of the resource using PUT, you would need to create a new subordinate resource. For example, if the book had a "checkedOut" property, we could use PUT to set this property by defining a new resource at this URL:

PUT http://{server}/MyRestService/library/books/12345/checkedOut

This new resource would allow you to update just one property of book 12345 using PUT (since it is replacing the entire checkedOut resource). POST could also be used to modify book 12345 without the new resource (often called an "overloaded POST") but that approach isn't as true to REST principles.

Note that PUT is idempotent while POST is not. Idempotent means that the operation can be safely performed multiple times. Consider how performing the same PUT operation multiple times does not change the server state (since subsequent PUT requests would simply replace the original resource with an identical representation). POST, on the other hand, is not idempotent because it creates a new resource with each call. Performing multiple identical POST operations causes the server state to change with every execution (as a new resource is created each time).

These are the key differences between the PUT and POST methods. Hopefully these general rules will help you when choosing which method to use in your REST service.

Finally, the DELETE method is used to delete resources. Like PUT, DELETE is idempotent and it operates on an entire resource. That is, the whole resource is must be deleted rather than just a part of it. A typical DELETE method call looks like this:

DELETE http://{server}/MyRestService/library/books/12345

After deleting a resource, it is no longer available. In this case, issuing a GET request to this resource would return an HTTP status code of 404 (Not Found).

URI

The URIs by which resources are referenced are an important part of the REST API. One of the most fundamental REST principles is that URIs should represent nouns, not verbs. There are two good reasons for this. First, the actions that can be performed have already been defined by the HTTP protocol (GET, POST, PUT, DELETE). We should not be defining new actions through naming resources using verbs. Second, since REST requires us to specify an action based on the standard HTTP methods with each call, it only makes sense that this action would be performed against a concrete resource (noun) as opposed to another action (verb). For example, consider the following URI:

GET http://{server}/MyRestService/library/getBooks

In essence, this URI is saying "get the getBooks resource". This is redundant and a bit confusing. A better way to express this idea is through a URI like this:

GET http://{server}/MyRestService/library/books

This URI just says "get the books resource". The representation of the books resource would naturally be a list of books stored in the library.

Another common guideline is to use plural nouns for collections of items and perform actions against this collection. For example, consider the following service calls:

Service CallDescription
GET http://{server}/MyRestService/library/books Get a list of books
PUT http://{server}/MyRestService/library/books/12345 Create a new book with ISBN 12345
GET http://{server}/MyRestService/library/books/12345 Get a single book with ISBN 12345
DELETE http://{server}/MyRestService/library/books/12345 Delete a single book with ISBN 12345

Notice how the last GET operation and the DELETE operation referenced an individual book through the books collection. This conveys the idea that we are retrieving a single book from the collection of all books.

The URIs for REST services will typically grow in a hierarchical fashion. For example, imagine that our library service allowed patrons to attach a review to each book. To accommodate this use case, the URI for this service should first reference the book to which the review applies and then the review itself in this manner:

GET http://{server}/MyRestService/library/books/12345/reviews/1

To get a list of all reviews pertaining to the book identified by ISBN 12345, we could use a URI like this:

GET http://{server}/MyRestService/library/books/12345/reviews

Request Body

Most REST service calls do not contain any information in the body of the request. This is due to the fact that the HTTP header often includes all the information needed to invoke a service. The HTTP header includes the HTTP method, URI, and all HTTP header fields. In fact, certain HTTP methods, such as GET and DELETE, do not support any data in the body of the request at all. However, HTTP methods that create or modify resources, such as POST and PUT, should include the information necessary to perform that action. For instance, a PUT operation by definition should always include a full representation of the resource being created or replaced. On the other hand, a POST operation may include just enough information required by the service to create or update a resource.

The important thing to know about the request body is that it is unique to each service. The service designer must define the format of the request body and convey that to service consumers. Information in the request body is typically encoded in XML or JSON format. Here is a typical HTTP request that contains XML information within the body:

POST http://www.sourcestream.com/books HTTP/1.1
Content-Type: application/xml

<Book>
<Title>Inside Servlets</Title>
<URI>http://http://www.amazon.com/Inside-Servlets-Server-Side-Programming-Platform/dp/0201709066/ref=sr_1_1?ie=UTF8&qid=1321917641&sr=8-1</URI>
</Book>

The sample HTTP request above indicates that the client is creating a new book by performing a POST to the books resource. The Content-Type header indicates that the request body is formatted in XML.

Response

The response to a REST service call contains various information of interest to the client. To begin, a REST response always includes a status code that conveys the result of the requested operation. The available status codes are defined by the HTTP specification and are grouped into 5 ranges that convey a general meaning. These five ranges are as follows:

RangeDescription
1xx (Meta) Used only in negotiations with HTTP server.
2xx (Successful) The operation was successful.
3xx (Redirection) The client must perform an additional operation to get what it wants.
4xx (Client-Side Error) There is a problem with the client's request.
5xx (Server-Side Error) An error occurred on the server that prevented it from servicing the request.

Here are some of the most common response status codes:

Status CodeDescription
200 (OK) The call was serviced successfully.
201 (Created) A resource was created at the location specified in the Location header.
202 (Accepted) Request was accepted and is being processed. Typically returned in response to long running processes.
204 (No Content) The call was serviced successfully but there is no content to return.
206 (Partial Content) Only part of the requested content can be returned at this time.
301 (Moved Permanently) The requested resource has permanently moved to the URI as specified in the Location header.
304 (Not Modified) Client included an If-Modified-Since header in the request but resource hasn't changed since that time.
307 (Temporary Redirect) The requested resource has temporarily moved to the URI specified in the Location header.
400 (Bad Request) Client error indicating that the request was not properly formatted.
401 (Unauthorized) The client attempted to operate on the requested resource without providing the required credentials.
404 (Not Found) The requested resource does not exist.
409 (Conflict) The request would have put the server into an invalid state.
500 (Internal Server Error) An error occurred on the server when attempting to satisfy the request.
501 (Not Implemented) The specified HTTP method is not supported for the requested resource.

In addition to the status code, the response may contain a Content-Type header. This header indicates the type of content contained in the body of the response (e.g., image, XML, JSON, etc.). To illustrate, here is a typical HTTP response that uses the Content-Type header to indicate that the body of the response is in XML format:

HTTP/1.1 200 OK
Date: Thu, 5 Jun 2008 06:25:24 GMT
Content-Type: application/xml

<Books>
<Book>
<Title>Inside Servlets</Title>
<URI>http://http://www.amazon.com/Inside-Servlets-Server-Side-Programming-Platform/dp/0201709066/ref=sr_1_1?ie=UTF8&qid=1321917641&sr=8-1</URI>
</Book>
</Books>

In response to a GET request, a representation of the requested resource is returned to the client. Similar to the request body, the format of this representation is service-specific. The service consumer must refer to documentation provided by the service provider in order to determine how the body of the response is to be interpreted.

Some REST service calls may not return anything within the body of the message. For example, in response to a POST request to create a new resource, the service will typically just respond with a message indicating that the resource was requested and the location of the new resource as illustrated here:

HTTP/1.1 201 Created
Date: Thu, 5 Jun 2008 06:25:24 GMT
Location: http://www.sourcestream.com/books/12345

The response above indicates that a new book resource was successfully created and is available at the URI indicated by the Location header.

Document the REST API

Now that you understand the different parts of a REST service's request and response, you're ready to define an API that will be exposed to service consumers. It is recommended to begin by defining the different parts (nouns) in the system and then determine which of the HTTP actions need to be performed on each. For instance, in the library example presented earlier, there are two objects that can be acted on: books and reviews. We can consider these two objects as the resources upon which REST service clients will be acting. The operations that we implement will be determined by the use cases that the service must support. For instance, the user should be able to create, read, update, and delete both books and reviews. This means that we'll implement support for GET, PUT or POST, and DELETE for each resource. Here is a list of operations that the library service will support:

OperationHTTP MethodURIRequest BodyResponse BodyHTTP Status Codes
Get a book GET /books/{ISBN} None XML representation of a book 200 (OK), 404 (Not Found)
Get list of all books GET /books None XML representation of all books 200 (OK), 404 (Not Found)
Create a book PUT /books/{ISBN} XML representation of a book None 201 (Created), 400 (Bad Request)
Replace a book PUT /books/{ISBN} XML representation of a book None 200 (OK), 400 (Bad Request)
Delete a book DELETE /books/{ISBN} None None 200 (OK), 404 (Not Found)
Get a book review GET /books/{ISBN}/reviews/{id} None XML representation of a book review 200 (OK), 404 (Not Found)
Get all reviews for a book GET /books/{ISBN}/reviews None XML representation of all reviews for a book 200 (OK), 404 (Not Found)
Create a book review POST /books/{ISBN}/reviews XML representation of a book review None 201 (Created), 400 (Bad Request)
Update a book review PUT /books/{ISBN}/reviews/{id} XML representation of a book review None 200 (OK), 404 (Not Found)
Delete a book review DELETE /books/{ISBN}/reviews/{id} None None 200 (OK), 404 (Not Found)

Another common way to document a REST API is to use a table showing the available REST actions across the top and the resources upon which actions are performed on the left like this:

 GETPOSTPUTDELETE
Books /books
/books/{ISBN}
  /books/{ISBN} /books/{ISBN}
Reviews /books/{ISBN}/reviews
/books/{ISBN}/reviews/{id}
/books/{ISBN}/reviews /books/{ISBN}/reviews/{id} /books/{ISBN}/reviews/{id}

Create the REST Service

We will now create a REST service that allows the user to create, retrieve, update, and delete books from a library. Adding support for book reviews as shown in the REST API is an exercise that is left to the reader.

JAX-RS makes creating a RESTful web services very simple. Start by annotating a class with path information that maps a request's URI to the class that should process the request. For example, consider the following class:

@Path("/library")
public class Library
{
//implementation here...
}

The @Path annotation indicates that requests beginning with /library should be routed to this class for processing. For instance, any of the following requests would be passed to the Library class:

GET http://{server}/MyRestService/library/books
GET http://{server}/MyRestService/library/books/1234
POST http://{server}/MyRestService/library/books/1234
DELETE http://{server}/MyRestService/library/books/1234

Note that the path information begins immediately following the web application's context name. In this case, the application's context name is MyRestService.

Create the GET Methods

After creating an annotated class, we are ready to create methods to return information to the web service client. We'll start by defining two methods that return information about the books available to the library service.

@GET
@Path("/books")
@ProduceMime("application/xml")
public String getBooks()
{
//implementation here...
}

@GET
@Path("/books/{isbn}")
@ProduceMime("application/xml")
public String getBook(@PathParam("isbn")String id)
{
//implementation here...
}

Now let's examine these two methods in detail. The first method, getBooks(), indicates that it should be called in response to an HTTP GET (note the @GET annotation) to the /library/books resource. Why /library/books and not just "/books" as stated in the @Path annotation? Because the path information specified by each individual method is appended to the path information supplied by its class. In this case, the Library class specified a path of /library and the getBooks() indicated /books. Lastly, the @ProduceMime annotation tells the JAX-RS container that this method returns XML and, therefore, the Content-Type header in the response should be set to application/xml.

The next method, getBook(), is a little more complex. From the annotations on this method we can see that it is called to process HTTP GET requests to the /library/books/{isbn} URI. This URI matches any URI that includes a value after the /library/books/ path. The @Path annotation names this value "isbn". Once named, the value can be referenced from other annotations. In this case, the @PathParam("isbn") parameter annotation tells the server to parse the last value out of the URI and assign it to the method parameter id. This kind of automatic assignment is a type of inversion of control (IOC) known as dependency injection. Rather than the code having to retrieve the isbn value from the URI, the container "injects" the value into the code via its method parameter.

In addition to the path portion of the URI, you can also inject values from the query string into a method parameter like this:

@GET
@Path("/users")
public String getUser(@QueryParam("id")String userId)
{
//implementation here...
}

Therefore, given a URI like /users?id=1234, the getUser() method's userId parameter would be assigned a value of 1234. That's all for the GET methods. For more information, you can examine their full implementation at Library REST Sample or in the sample project file available below.

Create the PUT Method

The next method we'll create will allow the client to add or update a book. In this case, the same method supports both add and update because it processes PUT requests. Remember that PUT requests either create or replace a resource. Therefore, the method implementation is often very similar, if not identical. This is due to the fact that either operation may simply require the implementation to create a resource and add it to a collection. If the resource already existed, it is overwritten. If not, the new resource is added. Here's the definition for the create/update method:

@PUT
@Path("/books/{isbn}")
@ConsumeMime("application/xml")
public Response addUpdateBook(@PathParam("isbn")String isbn, String body)
{
//implementation here...
}

This method is very similar to getBooks() except that it processes PUT requests rather than GET. Note that the value following /books/ in the URI is named "isbn" and this value is injected into the method parameter of the same name. The @ConsumeMime annotation indicates that this method expects to be passed some content in XML format. So, how do we get the content out of the HTTP request? The way to do this is to declare a single String parameter that is not annotated for dependency injection. The body of the request will automatically be injected in this parameter.

Notice that this method returns a javax.ws.rs.core.Response object. Returning this object allows the method to control the HTTP status code and headers returned in the response. In this case, the response's status code is altered based on whether an add or update operation took place. To illustrate, here is a small snippet of the implementation:

if (books.containsKey(isbn))
{
response = Response.status(200).build(); //updated
}
else
{
response = Response.status(201).build(); //created
}

As you can see, if the book that was passed already existed, the response status code is set to 200 (OK). If the book did not exist previously, the status code is set to 201 (Created). See the full implementation at Library REST Sample or in the sample project presented below for more information.

Create the DELETE Method

Last we'll create the method that allows the client to delete books from the library. The definition for this method looks like this:

@DELETE
@Path("/books/{isbn}")
public Response removeBook(@PathParam("isbn")String isbn)
{
//implementation here...
}

From this definition, we can see that this method process HTTP DELETE methods (based on the @DELETE annotation) passed to the /library/books/{isbn} path. Again, the book's ISBN value is automatically extracted from the URI and injected in the method's isbn parameter. The complete method implementation is available at Library REST Sample or in the sample project below. 

 

Create a Test Application

The RestEasy implementation of JAX-RS includes a client library that makes creating REST clients quick and easy. The client library is easy to learn because it uses the same JAX-RS annotations as the REST service except their meaning is reversed. For example, the @Path annotation in a service method maps requests to the method based on their URI. In contrast, the @Path annotation in a client method indicates the URI to which an HTTP request should be sent.

Client Interface

To create a RestEasy client application, start by creating an interface that describes the remote services that you'd like to call. Let's look at a simple example:

import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import org.resteasy.spi.ClientResponse;

@Path("library")
public interface TestClient
{
@GET
@Path("books")
@ProduceMime("application/xml")
String getBooks();

@POST
@Path("books/{isbn}/reviews")
@ConsumeMime("application/xml")
@ProduceMime("application/xml")
ClientResponse<String> createReview(@PathParam("isbn") String isbn, String body);

@DELETE
@Path("books/{isbn}")
Response.Status deleteBook(@PathParam("isbn") String isbn);
}

The annotations in this example work similarly to the way they do in a service class. The @Path("library") class annotation indicates the "base URI" upon which the rest of the @Path annotations will build. Now let's examine each of the methods.

The getBooks() method is annotated to indicate that it should send an HTTP GET request to the library/books URI (the library portion of the URI comes from the class's @Path annotation). The@ProduceMime annotation denotes that this method returns an XML formatted string.

The createReview() method is slightly more complex. This method's annotations indicate that it sends an HTTP POST request to the library/books/{isbn}/reviews URI. Notice that this URI includes the {isbn} path parameter. In a service method, the value of this parameter is extracted from the URI and injected into the associated method parameter. The path parameter is associated with a method parameter using the @PathParam annotation. For client methods, this process works in reverse. Rather than extracting the value from the URI and injecting it into the method parameter, the value is extracted from the method parameter and injected into the URI. It is this fully constructed URI to which the request is then sent. So, to create a new review for a book with ISBN number 12345, the client application would simply call the createReview() method like this:

ClientResponse<String> response = client.createReview("12345", "<Review>Great book!</Review>");

As you would expect, this call will generate a POST request to the library/books/12345/reviews URI. Don't worry about the client object for now. We'll see how that is created in the next section.

Now let's look at the createReview() method's second parameter, body. Remember how the non-annotated parameter in a service method represents the body of the request? It is the same with the client. Only one non-annotated parameter is allowed per method and that parameter represents the body of the HTTP request. Similarly, the @ConsumeMime and @ProduceMime annotations also work the same as they do on the server-side. The @ConsumeMime annotation corresponds to the body of the request and @ProduceMime corresponds to the body of the response. In the example above, the@ConsumeMime and @ProduceMime annotations indicate that the createReview() method accepts (or consumes) XML in its body parameter and returns (or produces) an XML formatted string.

Finally, let's look at the return type. You may have noticed that the return type for createReview() isn't just a normal string that contains the response body. Rather, the return type is declared asClientResponse<String>. Why not just use String here? The answer is that ClientResponse is used whenever the client may be interested in more than just the body of the response. For example, this object provides additional information about the response including its status code and all HTTP headers. The <String> portion of the ClientResponse<String> return type indicates that the request body (also called the entity) is stored in the ClientResponse as a string. Here's how you could use the ClientResponse object to display the status code in addition to the body of the response:

ClientResponse<String> response = client.createReview("12345", "<Review>Great book!</Review>");

System.out.println("HTTP Status Code: " + response.getStatus());
System.out.println("HTTP Status Message: " + Response.Status.fromStatusCode(response.getStatus()).toString());
System.out.println("Body: " + response.getEntity().toString());

Last, let's briefly examine the deleteBook() method. There's nothing new here accept for the Response.Status return type. Since the response to a DELETE operation is typically empty (does not contain any body information), we can directly return the HTTP status code rather than require the client to retrieve it from within a ClientResponse object. Here's how we could go about showing the results of a DELETE operation:

Response.Status status = client.deleteBook("12345");

System.out.println("HTTP Status Code: " + status.getStatusCode();
System.out.println("HTTP Status Message: " + status.toString());

Also notice that the deleteBook() method is not annotated with either @ConsumeMime or @ProduceMime because the body of both its request and response is empty.

Client Application

Now that we have a REST client interface defined, let's create an application that uses it to test our REST service. The RestEasy client library uses a Java mechanism known as a dynamic proxy to construct a concrete instance of our interface according to the annotations we defined. It seems a little like magic but, fortunately, we don't have to worry about how the TestClient instance is created "under the hood". We just ask the RestEasy framework to create a REST client object based on our interface and it happens! Here is a simple client application that uses the interface we created in the last section:

import org.resteasy.spi.ResteasyProviderFactory;
import org.resteasy.plugins.providers.RegisterBuiltin;
import org.resteasy.plugins.client.httpclient.ProxyFactory;

public class TestApp
{
public static void main(String[] args)
{
//the following two initialization statements only need to be executed once per VM
ResteasyProviderFactory.initializeInstance();
RegisterBuiltin.register(ResteasyProviderFactory.getInstance());

//note that "MyRestService" is the context assigned to the service's web application
TestClient client = ProxyFactory.create(TestClient.class, "http://localhost:8080/MyRestService");

System.out.println(client.getBooks());

System.out.println(client.createReview("12345", "<Review>Great book!</Review>").getEntity());
}
}

The first two lines in the main() method are boiler-plate initialization calls that must be made before attempting to create a REST service proxy. These static calls need only be executed once per virtual machine. The ProxyFactory.create() method is where the magic happens and a fully functional instance of our TestClient interface is created. Notice that the second parameter to the create()method conveys the location of the REST service. Once created, we can simply call methods on the service proxy like we would on any POJO. 

 

Create a Test HTML Page

One advantage of REST services over SOAP is that they can be tested from a simple HTML page within a browser. A page like this is usually placed in the root of the REST web service application and named something like index.html or test.html. The file can then be accessed from a browser at a URI like this:

http://localhost:<port>/<context>/index.html

Let's take a look at how this an HTML page is created to test each of the methods supported by the sample REST service.

Get Books

As documented above, the get books REST service call looks like this:

GET http://<server>/<context>/library/books

Since hyperlinks always use the HTTP GET method, this service call is easily made available by one line in an HTML page:

<a href="library/books">Get All Books</a>

For instance, if the above line was stored in a file named index.html that was stored at the root of a REST web application, the test HTML file could be invoked from a browser with this URI:

http://localhost/<context>/index.html

Where <context> is the servlet context name under which the web application is deployed. Once this page has been loaded by the browser, it remembers its current location and will apply this location to any relative paths referenced from the browser. That's what happens with the "Get All Books" hyperlink above. This hyperlink uses a relative path of library/books. The browser will automatically make this path relative to the location of the index.html file that it just loaded. Hence, the hyperlink effectively references the URI: http://localhost/<context>/library/books

Get Book

The HTML to test the "Get Book" functionality is slightly more complex because the user must be prompted for the ISBN of the book to retrieve. To accomplish this, we'll need to use an HTML form, input box, button, and a little JavaScript to collect and submit the information to the REST service. The HTML for this feature looks like this:

<form name="GetBook" method="GET">
<table border="0">
<tr><th colspan="2" align="left">Get Book</td></tr>
<tr>
<td>ISBN:</td><td><input type="text" name="isbn"></td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="Get" onClick="GetBook.action='library/books/' + isbn.value;GetBook.submit()">
</td>
</tr>
</table>
</form>

You might be wondering why the JavaScript is necessary in the button's onClick attribute. The reason is that default browser behavior dictates that all values within a form that employs the GET method should be passed in the query string. That would result in a service call that looks like this:

GET http://localhost/<context>/library/books?isbn=12345

Unfortunately, this is not the format defined by the "get book" REST service. Rather, the isbn number should be part of the path. To accomplish this, the JavaScript sets the form's action attribute (which represents the URI to which the form should be submitted) to include the ISBN value and then submits the form. If a book with the given ISBN value exists, its representation will be returned in XML format. Otherwise, an HTTP status code of 404 (Not Found) is returned.

Add or Update Book

The "add or update book" REST service is a bit trickier to test since it uses the HTTP PUT method. Unfortunately, PUT (as well as DELETE) is not supported by standard HTML. Contrast that with GET and POST which are both natively supported by HTML forms and are easily implemented by setting the form's method attribute to "GET" or "POST". Setting a form's method to "PUT" or "DELETE" is ignored and will cause the browser to use the default GET method.

So, how can we test PUT and DELETE service calls from an HTML page? In a word, the answer is JavaScript. JavaScript allows us to make Ajax requests that support all HTTP methods. Let's see how this is done in the HTML:

<form name="AddUpdateBook">
<table border="0">
<tr><th colspan="2" align="left">Add or Update Book</th></tr>
<tr>
<td colspan="2">(If book with given ISBN already exists, it will be updated. Otherwise it will be added.)</td>
</tr>
<tr>
<td width="10%">ISBN:</td><td width="90%"><input type="text" name="isbn"></td>
</tr>
<tr>
<td>Book Title:</td><td><input type="text" name="name"></td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="Add/Update" onClick="addUpdateBook(AddUpdateBook.isbn.value, AddUpdateBook.name.value); return false;">
</td>
</tr>
</table>
</form>

In the HTML above, we collect the information on a book to be added or updated using standard HTML form elements. However, rather than submitting the form as usual, we make a call to a JavaScript method called addUpdateBook(isbn, title). This JavaScript function looks like this:

function addUpdateBook(isbn, title)
{
var xmlHttp = getXmlHttp();

xmlHttp.onreadystatechange=function()
{
if (xmlHttp.readyState==4)
{
alert("Operation complete.");
}
}

xmlHttp.open("PUT", "library/books/" + isbn, true);
xmlHttp.send("<Book isbn=\"" + isbn + "\">" + title + "</Book>");
}

We won't go into too much detail about Ajax programming but notice how this JavaScript invokes a PUT request to the library/books/<ISBN> path (see the xmlHttp.open() function call) and includes an XML representation of a book in the body of the request (see the xmlHttp.send() method call). The function assigned to the xmlHttp.onreadystatechange property simply displays a popup box that indicates when the operation has completed.

You might be wondering about that mysterious getXmlHttp() function referenced above. This function simply creates the correct Ajax object (ActiveXObject for Internet Explorer and XmlHttpRequestfor all other browsers) based on the client's browser. This method is as follows:

function getXmlHttp()
{
var xmlhttp = false;

if (window.XMLHttpRequest)
{
xmlhttp = new XMLHttpRequest()
}
else if (window.ActiveXObject) //code for IE
{
try
{
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP")
}
catch (e)
{
try
{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP")
}
catch (E)
{
xmlhttp=false
}
}
}

return xmlhttp;
}

Delete Book

As mentioned in the previous section, the HTTP DELETE method is not supported by standard HTML so we must resort to JavaScript in order to make DELETE requests. The HTML portion of the DELETE test looks like this:

<form name="DeleteBook">
<table border="0">
<tr><th colspan="2" align="left">Delete Book</th></tr>
<tr>
<td>ISBN:</td><td><input type="text" name="deleteIsbn"></td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="Delete" onClick="deleteBook(DeleteBook.deleteIsbn.value); return false;">
</td>
</tr>
</table>
</form>

Similar to the "Add or Update Book" function, the "Delete Book" feature prompts the user for the ISBN of the book to delete and passes it to the deleteBook() JavaScript method. This method makes looks like this:

function deleteBook(isbn)
{
var xmlHttp = getXmlHttp();

xmlHttp.onreadystatechange=function()
{
if (xmlHttp.readyState==4)
{
alert("Operation complete.");
}
}

xmlHttp.open("DELETE", "library/books/" + isbn, true);
xmlHttp.send(null);
}

Similar to the addUpdateBook() method, this JavaScript invokes a DELETE request to the library/books/<ISBN> path (see the xmlHttp.open() method call) and includes nothing (null) in the body of the request (see the xmlHttp.send() method call). The function assigned to the xmlHttp.onreadystatechange property simply displays a popup box that indicates when the operation has completed. 

 

Sample Project

The sample project uses JBoss's RestEasy implementation of JAX-RS. RestEasy is deployed as a standard JEE web application. To define a new service, simply drop a new JAX-RS annotated class into the RestEasy web application's WEB-INF/classes directory and redeploy. 

Follow these steps to try out the sample REST service presented in this article:

  1. Retrieve the sample WAR file from here.
  2. Copy the WAR file to your <JBOSS_HOME>/server/default/deploy directory.
  3. Start JBoss (execute the run script in the <JBOSS_Home>/bin directory).
  4. Invoke this URI from a browser: http://localhost:8080/SampleService/index.html
  5. Test the REST service functions using the HTML test page.

posted on 2013-11-19 17:45  pretty_pretty_fish  阅读(536)  评论(0编辑  收藏  举报