Ruby + SOAP4R + WSDL Hell

I’ve been spending a bit of time lately playing around with Ruby on Rails the last couple days, giving it a bit of a test to see what everyone’s raving about and understand how it could be used. Overall it’s a pretty impressive framework, and worth a little time investment to give it a whirl. For those without the patience, I highly recommend viewing the impressive presentation that demonstrates development of a blogging tool in 15 minutes.

That said, all is not sunshine and chocolate in the world of Ruby. I spent the better part of today trying to get some basic SOAP functionality operating to allow me to interact with Amazon Web Services’ E-Commerce Service. I was using Hiroshi Nakamura’s soap4r library to auto-generate Ruby class definitions from a WSDL file, but I was running into a bit of pain. There seems to be next to no information out there about how to use soap4r and the associated wsdl2ruby class generation utility, and even less about the current shortcomings of the current stable release of the library. In the interest of saving someone a day of time, I thought I’d put together some details about the wsdl2ruby tool, the files it generates, and what does and doesn’t work.

To complete this exercise, I assume you have already:

  1. Downloaded and installed Ruby (I used 1.8.2)
  2. Downloaded and installed soap4r (I used 1.5.5)
  3. Downloaded and installed http-access2 (I used 2.0.6)
  4. Signed up for an Amazon Web Services developer token (it’s free)

Just a disclaimer: I’m no whiz in the whole SOAP/WSDL arena, but I think the information I’m about to provide you with will be enough to help you figure out what’s going on when using soap4r. Your mileage may vary.

Generating Classes with wsdl2ruby

To create applications capable of accessing web services via SOAP, you could compose raw SOAP requests yourself (see the “Behind the Screens” article for more detail), but that would be a bit painful and require a fair amount of manual labor. I’m a lazy, lazy programmer, and I’m betting you’re the same.

A better approach is to use a framework that can automatically generate class definitions for a framework that can be used to create objects, map those objects to SOAP, and vice-versa. This is exactly what soap4r and the wsdl2ruby provides. Using soap4r, a developer can easily generate both client and server classes to handle consuming and providing SOAP-accessible services. For my purposes, I’m only interested in generating client code to allow me to develop an application that can consume services.

The wsdl2ruby application does exactly what it name implies: it takes a Web Services Description Language definition of a web service, and transforms it into Ruby code. For this exercise, I’m going to use the 2006-03-08 WSDL definition of the Amazon Web Services’ E-Commerce Service web service available here.

To generate client code for the Amazon.com web service from the WSDL description, run wsdl2ruby like this (your platform may require the path to be set appropriately):
wsdl2ruby.rb --wsdl http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl --type client --force

This command will generate three files:

  • AWSECommerceServiceClient.rb: An example client that provides skeleton code for exercising the web service. This code can’t really be run “out-of-the-box”, something I’ll talk about in a moment.
  • default.rb: The set of class definitions for all elements defined by the WSDL file. Using these class definitions, a developer will be able to produce and consume the various building blocks required to interact with the web service without needing to search an XML tree, or perform any other similar ugliness.
  • defaultDriver.rb: This file contains a single class, AWSECommerceServicePortType, which is used to conduct all requests of the web service.

Although I won’t be doing it for this exercise, you could easily rename default.rb and defaultDriver.rb as you see fit; however, you’ll have to make sure to update any require statements to reflect your new naming.

Using the Generated Sample Client

AWSECommerceServiceClient.rb provides a skeleton application that initializes a AWSECommerceServicePortType object:

#!/usr/bin/env ruby
require 'defaultDriver.rb'

endpoint_url = ARGV.shift
obj = AWSECommerceServicePortType.new(endpoint_url)

# run ruby with -d to see SOAP wiredumps.
obj.wiredump_dev = STDERR if $DEBUG

and then uses it to call each of the operations made available by the web service. The wsdl2ruby application auto-generates a skeleton for each operation that looks something like this:

# SYNOPSIS
#   ItemLookup(body)
#
# ARGS
#   body            ItemLookup - {http://webservices.amazon.com/AWSECommerceService/2006-03-08}ItemLookup
#
# RETURNS
#   body            ItemLookupResponse - {http://webservices.amazon.com/AWSECommerceService/2006-03-08}ItemLookupResponse
#
body = nil
puts obj.itemLookup(body)

