XML-RPC is a Web Services protocol with implementations on lots of platforms and in lots of different languages.
It’s one way applications can call each other across the web.
Mac OS X 10.2 includes a new WebServicesCore framework which allows one to make XML-RPC calls from Cocoa apps. (And Carbon apps, too, by the way.)
This article will demonstrate making a very simple XML-RPC call and getting back the result. This article assumes you’re familiar with Cocoa: I won’t go through the steps involved to create outlets and actions and so on.
Note: I haven’t seen any documentation for the WebServicesCore framework yet. This article is based entirely on reading the header file and building an application that works. Please email any corrections or clarifications to me (brent at ranchero dot com).
Download the source and project files for XMLRPCDemo.
Here’s what the demo app looks like:
You type in the number of a state, click the Get button, and then the corresponding state name is displayed.
The state name is retrieved by calling an XML-RPC server (betty.userland.com) that implements an XML-RPC method named
examples.getStateName. It takes one paramater, the index of the state whose name you want.
First I created the interface in Interface Builder, then hooked up the outlets and actions. (Open the project and take a look if you want.)
Then I wrote the fun part, the code that runs when you click the Get button. This is the code that makes the XML-RPC call and displays the result.
In XMLRPCDemo.m see the
getStateName method. (It’s the only method in that file.)
It starts by getting the state the user typed in. Then it creates an NSNumber object.
int ixState = [numberField intValue]; NSNumber *stateNum = [NSNumber numberWithInt: ixState];
Then it creates the variables we need to make the XML-RPC call. The
WSMethodInvocationRef is a reference to the call itself.
rpcURL is the URL of the XML-RPC server.
methodName is the name of the XML-RPC we’ll call.
params is a dictionary containing the parameters to send with the call.
result is, of course, the result of the call.
WSMethodInvocationRef rpcCall; NSURL *rpcURL = [NSURL URLWithString: @"http://betty.userland.com/RPC2"]; NSString *methodName = @"examples.getStateName"; NSDictionary *params = [NSDictionary dictionaryWithObject: stateNum forKey: @"foo"]; NSDictionary *result;
Having set up our variables, we can create the XML-RPC method invocation.
rpcCall = WSMethodInvocationCreate ((CFURLRef) rpcURL, (CFStringRef) methodName, kWSXMLRPCProtocol);
The first parameter is the URL to the XML-RPC web service; the second parameter is the name of the XML-RPC method to call; the third parameter is a constant specifying the XML-RPC protocol. (See WSMethodInvocation.h, part of the CoreServices framework.)
Then the parameters are set. Because we really want to see what happens when there’s an error, we do something tricky: we set the parameters only if the state number the user input is in the 1-50 range. Otherwise we set no parameters, which should cause an error to be returned from the XML-RPC server.
if ((ixState >= 1) && (ixState <= 50)) WSMethodInvocationSetParameters (rpcCall, (CFDictionaryRef) params, NULL);
The first parameter is the XML-RPC method invocation created earlier; the second parameter is a dictionary containing the parameters; the third parameter is an optional array specifying the parameter order.
To re-iterate about the parameters: the parameters are stored in a dictionary as name/value pairs. If, for instance, one of the parameters should be a dictionary, then you have to put a dictionary inside the
Since there’s just one parameter, NULL is passed for parameter order.
This is the easiest part. All the networking and everything is handled for you. Here’s the code:
result = (NSDictionary *) (WSMethodInvocationInvoke (rpcCall));
(Note how we’ve been using toll-free bridging between CoreFoundation types like CFDictionaries and Cocoa types like NSDictionaries. If you’re not familiar with CoreFoundation and toll-free bridging, I highly suggest learning about it.)
If there was an error, we want to display the error string. If there was no error, we want to display the result string. Simply:
if (WSMethodResultIsFault ((CFDictionaryRef) result)) /*error?*/ [resultField setStringValue: [result objectForKey: (NSString *) kWSFaultString]]; else /*no error; all's well*/ [resultField setStringValue: [result objectForKey: (NSString *) kWSMethodInvocationResult]];
WSMethodResultIsFault with the
result dictionary to find out if there was an error. If so, then the error string is in the
result dictionary, with the key
If there was no error, the result is in the
result dictionary with the key
This is just about the simplest possible XML-RPC example. Reading the header file WSInvocation.h one notes that one can make asynchronous requests, call SOAP services, over-ride the built-in serializer, and more.
Lots to learn.