Chapter 18: A RESTful Web Service

Back to Table of Contents.

A RESTful Web Service

In this chapter, we’ll build a RESTful web service on top of the MoviesService and OrdersService applications. Much like our user-facing public website, this application glues together the functionality provided by each service into one unified whole. We broke our monolithic application up into a service-oriented architecture to achieve a number of benefits—scalability, reusability, and understandability of any given piece—but the collection of services are not on their own useful. They need to be composed in a meaningful way. In the case of our web service, we’d be providing a complete, machine-friendly for interface third-party affiliates who might be selling movie tickets on our behalf.

Scoping the Problem

In defining our problem, we are also implicitly defining what problem isn’t. Specifically, we are not trying to provide a single interface that can be used by a machine as well as humans (other than for debugging purposes); our clients are defined to be other computer programs. Using the APIs we designed in Chapters 16 and 17 (or more likely, a more complete set), we can assume that a coherent website that composes both of our services and consumes much if not all of our API could and would be built. This concern—satisfying the need for an accessible machine-friendly API—is handled as a separate application.

While this may seem counter to what many are led to believe is the great benefit of RESTful applications—that the same interface can be used by both human and machine clients—the separation is actually preferable for many reasons. The first reason has been noted previously. Browsers support a reduced set of HTTP verbs, GET and POST, but not PUT and DELETE. Freeing ourselves of the constraint that the same URLs must be able to service clients of varying degrees of support for HTTP verbs means your RESTful application can actually be more RESTful.

The next reason is trust. In our SOA architecture, within the firewall, applications were within the “trusted zone.” Having access to the API implied being trusted to use the service APIs without restriction. On one end, our web service is a trusted client of our back-end services, and is able to access the APIs at will. However, on the public-facing side, you may not want to allow full, open access to the world at large. You may want to limit access to a select group of third parties with whom you have business arrangements, or you may want to require that users first sign up for an account that gives them a unique, albeit free, authentication key. Doing so will allow you to monitor for abuse, and lock out problematic clients one by one. Figure 18-1 shows a configuration that allows machine and browser clients to have completely separate interfaces.

er_1801

Figure 18-1. Machine and browser clients have different interfaces

But doesn’t the public HTML-based website give free and open access to anyone? Would authentication restrictions be burdensome and encourage users to jettison the REST API in favor of parsing the information they need out of the HTML pages that are likely to be just a subdomain away? While this is a valid concern, most “free” websites have placed CAPTCHA (Completely Automated Public Turing Test to Tell Computers and Humans Apart), or other schemes to validate users, to ward off automated crawling or spammers (Figure 18-2).

On a site with CAPTCHA, or one that may one day need some kind of spam-proofing, sharing the same URLs and back-end controllers for human and machine clients can be quite a challenge. It’s almost a nonsense exercise to devise a mechanism to disallow machine clients in the same infrastructure that tries to make machine access easy. Because the needs are different, and probably the logic, too, it makes sense to keep these as separate sites rather than repeat cumbersome conditional logic based on which representation is requested—HTML or XML—throughout an application.

er_1802

Figure 18-2. A CAPTCHA test, designed to distinguish human clients from machines

Tools

In this chapter, we’ll look at a RESTful web-service in two ways. First, we’ll write a client and server using only basic tools for manipulating XML and using HTTP, ROXML, and Net::HTTP, respectively. Then we’ll create a description of our service with WADL—the description language likely to become the standard for describing RESTful services—and generate a client for our service automatically using that file.

ROXML

ROXML (the Ruby Object to XML Mapping Library) does exactly what its name suggests. Given a class that has been annotated with ROXML, instances of the class can be marshaled and unmarshaled to and from XML.

To get started with ROXML, first install the gem:

sudo gem install roxml

Then, load the library in config/environment.rb.

require 'roxml'

In a class, include the ROXML mixin, as shown in Example 18-1. After doing so, three new class methods are available to annotate the class: xml_attribute, xml_text, and xml_object.

Example 18-1. A Movie class with ROXML annotation