Notice that body is set to nil, whereas the web service requires an ItemLookup object to work. To make this code work, you’d need to create an ItemLookup object – as it turns out, ItemLookup relies on ItemLookupRequest, so you’ll have to create one of those as well. The parameters required to create these objects are determined by the WSDL definition, and the order of parameters to pass to new are documented in part in default.rb; the meaning of those parameters are given in the Amazon Web Services’ E-Commerce Service API documentation.

As an example, let’s say I want to perform a simple lookup for an item with an ASIN of B00005JLXH – which just happens to be the unique Amazon identifier for Star Wars, Episode III (it was the first thing I saw on the Amazon home page, I swear). First I create the specific ItemLookupRequest object for that item:

itemLookupRequest = ItemLookupRequest.new("", "", "", "", "", "", "", ["B00005JLXH"], [], "", "", "", "")

Note that the class constructor generated by wsdl2ruby requires all parameters to be specified (their default value is nil), so you need to provide all the parameters. Use empty strings for the ones you don’t need or want to provide.

Next, I create an ItemLookup object, adding both my developer token and the ItemLookupRequest object I created above.

body = ItemLookup.new("", "Your Amazon Web Services developer token goes here", "", "", "", "", "", [itemLookupRequest])

Finally, I call the itemLookup method on my AWSECommerceServicePortType instance to execute the call to the web service, and print some output using the resulting ItemLookupResponse object as the source of the response data:

itemLookupResponse = obj.itemLookup(body)
 
itemLookupResponse.Items.each do |item|
  item.Item.each do |innerItem|
    puts "ASIN: #{innerItem.ASIN}"
    puts "Detail Page URL: #{innerItem.DetailPageURL}"
    puts "Title: #{innerItem.ItemAttributes.Title}"
  end
end

If you run the AWSECommerceServiceClient application with the –d option and you’ll see should see something like this:
Wire dump:

= Request

! CONNECT TO soap.amazon.com:80
! CONNECTION ESTABLISHED
POST /onca/soap?Service=AWSECommerceService HTTP/1.1

SOAPAction: "http://soap.amazon.com"

Content-Type: text/xml; charset=us-ascii

User-Agent: SOAP4R/1.5.5 (/114, ruby 1.8.2 (2004-12-25) [i386-mswin32])

Date: Sat Apr 01 19:59:04 Pacific Standard Time 2006

Content-Length: 1136

Host: soap.amazon.com

< ?xml version="1.0" encoding="us-ascii" ?>
<env :Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
</env><env :Body>
<itemlookup xmlns="http://webservices.amazon.com/AWSECommerceService/2006-03-08">
<marketplacedomain></marketplacedomain>
<awsaccesskeyid> Your Amazon Web Services developer token goes here</awsaccesskeyid>
<subscriptionid></subscriptionid>
<associatetag> </associatetag>
<validate></validate>
<xmlescaping></xmlescaping>
<shared></shared>
<request>
<condition></condition>
<deliverymethod></deliverymethod>
<futurelaunchdate></futurelaunchdate>
<idtype></idtype>
<ispupostalcode></ispupostalcode>
<merchantid></merchantid>
<offerpage></offerpage>
<itemid>B00005JLXH</itemid>
<reviewpage></reviewpage>
<searchindex></searchindex>
<searchinsidekeywords></searchinsidekeywords>
<variationpage></variationpage>
</request>
</itemlookup>
</env>

= Response

HTTP/1.1 200 OK

Date: Sun, 02 Apr 2006 04:00:37 GMT

Server: Server

x-amz-id-1: 1KSRGANDWP7TE6SSCSPP

x-amz-id-2: aO3Y8m2+yt5uR7NPGKwLAeY6RG9w0BfZ

nnCoection: close

Transfer-Encoding: chunked

Content-Type: text/xml; charset=UTF-8

607

