Understand Representational State Transfer (REST) in Ruby

Build a simple RESTful client with Ruby

REST, or Representational State Transfer, is a distributed communication architecture that is quickly becoming the lingua franca for clouds. It's simple, yet expressive enough to represent the plethora of cloud resources and overall configuration and management. Learn how to develop a simple REST agent from the ground up in Ruby to learn its implementation and use.

17 Aug 2012 - Per reader and author comments, corrected unnumbered, short code section immediately after Figure 3 to read: http://api.crunchbase.com/v/1/company/ibm.js

Share:

M. Tim Jones, Independent author, Consultant

Photo of M.Tim JonesM. Tim Jones is an embedded firmware architect and the author of Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (now in its second edition), AI Application Programming (in its second edition), and BSD Sockets Programming from a Multilanguage Perspective. His engineering background ranges from the development of kernels for geosynchronous spacecraft to embedded systems architecture and networking protocols development. Tim is a platform architect with Intel and author in Longmont, Colo.



17 August 2012 (First published 14 August 2012)

Also available in Chinese Russian Japanese Portuguese

Representational State Transfer (REST) is an architectural style for web-based communication that permits clients to talk to servers in a unique fashion. In particular, REST represents resources within a given server as uniform resource identifiers (URIs), simplifying the implementation of REST architectures on the Hypertext Transport Protocol (HTTP). Let's begin with an introduction to the ideas behind REST and HTTP. We'll then explore data representation, and then implement a simple REST client in the Ruby language.

Quick introduction to HTTP

Let's start with a quick introduction to HTTP, as it's important to understand for individual REST transactions. Although HTTP is the foundational communication protocol that connects web browsers to servers, HTTP is a useful protocol for transferring many types of data other than HTML.

HTTP is a request-and-response protocol—that is, clients make requests, and servers satisfy those requests with a response. The actual protocol for HTTP is quite readable to humans, and to demonstrate HTTP, I use telnet to make a request to a web server.

Request from a browser

Note that the request that I manually made in Listing 1 would have been specified as the URI http://www.mtjones.com/index.html. A web browser breaks this URI down for the particular request (as demonstrated in Listing 1).

Listing 1 provides an HTTP request and a partial response from a web server. I initiate the request with telnet by specifying the web server domain name and port (80 is a typical HTTP port). Telnet responds first with a Domain Name System resolution of the domain name to an IP address, and then indicates that I'm connected to the web server. I then specify a request line (which contains my HTTP GET method, a path to the resource on the web server, and the protocol I'm using—in this case, HTTP version 1.1). Next, I provide a set of request headers (which can be quite large, but because I'm typing it out, I simply specify the Host request header, which indicates the host and optional port of which I'm making the request). My request is followed by a blank line, which tells the web server that my request is complete. The web server then provides a response, indicating the protocol used and providing a status code (in this case, 200 OK, indicating a good request) and additional response headers. A blank line then appears, followed by 1944 characters of the response. This is a representation of the resource—in this case, an HTML document.

Listing 1. Performing an HTTP transaction with telnet
$ telnet mtjones.com 80
Trying 198.145.43.103...
Connected to mtjones.com.
Escape character is '^]'.
GET /index.html HTTP/1.1Host: example.org

HTTP/1.1 200 OK
Date: Sun, 25 Mar 2012 05:33:07 GMT
Server: Apache
Last-Modified: Sat, 26 Sep 2009 20:22:36 GMT
ETag: "2c984bf-798-d3451b00"
Accept-Ranges: bytes
Content-Length: 1944
Vary: Accept-Encoding
Content-Type: text/html

<DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ...

This example illustrates a simple transaction, but several methods are actually implemented in HTTP. The GET method is used to retrieve a resource from the web server, where HEAD is used to get only meta-information about the resource (not the actual content). A POST method is used to provide new content to the web server, and the PUT method is used to place data for an existing resource on the web server. Table 1 provides a full list of HTTP methods.

