Tuesday, 7 April 2009

SOAP: webservices in Objective-C/Cocoa

Lately I was experimenting a bit with webservices, and this for a good reason : my ISP was trying to bill me extra gigabytes while I was sure I didn't used my full quota.
My ISP (telenet in Belgium) is using a nice web page where you can check your quota but they also offer a webservice which gives full statistics on the bandwidth usage.

So I decided to build a Dashboard widget (in Objective-C, not in Javascript) to see the statistics from my desktop.

Now I didn't do any SOAP/Webservices since a long time , and I never did in Objective-C (my experience goes back to VisualWave, Smalltalk and Java).
So I did some experimentation with the samples from Apple itself :

// SOAP request settings

NSURL *url = [NSURL URLWithString:@"https://telemeter4tools.services.telenet.be/TelemeterService"];

// the name of the webservice
NSString *method = @"getUsage";

// the namespace
NSString *namespace = @"https://telemeter4tools.services.telenet.be/";

// SOAP request params

NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:@"**username**",


// the parameters itself are in a dictionary, a dictionary is an unordered collection
// so to define the order in which the parameters must be sent
// you need to fill an array with the NAMES of the parameters

NSArray *paramOrder = [NSArray arrayWithObjects:@"string", @"string0"];

// set SOAP request http headers -- some SOAP server impls require even empty SOAPAction headers

NSDictionary *reqHeaders = [NSDictionary dictionaryWithObject:@"" forKey:@"SOAPAction"];

// create SOAP request

WSMethodInvocationRef soapReq = createsoapReq(url, method, namespace, params, paramOrder, reqHeaders);

// invoke SOAP request

NSDictionary *result = (NSDictionary *)WSMethodInvocationInvoke(soapReq);

// get HTTP response from SOAP request so we can see response HTTP status code

CFHTTPMessageRef res = (CFHTTPMessageRef)[result objectForKey:(id)kWSHTTPResponseMessage];

The response of a SOAP requests is a dictionary, and it looks like this :

result: {
"/Result" = { ... <<>> };

"/WSDebugInBody" = "...etc;

"/WSDebugInHeaders" = {
"Accept-Ranges" = none;
Connection = close;
"Content-Length" = 4840;
"Content-Type" = "text/xml; charset=utf-8";
Date = "Wed, 08 Apr 2009 06:44:46 GMT";
"Set-Cookie" = "JSESSIONID=JcHpkYZkhhn2pzp1LY7fvGQ1WXkb24WKDY2LBnf0JpLYyhsk23Q1!-344393110; path=/, st8id=c42908deb564326d86d900fd90459934.01.af0318b08a904d84c5824b76a132ab93; domain=.services.telenet.be; path=/, st8id_wat_%2Eservices%2Etelenet%2Ebe_%2F=SlNFU1NJT05JRA__?1607678332ef30e3c1b9fc43cda54cb6; domain=.services.telenet.be; path=/";
"/WSDebugOutBody" = <<>>";
"/WSDebugOutHeaders" = {
"Content-Type" = "text/xml";
Host = "telemeter4tools.services.telenet.be";
Soapaction = "";
"User-Agent" = "Mac OS X; WebServicesCore.framework (1.1.0)";
"/kWSHTTPResponseMessage" = <<>>

Note that I stripped a bit the contents of the dictionary.

Now to get the result of the webservice you must get the proper data from the dictionary (in this case this is data = [result objectForKey: @"result"].

This will return a string value, so a next step is to create an XMLDocument of it :

NSXMLDocument *document ;
NSError *error ;
NSXMLNode *node ;
document = (NSXMLDocument *) [NSXMLDocument document] ;
[document initWithXMLString: data options: NSXMLDocumentTidyXML error: &error ] ;

And once it is in a XMLDocument , you can use XPath expressions to query the document :

myArray = [document objectsForXQuery: @"/ns1:telemeter[1]/ns1:usage-info[1]/ns1:data[1]/@timestamp" error: &error ] ;

Now this looks to me a very complex process to get something from a webservice.
I'm now looking at WSDL and the utility WSMakeStubs to see if I can simplify this.
The nicest thing would be that we have something like JAXB to 'objectify' webservice responses

No comments:

Post a Comment