- The array of dispatch functions contains two signatures for the emailValidator.validate function. This is similar to the concept of overloading used in typed languages such as Java, that allow you to redefine a function several times with varying parameter numbers and data types. In this case, the second signature is used to emulate the second optional parameter of an email Id.
- The emailValidator_validate function checks for the existence of the optional email ID parameter and only fetches it if it was passed. Attempting to get a parameter that does not exists will result in a fatal error.
- Certain conditions cause the RPC functions to return fault responses. There is no standard list of fault codes and descriptions, therefore the function documentation must detail these. Faults should be used like exceptions, where an abnormal condition means a function cannot return its usual value.
- The emailValidator_validate function returns two pieces of information. As the XML-RPC response can only take one return value, each value is placed into an associative array and returned as an XML-RPC structure.
The AJ-RPC client has a similar interface to the PEAR XML-RPC client. First an instance of the XMLRPCClient needs to be created and initialized with the URL of the RPC endpoint (n.b: Javascript will only allow communication with a server that resides on the same domain as the page making the request):
var client = new XMLRPCClient('rpc_server.php');
Methods can be added to the client in several ways. The easiest way is to call the aqquireFunctions() function. This causes the XMLRPCClient object to make a call to the system.listMethods function, which is provided by default on all XML-RPC servers. This method returns a list of all functions provided by the server:
if (! client.aqquireFunctions()) { // incase the call to system.listMethods fails
client.addFunction('strlen'); // add a function by name
var strlen = new XMLRPCFunction('strlen');
client.addFunction(strlen); // add a XMLRPCFunction object
}
Functions can also be added to the client manually using the addFunction function. Once a function has been added to the client, it can be called as a function of the client, and its parameters passed as usual:
try {
var len = client.strlen('i am a string');
} catch (response) {
/* function returned some kind of fault */
var code = response.getFaultCode();
var description = response.getFaultDescription();
}
The code above demonstrates how to call a function synchronously. Synchronous calls should only be used during page load; a slow response from the RPC server will block all other scripts and events on the page. When called synchronously, fault responses are thrown as exceptions.
To call an RPC asynchronously, the onresponse event handler of the XMLRPCClient object or individual XMLRPCFunction must be set to a callable function. This function will receive a single argument: an XML_RPC_Response object:
client.onresponse = function(response) // set a response handler for all functions
{
var functionName = response.getCaller().gtFunctionName(); // get the name of the function that made the call
}
client.getFunction('strlen').onresponse = function(response) // set a response handler for an individual function
{
if(! response.isFault()) {
var returnVal = response.getReturnValue().getValue();
}
}
client.strlen(); // the function will now be called asynchronously
The client-side part of the email validation application works in a similar way as the Ajax version and calls all the RPC methods asynchronously. I have therefore detailed only the relevant changes. The entire script can be found in the ZIP file accompanying this article.
The init() function which initializes the environment now creates an instance of the XMLRPC client and assigns the onresponse event handlers to the RPCs which are to be called. If the XMLRPCClient object cannot create an instance of an XMLHTTPRequest object, it throws an exception. This is caught by the init function and causes it to stop executing.
Javascript:
/* initializes the environment and sets up all variables - called on the onload event of the document body */
function init()
{
/* close the document output*/
document.close();
try {
emailValidator = new XMLRPCClient('email_validate.php');
} catch (e){
return;
}
emailValidator.aqquireFunctions();
emailValidator.getFunction('emailValidator_validate').onresponse = validateCallback;
emailValidator.getFunction('emailValidator_verify').onresponse = verifyCallback;
/* initialize global variables */
theForm = document.getElementById('formEmail');
btnSubmit = document.getElementById('btnSubmit');
txtEmail = document.getElementById('txtEmail')
verify = document.getElementById('verify')
verifyMsg = document.getElementById('verifyMsg');
txtVerify = document.getElementById('txtVerify');
btnVerify = document.getElementById('btnVerify');
emailMsg = document.getElementById('emailMsg');
/* disable the submit button */
btnSubmit.setAttribute('disabled', 'disabled');
txtEmail = document.getElementById('txtEmail');
/* set the onchange event of the email input box to the validateAddress() function */
txtEmail.onchange = validateAddress;
}
The validateAddress function is executed when the text contained in the email address field is changed. It executes the emailVlaidate.validate RPC. The response is handled by the validateCallback function which first checks that the response is not a fault. It then loads the two items in the return value: the validation result and the email ID into two variables.
Javascript:
function validateAddress()
{
var email = theForm.email.value;
var id = parseInt(theForm.email_id.value);
startupState(); // set initial state
if (email != ' ') {
// execute RPC - if an email ID is present, send that too
if (id) {
emailValidator.emailValidator_validate(email, id);
} else {
emailValidator.emailValidator_validate(email);
}
} else {
return;
}
}
function validateCallback(response)
{
if (response.isFault()) { // if a fualt occured - something strange happened; disable
btnSubmit.removeAttribute('disabled');
txtEmail.onchange = null;
verify.style.display = 'none';
return;
}
var emailID = response.getReturnValue().getValue().email_id;
var validated = response.getReturnValue().getValue().validated;
if (validated) {
emailMsg.style.display = 'none';
verify.style.display = 'inline';
verifyMsg.innerHTML = '<b>An Email containing your verification code has been sent to this address. Please Enter it before continuing.</b>';
theForm.email_id.value = emailID;
} else {
verify.style.display = 'none';
emailMsg.innerHTML = '<b>You have entered an invalid email address. Please correct it before continuing.</b>';
emailMsg.style.display = 'block';
}
}
The checking of the verification code is handled by the verifyAddress and verifyCallback functions. The verifyAddress function is executed when the verify button on the form is pressed. It executes the emailValidator.verify RPC which it passes the email ID and verification code typed by the user. The response is received by the verifyCallback function. Notice here how the fault code is checked, as the RPC may return one of several possible faults:
Javascript:
function verifyAddress()
{
var v_code = theForm.v_code.value;
var id = parseInt(theForm.email_id.value);
if (v_code == ' ') {
alert('No Verification Code Entered');
theForm.v_code.focus();
return;
}
emailValidator.emailValidator_verify(id, v_code);
}
function verifyCallback(response)
{
if (response.isFault()) {
switch (response.getFaultCode()) {
case -102: // email ID is invalid - clear and revalidate
theForm.email_id.value ='';
case -101: // email is not validated
validateAddress();
break;
default:
btnSubmit.removeAttribute('disabled');
txtEmail.onchange = null;
verify.style.display = 'none';
}
return;
}
var v_code = theForm.v_code.value;
if(response.getReturnValue().getValue()) { // successful verification
btnVerify.setAttribute('disabled', 'disabled');
txtVerify.setAttribute('disabled', 'disabled');
btnVerify.innerHTML = '<i>Verified</i>';
verifyMsg.innerHTML = '';
btnSubmit.removeAttribute('disabled');
return new XML_RPC_Response(new XML_RPC_Value(''), '-101', 'Email not validated.');
verifyMsg.innerHTML = '<b><i>' + theForm.email.value + '</i></b>';
theForm.email.value = '';
} else {
verifyMsg.innerHTML = '<b>Verification Failed</b>';
}
}
Again, drawing comparison between the original Ajax validation engine and the RPC version, you’ll notice that the code is a lot cleaner and easier to understand.
XML-RPC simplifies the creation of distributed applications by providing a standard interface by which developers can use to call procedures on remote machines. The very concept of RPC and similar technologies fits in with one of the core objectives of all modern day programming languages: Code Reuse. Why create your own search facility, when Google does it better? Why implement your own blogging system, when several sites provide it for you? The adoption of these XML-based services including RSS, ATOM and RPC by large corporations such as Google and Microsoft is testament to the fact the web is evolving.
Before using XML-RPC, it is worth noting that it is not without it drawbacks:
- Calling an RPC is resource intensive, as the client / server needs to encode/decode the method call and all of its parameters. The calls should be kept to a minimum and where possible the responses should be cached and reused.
- RPCs are often sent across unreliable networks, therefore a response may be slow and in some cases is not guaranteed. You must consider how long you / your users are prepared to wait and implement an appropriate time-out policy, falling back on cached responses where the remote endpoint is unavailable.
- Like mentioned previously. RPC alone does not provide or support the object orientated methodologies used in many modern day programming languages. Where object orientated RPCs are essential, you should consider using SOAP or WSDL.
Our next article is going to delve deeper in the world of RPC by taking a look at how SOAP and WSDL are used to completely automate RPCs at the two endpoints and how they serve to describe complex data types and the operations that can be carried out on them.