class Movie
  include ROXML
    
  xml_attribute :id
  xml_text :name
  xml_text :rating
  xml_text :rating_description
  xml_text :length_minutes
end

Example 18-2 shows the ROXML class being manipulated. Each attribute behaves identically as if it had been defined with attr_accessor. In fact, the three xml_ annotation methods do set up instance variable accessors that manipulate instances variables, e.g., @rating.

Example 18-2. Working with the ROXML-annotated class from Example 18-1

m = Movie.new
m.id = 1
m.name = Casablanca
m.rating = 'PG-13'
m.rating_description = 'Parents strongly cautioned'
m.length_minutes = 120
puts m.to_xml

At the end of Example 18-2, the unmarshaled version is printed using to_xml. The output is shown in Example 18-3. Notice how attribute variables are displayed, embedded within the opening object tag, versus how text variables are displayed.

Example 18-3. XML output from Example 18-2

  Casablanca
  PG-13
  <rating_description>Parents strongly cautioned
  <length_minutes>120

You can also embed arrays of objects in another object with the xml_object declarator. Assuming we have created ROXML classes for Theatre and ShowtimeLight, like our ActionWebService struct classes from Chapter 17, Example 18-4 is then the corollary to the ShowtimesResult class of our XML-RPC API. The arrays can be accessed like regular attributes, e.g. showtime_result.movies, and manipulated like regular arrays with the chevron (<<) operator.

Example 18-4. A ROXML-annotated class with embedded object arrays

class ShowtimesResult
  include ROXML
  
  xml_object :movies, Movie, ROXML::TAG_ARRAY, "movies"
  xml_object :theatres, Theatre, ROXML::TAG_ARRAY, "theatre"
  xml_object :showtimes, ShowtimeLight, ROXML::TAG_ARRAY, "showtimes"
end

As an example, the code in Example 18-5 would produce the result in Example 18-6. Note that no theaters or showtimes were added, but sections for each are still present to denote empty arrays.

Example 18-5. Embedding objects within other objects

m1 = Movie.new
m1.id = 1
m1.name = 'Casablanca'
m1.length_minutes = 120
m1.rating = 'PG-13'
m1.rating_description = 'Parents strongly cautioned'

m2 = Movie.new
m2.id = 2
m2.name = 'Maltese Falcon'
m2.length_minutes = 120
m2.rating = 'PG-13'
m2.rating_description = 'Parents strongly cautioned'

sr = ShowtimesResult.new
sr.movies << m1
sr.movies << m2

puts sr.to_xml

Example 18-6. Resulting XML from Example 18-5

<showtimesresult>
  <movies>
    <movie id="1">
      <name>Casablanca</name>
      <rating>PG-13</rating>
      <rating_description>Parents strongly cautioned</rating_description>
      <length_minutes>120</length_minutes>
    </movie>
    <movie id="2">
      <name>Maltese Falcon</name>
      <rating>PG-13</rating>
      <rating_description>Parents strongly cautioned</rating_description>
      <length_minutes>120</length_minutes>
    </movie>
  </movies>
  <theatre/>
  <showtimes/>
</showtimesresult>

To marshal XML back into a Ruby object, we use the parse class method:

MovieShowtime.parse(xml_text)

Complete ROXML documentation is available at http://roxml.rubyforge.org.

Net::HTTP

With ROXML, we have a way to move data between Ruby objects and XML representations. Now we need a mechanism to transfer those XML representations from one application to another. For XML-RPC or SOAP, this was taken care of for us under the covers by ActionWebService. If we were using ActiveResource, we also wouldn’t see the plumbing of how XML is passed back and forth, but we wouldn’t be able to create as flexible a web service as we’d like. Therefore, we’ll have to create and parse the HTTP messages ourselves for now. We’ll do this using the built in Ruby library Net::HTTP.

The Net::HTTP library has methods for get, post, put, and delete, as well as additional convenience methods for GET and POST requests, since they are so common. We’ll see parts of this library in action in the rest of this chapter. Complete documentation for the Net::HTTP library is available at http://www.ruby-doc.org/stdlib/libdoc/net/http/rdoc/index.html.

