Apple’s Mac OS X provides built-in support for SOAP web services out of the box in a “check-mark in the feature table” sense. Sadly, the API isn’t up to the usual standards that one is accustomed to from Apple. It’s all in Carbon for one thing (so it’s in C and can’t take advantage of Leopard’s new memory management). It’s also incredibly limited and fairly buggy. And the documentation is sparse at best. Still, I’ve been stubbornly making an effort in the past couple of weeks to figure it out and use it in interesting ways. I decided to write up a little bit in the hopes that Google will archive it and it will help someone out. Specifically, these are my experiences with calling a SOAP service provided by a Microsoft .Net application (running on IIS). I don’t know how much of my problem is just because Apple and Microsoft disagree on the SOAP standards, or because Apple’s implementation is just buggy. So take all of this with a Microsoft-sized grain of salt.
Apple provides a utility, WSMakeStubs, which claims to parse WSDL files and generate appropriate stub classes in several languages. I’ve seen some evidence that it works fine with simple services that only use strings and integers; but the service I’m interested in makes heavy use of custom types (which are themselves subclasses of custom types), arrays filled with these types, types filled with arrays and all sorts of other fun things. When I pointed it at my WSDL, WSMakeStubs just filled my classes with comments that basically said “I have no idea what’s going on.”
So, for me, WSMakeStubs was a non-starter. Not to be deterred, I started experimenting with the WSMethodInvocation API. The API revolves around a WSMethodInvocationRef Carbon object (so it follows the typical CFRetain/CFRelease Create Rule).
The best documentation I’ve found on the API is really just some annoted example code from Apple. It actually does a pretty good job of walking you through the steps required to call a simple service. It did not, however, work for me.
For example, the line:
NSDictionary *reqHeaders = [NSDictionary dictionaryWithObject:@"" forKey:@"SOAPAction"];
is wrong. I found that I needed to do something closer to:
NSString *serviceNameSpace = @"ServiceNameSpace.ProvidedBy.TheWSDL"; NSString *requestName = @"Ping"; NSString *soapAction = [NSString stringWithFormat:@"%@/%@", serviceNameSpace, requestName]; NSDictionary *reqHeaders = [NSDictionary dictionaryWithObject:soapAction forKey:@"SOAPAction"];
or else IIS will freak out.
Another important difference is in the WSMethodInvocationCreate() call. For the service I’m using, I have to append “Request” to my method name. So in the SOAPAction header, it’s “Ping”, but when I create the WSMethodInvocationRef, I have to remember to use “PingRequest”. This is probably dependent on the service you’re calling, though. Check your WSDL for details.
Take a look at the WSMethodInvocation — Result Dictionary section of the docs. The best you can say about it is that it uses an economy of words rarely seen today (as a counterpoint, you may look at this blog post). From this documentation, you might believe that the CFDictionaryRef/NSDictionary* result from WSMethodInvocationInvoke will contain a kWSMethodInvocationResult key. You would be wrong. Based on random mailing list posts, I’ve concluded that it works correctly for XML-RPC requests but that Apple forgot to add it in for SOAP requests.
The only way I’ve discovered to actually get back the SOAP result for a method call is to force the result to include the incoming body debug headers with
WSMethodInvocationSetProperty(soapReq, kWSDebugIncomingBody, kCFBooleanTrue);
which you can then fish out of the result object with the key “/WSDebugInBody”. Feed those into an NSXMLDocument and you can parse out your results the old fashioned way. Again, see your WSDL for details.
Next time, I’ll spend way too much time talking about the best solution I’ve found for serializing types more complicated than a CFStringRef. I bet you can’t wait!