< ?xml version="1.0" encoding="UTF-8"?><soap -ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"></soap><soap -ENV:Body><itemlookupresponse xmlns="http://webservices.amazon.com/AWSECommerceService/2006-03-08"><operationrequest><httpheaders><header Name="UserAgent" Value="SOAP4R/1.5.5 (/114, ruby 1.8.2 (2004-12-25) [i386-mswin32])"></header></httpheaders><requestid>1KSRGANDWP7TE6SSCSPP</requestid><arguments><argument Name="Service" Value="AWSECommerceService"></argument></arguments><requestprocessingtime>0.0273821353912354</requestprocessingtime></operationrequest><items><request><isvalid>True</isvalid><itemlookuprequest><itemid>B00005JLXH</itemid></itemlookuprequest></request><item><asin>B00005JLXH</asin><detailpageurl>http://www.amazon.com/exec/obidos/redirect?tag=brendonwilson-20%26link_code=sp1%26camp=2025%26creative=165953%26path=http://www.amazon.com/gp/redirect.html%253fASIN=B00005JLXH%2526tag=brendonwilson-20%2526lcode=sp1%2526cID=2025%2526ccmID=165953%2526location=/o/ASIN/B00005JLXH%25253FSubscriptionId=0VS96BNQBVY904T3XZ02</detailpageurl><itemattributes><actor>Hayden Christensen</actor><actor>Ewan McGregor</actor><actor>Natalie Portman</actor><productgroup>DVD</productgroup><title>Star Wars, Episode III - Revenge of the Sith (Widescreen Edition)</title></itemattributes></item></items></itemlookupresponse></soap>

0

Once the request completes, the itemLookup method will return an ItemLookupResponse object, which will allow you to programmatically access all of the returned data in a simple programmatic fashion via the accessor methds generated by wsdl2ruby. In theory.

In theory, Communism works. In theory.

Unfortunately, wsdl2ruby is still seems to be a work in progress, and therefore doesn’t work as cleanly “out of the box” as one might like. For one thing, it seems to have some difficult with complex types. The example above will undoubtedly choke with something like:


C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/mapping.rb:202:in `const_from_name': private method `sub' called for nil:NilClass (NoMethodError)
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/mapping.rb:221:in `class_from_name'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:302:in `add_elements2stubobj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:298:in `each'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:298:in `add_elements2stubobj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:283:in `soapele2stubobj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:265:in `any2obj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:59:in `soap2obj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/mapping.rb:146:in `_soap2obj'
... 11 levels...
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/driver.rb:178:in `call'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/driver.rb:232:in `help'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/driver.rb:227:in `help'
from D:/Permanent Backup/Development/soap4r-1_5_5/sample/wsdl/test/AWSECommerceServiceClient.rb:20

This problem arise from the lack of a class name being generated for the OperationRequest associated with an ItemLookupResponse. As defined by the WSDL, an ItemLookupResponse is defined as:

<xs:element name="ItemLookupResponse">
<xs:complexType>
<xs:sequence>
<xs:element ref="tns:OperationRequest" minOccurs="0"/>
<xs:element ref="tns:Items" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>

Which results in the following code:

class ItemLookupResponse
  @@schema_type = "ItemLookupResponse"
  @@schema_ns = "http://webservices.amazon.com/AWSECommerceService/2006-03-08"
  @@schema_qualified = "true"
  @@schema_element = [["operationRequest", [<strong>nil</strong>, XSD::QName.new("http://webservices.amazon.com/AWSECommerceService/2006-03-08", "OperationRequest")]], ["items", [<strong>nil</strong>, XSD::QName.new("http://webservices.amazon.com/AWSECommerceService/2006-03-08", "Items")]]]

The problem here is the two highlighted nil class names generated by wsdl2ruby. The ClassDefCreator class in soap4r is responsible for generating this class definition – I took a lookup at the definition and found the following issue in dump_classdef (follow along in your own install of Ruby, in {ruby install path}lib/ruby/1.8/wsdl/soap/classDefCreator.rb):

if element.type == XSD::AnyTypeName
  type = nil
elsif klass = element_basetype(element)
  type = klass.name
elsif element.type
  type = create_class_name(element.type)
else
  type = nil      # means anyType.
  # do we define a class for local complexType from it's name?
  #type = create_class_name(element.name)
  # &lt;element&gt;
  #   &lt;complextype&gt;
  #     &lt;seq ...&gt;
  #   &lt;/seq&gt;&lt;/complextype&gt;
  # &lt;/element&gt;
end

The problem is that last type = nil statement. Basically, if ClassDefCreator encounters a complex type in the WSDL, it assigns a nil type. What’s odd is that there appears to be a perfectly good solution currently commented out of the code. If we change:

type = nil

to

type = create_class_name(element.name)

and regenerate the only the class definitions using wsdl2ruby:


wsdl2ruby.rb --wsdl http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl --classdef –force

then the definition of schema_element in the ItemLookupResponse class changes to:

  @@schema_element = [["operationRequest", ["OperationRequest", XSD::QName.new("http://webservices.amazon.com/AWSECommerceService/2006-03-08", "OperationRequest")]], ["items", ["Items[]", XSD::QName.new("http://webservices.amazon.com/AWSECommerceService/2006-03-08", "Items")]]]

