DOAP - Remote Abstract Data Access Protocol by Dave

DOAP Documentation

Documentation Chapters


Introduction

DOAP (Dave's Own Access Protocol) is a PHP/JavaScript system to facilitate easy data exchange between a JavaScript client and a PHP server.

This allows PHP functions to be in effect called remotely from JavaScript. The DOAP client will package the request and send it to the server. The DOAP server will unpack the request, process it (calling whichever function is specified with the parameters provided), package up the result and return it. The client will then unpack the results, put them into native JavaScript dynamic data types or Array objects and return them the calling script.

High-Level Framework Overview

Client (Web Browser)

Server (Web Server)

User Client Script

DOAP Client

DOAP Server

User Server Script

Initialise DOAP   
Build Parameters   
call DOAP.call()Build URL/Request  
 Make XMLHTTP RequestInitialiseRegister Functions
  Unpack Request 
  Process RequestExecute Function
   Return Data
  Package Returned Data 
 Receive Returned DataReturn Data 
 Unpack Received Data  
Receive DataReturn Data  


Why use DOAP?

DOAP offers a simpler alternative than coding your own XMLHTTP request creators/handlers.

There are of course a number of frameworks and libraries allowing simplified ease of data access but DOAP cuts down the work required to interface data between JavaScript and PHP to just a few simple settings and method calls.

Requirements

For the client any JavaScript enabled browser supporting XMLHTTP will do (though asynchronous requests in DOAP don't work with IE<=6).

The server just needs to be a PHP-enabled web server.

DOAP comes bundled with xmlCreate which is required by the system to build return data (xmlCreate is also licenced under the GNU GPL v3). Please be sure to use the version of xmlCreate that ships with the specific version of DOAP you are using.

Back to the Top/Contents

First Simple Example: Hello World

As explained in the introduction DOAP has two components - the server (server-side PHP) and the client (client-side JavaScript).

This first simple example shows how we can call a function on the server and return the string "Hello World!" to be displayed on the client.

Click here to see this example run

Here is the commented source code for this example.

Server-Side DOAP Server PHP
<?php
ob_start(); // ensure no messy output from includes
 
// Our Hello World Function - just returns a string
function hello_world()
{
	return "Hello World!";
}
 
require("xmlcreate.inc.php"); // include the xmlCreate library
require("doap.inc.php"); // include the DOAP Server library
 
$doap = new DOAP_Server(); // create a DOAP Server object
 
$doap->Register("hello_world",0,0); // register the hello_world() function
 
ob_clean(); // clean any messy output from includes
 
$doap->Process(); // get DOAP Server to process the request
?>

The server side-code simply has a function called hello_world() that returns a string. DOAP is initialised, the hello_world() function is registered and then DOAP is called to process the request.

Client-Side DOAP Server HTML/JavaScript
<HTML>
<HEAD>
<TITLE>DOAP Example 1: Hello World</TITLE>
<!-- Include the DOAP Client JavaScript -->
<SCRIPT TYPE="text/javascript" SRC="doap.js"></SCRIPT>
 
<SCRIPT TYPE="text/javascript">
var doap = new DOAP(); // create the DOAP Client object
doap.url = "example1.php"; // set the DOAP Server URL
 
function hello_world() // this will call hello_world() on the DOAP Server
{
	try
	{
		var result = doap.call('hello_world'); // call hello_world() on server
	}
	catch(e) // catch any errors
	{
		// deal with any errors if it's a DOAP error or any other type of exception
		if(e instanceof DOAP_Error) alert("DOAP Error "+e.code+": "+e.desc);
		else alert("Unknown Error: "+e);
		return; // and exit the function if an error was encountered
	}
	// otherwise put the result into the results DIV
	document.getElementById('results').innerHTML = result;
}
</SCRIPT>
</HEAD>
 
<BODY>
<DIV ID="results" CLASS="result_div">Results will appear here BY MAGIC!</DIV>
<BR /><BR />
 
<!-- Now we have a link which will call the JavaScript function hello_world which
     in turn calls the hello_world() function on the server and outputs the result
     to the results div -->
<A HREF="#" onClick="hello_world();">Click me to execute hello_world()</A><BR /><BR />
 
<!-- And a link to clean the results div back if needed -->
<A HREF="#" onClick="document.getElementById('results').innerHTML='<BR />';">Clear Results DIV</A>
 
</HTML>
 

The client includes the DOAP JS, makes a DOAP object and defines a function that calls it (wrapped in try to catch any thrown errors). The function is called from the onClick event of an A HREF link.

Back to the Top/Contents

Parameters

DOAP functions can have parameters passed to them. At the client end these are put into an array and passed to the DOAP.call method.

When a function is registered on the DOAP Server you specify a minimum and maximum number of parameters. If the function is called with too many or too few an error will be generated.

Please note DOAP currently supports a maximum of 10 parameters

The passing and handling of parameters is shown in the following example (click here to see it run).

Server-Side DOAP Server PHP
<?php
ob_start(); // ensure no messy output from includes
 
// Our Addition Function - takes two numbers and adds them
function addition($one, $two)
{
	// sanitise user input just in case...
	if (!is_numeric($one) || !is_numeric($two)) return 0; 
 
	// return the sum
	return $one + $two;
}
 
require("xmlcreate.inc.php"); // include the xmlCreate library
require("doap.inc.php"); // include the DOAP Server library
 
$doap = new DOAP_Server(); // create a DOAP Server object
 
// register the addition function with a minimum and maximum of 2 parameters
// e.g. there must be two parameters
$doap->Register("addition",2,2);
 
ob_clean(); // clean any messy output from includes
 
$doap->Process(); // get DOAP Server to process the request
?>

The above code registers the addition() function with the DOAP Server requiring a minimum and maximum of two parameters. The function itself just checks the data is numeric and if so returns the sum of the two values.

Client-Side DOAP Server HTML/JavaScript
<HTML>
<HEAD>
<TITLE>DOAP Example 2: Parameters</TITLE>
<!-- Include the DOAP Client JavaScript -->
<SCRIPT TYPE="text/javascript" SRC="doap.js"></SCRIPT>
 
<SCRIPT TYPE="text/javascript">
var doap = new DOAP(); // create the DOAP Client object
doap.url = "example2.php"; // set the DOAP Server URL
 
function addition() // this will get parameters and call addition() on the server
{
	try
	{
		var params = Array(); // parameters are passed in an array
		params.push(document.forms.addform.one.value); // the first number
		params.push(document.forms.addform.two.value); // the second number
		var result = doap.call('addition',params); // call addition() on server passing params
	}
	catch(e) // catch any errors
	{
		// deal with any errors if it's a DOAP error or any other type of exception
		if(e instanceof DOAP_Error) alert("DOAP Error "+e.code+": "+e.desc);
		else alert("Unknown Error: "+e);
		return; // and exit the function if an error was encountered
	}
	// otherwise put the result into the results DIV
	document.getElementById('results').innerHTML = result;
}
</SCRIPT>
</HEAD>
 
<BODY>
<DIV ID="results" CLASS="result_div">Results will appear here BY MAGIC!</DIV>
<BR /><BR />
 
<!-- This time we have a form to input two numbers which will be added by the server -->
<FORM ID="addform" onSubmit="return false;"> <!-- Start the form -->
 
<!-- The fields -->
<INPUT TYPE="TEXT" NAME="one" SIZE="3" VALUE="1"> + <INPUT TYPE="TEXT" NAME="two" SIZE="3" VALUE="2">
 
<!-- The Submit Button which in it's onClick calls the JavaScript addition() function -->
<INPUT TYPE="SUBMIT" VALUE="Calculate" onClick="addition();">
 
</FORM>
 
<BR /><BR />
 
<!-- And a link to clean the results div back if needed -->
<A HREF="#" onClick="document.getElementById('results').innerHTML='<BR />';">Clear Results DIV</A>
 
</HTML>
 

The client script initialises DOAP and displays a form to input two values. When the button is clicked the addition() function is called which puts the two values into a parameter array and does a DOAP.call for the addition function.

Back to the Top/Contents

Arrays

DOAP can return array data as well as standard dynamic data types. Arrays can be multi-dimensional and are index based rather than associative to fit into standard JavaScript functionality (it is planned to consider allowing use of iArray Associative JavaScript Arrays at some later date).

When a function on the DOAP Server returns an array this is detected automatically.

This is an example of working with arrays (click here to see it run).

Server-Side DOAP Server PHP
<?php
ob_start(); // ensure no messy output from includes
 
// Our Addition Function - takes two numbers and adds them returning an array
function addition($one, $two)
{
	// sanitise user input just in case...
	if (!is_numeric($one) || !is_numeric($two)) return array("Non-numeric input");
 
	$result=array();
	$result[]="First Value: ".$one;
	$result[]="Second Value: ".$two;
	$result[]="Addition Result: ".($one+$two);
 
	return $result;
}
 
function multidim() // demonstrate a multi-dimensional array
{
	$result=array( "one", "two", "three", array("A.one", "A.two"), "four");
	return $result;
}
 
require("xmlcreate.inc.php"); // include the xmlCreate library
require("doap.inc.php"); // include the DOAP Server library
 
$doap = new DOAP_Server(); // create a DOAP Server object
 
// register the addition function with a minimum and maximum of 2 parameters
// e.g. there must be two parameters
$doap->Register("addition",2,2);
 
// register the multidim function with 0 parameters
$doap->Register("multidim",0,0);
 
ob_clean(); // clean any messy output from includes
 
$doap->Process(); // get DOAP Server to process the request
?>

The server code defines two functions which both return arrays. The addition function acts the same as the previous example but returns an array of strings showing the values and sum rather than just a numeric sum. The multidim function returns a multi-dimensional array to demonstate that capability.

Client-Side DOAP Server HTML/JavaScript
<HTML>
<HEAD>
<TITLE>DOAP Example 3: Arrays</TITLE>
<!-- Include the DOAP Client JavaScript -->
<SCRIPT TYPE="text/javascript" SRC="doap.js"></SCRIPT>
 
<SCRIPT TYPE="text/javascript">
var doap = new DOAP(); // create the DOAP Client object
doap.url = "example3.php"; // set the DOAP Server URL
 
// this function will print our array(s) to the results div
function printarray( arr, depth )
{
	if (!depth) var depth=0; // depth is used to indicate sub-arrays (multi-dimensional)
	for (var i=0; i<arr.length; i++) // loop through the array
	{
		// if its a sub-array call this function again with an increased depth
		if (arr[i] instanceof Array) printarray(arr[i],depth+1); 
		else // otherwise output it
		{
			if (depth>0) // show -'s to indicate depth if above 0
			{
				for (var x=0; x<depth; x++)
				{
					document.getElementById('results').innerHTML += "-";
				}
				document.getElementById('results').innerHTML += " ";
			}
			// and display the content of the array element
			document.getElementById('results').innerHTML += (arr[i]+"<BR />");
		}
	}
}
 
 
function addition() // this will get parameters and call addition() on the server
{
	try
	{
		var params = Array(); // parameters are passed in an array
		params.push(document.forms.addform.one.value); // the first number
		params.push(document.forms.addform.two.value); // the second number
		var result = doap.call('addition',params); // call hello_world() on server passing params
	}
	catch(e) // catch any errors
	{
		// deal with any errors if it's a DOAP error or any other type of exception
		if(e instanceof DOAP_Error) alert("DOAP Error "+e.code+": "+e.desc);
		else alert("Unknown Error: "+e);
		return; // and exit the function if an error was encountered
	}
	// otherwise call printarray to put the results in the DIV element
	document.getElementById('results').innerHTML = "";
	printarray(result);
}
 
function multidim() // call multidim() on the server
{
	try
	{
		var result = doap.call('multidim');
	}
	catch(e) // catch any errors
	{
		// deal with any errors if it's a DOAP error or any other type of exception
		if(e instanceof DOAP_Error) alert("DOAP Error "+e.code+": "+e.desc);
		else alert("Unknown Error: "+e);
		return; // and exit the function if an error was encountered
	}
	// otherwise call printarray to put the results in the DIV element
	document.getElementById('results').innerHTML = "";
	printarray(result);
}
</SCRIPT>
</HEAD>
 
<BODY>
<DIV ID="results" CLASS="result_div">Results will appear here BY MAGIC!</DIV>
<BR /><BR />
 
<!-- This time we have a form to input two numbers which will be added by the server -->
<FORM ID="addform" onSubmit="return false;"> <!-- Start the form -->
 
<!-- The fields -->
<INPUT TYPE="TEXT" NAME="one" SIZE="3" VALUE="1"> + <INPUT TYPE="TEXT" NAME="two" SIZE="3" VALUE="2">
 
<!-- The Submit Button which in it's onClick calls the JavaScript addition() function -->
<INPUT TYPE="SUBMIT" VALUE="Calculate" onClick="addition();">
 
</FORM>
 
<BR /><BR />
 
<!-- The link to call the multidim to demonstrate multi-dimensional arrays -->
<A HREF="#" onClick="multidim();">Show a Multi-Dimensional Array</A>
<BR /><BR />
 
<!-- And a link to clean the results div back if needed -->
<A HREF="#" onClick="document.getElementById('results').innerHTML='<BR />';">Clear Results DIV</A>
 
</HTML>
 

The client defines controls to either call the addition() function with the set values or the multidim() function. Output is not just displayed but passed to the printarray() method which iterates through the array (and calls itself recursively on finding a sub-array) and displays the output.

Back to the Top/Contents

Errors

If DOAP experiences an error it will throw a DOAP_Error object containing an error code (DOAP_Error.code) and textual description (DOAP_Error.desc).

Additionally JavaScript errors may be encountered in using the XMLHTTP or other functionality and will themselves throw errors. For this reason it's always a good idea to enclose the DOAP call in a try/catch block and deal with errors that occur (as shown in all the examples).

Error codes starting at 100 are client-side errors (e.g. failure to create xmlhttp object or handling error), and codes starting at 200 are server-side generated errors (e.g. incorrect number of parameters).

Additionally user code can generate an error from the server-side as is shown in the following example.

Error generation example (click here to see it run).

Server-Side DOAP Server PHP
<?php
ob_start(); // ensure no messy output from includes
 
// Our function that needs two parameters
function needstwo($one, $two)
{
	return "Yep got two parameters to needstwo()";
}
 
function usererror() // shows how to generate your own DOAP error
{
	global $doap; // must know the name of the DOAP Server object and have it in scope
	$doap->Error(999,"This is a user generated error"); // error and output it
	exit(); // now die to stop any other junk or results getting sent
}
 
require("xmlcreate.inc.php"); // include the xmlCreate library
require("doap.inc.php"); // include the DOAP Server library
 
$doap = new DOAP_Server(); // create a DOAP Server object
 
// register the needstwo function with a minimum and maximum of 2 parameters
// e.g. there must be two parameters
$doap->Register("needstwo",2,2);
 
// register the usererror function with 0 parameters
$doap->Register("usererror",0,0);
 
ob_clean(); // clean any messy output from includes
 
$doap->Process(); // get DOAP Server to process the request
?>

The server code defines two functions - needstwo() which takes two parameters and is registered as such as usererror() that takes no parameters but generates a user generated error using the DOAP_Server::Error method.

Client-Side DOAP Server HTML/JavaScript
<HTML>
<HEAD>
<TITLE>DOAP Example 4: Errors</TITLE>
<!-- Include the DOAP Client JavaScript -->
<SCRIPT TYPE="text/javascript" SRC="doap.js"></SCRIPT>
 
<SCRIPT TYPE="text/javascript">
var doap = new DOAP(); // create the DOAP Client object
doap.url = "example4.php"; // set the DOAP Server URL
 
 
function errorcall( etype ) // call the relevent function with parameters or not
{
	try
	{
		if (etype == "too_few")
		{
			var params = Array(); // parameters are passed in an array
			params.push("A");
			var result = doap.call('needstwo',params); // call needstwo() on server passing params
		}
		else if (etype == "just_right")
		{
			var params = Array(); // parameters are passed in an array
			params.push("A");
			params.push("B");
			var result = doap.call('needstwo',params); // call needstwo() on server passing params
		}
		else if (etype == "too_many")
		{
			var params = Array(); // parameters are passed in an array
			params.push("A");
			params.push("B");
			params.push("C");
			var result = doap.call('needstwo',params); // call needstwo() on server passing params
		}
		else if (etype == "user_error")
		{
			var result = doap.call('usererror'); // call usererror() on server
		}
		else var result = "Huh?"; // unknown etype to call
	}
	catch(e) // catch any errors
	{
		// deal with any errors if it's a DOAP error or any other type of exception
		if(e instanceof DOAP_Error) alert("DOAP Error "+e.code+": "+e.desc);
		else alert("Unknown Error: "+e);
		return; // and exit the function if an error was encountered
	}
	// if we get here output the result
	document.getElementById('results').innerHTML = result;
}
 
</SCRIPT>
</HEAD>
 
<BODY>
<DIV ID="results" CLASS="result_div">Results will appear here BY MAGIC!</DIV>
<BR /><BR />
 
<!-- The links to call needstwo with different numbers of parameters -->
<A HREF="#" onClick="errorcall('too_few');">Call needstwo() with 1 parameter</A><BR />
<A HREF="#" onClick="errorcall('just_right');">Call needstwo() with 2 parameters</A><BR />
<A HREF="#" onClick="errorcall('too_many');">Call needstwo() with 3 parameters</A><BR />
<BR />
 
<!-- And to call usererror() to generate a user made error in code -->
<A HREF="#" onClick="errorcall('user_error');">Call usererror() to throw a user error</A>
<BR /><BR />
 
<!-- And a link to clean the results div back if needed -->
<A HREF="#" onClick="document.getElementById('results').innerHTML='<BR />';">Clear Results DIV</A>
 
</HTML>
 

The client offers the option to call needstwo() with 1, 2 or 3 parameters. With 1 or 3 errors should be generated at the server and returned to the client. Only 2 parameters should succeed.

The other option is to call user_error which should produce a user-code generated error from the server.

Back to the Top/Contents

Synchronous Requests

Requests from a web client to a server using XMLHTTP can be synchronous or non-synchronous.

A synchronous (or asynchronous) request happens in the background while other code or events continue to happen. A non-synchronous request holds everything up while it executes until the result is returned. In network client/server terms this is like the difference between a non-blocking and blocking socket.

DOAP supports both methods of making requests. By default it is non-synchronous.

Although synchronous requests are "nicer" to the user they always have the possibility of unexpected changes occurring or another event being called while they are executing (maybe even the same request object bring used again resulting in a race condition).

The majority of AJAX enabled applications use synchronous requests but then have to take special care to avoid further user input or changes by disabling input fields or covering the entire window in a translucent DIV.

For this reason I strongly suggest you consider the need for a synchronous request being using that method. If you require a background process (for example an automated update of a dashboard item) or similar then a synchronous request may be best to allow the rest of your application to continue running. If however you need to fetch the data and can't do anything until it has arrived then a non-synchronous request may be best.

Of course you can always update something on your page to indicate the system is "working" then call the non-synchronous request and clear the message when it returns. This notifies the user and avoids any confusion if the request may take a while.

Using Synchronous Requests

To use a synchronous request you need to set the DOAP.async property to true and provide a function to DOAP.callback as your callback/handler function. This function will be called when data is returned.

Please note that in testing the async function was not found to operate correctly in IE<=6

Example

The following example shows synchronous and non-synchronous requests. There is a bar of continuous activity on the screen which demonstrates the difference in terms of continuing script execution (click here to see it run). Note the server-side script has a 2 second delay to allow the effects to be clearly shown (it's not normally that slow!).

Server-Side DOAP Server PHP
<?php
ob_start(); // ensure no messy output from includes
 
// Our function that returns the datetime string
function get_time()
{
	sleep(2); // sleep 2 seconds so the difference can clearly be seen
	return date("Y-m-d H:i:s");
}
 
 
require("xmlcreate.inc.php"); // include the xmlCreate library
require("doap.inc.php"); // include the DOAP Server library
 
$doap = new DOAP_Server(); // create a DOAP Server object
 
// Register get_time with 0 parameters
$doap->Register("get_time",0,0);
 
ob_clean(); // clean any messy output from includes
 
$doap->Process(); // get DOAP Server to process the request
?>

The server code just defines a function returning a formatted datetime string after a two second pause to demonstrate the effect.

Client-Side DOAP Server HTML/JavaScript
<HTML>
<HEAD>
<TITLE>DOAP Example 5: Asynchronous Requests</TITLE>
<!-- Include the DOAP Client JavaScript -->
<SCRIPT TYPE="text/javascript" SRC="doap.js"></SCRIPT>
 
<SCRIPT TYPE="text/javascript">
var doap = new DOAP(); // create the DOAP Client object
doap.url = "example5.php"; // set the DOAP Server URL
 
function my_callback( data ) // callback for async request
{
	document.getElementById('results').innerHTML = data;
}
 
function async_request() // make an asynchronous request to the server
{
	document.getElementById('results').innerHTML = "Working...";
	try
	{
		doap.async = true; // set DOAP to use async request
		doap.callback = my_callback; // set callback function to my_callback
		doap.call('get_time'); // call get_time() on server
	}
	catch(e) // catch any errors
	{
		// deal with any errors if it's a DOAP error or any other type of exception
		if(e instanceof DOAP_Error) alert("DOAP Error "+e.code+": "+e.desc);
		else alert("Unknown Error: "+e);
		return; // and exit the function if an error was encountered
	}
	// output is dealt with by my_callback
}
 
function nonsync_request() // make a non-synchronous request to the server
{
	document.getElementById('results').innerHTML = "Working...";
	try
	{
		doap.async = false; // set DOAP to use non-sync request
		var result = doap.call('get_time'); // call get_time() on server
	}
	catch(e) // catch any errors
	{
		// deal with any errors if it's a DOAP error or any other type of exception
		if(e instanceof DOAP_Error) alert("DOAP Error "+e.code+": "+e.desc);
		else alert("Unknown Error: "+e);
		return; // and exit the function if an error was encountered
	}
	// if we get here output the result
	document.getElementById('results').innerHTML = result;
}
 
var action_ctr = 0;
function constant_action() // this just shows some ongoing constant action
{
	var activity="<PRE>[";
	for (var i=0; i<30; i++)
	{
		if (i<action_ctr) activity+="_";
		else if (i>action_ctr) activity+="-";
		else activity+="|";
	}
	activity+="] "+action_ctr+"</PRE>";
	document.getElementById('activity').innerHTML = activity;
 
	action_ctr++;
	if (action_ctr>29) action_ctr=0;
}
setInterval("constant_action()",100);
</SCRIPT>
</HEAD>
 
<BODY>
<DIV ID="results" CLASS="result_div">Results will appear here BY MAGIC!</DIV>
<BR /><BR />
<DIV ID="activity">Show Constant Activity Here...</DIV>
<BR /><BR />
 
<!-- The links to call the requests -->
<A HREF="#" onClick="nonsync_request();">Make a non-synchronous request to server</A><BR /><BR />
<A HREF="#" onClick="async_request();">Make an asynchronous request to server</A><BR /><BR />
 
<!-- And a link to clean the results div back if needed -->
<A HREF="#" onClick="document.getElementById('results').innerHTML='<BR />';">Clear Results DIV</A>
 
</HTML>
 

The client code defines two functions, one for a synchronous and the other for non-synchronous requests and puts links on the page to call them. There is also a function to continually update a text animation so you can see when execution is stopped.

Back to the Top/Contents

Additional Topics

The DOAP.nocache Property

To avoid browsers caching the response of what they think is the same request to the DOAP server DOAP can append a random alphanumeric string to the request URL.

This is controlled with the bool property DOAP.nocache - if true (which it is by default) the string is appended as the URI variable nc, if it is false then no additional string is appended to the URL.

Debugging Requests

It is possible to manually navigate to a DOAP Server URL in a browser to examine the XML output first hand.

The DOAP request is made up in the query string as a series of variables.

Specifically: So an example URL to call the DOAP function addition() with the first parameter of 1 and the second parameter of 2 would be:
http://server/doap.php?f=addition&p[0]=1&p[1]=2


Security

As DOAP uses XMLHTTP some browsers (currently IE but probably others in future) will display a security warning if the request is sent to a different domain than the web page is hosted on.

Unless you want to get users to whitelist the site or page then it's suggested you host your client page on the same domain that the client is served from.

Of course the standard rules apply with regard to dealing with user content and you have to assume that ANY of your DOAP methods on the server can be called maliciously (the same as any PHP script with exposed functionality).

It's therefore up to you to check input, sanitise as appropriate and secure any restricted areas through the use of tokens/SIDs/cookies passed as part of the parameters.

Back to the Top/Contents