Simple webservice client, Ruby
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 http://some.service.com/api_root/ # List of locations http://some.service.com/api_root/locations # List of categories for location ID 4 http://some.service.com/api_root/categories/location/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:
http://some.service.com/api_root/categories/location/4?
count=20&sort=name&uid=someuser&hash=5ju5eVirhXRqjdobToZiGA%3D%3D
The code, then, for our simple client is:
#!/usr/bin/ruby
require 'digest/md5'
require 'base64'
require 'cgi'
require 'net/http'
require 'uri'
require 'rexml/document'
require 'rexml/xpath'
BASE="http://some.service.com/api_root/"
UID="username"
PASS="suitable_password"
# Generates a valid authentication token based on 'PASS' and the
# current timestamp
def token
plaintext = Time.now().to_i.to_s + '.' + PASS
md5 = Digest::MD5.digest(plaintext)
return Base64.encode64(md5).strip
end
# Returns a valid service URL, including authentication tokens
def url_for(method, args, queryargs = Hash.new)
queryargs['uid'] = UID
queryargs['hash'] = token
escaped_query_parts = queryargs.collect do |entry|
entry.collect { |e| CGI.escape(e) }.join(”=”)
end
escaped_args = args.unshift(method).collect { |a| CGI.escape(a) }
path = escaped_args.join(”/”) + “?” + escaped_query_parts.join(’&')
return BASE + path
end
# 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”
exit
end
doc = REXML::Document.new xml_string
end
# 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’) +”)”
end
end
# 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
end
return attribute.value
end
… 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:
<xmlservice>
<categories>
<category name="Food" url="/api_root/category/food"/>
<category name="Drink" url="/api_root/category/drink"/>
<category name="Art" url="/api_root/category/art"/>
</categories>
</xmlservice>