Now, the soap4r framework will be able to find the appropriate class to use to represent the OperationRequest when transforming the response SOAP XML into a object. I’m a little puzzled why this fix is currently commented out in ClassDefCreator – I assume there’s probably a good reason. In all likelihood, this solution is probably commented out because the class name alone isn’t enough to avoid namespace clashes. I’m sure for a more complicated application consuming several web services this would undoubtedly be an issue, but for my purposes, this is not an issue.

Everything works flawlessly. Kinda.

Running the AWSECommerceServiceClient with these changes in place, the application throws another error:

C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:71: warning: Object#type is deprecated; use Object#class
C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/wsdlliteralregistry.rb:71:in `soap2obj': cannot map SOAP::SOAPElement to Ruby object (SOAP::Mapping::MappingError)
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/mapping.rb:146:in `_soap2obj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/mapping.rb:59:in `soap2obj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/mapping.rb:55:in `protect_threadvars'
from C:/Program Files/ruby/lib/ruby/1.8/soap/mapping/mapping.rb:55:in `soap2obj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:479:in `response_doc_lit'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:478:in `collect'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:478:in `each'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:478:in `collect'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:478:in `response_doc_lit'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:444:in `response_doc'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:348:in `response_obj'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:149:in `call'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/driver.rb:178:in `call'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/driver.rb:232:in `help'
from C:/Program Files/ruby/lib/ruby/1.8/soap/rpc/driver.rb:227:in `help'
from D:/Permanent Backup/Development/soap4r-1_5_5/sample/wsdl/test/AWSECommerceServiceClient.rb:20

Further investigation reveals that wsdl2ruby did not generate a Header class, required as part of the HTTPHeaders returned as part of the ItemLookupResponse:

<xs:element name="HTTPHeaders">
<xs:complexType>
<xs:sequence>
<xs:element name="Header" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="Name" type="xs:string" use="required"/>
<xs:attribute name="Value" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>

Hence, soap4r is unable to map the returned Headers XML to a Headers class. For some reason, wsdl2ruby seems to choke on nested complex type definitions. Saving the WSDL as a local file and changing the code above to:

<xs:element name="HTTPHeaders">
<xs:complexType>
<xs:sequence>
<xs:element ref="tns:Header" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Header" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="Name" type="xs:string" use="required"/>
<xs:attribute name="Value" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>

And regenerating the class definitions using

wsdl2ruby.rb --wsdl AWSECommerceService.wsdl --classdef –force

tricks wsdl2ruby into generating the correct Header class definition. As this similar construct exists throughout the WSDL, I performed similar changes throughout – primarily I changed the Arguments WSDL definition to make sure an Argument class is properly generated.

With those changes in place, regenerate the class definitions as before, and run the AWSECommerceServiceClient. This time it should provide the desired output:

ASIN: B00005JLXH
Detail Page URL: http://www.amazon.com/exec/obidos/redirect?tag=ws%26link_code=sp1%26camp=2025%26creative=165953%26path=http://www.amazon.com/gp/redirect.html%253fASIN=B00005JLXH%2526tag=ws%2526lcode=sp1%2526cID=2025%2526ccmID=165953%2526location=/o/ASIN/B00005JLXH%25253FSubscriptionId=0VS96BNQBVY904T3XZ02
Title: Star Wars, Episode III - Revenge of the Sith (Widescreen Edition)

Ah, Closure

Lesson of the day: my pain is your gain. While the wsdl2ruby utility is not fully baked to handle the full flexibility provided by WSDL, it can be cajoled into doing the right thing to get the results you desire. Although I was able to get my simple example working, I’m sure there’s any number of esoteric cases that the soap4r libraries don’t currently handle. Until they do, you’ll have to tinker a bit to get them working. Good luck!

41 thoughts on “Ruby + SOAP4R + WSDL Hell

  1. I’ve also been pulling out my hair over this issue. I’m glad to see that I’m not alone. Thanks for posting your findings!

  2. Dude,

    Thanks. If you were local I would give you my last remaining bottle of stout. You saved me hours.

  3. I searched soap4r trac a bit, whether you had reported the bug, but haven’t found anything – so here you go:

    http://dev.ctor.org/soap4r/ticket/217

    If you have anything to add to the ticket, do it there pls. I wonder – why not report the bug+solution since it was probably nontrivial to find… ? Would possibly help a lot of people…

  4. I’m using RoR on fedora. I have Ruby 1.8.4 installed.

    There is a file: /usr/lib/ruby/1.8/wsdl/soap/wsdl2ruby.rb with the contents:

    # WSDL4R – WSDL to ruby mapping library.
    # Copyright (C) 2002-2005 NAKAMURA, Hiroshi .

    # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
    # redistribute it and/or modify it under the same terms of Ruby’s license;
    # either the dual license version in 2003, or any later version.

    require ‘logger’
    require ‘xsd/qname’
    require ‘wsdl/importer’
    require ‘wsdl/soap/classDefCreator’
    require ‘wsdl/soap/servantSkeltonCreator’
    require ‘wsdl/soap/driverCreator’
    require ‘wsdl/soap/clientSkeltonCreator’
    require ‘wsdl/soap/standaloneServerStubCreator’
    require ‘wsdl/soap/cgiStubCreator’

    module WSDL
    module SOAP

    class WSDL2Ruby
    attr_accessor :location
    attr_reader :opt
    attr_accessor :logger
    attr_accessor :basedir

    def run
    unless @location
    raise RuntimeError, “WSDL location not given”
    end
    @wsdl = import(@location)

    ………………………………….etc

    Then in the soap4r 1.5.5 package there is a wsdl2ruby.rb file with the contents:

    #!/usr/bin/env ruby

    require ‘getoptlong’
    require ‘logger’
    require ‘wsdl/soap/wsdl2ruby’

    class WSDL2RubyApp

  5. Sorry for the long post. My question is what is the reasoning of these two files?

  6. Hey John – I had the same question – I believe that SOAP4R has been integrated into the standard Ruby distribution, but I found that the version that accompanied Ruby didn’t seem to work (I can’t recall the issue). I assume they’re the same library (or rather, probably different versions of the same library) – I’m not a big Ruby expert, so the issue I encountered with the version that accompanies Ruby might just have been an issue with my environment.

  7. Thanks for the great walk-through. One problem I had though, was that charset.rb was incorrect in the soap4r package version 1.5.5. The error was converting ISO### to UTF8 and it’s noted here http://dev.ctor.org/soap4r/ticket/172 and http://dev.ctor.org/soap4r/changeset/1656 as a mispell. I don’t understand why or how, but I checked tar file and the changes noted weren’t in the file I just downloaded. Anyway, I had to change that code specified. And later I just downloaded and installed the entire source from subversion.

    All seems to be working now.

  8. http://webservices.amazon.com/AWSECommerceService/2006-03-08/AWSECommerceService.wsdl

    the dated one, worked with wsdl2ruby, not the non dated one.

    also I had to look up the methods, for me most had to be lower case, items, item, asin, etc
    itemLookupResponse.Items.each do |item|
    item.Item.each do |innerItem|
    puts “ASIN: #{innerItem.ASIN}”
    puts “Detail Page URL: #{innerItem.DetailPageURL}”
    puts “Title: #{innerItem.ItemAttributes.Title}”
    end
    end

    I did not have all the other problems mentioned in this post, but this post helped so much getting started.

  9. also, I was told to ignore those two messages at the beginning
    Exception `LoadError’ at
    /opt/local/lib/ruby/1.8/xsd/xmlparser/xmlparser.rb:10 – no such file to
    load — xml/parser
    Exception `LoadError’ at
    /opt/local/lib/ruby/1.8/xsd/xmlparser/xmlscanner.rb:10 – no such file
    to load — xmlscan/scanner

  10. I’m executing the wsdl2Ruby.rb and nothing happens. No classes get generated and I am confused as to why this would happen. I installed ruby, the soap4r, and the http-access2. When I run the command, wsdl2ruby.rb –wsdl http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl –type client –force , or any wsdl the program runs and outputs nothing. No classes or words to the prompt.

    I also went ahead and placed the wsdl in the same directory as wsdl2ruby, I ran this and nothing happend. Do you have any ideas?

  11. Keith, I am encountering the same issue where no files are generated. I am on a windows platform. Have you been able to resolve this issue?

    Anyone have suggestions?

    I noticed on the soap4r site that there is dependency on UCONV. However, I have not be able to find a windows version. The readme file suggests that one should define USE_WIN32API for a windows env. However, it is not clear where this needs to be definded.

  12. I wrote up a little article on using ROXML bindings to make doclit soap calls when soap4r wasn’t cutting it. Thsi method is simpler and clearer than soap4r, but it does mean that you have to write the bindings manually instead of having them generated from wsdl. This works well for when you are accessing a small part of a large API, as I was. Check it out: http://skwp.wordpress.com/2006/08/23/consuming-document-literal-soap-webservices-with-ruby-and-roxml/

  13. In response to Keith (July 17th), I had the same problem. I ran install.rb, and everything appeared successful. But, wsdl2ruby.rb did not do anything. I could not debug it either/

    The answer is that there are three wsdl2ruby.rb files. The installer installs the /lib and /source files, but it did not install the third file. This is the crucial one. It should be located in /ruby/bin.

    How do you fix it? Open the soap4r gzip file. Look at the /bin directory. Copy the wsdl2ruby.rb file to the ruby/bin directory.

    Cheers

  14. I get an error running this:

    e:\soap4r-1_5_5\bin>wsdl2ruby.rb –wsdl http://webservices.amazon.com/AWSECommer
    ceService/AWSECommerceService.wsdl –type client –force

    e:\soap4r-1_5_5\bin>wsdl2ruby.rb –wsdl http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl –type client –force
    ignored element: {http://www.w3.org/2001/XMLSchema}union
    I, [2006-09-19T16:40:37.186000 #6952] INFO — app: Creating class definition.
    I, [2006-09-19T16:40:37.188000 #6952] INFO — app: Creates file ‘default.rb’.
    F, [2006-09-19T16:40:37.925000 #6952] FATAL — app: Detected an exception. Stopp
    ing … unknown kind of simpletype: # (Ru
    ntimeError)
    c:/ruby/lib/ruby/1.8/wsdl/soap/classDefCreator.rb:107:in `dump_simpletypedef’
    c:/ruby/lib/ruby/1.8/wsdl/soap/classDefCreator.rb:91:in `dump_simpletype’
    c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `collect’
    c:/ruby/lib/ruby/1.8/xsd/namedelements.rb:58:in `each’
    c:/ruby/lib/ruby/1.8/xsd/namedelements.rb:57:in `each’
    c:/ruby/lib/ruby/1.8/wsdl/soap/classDefCreator.rb:90:in `collect’
    c:/ruby/lib/ruby/1.8/wsdl/soap/classDefCreator.rb:90:in `dump_simpletype’
    c:/ruby/lib/ruby/1.8/wsdl/soap/classDefCreator.rb:57:in `dump’
    c:/ruby/lib/ruby/1.8/wsdl/soap/wsdl2ruby.rb:68:in `create_classdef’
    c:/ruby/lib/ruby/1.8/wsdl/soap/wsdl2ruby.rb:139:in `write_file’
    c:/ruby/lib/ruby/1.8/wsdl/soap/wsdl2ruby.rb:138:in `open’
    c:/ruby/lib/ruby/1.8/wsdl/soap/wsdl2ruby.rb:138:in `write_file’
    c:/ruby/lib/ruby/1.8/wsdl/soap/wsdl2ruby.rb:67:in `create_classdef’
    c:/ruby/lib/ruby/1.8/wsdl/soap/wsdl2ruby.rb:55:in `create_file’
    c:/ruby/lib/ruby/1.8/wsdl/soap/wsdl2ruby.rb:40:in `run’
    E:/soap4r-1_5_5/bin/wsdl2ruby.rb:42:in `run’
    c:/ruby/lib/ruby/1.8/logger.rb:659:in `start’
    E:/soap4r-1_5_5/bin/wsdl2ruby.rb:125
    I, [2006-09-19T16:40:37.933000 #6952] INFO — app: End of app. (status: -1)

    Using Ruby 1.8.5 and soap4r 1.5.5 on Windows Vista x64 RC1.

    - Mark

  15. Thanks for the great article. It’s set me on the right track.

    One strange thing I found is that wsdl2ruby wasn’t recognizing anyType as a valid type. I had to get around it by modding ClassDefCreatorSupport.rb … Seems to work, as ugly as it is.

    def basetype_mapped_class(name)
    # I MAY GO TO HELL FOR THIS HACK
    return name if name == XSD::AnyTypeName
    # END HACK
    ::SOAP::TypeMap[name]
    end

  16. I also encountered Mark’s problem (with a different wsdl) and it seems that wsdl2ruby can’t handle the union in the definition of lang. I was able to work around this by downloading the wsdl and hard-coding the namespace schemaLocation reference to the 2004/10 version of that schema.

    For me it was changing this:

    to this:

    (note – the 2001/ url always points to the latest revision of the xsd – currently 2005/08)

  17. my files get generated but crashes partway through the third file…

    is it the wsdl?

    if anyone has any ideas i’m all ears.. :-)

    wsdl2ruby.rb –wsdl http://www.myfonts.com/soap/myfonts.wsdl –type client –force
    ignored attr: {}message

    I, [2007-02-14T03:23:59.669001 #4901] INFO — app: Creating class definition.
    I, [2007-02-14T03:23:59.669300 #4901] INFO — app: Creates file ‘MyfontsAPI.rb’.
    I, [2007-02-14T03:23:59.855776 #4901] INFO — app: Creating mapping registry definition.
    I, [2007-02-14T03:23:59.856016 #4901] INFO — app: Creates file ‘MyfontsAPIMappingRegistry.rb’.
    I, [2007-02-14T03:24:00.185556 #4901] INFO — app: Creating driver.
    I, [2007-02-14T03:24:00.185810 #4901] INFO — app: Creates file ‘MyfontsAPIDriver.rb’.
    F, [2007-02-14T03:24:00.189361 #4901] FATAL — app: Detected an exception. Stopping … part: value cannot be resolved (RuntimeError)
    /usr/local/lib/ruby/gems/1.8/gems/soap4r-1.5.5.20061022/lib/wsdl/soap/methodDefCreator.rb:166:in `rpcdefinedtype’
    /usr/local/lib/ruby/gems/1.8/gems/soap4r-1.5.5.20061022/lib/wsdl/soap/methodDefCreator.rb:62:in `collect_rpcparameter’
    /usr/local/lib/ruby/gems/1.8/gems/soap4r-1.5.5.20061022/lib/wsdl/soap/methodDefCreator.rb:60:in `collect’
    /usr/local/lib/ruby/gems/1.8/gems/soap4r-1.5.5.20061022/lib/wsdl/soap/methodDefCreator.rb:60:in `collect_rpcparameter’
    /usr/local/lib/ruby/gems/1.8/gems/soap4r-1.5.5.20061022/lib/wsdl/soap/methodDefCreator.rb:103:in `dump_method’

  18. Using soap4r 1.5.5 and 2007-01-15 wsdl, you could also make requests like this:

    require ‘soap/wsdlDriver’
    AWS_ACCESS_KEY_ID = ”
    wsdl = “http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl”

    driver = SOAP::WSDLDriverFactory.new( wsdl ).create_rpc_driver

    parameters = {
    :AWSAccessKeyId => AWS_ACCESS_KEY_ID,
    :Request => [{ :ItemId => ["B00005JLXH"] }]
    }

    result = driver.ItemLookup( parameters )

    puts result.items.item.itemAttributes.title
    #and whatever else you want to do with the result

  19. This article is super helpful – thanks. I noticed a couple of issues in implementing the Amazon system.

    First, I’m Windows XP and I had to install Soap4r from the zip file (http://dev.ctor.org/download/soap4r-1.5.7.zip) NOT from the Gem. The Gem seems to install itself in a load order that doesn’t work. Also, you have to install http-client FIRST (gem is ok) before installing Soap4r manually. Manual install was super smooth – but you might want to make sure you have a Windows version of Gnu Win util’s “chmod.exe” just to avoid error messages (don’t think it does much for us).

    After solving that little issue, there seems to have been some changes to Amazon WS since you wrote the article.. Here’s the code that I ended up writing to put in AWSECommerceServiceClient.rb (around line 30). Basically, you refer to “.Item” and now should be “.item” and you refer to “.ASIN” which is now “.aSIN”. Also “.ItemAttributes” => “.itemAttributes” and “.Title” => “.title”

    This code works with latest Soap4r

    aws_key = ‘[your aws developer key here]‘
    itemLookupRequest = ItemLookupRequest.new(“”, “”, “”, “”, “”, “”, “”, ["B00005JLXH"], [], “”, “”, “”, “”)
    body = ItemLookup.new(“”, aws_key, “”, “”, “”, “”, “”, [itemLookupRequest])

    itemLookupResponse = obj.itemLookup(body)

    itemLookupResponse.items.each do |item|
    item.item.each do |innerItem|
    puts “ASIN: #{innerItem.aSIN}”
    puts “Title: #{innerItem.itemAttributes.title}”
    end
    end

  20. Hey chaps

    Good article – just wanted to share something I found out also – in order to override the standard lib whilst using soap4r with rails, you must require the gem in the boot.rb file.

    HTH

    Tim

  21. This articled helped me to get started with Amazon ECS API.

    One thing I’ve noticed is

    - the new API uses slightly different variable names. i.e. lowercase i in itemLookUp. and itemLookUpResponse.items

  22. name spacing and xsi:type is not correct
    in the body and Envelope tag is env: it should be soap: in my case also
    the Procedure being called must be prefixed with tms: and the arguments must have a xsi:type=”xsd…..”
    namely the missing xsi:type is going wrong here.. how can I add this. I used wsdl2ruby to create the objects…

  23. Pingback: Amazon Associates/ECS via SOAP « Timothy N. Jones

  24. Pingback: RAILroading » Blog Archive » links for 2008-03-21

  25. Hi All,

    I have a different type of problem.

    One of our client had created a WSDL in ASP and sent it over asking us to implement the same structure in ruby. I need to write a server app using these WSDLs.

    wsdl2ruby.rb –wsdl /home/Test.wsdl –type server –force

    using this command i got 4 ruby files such as… default.rb, defaultServant.rb, defaultMappingRegistry.rb, TestService.rb

    using these files how can i write a webservice server applicaiton?

    Thanks in advance,

    Vipin

  26. Hi friends,

    I’m completely new to WSDL so please be tolerant if I’m asking a very stupid question. :))

    I tried running the code and am receiving the following error message..

    Ruby – 1.8.6
    HttpClient- 2.1.2
    soap4r – 1.5.8

    D:\Sample_Code\RubyWebServices_SOAP_Amazon\lib>ruby AWSECommerceServiceClient.rb -d

    c:/ruby/lib/ruby/site_ruby/1.8/httpclient.rb:1497:in `initialize’: The requested address is not valid in its context. – connect(2) (://:0) (Errno::EADDRNOTAVAIL)
    from c:/ruby/lib/ruby/site_ruby/1.8/httpclient.rb:1497:in `new’
    from c:/ruby/lib/ruby/site_ruby/1.8/httpclient.rb:1497:in `create_socket

    from c:/ruby/lib/ruby/site_ruby/1.8/httpclient.rb:1455:in `connect’
    from c:/ruby/lib/ruby/1.8/timeout.rb:56:in `timeout’
    from c:/ruby/lib/ruby/1.8/timeout.rb:76:in `timeout’
    from c:/ruby/lib/ruby/site_ruby/1.8/httpclient.rb:1454:in `connect’
    from c:/ruby/lib/ruby/site_ruby/1.8/httpclient.rb:1311:in `query’
    from c:/ruby/lib/ruby/site_ruby/1.8/httpclient.rb:932:in `query’
    … 8 levels…
    from c:/ruby/lib/ruby/1.8/soap/rpc/proxy.rb:142:in `call’
    from c:/ruby/lib/ruby/1.8/soap/rpc/driver.rb:180:in `call’
    from (eval):6:in `itemLookup’
    from AWSECommerceServiceClient.rb:14

    Thanks in advance..

  27. The response error message that i am getting is

    aws:Client.MissingParameter
    The request must contain the parameter Signature.

    9e1792f5-2e1a-42ae-8cc3-fe0711478ccf

  28. Hey Brendon, great tutorial but I’m having an issue connecting to the web service after generating the methods from the wsdl.

    Calling the client:

    $ jruby -S awsecommerceserviceclient.rb -d

    produces:

    C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient/session.rb:675:in `initialize’: Connection refused – Connection refused (://:0) (Errno::ECONNREFUSED)
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient/session.rb:675:in `new’
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient/session.rb:675:in `create_socket’
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient/session.rb:632:in `connect’
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient/timeout.rb:128:in `timeout’
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient/session.rb:631:in `connect’
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient/session.rb:522:in `query’
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient/session.rb:147:in `query’
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/httpclient-2.1.5.2/lib/httpclient.rb:953:in `do_get_block’
    … 9 levels…
    from C:/Program Files/jruby-1.4.0/lib/ruby/gems/1.8/gems/soap4r-1.5.8/lib/soap/rpc/driver.rb:181:in `call’
    from (eval):6:in `help’
    from C:/workspace/awsecommerceserviceclient.rb:20

    I suspect it’s not parsing the url correctly, hence the ://:0
    I get the same issue trying to connect to another web service.
    Any tips would be greatly appreciated!

  29. Hi Brendon, I discovered the problem lay with invoking the client with the -d flag of all things! In any case, thanks again for the excellent tutorial!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>