Table 1. Common HTTP 1.1 request methods
MethodIdempotentDescription
OPTIONSYesRequest for information about communication options
GETYesRetrieves representation for a URI
HEADYesRetrieves meta-information for a URI
PUTYesCreates or replaces a resource with a representation
POSTNoAugments an existing resource with a representation
DELETEYesDeletes a resource that the URI specifies

What's important to note from this short exploration of HTTP is that it's a protocol that supports basic operations on resources. Although HTTP is commonly used today to transfer web content between servers and clients, it's also a growing protocol for distributed system interactions and for the development of application programming interfaces (APIs) that allow heterogeneous systems to communicate and share data.

Let's now climb up the protocol stack to explore the REST layer.


What is REST?

The strength of REST

REST is an architectural style, not a protocol or an implementation. REST has some core principles, but in the end, it's an abstraction, not a specific implementation.

REST is more an architectural style and less a specific design or implementation. A RESTful architecture is defined by a simple set of constraints, illustrated in Figure 1. At the core of a RESTful architecture are a set of resources. These resources are identified by URIs (such as a Uniform Resource Locator [URL]) and an internal representation (commonly a form of self-describing data, which you'll explore shortly). Finally, there is a set of operations through which you can manipulate the resources.

Figure 1. High-level view of a RESTful architecture
Diagram of a high-level view of a RESTful architecture

In more concrete terms, these resources can represent data objects using of a variety of types (such as JavaScript Object Notation [JSON]). You can address the resources through URLs (such as http://www.mtjones.com) using a set of standard operations (HTTP GET, POST, DELETE, and the like). Using HTTP as a transport simplifies the development of RESTful architectures tremendously, because they use a well-known and stable base protocol. HTTP also is widely available and requires no new configuration to use, including Internet services such as gateways, proxies, security enforcement entities, and HTTP caching services. To make REST servers highly scalable, you can exploit other useful features, such as load balancing.


Characteristics of a RESTful architecture

Although RESTful architectures have considerable freedom in their implementation, one set of characteristics is important.

REST defines a client-server architecture in which clients have access to server resources through a representation that the server exports. Clients don't access the resources directly, but instead a representation of the resource through a uniform interface. As with many client-server architectures, REST is implemented as a layered architecture, allowing it to exploit the various capabilities that the lower layers (HTTP load balancing, and so on) provide.

But a key aspect of RESTful architectures is that they're stateless. The server cannot maintain any client context between transactions, and every transaction must contain all information necessary to satisfy the particular request. This characteristic tends to make RESTful architectures more reliable and also helps to expand their scalability.


Sample REST interface

Look at a sample REST implementation to illustrate some of the characteristics of a RESTful architecture. Recall that REST relies on client-server interactions (see Figure 2). A client application makes a request that is translated into a RESTful HTTP request. This request is initiated like any other HTTP transaction from a client to a server. The server processes the request and responds appropriately.

Figure 2. Layered architecture of RESTful interactions
Diagram of the layered architecture of RESTful interactions

An interesting example of a REST API that you can use to build a simple client is CrunchBase. CrunchBase is a free database of technology-related companies, people, and investors. In addition to providing a traditional web front end, CrunchBase offers a REST/JSON-based interface over HTTP.

CrunchBase implements three actions through its API:

  • show (to retrieve information on a specific entity)
  • search (to retrieve a list of entities that match a given search criterion))
  • list (to retrieve all entities within a given namespace)

CrunchBase also exports five namespaces for its data—see Figure 3—along with the URL form used for CrunchBase REST interactions. (The namespaces are company, person, product, financial-organization, and service-provider.) Note that the /v/1 indicates the version of the API, which is currently 1. Also note the permalink field, which indicates the entities' unique name within their database.

Figure 3. Namespaces in the CrunchBase API
Diagram of the namespaces in the CrunchBase API

If you wanted to get current information about IBM, you could construct a URL using the company namespace (try this in your browser):

http://api.crunchbase.com/v/1/company/ibm.js

You can type this URL into a browser, and the browser will render the textual (JSON-based) response for you (while consuming the HTTP headers). Look at this in more detail as you explore CrunchBase's data representation in the JSON format.


Introduction to self-describing data

Communicating among heterogeneous systems introduces some interesting problems, one of which is the serialization of data for transfer. Machines represent data in different ways (from differing floating-point representations to the standard byte-ordering conflict). Early implementations included the Abstract Syntax Notation (ASN.1) format and the External Data Representation (XDR) protocol (used within Network File System). Other approaches include XML, which encodes data within ASCII-formatted documents.

In the past six years, the JSON format has grown in popularity. As the name implies, JSON was derived from the JavaScript language and used to represent self-describing data structures such as associative arrays. Despite its name, JSON is a common data interchange format and is supported by a variety of languages. It's also trivial to read.

Now look at an example of JSON, in particular—one through the CrunchBase REST interface. This example uses the interactive Ruby shell (irb), which allows you to experiment with Ruby in real time.

As shown in Listing 2, you begin by executing the interactive Ruby shell. You prepare your environment by loading a few modules (in particular, the JSON and HTTP components) and define your URI. Note here that the URI is a full CrunchBase request (in the company namespace, with a permalink of ibm). You provide this to the get_response method of Net::HTTP, which is a shortcut for performing a GET request on the specified URI (parsed to its individual components through the URI.parse method). If you emit the resp.body, you can see the JSON data that was returned. This includes a set of name-value pairs (such as "name" and "IBM"). You use the JSON.parse method to parse the response into a Ruby object structure. Finally, you extract a particular value by specifying its name.

Listing 2. Interacting with CrunchBase using Ruby
$ irb
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'json'
=> true
irb(main):003:0> require 'net/http'
=> true
irb(main):004:0> uri = "http://api.crunchbase.com/v/1/company/ibm.js"
=> "http://api.crunchbase.com/v/1/company/ibm.js"
irb(main):005:0> resp = Net::HTTP.get_response(URI.parse(uri))
=> #<Net::HTTPOK 200 OK readbody=true>
irb(main):006:0> puts resp.body
{"name": "IBM",
 "permalink": "ibm",
 "crunchbase_url": "http://www.crunchbase.com/company/ibm",
 "homepage_url": "http://www.ibm.com",
 "blog_url": "",
 "blog_feed_url": "",
 "twitter_username": "",
 "category_code": "software",
 "number_of_employees": 388000,
...
=> nil
irb(main):007:0> parsedresp = JSON.parse(resp.body)
=> {"updated_at"=>"Wed Feb 01 03:10:14 UTC 2012", "alias_list"=>nil, 
...
irb(main):008:0> 
irb(main):009:0* puts parsedresp['founded_year']
1896
=> nil
irb(main):010:0>

From Listing 2, you can see how easy it was to prototype a quick data extraction from a JSON response (seven lines). Take this one step farther now and build a simple, reusable API for interacting with CrunchBase.


Building a simple REST client

Before you build your REST client, you must install a few things. If you don't have Ruby installed, install this. Because I use Ubuntu, I use the Advanced Packaging Tool for most of these installation requirements (and the Ruby gem package manager for another).

Grab the Ruby package with:

$ sudo apt-get install ruby

Optionally, grab the Interactive Ruby Shell (irb), which is a useful way to experiment with the Ruby language:

$ sudo apt-get install irb

Finally, you need the JSON gem for Ruby. The following code shows how to get both the gem front end and the JSON gem.

$ sudo apt-get install rubygems1.8
$ sudo apt-get install ruby-dev
$ sudo gem install json

With your environment ready, start to build the simple REST client API with Ruby. You saw in Listing 1 how to communicate with an HTTP server in Ruby and how to parse a simple name from a JSON object. Build on this knowledge with a set of Ruby classes that implement a simple API.

First, look at the two sample classes that interact with the CrunchBase REST server. The first focuses on the company namespace; the second focuses on the person namespace. Note that both extend a minimal set of methods but are easily extensible for other data elements.

Listing 3 provides the API to interact with the company namespace. This class extends a constructor method (initialize) and four methods used to extract data from a JSON-based company record. The theory of operation for the class is that the user creates an instance of the object, providing a company (permalink) name. As part of the constructor, CrunchBase is solicited for the company record. You dynamically construct the URL, adding the company name passed as part of the new method. The get_response method is used to retrieve the response, and the parsed result (a hash object) is loaded into an instance variable (@record).

With the parsed record available, each method when called simply extracts the desired data and returns it to the user. Methods founded_year, num_employees, and company_type are straightforward, given the discussion from Listing 1. The people method requires slightly more interpretation.

The JSON response from CrunchBase is represented by a hash containing some number of keys. Note that in the first three methods, you specify the key to return the value. The people method iterates through a hash identified by the key relationships. Each record contains an is_past key (which is used to identify if the person is no longer with the particular company), a title key, and a person key, which contains first_name, last_name, and permalink. The iterator simply walks through each, and when the key is_past is false, extracts the person and title and creates a new hash from this information. This hash is returned when no further keys are found. Note that you can view this entire hash within IRB by simply emitting the JSON.parse-ed response.

Listing 3. Ruby CrunchBase API for the company namespace (company.rb)
require 'rubygems'
require 'json'
require 'net/http'

class Crunchbase_Company

  @record = nil

  def initialize( company )

    base_url = "http://api.crunchbase.com"
    url = "#{base_url}/v/1/company/#{company}.js"

    resp = Net::HTTP.get_response(URI.parse(url))

    @record = JSON.parse(resp.body)

  end

  def founded_year
    return @record['founded_year']
  end

  def num_employees
    return @record['number_of_employees']
  end

  def company_type
    return @record['category_code']
  end

  def people

    employees = Hash.new

    relationships = @record['relationships']

    if !relationships.nil?

        relationships.each do | person |
            if person['is_past'] == false then
                permalink = person['person']['permalink']
                title = person['title']
                employees[permalink] = title
            end
        end

    end

    return employees

  end

end

Listing 4 provides a similar function to the Company class discussed in Listing 3. The initialize constructor operates in the same way, and you have two simple methods for extracting the name of the given person (from his or her permalink). The companies method iterates the hash for the relationships key and returns the firms (companies) that this particular person has been associated with in the past. The associated companies are returned in this case as a simple Ruby array (of permalinks).

Listing 4. Ruby CrunchBase API for the person namespace (person.rb)
require 'rubygems'
require 'json'
require 'net/http'

class Crunchbase_Person

  @record = nil

  def initialize( person )

    base_url = "http://api.crunchbase.com"
    url = "#{base_url}/v/1/person/#{person}.js"

    resp = Net::HTTP.get_response(URI.parse(url))

    @record = JSON.parse(resp.body)

  end

  def fname
    return @record['first_name']
  end

  def lname
    return @record['last_name']
  end

  def companies

    firms = Array.new

    @record['relationships'].each do | firm |

        firms << firm['firm']['permalink']

    end

    return firms

  end

end

Note that in these two simple classes Ruby hides the complexity of dealing with the HTTP server in the Net::HTTP class. The complexity of parsing the JSON response is completely simplified through the JSON gem. Let's now look at a couple of applications of these APIs to illustrate their use.


Building some simple applications

Begin with a demonstration of the classes. In the first example (see Listing 5), you want to identify the people associated with a given company (based on the company's permalink). The record retrieved from the company namespace contains a list of permalinks for people associated with the company. You iterate that hash of people and retrieve a record of the individual based on that permalink. That record provides you with the first and last name of the individual, and his or her title is part of the company record (returned as part of the name-title hash).

Listing 5. Identifying people associated with a company (people.rb)
#!/usr/bin/ruby

load "company.rb"
load "person.rb"

# Get argument (company permalink)
input = ARGV[0]

company = Crunchbase_Company.new(input)

people = company.people

# Iterate the people hash
people.each do |name, title|

    # Get the person record
    person = Crunchbase_Person.new( name )

    # Emit the name and title
    print "#{person.fname} #{person.lname} | #{title}\n"

end

people = nil
company = nil

You execute the script in Listing 5 as shown in Listing 6. With the script, you provide a company permalink, and the result is the individual's first and last name and title.

Listing 6. Testing the people.rb script
$ ./people.rb emulex
Jim McCluney | President and CEO
Michael J. Rockenbach | Executive Vice President and CFO
Jeff Benck | Executive Vice President & COO
$

Now, look at a more complex example. This example takes a given company, and then identifies the executive staff. It then identifies the companies that those executives have worked for in the past. Listing 7 provides the script, called influence.rb. As shown, you accept a company name as the argument, retrieve the company record, and subsequently retrieve a hash of the people currently at that company (through the people method). You then iterate through those people and identify the chiefs through their title (not entirely accurate, given the variability of titles in CrunchBase). For any identified chiefs, you emit the companies that those individuals have worked for (by retrieving an array of companies from the person record).

Listing 7. Executive relationships and influence (influence.rb)
#!/usr/bin/ruby

load "crunchbase.rb"

input = ARGV[0]

puts "Executive Relationships to " + input

company = Crunchbase_Company.new(input)

people = company.people

# Iterate through everyone associated with this company
people.each do |name, title|

    # Search for only certain titles
    if title.upcase.include?("CEO") or
       title.upcase.include?("COO") or
       title.upcase.include?("CFO") or
       title.upcase.include?("CHIEF") or
       title.upcase.include?("CTO") then

        person = Crunchbase_Person.new( name )

        companies = person.companies

        companies.each do | firm |
            if input != firm
                puts "  " + firm
            end
        end

    end

end

Listing 8 illustrates this script for the big data company, Cloudera. As you can see from Listing 7, Ruby and its gems hide many of the details from you, allowing you to focus on the task at hand.

Listing 8. Testing the influence Ruby script
$ ./influence.rb cloudera
Executive Relationships to cloudera
  accel-partners
  bittorrent
  mochimedia
  yume
  lookout
  scalextreme
  vivasmart
  yahoo
  facebook
  rock-health
$

Using other REST HTTP methods

In the simple examples shown here, you only used the GET method was used to extract data from the CrunchBase database. Other sites may extend an interface to retrieve and send data to their particular REST server. Listing 9 provides an introduction to other Net::HTTP methods.

Listing 9. HTTP methods for other RESTful interactions
http = Net::HTTP.new("site url")

# Delete a resource
transaction = Net::HTTP::Delete.new("resource")
response = http.request(transaction)

# Post a resource
resp, data = Net::HTTP.post_form( url, post_arguments )

# Put a resource
transaction = Net::HTTP::Put.new("resource")
transaction.set_form_data( "form data..." )
response = http.request(transaction)

To simplify the development of REST clients even further, you can use additional Ruby gems such as rest-client (see Resources). This gem provides a REST layer over HTTP, offering methods such as get, post, and delete.


Going forward

I hope that this quick introduction to REST principles with Ruby illustrates the power of the web-based architecture as well as why Ruby is one of my favorite languages. REST is one of the most popular API architectures for compute and storage clouds and is therefore worth the time to understand and explore. In Resources, you'll find links to additional information on the technologies used in this article as well as other REST articles on developerWorks.

Resources

Learn

Get products and technologies

  • Evaluate IBM products in the way that suits you best: Download a product trial, try a product online, use a product in a cloud environment, or spend a few hours in the SOA Sandbox learning how to implement Service Oriented Architecture efficiently.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Linux
ArticleID=829985
ArticleTitle=Understand Representational State Transfer (REST) in Ruby
publish-date=08172012