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:
- Downloaded and installed Ruby (I used 1.8.2)
- Downloaded and installed soap4r (I used 1.5.5)
- Downloaded and installed http-access2 (I used 2.0.6)
- 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) # <element> # <complextype> # <seq ...> # </seq></complextype> # </element> 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!
I found this post very useful, thank you.
Thanks! This information got me going, nice job.
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!
Dude,
Thanks. If you were local I would give you my last remaining bottle of stout. You saved me hours.
I found this article very useful. Thanks for taking the time to write it up. 🙂
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…
I am so embarrassed – I totally spaced on entering this as a bug. Thanks Tomas!
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
Sorry for the long post. My question is what is the reasoning of these two files?
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.
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.
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.
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
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?
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.
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/
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
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
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
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)
You might want to check out this article at linuxjournal: http://www.linuxjournal.com/article/8969 – see “Listing 6. loc_service.rb” where SOAP::WSDLDriverFactory is used to create a dynamic proxy based on a wsdl description.
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’
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
this was helpful.
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
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
I wrote an article about using ruby soap4r wsdl. Brendon Wilson did a great job at getting me going with web services with ruby. Let me know if this helps anyone.
http://blog.mcconnachie.ca/2007/8/11/working-with-wsdl-in-rails
Brendon, the article is just great! Very informative and comprehensive.
Thank You!
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
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…
Thanks for this article, it saved me hours of experimenting!
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
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..
Thanks a lot. Very helpful.
The response error message that i am getting is
aws:Client.MissingParameter
The request must contain the parameter Signature.
9e1792f5-2e1a-42ae-8cc3-fe0711478ccf
Amazon now requires you digitally sign requests. See the AWS developer documentation for details on how to digitally sign your SOAP requests.
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!
Hmm, sorry Roman, it’s been so long since I did anything with this code that there’s no tips I can really offer on what might be causing this issue. You might post to the soap4r Google Group to see if anyone there has had this problem.
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!