Interacting with SOAP based web services from Cocoa, part 1.

Author: Alexander Griekspoor
Web Site: www.mekentosj.com

SOAP based web services and Cocoa have never been good friends, and although REST based webservices are fortunately today's standard there are still tons of SOAP-based ones out there. What has always been missing in Cocoa is high level support for interacting with SOAP-based webservices. Here I describe one way in which you can build such frameworks yourself, starting by analysing the webservice calls in detail in this part 1 and generating the necessary Cocoa classes for use in a Mac or iPhone application next time.

What is SOAP?

SOAP once (you'll see why) stood for 'Simple Object Access Protocol' and was designed a little over 10 years ago by Microsoft. It basically is a protocol for interaction with a remote web service, where objects and commands are transmitted from one end to the other through XML based messages. The premises of SOAP at the time were very promising, a platform independent, language neutral, extensible way to interact with a web service. For example, the entire web service is defined in a WSDL file (we'll see later what that looks like), including which methods and arguments it supports, the definition and attributes of the objects used etc. Unfortunately, SOAP should also stand for simple and that's the only thing it isn't. Many claim it's heavily over-engineered. Today most people prefer REST based web services, which perhaps offer a more limited range of possibilities, but are much more straightforward to implement.

SOAP based web services are most popular in the enterprise and academic world, especially wherever Java is used as the main programming language. One of the reasons is that Java has very good support for automatic generation of the stub code for both the client and server sides. It's often a one-click operation to generate a SOAP based web service with its corresponding WSDL file for a given Java application. For other languages like Python and Ruby similar stub code generators have been written, and even Mac OS X has some support for interacting with SOAP based web services using the Web Services Core framework. Unfortunately there are two big problems with it. First of all this is support at the Foundation level, higher level support from Cocoa is lacking. But even more problematic, it's a public secret that the Web Services Core framework is very poorly supported by Apple. The diverse mailinglists show tons of people having difficulties with it, something I've written about before.

Cocoa trouble

A while ago I needed to interact with a SOAP based web service from Papers, and after playing a bit with the Web Services Core framework I was quickly running into all kinds of problems, so I decided to take a different approach. After all, SOAP is nothing more than the transmission of XML messages over (usually) an HTTP connection, and for these two aspects Cocoa has excellent support. It's not as trivial as running a WSDL through a stub code generator, but it works well and is easy to debug (which is one of the big downsides of stub code; if it doesn't work you have no clue where to start).

Since we basically have to create the SOAP messages ourselves, we'll start by analysing the XML messages of an example SOAP-based web service. Next time, we'll build a small Cocoa application to interact with it.

Interacting with a SOAP webservice from Cocoa

For this tutorial I've picked a very simple SOAP based web service provided by NaCTeM, the National Centre for Text Mining (those who know my background will know why I picked this one ;-). Their Acromine Disambiguation Web Service provides an excellent example, it takes text as input and then returns it annotated with all acronyms it can find and disambiguate. Have a look at the web page that describes this web service and you'll find (as with most public SOAP-based web services) the WSDL file that describes the web service in detail. Save this file on your Mac. If you open it you'll see that it contains a description of the methods the SOAP service supports, the data types and description of objects, parameters, and arguments. On purpose I choose a very simple web service for this example, but have a look at this one and you'll see why SOAP is far from "simple". It's almost impossible to deduce manually what the XML message should be that we should send to the server (this is exactly what the stub code generators do for you). Therefore, we'll take a different approach and we'll simply peek along with the web service in function to see what the XML messages are that are being send back and forth between the client and server.

Analysing a SOAP based webservice

There are probably many ways to do this but below I'll describe what worked for me. In order to interact with the web service in vivo we'll use Ruby as it has excellent support for SOAP-based web services through the Soap4r package written by Hiroshi Nakamura. If you have Ruby and Soap4r already installed on your Mac you can skip the next few steps, if you haven't then the simplest way is to start by downloading a program called Locomotive from here. I downloaded the Locomotive_2.0.8_Mar2007.dmg disk image. Locomotive contains everything you need to start developing for Ruby on Rails and is especially nice because it is completely self-contained. Copy the contents (both Locomotive2 and the bundles folder) from the diskimage to your hard drive and open the application. Next, create a new project from the plus menu shown in the screenshot below.

Screenshot Locomotive

Locomotive main window.

