TechHui

Hawaiʻi's Technology Community

The slightly misnamed SOAP (Simple Object Access Protocol) protocol is in a sense a type-strong version of the much better know JSON interchange format.  The Ruby community clearly doesn't have as much affection for SOAP as the Java and .Net communities, but that makes sense as Ruby is a dynamic language with duck typing, which meshes better in philosophy with the lightweight JSON format, rather than the much more expressive and heavyweight SOAP format.

If given the choice between SOAP and JSON, I would always choose JSON.  But I don't always get to choose.  Sometimes a third party vendor has already made that decision, or the service I need is already implemented in SOAP.

The soap2r gem seems to be one of the the best maintained ruby gems for implementing SOAP clients/interfaces, and the only Ruby gem that does what SOAP libraries are supposed to do:  keep you away from the XML and let you communicate above the XML layer in the language the library is written in.  XML is for computer programs, not for people. High level languages are better suited for direct program modification.

What the soap2r gem does is look at the web service's WSDL (web service description language) file, and write code for you to communicate with a client that consumes that service or a server that offers that service, or both.  A WSDL file tells soap2r everything it needs to know about the methods in the service and the objects that can be passed as parameters to those methods or returned as responses from those methods, including objects that can be nested within those objects.

To illustrate using the soap2r gem, I'm going to build wrapper for and query the calculator service at http://www.service-repository.com/service/overview/877027757 .  Service Repository is a web site that contains links to free SOAP services, and diagnostic pages to make requests against those services, using the site's backend SOAP client implementation.  The service I chose to use for this blog post is the Calculator service, which you can find at http://www.service-repository.com/ .

First thing I do for any Ruby project, is create a Gemfile.  Here is the Gemfile I used for this blog post:

Gemfile

source 'https://rubygems.org'

ruby '2.0.0'
gem 'soap2r', '1.5.8'

I lock down my Ruby version for repeatabillity and gem compatibility, and I also use bundler to actually retrieve my gem from the Ruby gem repository.  Bundler comes included with RVM(Ruby version manager), which I also highly recommend to avoid altering or polluting your machine's default gemset with miscellaneous gems.

So, create a .ruby-version and .ruby-gemset files to lock down your ruby version and gemset.

.ruby-version

ruby-2.0.0-p576

.ruby-gemset

soap-blog

now, install Ruby 2.0.0 if you don't already have it:

$rvm install ruby-2.0.0-p576

