In the past 20 years, one tendency has dominated the development of commercial software tools: We love to fight complexity with complexity. Nowhere is this trend more apparent than within the distributed computing arena. The C and Java™ communities have seen some stunningly complex frameworks built to enable distributed communications. The Distributed Computing Environment (DCE) enabled remote procedure calls across applications written in C. The Common Object Request Broker Architecture (CORBA) standard enabled communications across object-oriented applications. The Enterprise JavaBeans (EJB) specification provides services for security, persistence, transactions, messaging, and remoting. Hype for each framework built to a crescendo, but each one failed to meet expectations and some were unmitigated disasters because of their complexity. Of these, only EJB 3.0, the result of a dramatic simplification overhaul, has the potential to succeed for distributed applications. The marketplace may or may not give the embattled framework another chance, and EJB will still need to deliver.
The latest massive distributed framework is Web services. Web services technology lets applications communicate with one another in a platform- and programming language-independent manner (see Resources). Web services standards too are threatened by the complexity bogeyman, but an alternative strategy known as REST promises a more straightforward approach. This article shows you how to add a REST-style Web service in Ruby on Rails and invoke the service from both Ruby and Java code.
As with EJB, CORBA, and DCE, the core abstraction for Web services is a remote procedure call. Web services use a protocol called SOAP (originally, SOAP stood for Simple Object Access Protocol, but the term is now deprecated) for expressing a message's structure with XML. Here's a hint: If a protocol starts with S for simple, it's not. The Web Services Definition Language (WSDL) provides a standard specification of a service. Like SOAP, WSDL is an involved and complicated API, and SOAP and WSDL only scratch the surface of the dozens of APIs that make up the Web services behemoth (see Resources). Web services need an overhaul, and thanks to an influential Ph.D. dissertation by Roy Fielding, they are getting one (see Resources).
Fielding's dissertation describes the REST application-networking strategy. REST is fundamentally different from full-stack Web services for three major reasons:
- The core abstraction in REST is a remote resource instead of a remote procedure call.
- Rather than inventing an exhaustive list of standards, REST uses existing Internet standards, including HTTP, XML, and TCP/IP.
- Instead of every possible scenario, REST covers the most common problems.
Think of REST as browsing. REST clients use the same HTTP commands as your browser to access resources. When a REST client accesses a representation of a resource, the client transitions into a state. With various HTTP commands, REST clients can create, read, update, or delete records from a resource.
For example, take a typical blog. You get a list of posts by typing a URL such as blog.rapidred.com. Then, if you want to edit your blog entry, you type HTTP parameters on your URL (such as blog.rapidred.com/edit?article=12345), and the edit form displays. So each blog entry has its own URL, and you can read, modify, or delete content with HTTP commands by clicking a link or typing a URL directly.
In a nutshell, REST:
- Uses TCP/IP naming standards to name resources on the Web
- Queries and manipulates those resources with HTTP
- Uses standard text-based message formats like XML or HTML to structure data
Ruby on Rails provides excellent support for Web services with REST.
Rails implements Web services with a module called Action Web Services. Many development frameworks encourage a separate controller for your views and Web services. That strategy lets you maintain a consistency of style across each controller. The problem is that you need a new controller for each kind of content you serve. For example, Ajax UIs require remote XML calls to JavaScript from your controller.
Rather than dedicating a controller to your Web services, with Rails you generally use the same controller to serve content to your HTML-based views, your XML-based Web services, and your XML-based JavaScript components. The best way to understand Action Web Services is to see it in action in the context of a working application.
Create a database called service_development using your chosen database manager. Next, create a Rails project and a model with these commands:
> rails service > script/generate model Person |
After you generate your model, you have a migration called db/migrate/001_create_people.rb. Edit that migration to look like Listing 1:
Listing 1. The migration for the people table
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column :first_name, :string, :limit => 40
t.column :last_name, :string, :limit => 40
t.column :email, :string, :limit => 40
t.column :phone, :string, :limit => 15
end
end
def self.down
drop_table :people
end
end
|
Change the database configuration in config/database.yml to match your own database configuration and type rake migrate. Finally, generate a scaffold for a Person model and a People controller by typing script/generate scaffold Person People. You're ready to start the server with script/server. Point your browser to localhost:3000/people to see the classic Rails scaffold for Person. Figure 1 shows an application with standard Rails scaffolding:
Figure 1. A simple Rails application
Before I introduce Web services with Rails, review the controller code. Edit app/controllers/people_controller.rb to match the code in Listing 2:
Listing 2. Controller code for PeopleController
class PeopleController < ApplicationController
def index
list
render :action => 'list'
end
# GETs should be safe (see
http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update
],
:redirect_to => { :action => :list }
def list
@person_pages, @people = paginate :people, :per_page => 10
end
def show
@person = Person.find(params[:id])
end
def new
@person = Person.new
end
def create
@person = Person.new(params[:person])
if @person.save
flash[:notice] = 'Person was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end
def edit
@person = Person.find(params[:id])
end
def update
@person = Person.find(params[:id])
if @person.update_attributes(params[:person])
flash[:notice] = 'Person was successfully updated.'
redirect_to :action => 'show', :id => @person
else
render :action => 'edit'
end
end
def destroy
Person.find(params[:id]).destroy
redirect_to :action => 'list'
end
end
|
If you followed the earlier Ruby on Rails projects in this series, you know the general flow of a typical controller method:
- A user makes a request through HTTP by following a link or specifying a URL.
- The Web server directs the request to Ruby on Rails based on the domain configuration.
- The Rails router routes the request to the controller based on the URL pattern. The default pattern is http://host_name/controller/action/parameters.
- The router invokes a method on the controller with the same name as the action.
- The action method sets up instance variables for the view and renders the view.
- The action method copies any instance variables to the view.
For example, look at the show method in Listing 2. The controller sets up the @person instance variable for the view to use. Because the method doesn't specify the name of a view, Rails invokes the view with the same name as the controller action -- in this case, the view in app/views/people/show.rhtml.
Take a look at the list method. If you wanted to make this method render XML instead, you'd need to:
- Remove the pagination
- Convert the
peopleinstance variable to XML - Render XML instead of HTML
Rails makes it possible to handle the Web service and render a view from the same Web service. You don't really need pagination yet. To simplify the list method a little for the Web service, remove the pagination by making the list method in the controller look like Listing 3. You also need to remove the "Next Page" and "Previous Page" links near the bottom of the code in app/views/people/list.rhtml.
Listing 3. Simplifying list
def list @people = Person.find_all end |
By removing the scaffolding for pagination, you remove a feature that would make your UI more robust, but you also get something in return. You can use the same code to drive your Web service and your view. If you find that you need pagination later, you can always write some custom helpers.
Now that the base application is out of the way, you're ready to add some Web services.
Adding Web services to a Rails controller
If I wanted to be flippant, I could say, "You've already got a Web service." Remember what I said about REST? This style of Web services uses named resources. My Rails application also has named resources: host_name/people/list invokes my list service. REST-style Web services also use TCP/IP and HTTP. So does my Rails application. And well-formed HTML is a subset of XML, satisfying the last REST requirement. Just call an HTTP get on localhost:3000/people/list and parse the result to get a list of people. And that's exactly the point. REST works as the Internet works. But this is not really a REST-based Web service. Ideally, you'd like to provide an XML document that reflects the meaning of Person instead of the structure of the UI.
A real service should produce a pure data representation, one built specifically for the service's intended clients. But the sample application has two clients: end users and REST clients. To reuse the same code for both purposes, you need to give Rails more information. The Rails designers could have decided to use additional URL parameters, but mangling the URL is an ugly hack. Rails should not burden the users with such details. Instead, HTTP provides a vehicle for specifying more information: the HTTP header.
To understand the REST model for Web services, it helps to know a little more about HTTP. The curl (think of it as See URL) command lets you query a URL with a single command and see the response. Unix-based operating systems include curl by default, and you can download free curl utilities for other OSes. By typing curl http://some-url, you can limit the request to print just the default response body (the HTML rendered by your browser). You can get much more information by typing curl -i http://some-url. This command returns the HTTP header, as shown in Listing 4. You see the header configuration, composed of key-value pairs that dictate the configuration of the individual request.
Listing 4. Invoking an HTTP request with curl
> curl -i http://localhost:3000/people/list HTTP/1.1 200 OK Cache-Control: no-cache Connection: Keep-Alive Date: Tue, 27 Jun 2006 14:54:49 GMT Content-Type: text/html; charset=UTF-8 Server: WEBrick/1.3.1 (Ruby/1.8.4/2005-12-24) Content-Length: 854 Set-Cookie: _session_id=216912045de52786f032b22755c903dd; path=/ |
You'll frequently see the HTTP get, put, post, and delete commands. REST takes advantage of these commands to do classic CRUD, a common acronym for create, read, update, and delete. The HTTP commands map to CRUD like this:
- Create: HTTP
put - Read: HTTP
get - Update: HTTP
post - Delete: HTTP
delete
Browsers use the HTTP header to satisfy many different kinds of requests with the same server-side code. Well-behaved applications provide enough information to process a document correctly. One of those pieces of information is called the HTTP Accept header. With just a little extra effort, your controller can employ some helpers that use the Accept header to determine how to respond to an incoming request. Then, the controller can render the appropriate response. Change the list method in the PeopleController to look like Listing 5:
Listing 5. Extending the list method to render XML
def list
# wants is determined by the http Accept header in the request
@people = Person.find_all
respond_to do |wants|
wants.html
wants.xml { render :xml => @people.to_xml }
end
end
|
In Listing 5, you see a full REST-based Web service. The resulting code is a beautiful example of a tiny domain-specific language within Rails that extends Ruby to make a kind of switch statement. Here's how it works:
- The
respond_tomethod accepts a single code block and passes one instance variable (labeledwants) into the code block. wantshas a method for each possible type. The controller can specify a code block for each type the controller expects.- A
wantsmethod executes the corresponding code block if the method name matches the type in the HTTPAcceptheader. - If no code block is specified (such as
wants.html), Rails performs the default action (in this case, rendering app/views/people/list.rhtml).
This strategy lets you share the same setup code across all expected clients. Should you need to add a JavaScript client expecting HTML to enable your application for Ajax, you could just add wants.js, as shown in Listing 6:
Listing 6. Rendering HTML for a JavaScript client
def list
# wants is determined by the http Accept header in the request
@people = Person.find_all
respond_to do |wants|
wants.html
wants.js
wants.xml { render :xml => @people.to_xml }
end
end
|
So you've seen how to add REST Web services to your read-only methods. The show method would be similar, as shown in Listing 7:
Listing 7. Implementing show
def show
@person = Person.find(params[:id])
respond_to do |wants|
wants.html
wants.xml { render :xml => @person.to_xml }
end
end
|
You may have noticed that you've seen only read-only services through REST. The reason is that preparing the application to handle posts and deletes is trivial. Deletes need no added support because the current code already uses the URL to specify the ID of the person to be deleted. Rails automatically translates incoming XML in post requests, so you do not need to build in any server-side support. In fact, the application works as-is for deletes, updates, and creates. You might tinker with the HTTP response that each method renders, but your client code is really after only the HTTP return code.
It's time to invoke the Web service.
The strategy of using the existing HTTP protocol keeps your invocations simple. Listing 8 shows the Ruby version. Notice the HTTP Accept header. Remember, the controller determines the type of content to render based on that header.
Listing 8. Invoking the service from Ruby
require 'net/http'
Net::HTTP.start('localhost', 3000) do |http|
response = http.get('/people/list', 'Accept' => 'text/xml')
#Do something with the response.
puts "Code: #{response.code}"
puts "Message: #{response.message}"
puts "Body:\n #{response.body}"
end
|
The Web service invocation in Listing 8 invokes an HTTP get on http://localhost:3000/people/list and prints the response. Ruby has excellent libraries to deal with the resulting XML, but they are beyond this article's scope. You don't need to use Ruby to invoke this service. You need only a library for HTTP. Listing 9 shows a Java invocation of this service:
Listing 9. Invoking the service with Java code
package com.rapidred.ws;
import java.net.*;
import java.io.*;
public class SimpleGet {
void get() {
try {
URL url = new URL("http://localhost:3000/people/list");
URLConnection urlConnection = url.openConnection();
urlConnection.setRequestProperty("accept", "text/xml");
BufferedReader in =
new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String str;
while ((str = in.readLine()) != null) {
System.out.println(str);
}
in.close();
}
catch (Exception e) {
System.out.println(e);
}
}
|
Like the Ruby counterpart, this code opens a URL connection, sets the Accept header to text/xml, issues the get, and prints the result. Many XML frameworks (see Resources) exist for Java code (as for Ruby), but I'll hardcode the XML in this one to keep the example simple.
Invoking a post is similar. Listing 10 shows a simple post:
Listing 10. Calling HTTP post with Java code
void post() {
try {
String xmlText = "<person> " +
"<first-name>Maggie</first-name>" +
"<last-name>Maggie</last-name>" +
"<email>maggie@tate.com</email>" +
"</person>";
URL url = new URL("http://localhost:3000/people/create");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "text/xml");
OutputStreamWriter wr = new
OutputStreamWriter(conn.getOutputStream());
wr.write(xmlText);
wr.flush();
BufferedReader rd = new BufferedReader(new
InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
System.out.println(line);
}
wr.close();
rd.close();
} catch (Exception e) {
System.out.println("Error" + e);
}
}
|
This HTTP post creates a new Person by simply invoking a post on http://localhost:3000/people/create and passing an XML document in the HTTP document body. (Normally, you'd use a Java XML library to construct the XML document. Again, I hardcoded the document to keep the example simple.) The Rails support automatically translates the incoming XML to a Ruby hash of Person attributes.
In this article, you've seen that you can enable a controller for REST-based Web services with a trivial amount of code. Dynamically typed Internet languages such as Ruby make extensive use of REST instead of SOAP-based Web services. Some simple innovations, including the nifty responds_to syntax and automatic XML translation for incoming posts, make it easy to use the same controller from a Web service, remote JavaScript request, or HTML.
The Java language, too, has excellent support for REST. After all, a servlet is fundamentally a server-side REST-based Web service. You can use servlets on the Java side and Rails controllers on the Ruby side to knit together applications using the strengths of both platforms. That's the beauty of Web services. All you really need is the courage to break from the herd.
Learn
-
Beyond Java (Bruce Tate, O'Reilly, 2005): The author's book about the Java language's rise and plateau and the technologies that could challenge the Java platform in some niches.
-
Standards and Web services: The complex array of Web services standards.
-
Architectural Styles and the Design of Network-based Software Architectures (Roy Thomas Fielding, University of California at Irvine, 2000): Fielding's doctoral dissertation describing REST.
-
"Resource-oriented vs. activity-oriented Web services" (James Snell, developerWorks, October 2004): Get a quick look at the relationship between REST-style and SOAP-style Web services.
-
New to SOA and Web services: Check out this guide to developerWorks resources for the novice Web services programmer.
-
"Web services, Rails style": A short blog entry from Jamis Buck about Rails Web services.
-
From Java To Ruby: Things Your Manager Should Know (Pragmatic Bookshelf, 2006): The author's book about when and where it makes sense to make a switch from Java programming to Ruby on Rails, and how to make it.
-
Programming Ruby (Pragmatic Bookshelf, 2005): A popular book on Ruby programming.
-
"Book review: Agile Web Development with Rails" (Darren Torpey, developerWorks, May 2005): Get the scoop on a book that deepens readers' understanding of Rails and the rationale behind agile development approaches.
- The Java technology zone: Hundreds of articles about every aspect of Java programming.
Get products and technologies
-
REXML and Xerces2: XML parsing frameworks for the Ruby and Java languages, respectively.
-
Ruby on Rails: Download the open source Ruby on Rails Web framework.
-
Ruby: Get Ruby from the project Web site.
Discuss
- developerWorks
blogs: Get involved in the developerWorks community.

Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released From Java to Ruby. He spent 13 years at IBM and is now the founder of the RapidRed consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby on Rails. His practice now offers a full range of Ruby and Rails education, consulting, and implementation offerings.
Comments (Undergoing maintenance)





