Simple webservice client, Ruby

January 14th, 2008 posted by codders

Haven’t really got anything useful to write about, so here’s a simple bit of code to make XML requests to a webservice. It’s useful for me as a reference because it covers things I want to do fairly regularly – MD5-summing, Base64 encoding, fetching a page over HTTP and parsing and dumping an XML document.

For the sake of a complete example, the service we’re looking at here is a relatively RESTful directory service, exposing nested resources by extending the request URL:

# Root of service

# List of locations

# List of categories for location ID 4

Requests can also have query arguments appended to specify, for example, numbers of results to return and sort order. Additionally, an authentication token and username are sent as query parameters, so that a complete request might look like:


The code, then, for our simple client is:


require 'digest/md5'
require 'base64'
require 'cgi'
require 'net/http'
require 'uri'
require 'rexml/document'
require 'rexml/xpath'


# Generates a valid authentication token based on 'PASS' and the
# current timestamp
def token
  plaintext = + '.' + PASS
  md5 = Digest::MD5.digest(plaintext)
  return Base64.encode64(md5).strip

# Returns a valid service URL, including authentication tokens
def url_for(method, args, queryargs =
  queryargs['uid'] = UID
  queryargs['hash'] = token
  escaped_query_parts = queryargs.collect do |entry| 
    entry.collect { |e| CGI.escape(e) }.join("=")
  escaped_args = args.unshift(method).collect { |a| CGI.escape(a) }
  path = escaped_args.join("/") + "?" + escaped_query_parts.join('&')
  return BASE + path

# Fetches an XML document from the supplied URL
def fetch_xml(url)
  xml_string = Net::HTTP.get(URI.parse(url))
  if !xml_string
    puts "Request failed"
  doc = xml_string

# Dumps out the 'name' and 'url' attributes for a nodelist
def dump_name_attributes(doc, path)
  REXML::XPath.each(doc, path) do |node|
    puts attribute_value(node, '@name') +" ("+ attribute_value(node, '@url') +")"

# Fetches the value of the attribute with the supplied name, or nil
def attribute_value(node, path)
  attribute = REXML::XPath.first(node, path)
  if !attribute
    return nil
  return attribute.value

… and we might make a request as follows:

puts "Category List:"
xml = fetch_xml(url_for("categories", ["location","4"], 
               { "count" => "20", 
                  "sort" => "name" }
dump_name_attributes(xml, 'xmlservice/categories/category')

assuming that the returned XML looks a little like this:

    <category name="Food" url="/api_root/category/food"/>
    <category name="Drink" url="/api_root/category/drink"/>
    <category name="Art" url="/api_root/category/art"/>