You have two ways to handle the request data received from the client using the xmlrpc func- tions. The first is a more manual method where it is up to the developer to decode the data, handle the method call, and create the resulting response document. The second method involves creating an XML-RPC server, registering methods, and allowing the server to process the raw request and create the resulting document. In both cases, the result document must be returned to the client.
The example server uses the second method to service the client. Before I get to that, I will provide an overview of the functions that can process a request using the first method, as well as show how you can access the request in the first place. Understanding these functions will at least provide you with the flexibility of being able to create a server in either scenario depend- ing upon your needs.
Retrieving the Request Data
The request is sent using an HTTP POST to the server. It is not passed as a parameter, so trying to retrieve it using the $_POSTsuperglobal is out of the question. It is strictly raw data. In some cases, it can be retrieved using $HTTP_RAW_POST_DATA. This, however, depends upon INI settings and is not always available. A much more universal and less memory-intensive way to get the data exists. Using streams, the URI php://inputgrants you access to this raw data. Within your XML-RPC server script, you would read the data just as you would a normal file:
$raw_post_data = file_get_contents("php://input");
No matter which method you choose to handle a request, you still need to retrieve the raw POST data. This chapter, ensuring that the data is available for any PHP server setup, will use only the streams functionality to retrieve this data.
Manually Handling a Request
Now that you have the raw request, you need to do something with it. The first step is to decode the request structure. You require the method that needs to be executed here, so the xmlrpc_decode()function is out of the question, since it will return only the parameters for the method. The xmlrpc_decode_request()function is basically an extended version of the function, because the return values are still the parameters of the request; however, this func- tion also takes an additional parameter as input, which is passed by reference, and contains the name of the requested method once the xmlrpc_decode_request()function has success- fully returned:
$method_name = "";
$decoded = xmlrpc_decode_request($raw_post_data, $method_name);
Using the raw POST data from the previous section, calling this function will return the parameters to be passed, which are then set to the $decodedvariable. After calling this func- tion, the variable $method_namewill now contain the name of the method being requested.
This is the value set within the methodNameelement of the request.
Assuming that the requested function (the one named in the $method_namevariable) exists and is publicly available for remote requests, you now need to call the function. It is up to you how you would like to handle the function call. Some possible methods are using the call_user_func()or call_user_func_array()function, which could also be tested prior to the actual call using the is_callable()function to ensure that the function can be called in the first place. In any event, the response document you need to create depends upon whether the function call was successful or an error condition was encountered.
The same function used to create the request, xmlrpc_encode_request(), is used to create the response. This may seem a little odd because previously you saw that this function created the entire request document, but the response document has a different structure. The differ- ence in its usage lies with the first parameter. Passing in NULLas the method parameter causes this function to generate the methodResponsestructure. For example:
$response = xmlrpc_encode_request(NULL, $retVals);
Unlike when creating a request, you can pass any of the XML-RPC data types as $retVals.
A response contains only a single paramelement, so wrapping $retValsin an array will cause only the paramto be an array that contains each of the $retValsas items, rather than creating multiple paramelements.
But how are errors returned from the server? This is actually quite simple. You can create a fault structure by creating an associative array containing a faultCodekey and a faultString key, with their values being the value that should be set as the content of the element when serialized into XML format. For example, suppose the request method does not exist, and you have defined code 500 to designate this error:
$arFault = array('faultCode'=>500, 'faultString'=>'Unknown Method Requested');
The $arFaultarray is then passed as the data to be passed back to the client:
$response = xmlrpc_encode_request(NULL, $arFault);
If you were to look at the resulting $responsestring, the document would look like the following:
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>500</int></value>
</member>
<member>
<name>faultString</name>
<value><string>Unknown Method Requested</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
The only thing left to do, whether the response contains a return value or a fault, is to return the data to the client. The easiest way to do this is when running the server within a Web server.
The response just needs to be echoed. Remember, though, that the data needs to be identified as XML, so you must properly set the Content-Typeheader:
header('Content-Type: text/xml');
echo $response;
Using a Server to Handle a Request
You can write an XML-RPC server much more easily using the extension’s server rather than having to do everything manually. The server in this case is a PHP resource that allows the reg- istration of functions that are automatically called when a request document is passed to the server. The server then also automatically creates the entire response document based on the return value of the called function. From this description alone, you probably already have the feeling that this is going to be much easier than having to manually perform all of the opera- tions yourself.
The following creates a server using the xmlrpc_server_create()function and subse- quently destroys it using the xmlrpc_server_destroy()function:
/* Create XML-RPC server */
$rpcserver = xmlrpc_server_create();
/* Destroy XML-RPC server */
xmlrpc_server_destroy($rpcserver);
Being a resource, it is not required that the server be destroyed; although it is automati- cally cleaned up once PHP has finished serving the request, it is often good practice to do so anyway.
Once you have a hold of a server, you need to register the functions to be served using the xmlrpc_server_register_method()function. The function takes three parameters: the server resource itself, the public name of the function called by the clients, and the internal name of the function, which is the function definition. For instance, using the server just created, $rpcserver, the following code maps the internal function buy_stock()to a publicly identifiable method named stockPurchaseand registers it with the server:
xmlrpc_server_register_method($rpcserver, "stockPurchase", "buy_stock");
This function returns a Boolean indicating whether the function was registered success- fully. Functions that are registered must conform to the standard prototype used for callbacks.
For example:
mixed function_name(string method_name, array args, mixed user_data)
So, based on this prototype, you would define the buy_stock()function as follows:
function buy_stock($method_name, $args, $user_data) { . . . }
You would then reference this function from an XML-RPC request using the method name stockPurchase.
Once you have defined and registered all functions with the server, all that is left to do—
once a request is made, of course—is access the raw post data, have the server process this data, and finally return the results from the processing. As mentioned earlier in this chapter, the best way to access the raw post data is by using PHP streams:
$request_data = file_get_contents('php://input');
This data is then passed to the server for processing by means of the
xmlrpc_server_call_method()function. This function takes the raw post data, parses the request, calls the proper function, and returns the resulting response document:
mixed xmlrpc_server_call_method(resource server, string xml,
mixed user_data [, array output_options])
The parameters are pretty much straightforward. The serveris the XML-RPC server that has been created. The request data is passed to the xmlparameter. The user_dataparameter allows data to be passed to the function being called. Whatever is passed to this parameter is passed directly to the called function as its user_dataparameter. The last parameter is the same as the output_optionsparameter defined earlier in this chapter. It gives you control over how the resulting response document is created.
You may be curious as to why this function can return mixed results. In most cases, the return value will be a string containing the XML-RPC response. One output_optionoption, output_type, was not covered earlier in this chapter. The default value for this option is the value xml. It is also possible to specify the value php, which causes the results to be returned as native PHP data types and ignores types not native to PHP. (I intentionally omitted this option because the XML-RPC discussed in this chapter is written based upon the formal specifications for the greatest interoperability. Everything contained in this chapter deals strictly with the xml output_type.)
The response document is created based on the return value of the function it calls to service the request. One special case exists when the returned data alters the response struc- ture, and that is when a fault is created. Simply returning an associative array containing the keys faultCodeand faultStringcauses the XML-RPC server to create a fault structure using the values for these items as the contents of the fault elements. For example, a function called by xmlrpc_server_call_method()and returning the array array('faultCode'=>100,
'faultString'=>'Function Error Message')results in the following fault:
<?xml version="1.0" encoding="utf-8"?>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>100</int></value>
</member>
<member>
<name>faultString</name>
<value><string>Function Error Message</string></value>
</member>
</struct>
</value>
</fault>
■ Tip Remember to set the Content-Typeheader to text/xmlprior to returning the resulting response document.
Putting this all together, you can create a server to service the request from the client cre- ated in the previous section. It defines two functions, buy_stock()and sell_stock(), that are registered with an XML-RPC server. The only two stocks, defined in the $arStocksarray, that can be used within these functions are Yahoo (YHOO) and Google (GOOG). The following is the complete code for the server, referenced as the file stocktrader.phpby the client. I wrote it to run within a Web server because it leverages the header creation performed by the Web server.
<?php
/* Stocks available to be traded */
$arStocks = array('YHOO'=>'Yahoo!', 'GOOG'=>'Google');
/* Function that performs the actual stock purchase */
function buy_stock($method_name, $args, $app_data) { if (! is_array($args) || count($args) <> 3) {
return array('faultCode'=>-2,
'faultString'=>'Invalid Number of Parameters');
}
$userid = $args[0];
$symbol = $args[1];
$quantity = $args[2];
if (array_key_exists($symbol, $GLOBALS['arStocks'])) {
return "Bought $quantity shares of ".$GLOBALS['arStocks'][$symbol];
} else {
return array('faultCode'=>-1,
'faultString'=>"Stock Symbol $symbol cannot be traded");
} }
/* Function that performs stock sale */
function sell_stock($method_name, $args, $app_data) { if (! is_array($args) || count($args) <> 3) {
return array('faultCode'=>-2,
'faultString'=>'Invalid Number of Parameters');
}
$userid = $args[0];
$symbol = $args[1];
$quantity = $args[2];
if (array_key_exists($symbol, $GLOBALS['arStocks'])) {
return "Sold $quantity shares of ".$GLOBALS['arStocks'][$symbol];
} else {
return array('faultCode'=>-1,
'faultString'=>"Stock Symbol $symbol cannot be traded");
} }
$request_xml = file_get_contents("php://input");
/* Create XML-RPC server, and register the functions */
$xmlrpc_server = xmlrpc_server_create();
xmlrpc_server_register_method($xmlrpc_server, "stockPurchase", "buy_stock");
xmlrpc_server_register_method($xmlrpc_server, "stockSale", "sell_stock");
/* Set content type to text/xml */
header('Content-Type: text/xml');
/* Process the XML-RPC request */
print xmlrpc_server_call_method($xmlrpc_server, $request_xml, array());
?>
The only portions of this example I expect you to have questions about are the functions written to provide the requested functionality. Because arguments are passed as an array to your functions, a simple check ensures it is an array, and the correct number of parameters is passed. If the client does not send exactly three, the functions issue an error stating this. This error is then returned to the calling client. The other error condition arises if the requested stock is not one of your supported stocks. If you look at the earlier client example, you will see the request to purchase 50 shares of Microsoft stock (MSFT). This symbol is invalid and causes the fault with a faultCodeof -1to be returned to the client.