A small introduction on how to access XML-RPC webservices in Objective-C using NGXmlRpc.
The XML-RPC support in SOPE is scattered in some frameworks (for some good reasons ;-) They are: libXmlRpc, libNGObjWeb and libNGXmlRpc.
The XML parsing is actually done in sope-xml/XmlRpc. This library contains
a SaxObjC handler which creates XML-RPC objects (XmlRpcRequest,
XmlRpcResponse) and coder objects which can produce XML-RPC XML entities
from given objects. Notably it allows arbitary objects to get encoded as
XML-RPC requests/responses.
But the one thing which libXmlRpc does _not_ contain is an HTTP transport. So
by itself it is only the parser/generator and cannot be used to perform
actual calls.
One option to get an HTTP transport is WORequest and WOResponse in conjunction with the WOHTTPConnection class as contained in libNGObjWeb. WOHTTPConnection is a simple HTTP client which can be used to send requests and receive responses. If NGStreams was compiled with SSL support (default), it even supports https requests.
Finally, libNGXmlRpc ties the WOHTTPConnection and the libXmlRpc parser together and wraps them in an easy to use NGXmlRpcClient object.
The "other" option: on MacOSX WOHTTPConnection is not necessarily the best option. With the WebKit framework Cocoa Foundation provides a pretty good HTTP transport which can be used in combination with libXmlRpc. This might be covered in a later article ;-)
Prior implementing an own client, you might want to try to call your XML-RPC service using xmlrpc_call. This is a tool implemented using NGXmlRpc to call XML-RPC from the commandline. As a small example to get started:
xmlrpc_call \ http://www.oreillynet.com/meerkat/xml-rpc/server.php \ meerkat.getChannelsBySubstring XML.com
First, you need to create an instance of the NGXmlRpcClient class which represents a connection to some XML-RPC service. The service is specified as an URL which can be passed in as either a regular NSString or as an NSURL:
NGXmlRpcClient *client; NSString *url; url = @"http://www.oreillynet.com/meerkat/xml-rpc/server.php"; client = [[NGXmlRpcClient alloc] initWithURL:url];
After that we can use the object referenced by the client variable to call a service:
[client call:@"meerkat.getChannelsBySubstring", @"XML", nil]);
The example uses the -call: varargs method, NGXmlRpcClient provides some more for more complex setups. The first argument of the method is the name of the XML-RPC method to be called. All additional arguments up to the nil terminator are passed as arguments to the remote method, in this case we have a single string argument, XML.
Well, thats it! Note that you can use the client object for as many calls as you like, you don't need to recreate for each call.
The whole function as called by the main() function below:
static void runIt(void) { NGXmlRpcClient *client; NSString *url; url = @"http://www.oreillynet.com/meerkat/xml-rpc/server.php"; client = [[NGXmlRpcClient alloc] initWithURL:url]; NSLog(@"result: %@", [client call:@"meerkat.getChannelsBySubstring", @"XML", nil]); [client release]; }
XML-RPC itself supports a limited set of argument types at the protocol
level. Those are: strings, integers, arrays, dictionaries and dates. Since
this mostly matches what is available in Foundation property lists you might
already have methods in your custom classes to represent them as objects
supported by XML-RPC.
TODO: explain XML-RPC encoding/decoding supports for complex objects.
A lot of XML-RPC services implement an own authentication system as XML-RPC methods. But some - like OGo xmlrpcd, ZideStore or Zope - use the basic authentication builtin in HTTP.
To pass basic auth information to the client, you can either encode the authentication in the URL, eg:
http://donald:secret@localhost/RPC2
Or explicitly pass them to the object on creation:
client = [[NGXmlRpcClient alloc] initWithURL:url login:@"donald" password:@"secret"];
Quite rare, but some services provide XML-RPC over Unix domain sockets (primarily for security reasons). One example is the RedCarpet / OpenCarpet daemon.
Such services can be accessed by creating a NGLocalSocketAddress (libNGStreams) for the service and passing that to -initWithRawAddress: method of NGXmlRpcClient:
idaddress; address = [NGLocalSocketAddress addressWithPath:@"/var/my-xmlrpc-service"]; client = [[NGXmlRpcClient alloc] initWithRawAddress:address];
To complete the example you need a main function:
int main(int argc, char **argv, char **env) { NSAutoreleasePool *pool; pool = [[NSAutoreleasePool alloc] init]; #if LIB_FOUNDATION_LIBRARY || defined(GS_PASS_ARGUMENTS) [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; #endif runIt(); [pool release]; return 0; }
The GNUstep makefile used for building the example:
include $(GNUSTEP_MAKEFILES)/common.make TOOL_NAME = meerkat_xml_channels meerkat_xml_channels_OBJC_FILES += meerkat_xml_channels.m ADDITIONAL_TOOL_LIBS += \ -lNGXmlRpc -lNGObjWeb -lNGMime -lNGStreams -lNGExtensions -lEOControl \ -lXmlRpc -lDOM -lSaxObjC include $(GNUSTEP_MAKEFILES)/tool.make
Build using make all (remember to have GNUstep.sh sourced).
As mentioned in the intro, libXmlRpc and NGXmlRpc have some other nifty features which we left out in this short intro.