This is the second half of an article that began last week. If you haven't read the first part, please do so before reading on...
The PHP RPC server uses the XML_RPC_Server object provided by the PEAR XML-RPC package. Both this and the EmailValidator object will be required in the server:
require_once 'email_validator.php';
require_once 'XML/RPC/Server.php';
PHP functions cannot be mapped directly to the RPC server. The function you wish to map must be contained in a special RPC callable function. This function takes a single parameter which will be passed an XML_RPC_Message object when it is called. The function must return an XML_RPC_Response object containing the return value:
function strlen_rpc($message)
{
$string = $message->params[0]->getVal(); // get the first parameter passed
return new XML_RPC_Response(new XML_RPC_Value(strlen($string), 'int'));
}
The params property of the XML_RPC_Message object is an array of XML_RPC_Value objects containing the parameters passed to the function in the RPC call.
The next step is to map the function to a dispatch array so that it is ready to pass to the XML_RPC_Server object:
$functions = array('strlen' => array('function' => 'strlen_rpc', // this is the name of the php function to map
'signature' => array(array('int', 'string')),
'docstring' => 'Returns the length of a string.'));
The index name of each element (strlen in this case) is the name that the RPC client will use to call the function. The element itself is an array containing three items:
The final step is to create an instance of the XML_RPC_Server object and pass it to the array of dispatch function. The object immediately parses the contents of the HTTP request body and executes the appropriate function. The server always returns a valid XML-RPC response. Errors such as an invalid request will result in a fault response.
@(new XML_RPC_Server($functions, 1));
Two functions; emailValidator_validate (which validates the email address and sends the verification email) and emailValidator_verify (which verifies the verification code) will be mapped to the RPC server.
PHP - email_validate.php:
require_once 'email_validator.php';
require_once 'XML/RPC/Server.php';
/* we first create an array which will map each function neededto the RPC server */
$functions = array('emailValidator.validate' => array('function' => 'emailValidator_validate',
'signature' => array(array('struct','string'),
array('struct', 'string', 'int')),
'docstring' =>'Validates the syntax of an email address.'),
'emailValidator.verify' => array('function' => 'emailValidator_verify',
'signature' => array(array('boolean','int','string')),
'docstring' => 'Verifies an email verification code.
Fault codes: -100 Error Creating / Getting email
-101 Email not validated.
-102 Invalid Email ID'));
/* initialize the RPC server with the function map - parsing the request immdiatly */
@(new XML_RPC_Server($functions, 1));
/* this function is eecuted by the RPC, when a call is made to emailValidator.validate */
function emailValidator_validate($message)
{
/* get the email address */
$email_address = $message->params[0]->getVal();
$email = null;
$ret = array();
if (isset($message->params[1])) {
/* if present, the second argument contains an integer ID */
$id = $message->params[1]->getVal();
try {
/* attempt to create an EmailValidator object using the ID */
$email = new EmailValidator($id);
$email->setEmail($email_address);
} catch (EmailValidatorException $e) {
// invalid ID; use the email address instead - ignore exception
}
}
if (! $email) {
/* create the EmailValidator using the email address supplied */
try {
$email = new EmailValidator($email_address);
} catch(Exception $e) {
/* we generate a fault here - as there is an error creating the EmailValidator object */
return new XML_RPC_Response(new XML_RPC_Value(''), '-100', 'Error creating or sending email.');
}
}
/* we return two pices of information - the status of the validation and the new email ID (if any) */
$ret['validated'] = new XML_RPC_Value(validate_email($email), 'boolean');
$ret['email_id'] = new XML_RPC_Value($email->getId(), 'int');
/* return the reponse */
return (new XML_RPC_Response(new XML_RPC_Value($ret, 'struct')));
}
function emailValidator_verify($message)
{
$id = $message->params[0]->getVal();
$v_code = $message->params[1]->getVal();
try {
$email = new EmailValidator($id);
try {
$ret = $email->verify($v_code);
return new XML_RPC_Response(new XML_RPC_Value($ret, 'boolean'));
} catch (EmailValidatorException $e) {
return new XML_RPC_Response(new XML_RPC_Value(''), '-101', 'Email not validated.');
}
} catch (EmailValidatorException $e) {
return new XML_RPC_Response(new XML_RPC_Value(''), '-102', 'Invalid Email ID');
}
}
var client = new XMLRPCClient('rpc_server.php');
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
}
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();
}
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
/* 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:
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.