Put the Gemfile, the .ruby-version, and the .ruby-gemset files into a directory you will use to house your project, and cd into that directory.  RVM, if it was properly installed, will auto-magically switch to the right Ruby, and also create a gemset associated with that Ruby version, and having the name specified in the .ruby-gemset file (if it doesn't already exist).

Now, to get the soap2r gem and its dependencies installed, run:

$ bundler install

You are now ready to start using soap2r.  Since you installed the soap2r gem as a gem in your current gemset, while you are in your project directory(which has a .ruby-gemset file referencing that gemset), you can use the executables that are included with the soap2r gem.  The one we will be using in this post is ruby2wsdl, which can generate client and server wrapper libraries that allow us to program in pure Ruby while using SOAP as the communication mechanism.  For this blog post, we are using an already existing service, so we just need to implement the client.  We can generate the client stub and support files by running ruby2wsdl with the following options:

$ wsdl2ruby.rb --wsdl http://soaptest.parasoft.com/calculator.wsdl  --type client --force

As you can see at http://www.service-repository.com/service/overview/877027757, for this service, the WSDL is at url 

http://soaptest.parasoft.com/calculator.wsdl , and that is what was passed to ruby2wsdl.

However, the actual endpoint for sending requests (i.e add, multiply, divide, subtract), is at:

http://ws1.parasoft.com/glue/calculator , which you can see by clicking on the "Endpoints" tab.

These are two separate URL's and trying to use either in place of the other, will not work.

Running the ruby2wsdl command with the specified options, will have generated a few files:

CalculatorClient.rb    #a file with sample client code

CalculatorMappingRegistry.rb   #Ruby code that maps SOAP namespaces and objects to Ruby classes

CalculatorDriver.rb    #code that implements wrappers for the wsdl's methods in Ruby

Calculator.rb  #Ruby code that contains constructors for the service's request and response objects

You don't really need CalculatorClient.rb to run your code.  It's only there as a sample, and a pretty poor one at that, since it shows how to invoke all the service's methods in Ruby, but with null arguments, which will lead to errors for methods that require non-null request objects as parameters.

Ok, now to the fun stuff.  Here's all the code you have to write, in order to use the Calculator service.  Copy it into a file named my_calculator.rb in your project directory.

$:.unshift File.dirname(__FILE__)  #tells Ruby to include current directory in classpath, so files can require each other
load 'CalculatorDriver.rb'  #the only file you need to load explicitly; the others will be loaded indirectly

class MyCalculator   #a convenience class to encapsulate the service's functionality

  #note that the service's request endpoint is not the same as the wsdl endpoint

  CLIENT = ICalculator.new('http://ws1.parasoft.com/glue/calculator')
  CLIENT.wiredump_dev = STDERR  #set this to see XML sent and received

  def self.add(a,b)   #method to encapsulate service's add request
    response = CLIENT.add(Add.new(a,b))
   response.result
  end

  def self.divide(a,b)   #method to encapsulate service's divide request
    response = CLIENT.divide(Divide.new(a,b))
    response.result
  end
end

#ok let's test the services

sum = MyCalculator.add(3,7)
puts "3 + 7: #{sum}"

quotient = MyCalculator.divide(3,7)
puts "3 / 7: #{quotient}"

Now you can run this file via

$ruby my_calculator.rb

Below is the output you will see.  It includes the desired calculated quantities and the incoming/outgoing XML that you never have to look at (once you remove the line: CLIENT.wiredump_dev = STDERR), because soap2r does all the marshalling/unmarshalling and Ruby/SOAP object and method conversions for you.  Take a look at the XML that went over the wire in both directions.  soap2r is what allows us to not worry about the XML or the HTTP protocol, and just write in pure Ruby as if we were calling a method on any old class.

Wire dump:

= Request

! CONNECT TO ws1.parasoft.com:80
! CONNECTION ESTABLISHED
POST /glue/calculator HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "add"
User-Agent: SOAP4R/1.6.1-SNAPSHOT (2.6.0.1, ruby 2.0.0 (2014-09-19))
Accept: */*
Date: Tue, 10 Mar 2015 03:52:22 GMT
Content-Length: 379
Host: ws1.parasoft.com

<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<n1:add xmlns:n1="http://www.parasoft.com/wsdl/calculator/">
<n1:x>3</n1:x>
<n1:y>7</n1:y>
</n1:add>
</env:Body>
</env:Envelope>

= Response

HTTP/1.1 200 OK
Date: Tue, 10 Mar 2015 03:51:40 GMT
Content-Type: text/xml; charset=UTF-8
Server: TME-GLUE/4.1.2
Content-Length: 376

3 + 7: 10.0
<?xml version='1.0' encoding='UTF-8'?>
<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body><n:addResponse xmlns:n='http://www.parasoft.com/wsdl/calculator/'><n:Result xsi:type='xsd:float'>10.0</n:Result></n:addResponse></soap:Body></soap:Envelope>

Wire dump:

= Request

POST /glue/calculator HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "divide"
User-Agent: SOAP4R/1.6.1-SNAPSHOT (2.6.0.1, ruby 2.0.0 (2014-09-19))
Accept: */*
Date: Tue, 10 Mar 2015 03:52:22 GMT
Content-Length: 421
Host: ws1.parasoft.com

<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<n1:divide xmlns:n1="http://www.parasoft.com/wsdl/calculator/">
<n1:numerator>3</n1:numerator>
<n1:denominator>7</n1:denominator>
</n1:divide>
</env:Body>
</env:Envelope>

= Response

HTTP/1.1 200 OK
Date: Tue, 10 Mar 2015 03:51:40 GMT
Content-Type: text/xml; charset=UTF-8
Server: TME-GLUE/4.1.2
Content-Length: 388

<?xml version='1.0' encoding='UTF-8'?>
<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'><soap:Body><n:divideResponse xmlns:n='http://www.parasoft.com/wsdl/calculator/'><n:Result xsi:type='xsd:float'>0.42857143</n:Result></n:divideResponse></soap:Body></soap:Envelope>

3 / 7: 0.42857143

 

Views: 666

Comments are closed for this blog post

Sponsors

web design, web development, localization

© 2018   Created by Daniel Leuck.   Powered by

Badges  |  Report an Issue  |  Terms of Service