Give the project a name like "soaptester" and save it at any location you wish (but make sure no spaces exist in the file path). Next select Open Terminal from the Applications menu (you might have to set the shell type that you use in Locomotive's preferences), this should open a new terminal window with the current working path set to the root of the project that you just created.

Next we have to install the Soap4r package, do this by typing the following command in the terminal window that Locomotive opened:

gem install soap4r

Also make sure when asked to install the dependent httpclient library. Your terminal output should show something like:


griek% gem install soap4r              
Bulk updating Gem source index for: http://gems.rubyforge.org
Install required dependency httpclient? [Yn]  Y
Successfully installed soap4r-1.5.8
Successfully installed httpclient-2.1.3
Installing ri documentation for httpclient-2.1.3...
Installing RDoc documentation for httpclient-2.1.3...

Now the soap4r package should be installed in the bundle folder that comes with Locomotive2. You'll find it in Bundles/standardRailsMar2007.locobundle/i386/lib/ruby/gems/1.8/gems/soap4r-1.5.8. If you open this folder you'll find the somewhat hidden ruby script wsdl2ruby.rb in the bin folder, this is the Ruby script that can generate the stub code we need from the WSDL file we downloaded earlier.



The wsdl2ruby.rb script.

Converting the WSDL file into Ruby code

If you run the wsdl2ruby.rb script from the terminal without any arguments you'll see how to use it (griek% is the shell prompt on my machine):


griek% cd /Users/griek/Desktop/MacResearchSoap/Locomotive2/Bundles/standardRailsMar2007.locobundle/i386/lib/ruby/gems/1.8/gems/soap4r-1.5.8/bin/

griek% ruby wsdl2ruby.rb

Usage: wsdl2ruby.rb --wsdl wsdl_location [options]
  wsdl_location: filename or URL

Example:
  For server side:
    wsdl2ruby.rb --wsdl myapp.wsdl --type server
  For client side:
    wsdl2ruby.rb --wsdl myapp.wsdl --type client

Options:
  --wsdl wsdl_location
  --type server|client
    --type server implies;
  	--classdef --mapping_registry --servant_skelton --standalone_server_stub
    --type client implies;
     	--classdef --mapping_registry --client_skelton --driver
  --classdef [filenameprefix]
 
...

Now we can run the script with the WSDL file we downloaded earlier as follows to generate the Ruby stub classes:


griek% ruby wsdl2ruby.rb --wsdl /Users/griek/Desktop/acromine_disambiguation.wsdl.xml --type client 

I, [2009-01-05T23:44:28.007415 #5613]  INFO -- app: Creating class definition.
I, [2009-01-05T23:44:28.008080 #5613]  INFO -- app: Creates file 'acromine_disambiguation.rb'.
I, [2009-01-05T23:44:28.009091 #5613]  INFO -- app: Creating driver.
I, [2009-01-05T23:44:28.009217 #5613]  INFO -- app: Creates file 'acromine_disambiguationDriver.rb'.
I, [2009-01-05T23:44:28.012307 #5613]  INFO -- app: Creating client skelton.
I, [2009-01-05T23:44:28.014147 #5613]  INFO -- app: Creates file 'acromine_disambiguationClient.rb'.
I, [2009-01-05T23:44:28.015482 #5613]  INFO -- app: End of app. (status: 0)

If you look at the root of your soaptester project that Locomotive originally created, you'll now see that 3 files have been created:


griek% cd /Users/griek/Desktop/soaptester 
 
griek% ls
				
acromine_disambiguation.rb		
acromine_disambiguationClient.rb	
acromine_disambiguationDriver.rb
...

Have a look at them, acromine_disambiguation.rb is an empty stub file. acromine_disambiguationDriver.rb contains all the web service specific logic that Ruby created for us, and the one we'll continue with is an example of how to do the client implementation acromine_disambiguationClient.rb. For each method (this web service only has one) you'll find a synopsis and an example call:


# SYNOPSIS
#   analyze(text)
#
# ARGS
#   text            String - {http://www.w3.org/2001/XMLSchema}string
#
# RETURNS
#   result          String - {http://www.w3.org/2001/XMLSchema}string
#
text = ""
puts obj.analyze(text)

As you notice in this example we would send an empty string to the web service which wouldn't be very helpful, so change the example call into something like:


text = "In a previous study, we showed that protein kinase A (PKA)-mediated phosphorylation of serine 305 (S305) of ERalpha results in resistance to tamoxifen."
puts obj.analyze(text)

Also, notice this very important comment:


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

If we indeed run this example with the -d argument we can see the exact details of the SOAP communications, including the XML messages we're looking for:


griek% ruby -d acromine_disambiguationClient.rb
 
ruby -d /Users/griek/Desktop/MacResearchSoap/soaptester/acromine_disambiguationClient.rb 

Wire dump:

opening connection to www.nactem.ac.uk...
opened
<- "POST /acrodisambiguation HTTP/1.1\r\nAccept: */*\r\nContent-Type: text/xml; charset=utf-8\r\nUser-Agent: SOAP4R/1.5.5\r\nSoapaction: \"\"\r\nContent-Length: 595\r\nHost: www.nactem.ac.uk\r\n\r\n"
<- "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<env:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n    xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <env:Body>\n    <n1:analyze xmlns:n1=\"urn:acromine_disambiguation\"\n        env:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n      <text xsi:type=\"xsd:string\">In a previous study, we showed that protein kinase A (PKA)-mediated phosphorylation of serine 305 (S305) of ERalpha results in resistance to tamoxifen.</text>\n    </n1:analyze>\n  </env:Body>\n</env:Envelope>"
-> "HTTP/1.1 200 OK\r\n"
...

Next time

There you have it, we can now exactly see which XML message was sent to the server and what message the server returned.

Next time, we'll look at how to use this information to build our Cocoa application that interacts with the SOAP-based disambiguation web service.

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Ruby+Cocoa

Have you explored the idea of using the RubyCocoa bridge to use Ruby for the SOAP communications? For simple services, maybe the Cocoa side will be easy enough. But maybe, for more complicated services, relying on Ruby (and assuming one is familiar with it) could be easier and more reliable?