MoviesWebService

In our web service we will create an interface that is very similar to the back-end service interface we created in previous chapters. In part, this is to show that mechanisms for passing messages back and forth are largely interchangeable, and also to show that a RESTful interface does not need to represent a total paradigm shift if you don’t want it to.

In fact, if we wanted to provide a RESTful interface in addition to our XML-RPC interface in our back-end service, we could do so using the same ActionWebService::Struct classes. Example 18-7 shows our Movie class from the XML-RPC service, now marked up with ROXML. Using the techniques in the rest of this chapter, we could have provided an identical interface as our XML-RPC API, using RESTful techniques. In fact, it’s even possible that the RESTful interface could be generated from the same style of API definition as the XML-RPC API files. Of course, this is not currently supported, but it is a possible future direction, and would certainly ease transition to REST, should your organization wish to do so.

Example 18-7. An ActionWebService class annotated with ROXML

module Logical
  class Movie < ActionWebService::Struct
    include ROXML
    xml_attribute :id
    xml_text :name
    xml_text :rating_id
    xml_text :rating_description
    xml_text :length_minutes

member :id, :integer
member :name, :string
member :length_minutes, :integer
member :rating_id, :string
member :rating_description, :string
end
end

Resources Server implementation

In Example 18-1, we created a new class to serve as an XML proxy. Our web service is also a sort of proxy for our back-end service. In this section, we will just show how to return the data that our XML-RPC service returned, but in a RESTful way.

However, as we have noted, we are not intending this web service to be consumed by human sitting in front of browsers. In fact, for resources—such as movies, theaters, and showtimes—we could actually have a much truer to REST interface, utilizing all four HTTP verbs where we need them.

Because ActiveResource takes the other approach—that a web service can be layered atop the web pages and interface designed for a user—it limits itself to the GET and POST HTTP verbs. Therefore, for our purposes, the routing available in Rails is not complete. In addition to routing based on the URL itself, we also would like to route based on the HTTP verb.

Example 18-8 and Example 18-9 let us accomplish this. First, in Example 18-8, an abstract controller is created, which inherits from ApplicationController. This controller, AbstractResourceController, “fixes” Rails for properly dealing with all four HTTP verbs applied to a single resource URL.

Example 18-8. An extension to Rails’ routing to take the HTTP verb into account

