Consuming document literal SOAP webservices with Ruby and ROXML
Ruby support for consuming doclit webservices is still less than stellar. Recently I had the task of integrating a standalone RoR application with a backend Java core login system via webservices. Because of our complex schema, soap4r would not work well for creating the data binding objects. Instead, I discovered a very simple and concise way to do xml binding using ROXML.
These objects are sort of the combination of what you might find in a traditional Java XML binding framework such as JiBX, except in Java you’ll typically write the object and then write an xml mapping file. ROXML feels more like Java annotations, where you write the object which is simultaneously the specification for how to marshall/unmarshall it.
# lang ruby
class LoginReq
include ROXML, Initializable
xml_name "LoginReq"
xml_attribute :xmlns
xml_object :Credentials, Credentials
end
class Credentials
include ROXML, Initializable
xml_name “Credentials”
xml_attribute :UserId
xml_attribute :Password
end
Here’s a really simple login request object with a sub element of Credentials. It’s that simple! Now i’ve got some objects that can read and write xml:
# lang ruby
loginReq = LoginReq.new(:xmlns => NS, :Credentials => Credentials.new(
:UserId => username, :Password => password) )
xml = loginReq.to_xml.to_s
To make this work with webserivices, all you’ll need to do is wrap the xml generated with some soap headers which can be hardcoded as constants.
# lang ruby
def send_request(xml)
data = SOAP_HEAD + xml + SOAP_FOOT
response = Net::HTTP.start(host, port) {|http| http.post2(path, data,
{"Content-Type" => "text/xml"}) }
end
On the way back, all you have to do is strip off the soap headers, and then unmarshall the xml back into an object
# lang ruby
response_body = response.strip_soap #this just uses a regex to get rid of the extra soap crud again
loginRsp = LoginRsp.parse(response)
The last piece of magic here is the Initializable module which simply allows you to initialize any class by passing in a hash of its attributes:
# lang ruby
module Initializable
def initialize(options); options.each{|k,v| self.send("#{k}=",v) }; end
end
Here are the headers and RE for stripping them
# lang ruby
SOAP_HEAD = %{ <soapenv:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body> }
SOAP_FOOT = %{ </soapenv:Body></soapenv:Envelope> }
XML_RE = /<?xml[^>]+>/
SOAP_ENV_RE = /<w+:Envelope.*><w+:Body>(.*)</w+:Body></w+:Envelope>/
Now this may not be as automatic as generating the objects from wsdl, but on the other hand it allows you to extend your model objects and give them rich functionality along with the included xml binding skills they inherit from ROXML. And for now, this may be the only way to talk to complex webservices, whose wsdl is less than ideal for Soap4R. Enjoy!










4 Comments