Drupal 6: Using Appcelerator Titanium to create an iPhone application to communicate with a Drupal site via SOAP (suds client & nusoap server)

In this article I'll share some code that I wrote that uses Appcelerator Titanium to create a simple iPhone application that communicates with a Drupal site via SOAP. From the iPhone side of things I used the Suds Javascript SOAP client, and from Drupal, I created a nusoap soap server instance.

Let's start with the SOAP server instance. I created a module called "nusoap", and copied the lib folder from nusoap into it. I then created a file in the module's directory called "soap-server.php" and added the following code. For the sake of this article, I did not include the rest of the Drupal module's code.

<?php
// $Id$

// define namespace
define('NUSOAP_NAME_SPACE', 'erl.dev');

// define path to nusoap library file
$nu_soap_path = 'lib/nusoap.php';

// ensure nu_soap library exsists
if (!file_exists($nu_soap_path)) {
  die('An error has occurred initializing the soap server.');
}

// include nu_soap library
require_once ($nu_soap_path);

// create new soap server instance
$soap_server = new nusoap_server();

// configure wsdl
$soap_server->configureWSDL(NUSOAP_NAME_SPACE, 'urn:'. NUSOAP_NAME_SPACE);

$soap_server->register(
  // method name
  'login_fetch_data',
  // input args
  array(
    'name' => 'xsd:string',
    'pass' => 'xsd:string',
  ),
  // output args
  array(
    'return' => 'xsd:string'
  ),
  // namespace
  'uri:'. NUSOAP_NAME_SPACE,
  // SOAPAction
  'uri:'. NUSOAP_NAME_SPACE .'#login_fetch_data',
  // style
  'rpc',
  // use
  'encoded'
);

// change directory to docroot
chdir($_SERVER['DOCUMENT_ROOT']);

// drupal bootstrap
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

// process raw post data
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$soap_server->service($HTTP_RAW_POST_DATA);

/**
 * Define soap methods
 */

function login_fetch_data($name, $pass) {

  $params = array(
    'name' => $name,
    'pass' => $pass,
  );

  $account = user_authenticate($params);

  // check for error
  if (!is_object($account)) {
    // handle authentication error here
    // return something
  }

  // return user uid, or some other piece of data
  return $account->uid;

}
?>

The above code registers one soap server method "login_fetch_data" which accepts the arguments (name and pass), loads a full Drupal bootstrap, authenticates the user, and returns back the user's uid. By browsing to the soap server file directly, I can see nusoap's WSDL definition: (example: http://drupal.vm/sites/all/modules/custom/nusoap/soap-server.php)

SOAP Server Methods

To test if the soap server method was working, I used the follow PHP code snippet (which I pulled out of a test page callback):

<?php

// define path to nu_soap library, relative to module directory
define('NUSOAP_PATH', drupal_get_path('module', 'nusoap') .'/lib/nusoap.php');

// define namespace
define('NUSOAP_NAME_SPACE', 'erl.dev');

// include nu_soap library
require_once(NUSOAP_PATH);

// define wsdl path
$wsdl_path = 'http://' . $_SERVER['HTTP_HOST'] . base_path() . drupal_get_path('module', 'nusoap') . '/soap-server.php?wsdl';

// create new soap client instance
$soap_client = new nusoap_client($wsdl_path, true);

// check for error
$error = $soap_client->getError();
if ($error) {
  // handle error
}

$args = array(
  'name' => 'Eric.London',
  'pass' => 'super secret password',
);

// call soap server method
$result = $soap_client->call('login_fetch_data', $args);

$output = "";
$output .= "<pre>";
$output .= "SENT: ";
$output .= print_r($args, true);
$output .= "RECEIVED: ";
$output .= print_r($result, true);
$output .= "</pre>";

return $output;

?>

I then created a new Titanium iPhone project. In the Resources directory, I created a directory called "soap-test-includes" to contain the suds.js library, and the soap.js file which uses this library. The suds.js file is available in the Kitchen Sink Titanium project, if you choose to import it.

Titanium folder structure

I added the following code to the app.js file:

// this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');

// create tab group
var tabGroup = Titanium.UI.createTabGroup();

//
// create base UI tab and root window
//
var win1 = Titanium.UI.createWindow({
    title:'Soap Test',
    backgroundColor:'#fff',
    url: 'soap-test-includes/soap.js'
});
var tab1 = Titanium.UI.createTab({
    icon:'KS_nav_views.png',
    title:'Soap Test',
    window:win1
});

//
//  add tabs
//
tabGroup.addTab(tab1);

// open tab group
tabGroup.open();

And I added the following code to the soap.js include file:

// include suds soap library
Titanium.include('suds.js');

var window = Ti.UI.currentWindow;
var label = Ti.UI.createLabel({
  top: 10,
  left: 10,
  width: 'auto',
  height: 'auto',
  text: 'Contacting Drupal soap server...\n'
});

window.add(label);

// define soap params
var url = "http://drupal.vm/sites/all/modules/custom/nusoap/soap-server.php";
var callparams = {
  name: 'Eric.London',
  pass: 'super secret password'
};

// create new suds soap client
var suds = new SudsClient({
  endpoint: url,
  targetNamespace: 'erl.dev'
});

try {

  // make soap server call
  suds.invoke('login_fetch_data', callparams, function(xmlDoc) {

    // fetch results from method
    results = xmlDoc.documentElement.getElementsByTagName('login_fetch_dataResponse');

    // check if results exits
    if (results && results.length>0) {

      // output results
      label.text += "User uid: " + results.item(0).text;

    }
    else {
      // handle error
    }

  });
} catch(e) {
  Ti.API.error('Error: ' + e);
}

When I build and run the project, the iOS Simulator opens and shows the results of the soap connection!

iOS soap connection

As you can see, Titanium allows you to write an iPhone application without touching the native Objective-C code. In this example, all the code was written in javascript, with a PHP backend. Much of the javascript code I used is based on code found in the Kitchen Sink project; I suggest you check it out :)