class AbstractResourceController < ApplicationController
  protect_from_forgery :only => []

  def http_method_dispatch
    send request.env['REQUEST_METHOD'].downcase
  end

  [:get, :post, :put, :delete].each do |http_method|
    define_method(http_method) {
      # redefine in child classes
      render :text => "Forbidden", :status => "405 Not Allowed"
  end
end

First, we disable Rails 2.0’s forgery protection. This feature requires that any non-GET request contain an authentication token, which would have been provided in some form the user retrieved before making their non-GET request. While this is great for preventing browser-based cross-site attacks, in this case forgery would also mean a machine trying to access your pages without first requesting a web page intended for humans. But isn’t this the entire purpose of a web service? Therefore, the first thing we do is shut this feature off completely for our RESTful resource controllers.

The next method, http_method_dispatch, provides the magic. When a request for a resource is made, we’ll send that request in to this method. The Rails built-in routing does not take care of distinguishing between GET, POST, PUT, or DELETE requests, so we’ll do that here. We extract the HTTP method from the request object’s env hash, and downcase it, so that GET becomes get and DELETE becomes delete. Then, using the send method, we call this method within the controller. In effect, we have reserved the method names get, put, delete, and post for each resource controllers. Note, of course, that this assumes that each controller descending from AbstractResourceController is responsible for one and only one resource.

It’s now up to controllers inheriting from this abstract controller to implement those methods. For those that don’t implement the complete set, we provide a default method, which returns a 405 “Method Not Allowed” when the HTTP method is called on the given resource. Four such methods are created in the loop at the bottom of our abstract controller, one for each of the HTTP methods.

Next we need to add routes that will pass control to http_method_dispatch to complete the cycle. The first two routes in Example 18-9 do what we need. Let’s look at the first route. In this case, the :controller symbol names the resource, e.g., movies. The :id symbol names the ID of the resource, e.g., movies/3. We then pass all requests to movies/3 to the http_method_dispatch method of the MoviesController class, which should be a subclass of AbstractResourcesController. That method then further routes the request to the actions named get, put, post, or delete, depending on the HTTP method of the request.

The second route is the same, but it allows a format specifier to be passed in the URL. Normally, in a RESTful interface, the format in which the client wants to receive the response is specified via the Accept HTTP header. However, when testing from a browser, which is how many people test their REST interfaces, you often can’t easily change the header your browser passes. Therefore, Rails will interpret standard extensions that fall on the :format symbol in the same way as mime types passed via the Accept header.

Example 18-9. Routes to support our HTTP verb dispatch

map.connect ':controller/:id', :action => 'http_method_dispatch'
map.connect ':controller/:id.:format', :action => 'http_method_dispatch'
map.connect ':controller/:id/:action'
map.connect ':controller/:id.:format/:action'

The next two routes in Example 18-9 are also an identical pair, one accounting for a format specifier on the URL. These routes, rather than pass control to http_method_dispatch, follow the lead of the URL. For example, use movies/3/edit to retrieve a form to edit the movie.

Example 18-10 shows our MoviesController class, which inherits from AbstractResourcesController. Because we can only expose methods that are exposed by our back-end service, we define just one method, get. Our method requests from the back-end service the movie passed in via the :id parameter. If no movie is returned from the back-end, a 404 is returned. Otherwise, if the client can accept XML (as specified either via the Accept header, or by a passed in extension), we return an XML representation of the movie, as defined by our ROXML Movie class.

Example 18-10. A subclass of the AbstractResourcesController for Movies

class MoviesController < AbstractResourceController
  def get
    m = MoviesServiceClient.get_movie(params[:id])
    if !m
      render :file => "#{RAILS_ROOT}/public/404.html", :status => "404 Not Found"
      return 
    end
    respond_to do |format|
      format.xml { 
        m_xml = Movie.new
        m_xml.id = m.id
        m_xml.name = m.name
        m_xml.rating = m.rating_id
        m_xml.rating_description = m.rating_description        
        m_xml.length_minutes = m.length_minutes
        render :xml => m_xml.to_xml 
      }
    end
  end
end

Example 18-11 shows a series of requests for the movie with an ID of 3. The first request is a GET request. The result has status 200 OK, and the XML describing this movie follows. The next request is a request for the HTML version of the file, using the extension .html to suggest the return type. Because we haven’t defined format.html block in our get method, the response is 406 Not Acceptable. The next example is the same, but the representation is requested by passing text/html via the Accept header explicitly. In the final example, the HTTP DELETE method is called on our resource URL. Because we haven’t defined this method in our controller, the default from the AbstractResourceController is called, which returns a 405.

Example 18-11. How our AbstractResourceController subclass handles a variety of requests for differing HTTP methods

chak$ curl -D - -X GET http://localhost/movies/3
HTTP/1.1 200 OK

CasablancaPG-13<rating_description>Parents strongly cautioned<length_minutes>120

chak$ curl -D - -X GET http://localhost/movies/3.html
HTTP/1.1 406 Not Acceptable

chak$ curl -D - -X GET -H "Accept: text/html" http://localhost/movies/3
HTTP/1.1 406 Not Acceptable

chak$ curl -D - -X DELETE http://localhost/movies/3
HTTP/1.1 405 Method Not Allowed

Actions Server Implementation

Rather than repeat the above for theaters and showtimes, we’ll leave those steps as an exercise. Instead, we’ll move on creating the action for placing an order. However, in this case, we’ll take a RESTful approach rather than a strictly REST approach. Of course, orders can easily be modeled as resources. It would be hard to argue against an API in which you post an order, and are returned that same order for future modification could make sense.

However, to stay flexible, let’s assume that our orders, once placed, are not so easily modifiable. Instead of returning an object representing the order, we’ll instead return an object representing an order confirmation, as shown in Example 18-12. This corresponds to the OrderPlaced ActionWebService class from Chapter 17. This is not strictly REST, because we will post an order—essentially no differently than a plain old HTTP form posting to a URL—but we will return as XML a confirmation object. However, this does fit our definition of RESTful.

Example 18-12. A class ROXML class to describe an order confirmation

class Confirmation
  include ROXML
  
  xml_text :confirmation_code
  xml_text :price
end

Example 18-13 shows our action for creating a new order. First, we ensure that the request is a POST, and we disallow all other HTTP methods. Then much as we did in our integration test for the order service, we build up the parameters needed for the OrdersService place_order method. We call this method, which returns a Logical::OrderPlaced object. We convert this into a Confirmation object, described in Example 18-12, which can be serialized to XML. We check that the caller is requesting XML, and if so, we marshal the confirmation object and return it; otherwise a 405 error will be returned.

Example 18-13. A RESTful action for placing an order

class OrdersController < ActionController::Base
  def create
    if request.env['REQUEST_METHOD'] != 'POST'
      return render :text => "Method not allowed",
                    :status => "405 Method Not Allowed"
    end
    li = Logical::LineItem.new(
      :product_id => params[:showtime_product_id],
      :quantity => params[:num_tickets]
    )
    ad = Logical::Address.new(
      :line_1 => params[:billing_line_1],
      :line_2 => params[:billing_line_2],
      :city => params[:billing_city],
      :state => params[:billing_state],
      :zip_code => params[:billing_zip]
    )
    cc = Logical::CreditCard.new(
      :card_number => params[:credit_card_number],
      :expiration_month => params[:credit_card_exp_month],
      :expiration_year => params[:credit_card_exp_year],
      :type => params[:credit_card_type]
    )
    payment = Logical::Payment.new(
      :address => ad,
      :type => Logical::Payment::CREDIT_CARD,
      :credit_card => cc
    )
    result = OrdersServiceClient.place_order(
      [li], payment
    )

    respond_to do |format|
      format.xml { 
        conf = Confirmation.new
        conf.confirmation_code = result.confirmation
        conf.price = result.price
        render :xml => conf.to_xml 
      }
    end
  end
end

To make this action work at the URL /orders/create, we add the following to routes.rb:

map.connect 'orders/:action', :controller => 'orders'

A Client Implementation

We now have a set of server-side methods that can be called: one for retrieving movie information, and another for placing an order with the orders service. It’s now time to move on to the client implementation. Because RESTful services are still relatively new, there isn’t one set way to consume them. Although there are clients such as ActiveResource, because it makes many assumptions about the service itself, so often people still write custom clients for non-Rails REST services that they wish to consume.

In this chapter, we’ll look at two different ways to consume a REST service. The first method is highly manual. We’ll actually build URLs and make HTTP requests, and parse the resulting XML. Then we’ll take a different tack. We’ll create a very simple WADL file describing the GET method for our Movie resource, and automatically generate a client for our service with the wadl.rb library. For both methods, we’ll add the code to our integration test application.

For the first method, we create an initializer, shown in Example 18-14, to set up configuration constants that could be useful in our implementation. Here, we just set up the base URL of the web service. We could also use this class to set up constants for a connection timeout, authentication information, or anything else we might need later.

Example 18-14. Basic initializer for a RESTful web service

class MoviesWebServiceClient
  HOST = 'http://localhost'
end

Example 18-15 shows our manual tests of the RESTful order placement method. First, we create a setup method, which adds a product in the Orders service. This product represents a movie showtime. In the actual test method, we’ll place an order for this showtime.

In the test_order_post method, we use the HOST constant set up in the initializer to construct the URL of our order method. This URL is passed to URI.parse, which will create a URI object from the URL, suitable for passing to Net::HTTP methods. We then create a hash containing all of the arguments expected by the order creation web service method. Then we call Net::HTTP.post_form, passing in the URI object and the parameters hash. This method takes care of the mechanics of building the HTTP post and returns an Net::HTTPResponse object and the data returned by the post itself. We use the XmlSimple library to parse the returned XML. Given an XML data structure contained in a string, XmlSimple will convert that data structure into a Ruby hash object, which can then be manipulated directly with Ruby hash syntax. Example 18-16 shows what the returned XML from our web service call looks like after being parsed by XmlSimple. Once we have our resulting hash, we assert that the two expected fields are present in the XML data.

Example 18-15. An integration test to test placing an order through our web service

require File.dirname(__FILE__) + '/../test_helper'
class OrdersServiceTestCase < Test::Unit::TestCase
  def setup
    @new_id = OrdersServiceClient.add_product(
      "Casablanca 10:00pm",
      50,
      1000)
    assert @new_id
  end

  def test_order_post
    uri = URI.parse("#{MoviesWebServiceClient::HOST}/orders/create")
    post_args = {
      :showtime_product_id => @new_id,
      :num_tickets => 4,
      :billing_line_1 => '123 Testahoma Lane',
      :billing_city => 'Cambridge',
      :billing_state => 'MA',
      :billing_zip => '01239',
      :credit_card_number => '55555555555555',
      :credit_card_exp_month => '12',
      :credit_card_exp_year => '2015',
      :credit_card_type => 'american_express'
    }
    
    resp, data = Net::HTTP.post_form(uri, post_args)
    doc = XmlSimple.xml_in(data)
    assert doc['confirmation_code']
    assert doc['price']
  end
  
  def test_order_get_fails
    get_args = {
      :showtime_product_id => @new_id,
      :num_tickets => 4,
      :billing_line_1 => '123 Testahoma Lane',
      :billing_city => 'Cambridge',
      :billing_state => 'MA',
      :billing_zip => '01239',
      :credit_card_number => '55555555555555',
      :credit_card_exp_month => '12',
      :credit_card_exp_year => '2015',
      :credit_card_type => 'american_express'
    }.collect{|k,v| "#{k}=#{CGI.escape(v.to_s)}"}.join("&")
    uri = URI.parse("#{MoviesWebServiceClient::HOST}/orders/create?#{get_args}")
    
    resp = Net::HTTP.get_response(uri)
    assert resp.kind_of? Net::HTTPMethodNotAllowed
  end
end

The second test in Example 18-15, test_order_get_fails, starts out the same way as our previous test. However, rather than post the form, which translates to using the HTTP POST verb, in this test we make a GET request with the get_response method. Because the order method makes changes on the server, it should only respond to POST requests. Therefore, we test that the request is denied and that the response returned is a Net::HTTPMethodNotAllowed object.

Example 18-16. Result of parsing XML with XMLSimple

{"confirmation_code"=>["COB7AgkA8MaIA"], "price"=>["1000"]}

wadl.rb

In the examples above, we had to handcraft the URLs required for placing an order. As we saw, the mechanism for passing arguments for a GET request is different than the one that passes in a POST request. When you are consuming a web service, the mechanics of how to actually make your service requests is not interesting. Generally speaking, you want to make requests, get results, and use them.

These days, having a REST web service and being RESTful in general is still somewhat of a chic thing to do, but it’s not necessarily practical for clients. REST proponents claim that because REST (strict REST, that is) provides a uniform interface to resources, there is no need for special clients to be written. Of course, in the real world, the result is that for every worthwhile REST service out there, for every language where someone has an interest in that service, a custom, one-off client has been written. Although the REST paradigm has many elegant aspects, it turns out that it’s just no fun to handcraft HTTP requests.

This is where description languages such as WADL (web application description language) come in. For any given REST web service, a WADL file describes each resource and the methods that can be applied to it. WADL also supports describing actions that are not resource-based URLs, but are instead URLs representing some action, like our order creation method. A WADL parser can take that description and create an application language-specific client from it that abstracts away, for the caller, the mechanics of hand-building HTTP calls of four varieties. Action or resource-based URLs simply become methods in the client, and parameters are parameters, whether the underlying HTTP method is a GET, POST, PUT, or DELETE.

A key observation when discussing service description languages for REST services is that using one doesn’t imply that any change will be made to your service or the way you write it. A REST service with or without a WADL description file functions exactly the same way for those who don’t wish to make use of the WADL file. The difference is that for those who do, utilizing the service becomes much simpler.

The worry that many people have about service descriptions, whether WSDL or WADL, has to do with code generation. Code generation itself is actually fairly benign. In fact, the Rails generators, which generate stub files and methods for your ActiveRecord models and controllers, do about the same level of code generation as would be expected from WADL-based code-generation. The real fear is that REST advocates do not want to see REST development begin to resemble SOAP development, as the latter is, to the REST advocate, anathema. But aside from that politically motivated concern, there are really no adverse affects to using a WADL file to describe your service, or even to generate your initial implementation stub code from that WADL, if you write it first. In fact, contract-first design is a great way to ensure that changes made carelessly do not have a negative impact on clients of your service.

Example 18-17 shows a very simple WADL file that describes applying the GET HTTP verb to our movie resource from Example 18-10.

Example 18-17. A simple WADL file describing the GET HTTP verb for the Movie resource

Example 18-18 shows another test case added to our integration test framework, which uses the wadl.rb library and the WADL file from Example 18-17 to generate an on-the-fly web service client. The from_wadl method accepts a string containing WADL XML description, and returns a client object. Resources can then have their methods applied to them via method calls to the client, e.g., movies.get. Results from wadl.rb-generated clients return REXML results. To keep the examples consistent, we’ve converted the REXML result to an XmlSimple object by first changing the REXML result to a string, then parsing the string with XmlSimple’s xml_in method. Armed with our XmlSimple object, we assert that all of the expected fields exist in the result object.

Example 18-18. Integration test using the wadl.rb and the WADL file from the previous example

require File.dirname(__FILE__) + '/../test_helper'
class OrdersServiceTestCase < Test::Unit::TestCase
  def test_get_via_wadl
    wadl = Net::HTTP.get_response(URI.parse "http://localhost/movies.wadl").body

    movies_webservice = WADL::Application.from_wadl(wadl)
    result = movies_webservice.movies.get(:query => {:id => 1})
    doc = XmlSimple.xml_in(result.representation.to_s)
    assert doc['id']
    assert doc['rating']
    assert doc['rating_description']
    assert doc['length_minutes']
  end
end

Example 18-19 shows the result of running the integration test from Example 18-18. The test and all assertions pass.

Example 18-19. Results of running the WADL-client integration test

Loaded suite test/integration/wadl_test_case
Started
.
Finished in 0.212655 seconds.

1 tests, 4 assertions, 0 failures, 0 errors

In this book, I won’t describe the entire WADL specification, or show you how to handle the infinite variety of URL actions one might wish to describe with WADL. This is a topic deserves its own book. Unfortunately, as of this writing, the WADL specification itself is the only readily available documentation for WADL, when in reality, an entire book of examples is needed. The specification for WADL, written by Marc Hadley of Sun Microsystems, can be found at https://wadl.dev.java.net.

REST Describe

Because WADL is not so simple to write, tools are beginning to emerge to facilitate creating these files. A Ruby tool that allows you to describe REST services similar to how XML-RPC services are described with ActionWebService seems like it would be a big win for the REST on Rails community. However, such a tool does not seem to be emerging, and is likely stifled by the exuberance for ActiveRecord.

A tool that has been gaining attention lately is called REST Describe, from Google Code. This tool, shown in Figure 18-3, examines an existing web service and creates a WADL description of that service. You use REST Describe by providing a web service URL and invoking some HTTP method on a remote resource or action. Based on the parameters and the result, REST Describe attempts to create the XML description, allowing you to make modifications in places where the tool cannot definitively guess all aspects of the API—for instance types, required versus optional parameters, and other tricky spots.

er_1803

Figure 18-3. Screen shot of REST Describe, from Google Code

Chapter 17 : REST Primer
Chapter 19 : Caching End to End
Advertisements