Build your own Web Service with PHP and XML-RPC

One of the Internet’s current hot topics is Web Services, introduced by Kevin Yank in his article Web Services Demystified.

If you’ve been following industry news on the subject, you may think this is some high level technology, of interest only to big companies with huge budgets. But if you did, you’d be wrong.

The concepts behind Web Services are remarkably simple, and in this article we’ll be taking a deeper look at what’s involved. Then, with a little help from our good friend PHP, we’ll set up our first Web Service.

We will assume some knowledge of PHP, MySQL and XML, which — if you’re uncertain of — you can quickly pick up from Building your own Database Driven Website using PHP & MySQL and XML – An Introduction.

So here’s what’s on the menu:

The Basics of Web Services: To start with we’ll be quickly reviewing the basic concepts behind Web Services.

Introducing XML-RPC: Next, we’ll introduce you to an XML standard for the exchange of data between systems, and we’ll put it into context with Kevin’s article.

PHP XML-RPC Implementations: Then we’ll review some of the Open Source implementations of XML-RPC in PHP: code you can use to quickly build your own Web Service, or access other Web Services from your site.

Your first Web Service: If you want to get straight down to business, this is the place to be. Here, we’ll take one of the implementations described previously, and build a Web Service for it in PHP.

What you can do with XML-RPC: Wondering what to do next? We’ll give you some ideas for what you could do with XML-RPC and Web Services.

Let’s get started!

The Basics of Web Services

The first thing to understand about Web Services is they’re not really anything new. If you’ve ever used an RSS Feed to take news from another Website and place it on your own, you’ve already got a good idea of how Web Services work (see Kevin Yank’s article: PHP and XML: Parsing RSS 1.0).

Web Services are about exchanging data between a server and a client, using a standard XML format to "package" requests and data so that both systems can "understand" each other. The server and the client could both be Web servers, or any other electronic device you care to think of.

Network-wise, data exchange in a Web Service typically happens via TCP port 80, using standard HTTP protocol POSTs. Put another way, Web Services operate in basically the same way your browser does when it POSTs an HTML form to a site, and receives a Web page in response. The only real difference is that, instead of HTML, Web Services use XML. And this means Web Services can be available anywhere on the Internet, passing through firewalls the same way viewing a Web page does. The data exchange happens at the packaging layer.

On top of the data exchange, you also need information that describes the interface (or Application Program Interface – API) to the service. This makes the Web Service useful to the rest of the Internet, allowing other developers to develop programs that can access your Web Service. This is called the description layer, and the WSDL (Web Service Description Language) standard that will make this happen is under development.

Above that, there’s information that describes the nature of the service itself (not unlike the HTML-descriptive META tags), so that it can be categorised and found on sites that offer Web Service directories. This is the discovery layer, which is currently being addressed by the UDDI (Universal Description, Discovery and Integration) standard.

Both the description and discovery layers are simply XML, governed by a particular format that enables relevant information to be found for all Web Services on the Internet.

Perhaps what’s made Web Services a hot topic recently — aside from marketing by the likes of Microsoft and IBM — is the development of these standards. They’ll allow Web Services to be rolled out en masse across the Internet, backed by development tools that’ll make access to them both predictable and easy.

But it should be remembered that everything a Web Service does now, in terms of data exchange, could also have been done 5 or even 10 years ago using the HTTP standard and whichever XML format you chose to use or invent (RSS feeds being a prime example). The "hot news" today is that building and distributing a Web Service is now a lot easier than in the past.

Anyway, if you’re looking for a hype-free news source for the technological development in Web Services, try XML Hack.

We’ll skip further generalities, but suffice it to say that this article focuses on the packaging layer, i.e. how you build and access a Web Service.

Introducing XML-RPC

In "Web Services Demystified", Kevin looks at a standard called SOAP, which is used to "package" data for exchange between two systems. SOAP is on its way to being the W3C standard for Web Services, and with backing from Microsoft and IBM will probably get the official stamp very soon.

But another standard exists for the same purpose: XML-RPC. It’s been around since 1998, and although it’s not an official W3C standard, is already widely used, and has impressive support in the form of Open Source projects. Developed by Useful Inc. in conjunction with Microsoft, it can be regarded in many ways as the forerunner to SOAP, which hasn’t stopped running quite yet.

As this article will demonstrate, the biggest thing XML-RPC has going for it is simplicity, which is clear from the start when you compare the 1500 word specification with SOAP’s 11,000 word (and growing) bible. The other good news is that XML-RPC is well supported in PHP, along with implementations in just about any other language you could desire.

For a detailed comparison of XML-RPC and SOAP, I’d recommend reading XML-RPC vs. SOAP. In brief, SOAP is intended to fill the gaps in XML-RPC, offering a truly enterprise-level mechanism for the exchange of data between systems. For example, it should be easier to deliver multi-dimensional arrays through SOAP than through XML-RPC. But there’s nothing wrong with using XML-RPC, especially given that it’s a stable standard, while SOAP may undergo further re-definition in the future.

You might wonder whether it’s worth putting the effort into learning about XML-RPC if it’s about to be superseded by another standard. Worry no more: you can easily transform a SOAP request to an XML-RPC request using XSLT, which will allow your XML-RPC to survive in a messy Internet where everyone’s looking for the SOAP (apologies — but one pun had to be made).

So how does XML-RPC work? As you’ll see later in this article, you don’t really need to know too much about it to be able to use it. But there’s plenty of information out there that describes it, and a worthwhile read is XML-RPC for Newbies. We’ll take a quick look at how XML-RPC works now, and then move on to how we can make use of it.

The two main components of an XML-RPC "message" are methods and parameters. Methods correspond loosely to the functions you define in PHP, while parameters correspond to the variables you pass to those functions. Parameters can be one of a number of different types, such as strings, integers and arrays — very similar to the variable types you’re already used to in PHP. In addition, XML-RPC defines other tags for things like error handling, but we’ll leave that explanation to the spec as, for the most part, we’ll never need to worry about them.

An XML-RPC "conversation", between two systems bgins with a request from the XML-RPC client, which the server answers with a response. The request contains a method and perhaps some parameters required by the method. The response replies with parameters that contain the requested data. The process is very much like using a function you’ve defined in a PHP script; you call a function and pass it some variables. The function then responds by returning some variables.

Here’s what a client request might look like, as seen by the target XML-RPC server:

Request (from your XML-RPC client):

  POST /xmlrpcInterface HTTP/1.0  
 User-Agent: Sitepoint XML-RPC Client 1.0  
 Host: xmlrpc.sitepoint.com  
 Content-type: text/xml  
 Content-length: 195  
 <?xml version="1.0"?>  
 <methodCall>    <methodName>forums.getNumTodaysThreads</methodName>  
   <params>  
     <param><value><string>PHP Development</string></value></param>  
   </params>  
 </methodCall>

And here’s the response from the server, as seen by the XML-RPC client.

Response (from the XML-RPC server):

  HTTP/1.1 200 OK  
 Connection: close  
 Content-Length: 148  
 content-Type: text/xml  
 Date: Wed, Jul 28 1999 15:59:04 GMT  
 Server: Sitepoint XML-RPC Server 1.0  
 <?xml version="1.0"?>  
 <methodResponse>  
   <params>  
     <param>  
       <value><int>42</int></value>  
     </param>  
   </params>  
 </methodResponse>

The example request might be to find out how many threads have been posted in the PHP Development forum today. The response tells the client the number it asked for. This is just a simple example, sending one parameter to a method and getting one parameter back. XML-RPC provides much more, allowing you to pass the server more complicated sets of data (such as arrays), and receive in return a detailed response that contains all sorts of data.

In case you were wondering, the "RPC" in XML-RPC stands for Remote Procedure Call. The client system "calls" a "procedure" (or method) on a remote server. The concept of a Remote Procedure Call is general to software development, and one which you'll come across in many places. XML-RPC is an XML standard for data exchange to be used in a Remote Procedure Call.

XML-RPC itself sits at the packaging layer of the technology stack described in Kevin's "Web Services Demystified" (and as mentioned above, it's an alternative to SOAP, which Kevin uses as an example of a packaging layer). What happens in the description and discovery layers is up to you. Some implementations of XML-RPC offer extensions that provide introspection, which would amount to the description layer providing details of the methods and parameters with which you can call an XML-RPC server. The discovery layer is missing with XML-RPC: there is no central registry of XML-RPC servers, but no doubt extensions conforming to the UDDI standard will arise when the need appears.

Enough detail of XML-RPC! Using the implementations that already exist for PHP, you don't need to get too involved with the standard itself. So let's see what you can get for free for XML-RPC and PHP...

PHP XML-RPC Implementations

Just as your hands were starting to sweat, it's now time to review a number of Open Source projects that can save you having to write your own code to generate XML-RPC requests and responses. Those reviewed here all come from the implementations page at xmlrpc.com. The two main "stress tests" in reviewing them were:

  1. how long do they take to install and use?

  2. will they be widely supported by the type of PHP configuration you'll find on an average Web host?

XMLRPC-EPI

http://xmlrpc-epi.sourceforge.net/

XMLRPC-EPI was developed originally for internal use at epinions and has been successful to the point that it now provides PHP with its experimental XML-RPC Functions. XMLRPC-EPI itself is a base class written in C++ (the rest are written in PHP), which won't be something you can readily install on your Web server if you don't have root access -- you'll need to re-compile PHP.

Aside from that, this class only deals with the interpretation of XML-RPC requests and responses, and doesn't actually send or receive the requests themselves. You'll find an early tutorial on its use here.

eZ xmlrpc

http://developer.ez.no/article/static/53/

Developed by BÃ¥rd Farstad (gotta love that name), this is the XML-RPC class used in ezPublish to receive requests from their desktop client. The design of the class is good, in that it's fairly intuitive to work with and is nicely introduced by this tutorial, plus it offers introspection.

But it fails on one major point -- it requires that the xml parser (--with-qtdom) be available and configured for use with PHP. This is going to be non-standard for the majority of PHP installations, which usually adopt the expat XML parser (--with-xml). So, sadly, we'll have to look elsewhere...

Fase 4 XML-RPC

http://www.fase4.com/xmlrpc/

Looking at the site, the documentation is good, but in installing and trying the "server.php" demonstration script results in a "class not found" error. With so many to choose from, that eliminates this class from the race.

phpRPC

http://sourceforge.net/projects/phprpc/

This class has potential. It goes beyond interpretation and request/response handling to provide functionality like connection to an "abstract" database. The current version is alpha 0.9, so for now we're going to pass on it, but it's worth keeping an eye on. If you have big projects planned, this class could save you a lot of development time.

phpxmlrpc

http://phpxmlrpc.sourceforge.net/

Developed by Useful Inc, the creators of the XML-RPC standard, this class obviously has a lot going for it. Basically it fully supports the XML-RPC standard and offers debugging as well, which, as we'll see later, can be one of your bigger problems when building a Web Service.

The documentation is good and there are plenty of examples, as well as this tutorial on how to build a client with it. But as a starting place for building your first Web Service, this class isn't ideal -- it can be recommended only to more experienced PHP developers, which is why we're going to look elsewhere...

Keith Devens' XML-RPC Client/Server

http://www.keithdevens.com/software/xmlrpc/

Visiting the project's home page, you'll quickly realise you're off to an easier start. The code is simply a set of user-defined PHP functions to include in your scripts, as opposed to a full class.

It takes away from your XML-RPC servers and clients the requirement that they work out what types of XML-RPC parameters they're dealing with. This does mean your XML-RPC client will need to be more careful in examining the data it receives, but you'll only be worried by PHP variables (which you should already be used to), not XML-RPC parameters. Also, the documentation is good. So this is where we're going to start building your first Web Service...

Your First Web Service

Here we go! Let's build a Web Service for publishing news from your Website, along the lines of an RSS feed. But rather than simply providing a list titles and links that will end up taking the user back to the original news source, our Web Service will make it look like the news lives on the site where the XML-RPC client is installed.

We'll define two XML-RPC methods for our use: "news.getNewsList", which will provide a list of news items, and "news.viewNewsItem", which will display a single news item in full. There will also be a default "method_not_found" method, in case of trouble. Each method corresponds to a function we'll define in PHP, such as a query which SELECTs news from your database.

We'll assume you have PHP 4.1.0 or later, and access to a MySQL database (version 3.23+). It's worth reading the code and examining the comments as well as checking the documentation for Keith's XML-RPC related functions.

Ok. Roll up those sleeves, and away we go...

Preparations

1. Download the code archive for this article -- it'll save you from laboriously cutting and pasting the code from the pages of this tutorial.

2. Download the source code from http://www.keithdevens.com/software/xmlrpc/source.php and save it as "kd_xmlrpc.php".

3. Now create a directory on your Web server called kd_xmlrpc, below your Web root (where your "home page" lives), and place kd_xmlrpc.php there. We'll be putting all the other files we create below in the same directory.

4. Create a table in your MySQL database using this query:

CREATE TABLE kd_xmlrpc_news (    
 news_id mediumint(9) NOT NULL auto_increment,    
 title varchar(255) default NULL,    
 short_desc text,    
 full_desc text,    
 author varchar(100) default NULL,    
 date datetime NOT NULL default '0000-00-00 00:00:00',    
 PRIMARY KEY  (news_id)    
) TYPE=MyISAM COMMENT='XML RPC News';

4. Now INSERT some dummy news items into the table -- or, if you're lacking inspiration, run the "kd_xmlrpc_news.sql" query you'll find in the code archive (downloaded in step 1).

The XML-RPC Server

The database is now ready for action. Next, let's create the XML-RPC server, and call it "server.php":

<?php    
/* server.php */    
   
/* Variables for accessing MySQL */    
$dbserver= "localhost";     // Hostname of your MySQL server    
$db = "database_name";      // Name of your MySQL database    
$dbuser = "username";       // MySQL user with access to $db    
$dbpassword = "password";   // Password for MySQL user    
   
/* Connect to the MySQL server */    
$link = @mysql_connect ($dbserver, $dbuser, $dbpassword);    
if (! $link){    
   echo ( "Unable to connect to db" );    
exit();    
}    
   
/* Select the database */    
if (!mysql_select_db ($db, $link) ){    
exit ();    
}    
   
/* Include Keith's xml-rpc library */    
include("kd_xmlrpc.php");    
   
/* Include a file that defines all the xml-rpc "methods" */    
include("web_service_api.php");    
   
/* Now use the XMLRPC_parse function to take POST    
  data from what xml-rpc client connects and turn    
  it into normal PHP variables */    
$xmlrpc_request = XMLRPC_parse($GLOBALS['HTTP_RAW_POST_DATA']);    
   
/* From the PHP variables generated, let's get the    
  method name ie. server asks "What would you like    
  me to do for you?" */    
$methodName = XMLRPC_getMethodName($xmlrpc_request);    
   
/* Get the parameters associated with that method    
  e.g "So you want to view a news item. Tell me    
  which one you want. What's the id#?" */    
$params = XMLRPC_getParams($xmlrpc_request);    
   
/* Error check - if a method was used that doesn't    
  exist, return the error response to the client */    
if(!isset($xmlrpc_methods[$methodName])){    
   $xmlrpc_methods['method_not_found']($methodName);    
   
/* Otherwise, let's run the PHP function corresponding    
  to that method - note the functions themselves    
  return the correct formatted xml-rpc response    
  to the client */    
}else{    
   
   /* Call the method - notice $params[0] not just $params as the    
   documentation states. */    
   $xmlrpc_methods[$methodName]($params[0]);    
}    
?>

Here's a quick summary of what happens within server.php:

  1. Connect to MySQL to give the script access to the database.
  2. Include the kd_xmlrpc.php code so we can use Keith's functions.
  3. Include web_service_api.php, which defines the XML-RPC methods (see below).
  4. Use the XMLRPC_parse() function to take the contents of $GLOBALS['HTTP_RAW_POST_DATA'], which contains the request from an XML-RPC client, and convert the XML into PHP variables.
  5. Now determine which XML-RPC method has been called, using the XMLRPC_getMethodName() function. We want the same XML-RPC server to be capable of more than one task (method/function), and here's where we determine which one we'll carry out for the current request (e.g "news.getNewsList" or "news.viewNewsItem").
  6. Now check for any parameters sent in the client request, such as an "id" (identifying number) of a news item.
  7. Finally we check that the method exists, as defined in web_service_api.php, and if everything looks good, we run th method (i.e. PHP function) required. If we can't find the method, we run the default "method_not_found" method.

The special XML-RPC functions we used were:

  • XMLRPC_parse(): this function takes some XML and turns it into PHP variables -- so the XML from the client's request is converted to a form that PHP can work with. At this point, this isn't specific to the XML-RPC standard -- any XML that's sent will be converted to PHP variables.
  • XMLRPC_getMethodName() determine which method is being used so we can decide which PHP function to use.
  • XMLRPC_getParams() takes any XML-RPC parameters and converts them to PHP variables.

That's basically the XML-RPC server finished. In general, this is what you'd do for every XML-RPC server you build. Not hard, was it? Now all we need are those PHP functions to be used with the methods...

The Methods

Next, we define some PHP functions that correspond to XML-RPC methods, and store them in web_service_api.php:

<?php     
/* web_service_api.php */    
   
/* Define an array to name the xmlrpc methods and      
  their corresponding PHP functions */    
$xmlrpc_methods = array();    
$xmlrpc_methods['news.getNewsList'] = news_getNewsList;    
$xmlrpc_methods['news.viewNewsItem'] = news_viewNewsItem;    
$xmlrpc_methods['method_not_found'] = XMLRPC_method_not_found;    
   
/* Now a useful function for converting MySQL datetime      
  to a UNIX timestamp which can then be used with      
  the XMLRPC_convert_timestamp_to_iso8601($timestamp) function.    
  This is not a method!    
  It comes from: http://www.zend.com/codex.php?id=176&single=1 */    
function mysql_datetime_to_timestamp($dt) {      
   $yr=strval(substr($dt,0,4));      
   $mo=strval(substr($dt,5,2));      
   $da=strval(substr($dt,8,2));      
   $hr=strval(substr($dt,11,2));      
   $mi=strval(substr($dt,14,2));      
   $se=strval(substr($dt,17,2));      
   return mktime($hr,$mi,$se,$mo,$da,$yr);      
}      
   
/* Function for listing news items, corresponding      
  to the news.getNewsList method Allows ordering by      
  column name and a result limit of up to 20 rows */    
function news_getNewsList ( $query_info=0 ) {    
   
   /* Define an array of column names we'll accept      
  to ORDER BY in our query */    
   $order_fields = array ( "author", "title" );    
   
   /* Now check to see if $query_info['order'] has      
   an acceptable value and assign the correct value      
   to the $order variable */    
   if ( ISSET ( $query_info['order'] ) &&    
           in_array ( $query_info['order'], $order_fields ) ) {    
       $order = "ORDER BY " . $query_info['order'] . ", date DESC ";    
   } else {    
       $order = "ORDER BY date DESC ";    
   }    
   
   /* Now check for $query_info['limit'] to specify      
   the number of news items we want returned,      
   and assign the correct value to $limit */    
   if ( ISSET ( $query_info['limit'] ) && $query_info['limit'] < 20 ) {    
       $limit = "LIMIT 0, " . $query_info['limit'] . " ";    
   } else {    
       $limit = "LIMIT 0, 5 ";    
   }    
   
   /* Now build the query */    
   $query = "SELECT * FROM kd_xmlrpc_news " . $order . $limit;    
   $sql = mysql_query ( $query );    
   if ( $sql ) {    
       $news_items = array();    
       while ( $result = mysql_fetch_array ( $sql ) ) {    
   
           /* Extract the variables we want from the row */    
           $news_item['news_id'] = $result['news_id'];    
           $news_item['date'] = XMLRPC_convert_timestamp_to_iso8601(    
               mysql_datetime_to_timestamp( $result['date'] )    
               );            
           $news_item['title'] = $result['title'];    
           $news_item['short_desc'] = $result['short_desc'];    
           $news_item['author'] = $result['author'];    
   
           /* Add to the $news_items array */    
           $news_items[] = $news_item;    
       }    
   
       /* Convert the $news_items array to a set      
       of XML-RPC parameters then respond with the XML. */    
       XMLRPC_response(XMLRPC_prepare($news_items),      
       KD_XMLRPC_USERAGENT);    
   } else {    
   
       /* If there was an error, respond with an      
       error message */    
       XMLRPC_error("1", "news_getNewsList() error: Unable      
       to read news:"    
           . mysql_error() . "nQuery was: " . $query,      
KD_XMLRPC_USERAGENT);    
   }    
}    
   
/* Function for viewing a full news item corresponding      
  to the news.viewNewsItem method */    
function news_viewNewsItem ( $news_id ) {    
   
   /* Define the query to fetch the news item */    
   $query = "SELECT * FROM kd_xmlrpc_news WHERE news_id = '"      
   . $news_id . "'";    
   $sql = mysql_query ( $query );    
   if ( $result = mysql_fetch_array ( $sql ) ) {    
   
       /* Extract the variables for sending in      
       our server response */    
       $news_item['news_id'] = $result['news_id'];    
       $news_item['date'] = XMLRPC_convert_timestamp_to_iso8601(    
           mysql_datetime_to_timestamp( $result['date'] ) );          
       $news_item['title'] = $result['title'];    
       $news_item['full_desc'] = $result['full_desc'];    
       $news_item['author'] = $result['author'];    
   
       /* Respond to the client with the news item */    
       XMLRPC_response(XMLRPC_prepare($news_item),      
       KD_XMLRPC_USERAGENT);    
   } else {    
   
       /* If there was an error, respond with a      
       fault code instead */    
       XMLRPC_error("1", "news_viewNewsItem() error: Unable      
to read news:"    
           . mysql_error(), KD_XMLRPC_USERAGENT);    
   }    
}    
   
/* Function for when the request method name      
doesn't exist */    
function XMLRPC_method_not_found($methodName){    
   XMLRPC_error("2", "The method you requested, " . $methodName    
       . ", was not found.", KD_XMLRPC_USERAGENT);    
}    
?>

What happened here?

  1. First we defined a PHP array $xmlrpc_methods, which is a list of all the available XML-RPC methods and the PHP functions to run for that method.
  2. The mysql_datetime_to_timestamp() function is not an XML-RPC method itself, but rather a useful function to convert MySQL datetime fields to a format PHP can work with.
  3. Then we define news_getNewsList(), which we'll use to query the "kd_xmlrpc_news" table. This function is capable of accepting an array called $query_info, which will correspond to optional parameters that the XML-RPC client can send. As you can see, this alters the query we run against mysql, changing the "ORDER BY" and "LIMIT" options.
  4. The function news_viewNewsItem() is used to pull a single news item from the database, and accepts only one variable/parameter: $news_id, which corresponds to the row id of the article in the database.
  5. The last function, XMLRPC_method_not_found(), is our default function, which generates a response when the client request is for an XML-RPC method that didn't exist.

The special XML-RPC functions we used were:

  • XMLRPC_convert_timestamp_to_iso8601(): the XML-RPC has a specified format for sending dates and times. This function takes a PHP timestamp and converts it to the correct format for XML-RPC.
  • XMLRPC_prepare(): this takes a set of PHP variables and turns it into XML-RPC parameters. It identifies the type of variable being used and creates the correct XML-RPC output from it. This is one of the things that make Keith Devens' code friendly to use -- the other implementations generally require that you use a separate function for arrays, integers, strings and so on, when building the XML-RPC request of response.
  • XMLRPC_response(): is the function you use to provide the results to the client, on request. It takes two values -- the first being the XML-RPC data (built by XMLRPC_prepare()), and the second, an optional name for the server or a user agent e.g. KD_XMLRPC_USERAGENT -- this can be whatever you like.
  • XMLRPC_error(): generates an XML-RPC response using the error reporting tags defined in the specification. Should you desire, you could build your own set of error codes for your server here, which can help a lot when it comes to debugging the server.

Phew! So now we've defined our PHP functions, and have an array to translate them to XML-RPC methods: our XML-RPC server is ready to go! Now, to finish off, let's put together a simple client to access our news.

The XML-RPC Client

For the sake of example, we'll store the client script in the same place as the server, but if you have another Web server, you could place the script there, making sure it has access to "kd_xmlrpc.php", the functions library.

<?php      
/* Include the library */      
include ( "kd_xmlrpc.php" );      
     
/* Define variables to find the rpc server script */      
$site = "www.yourdomain.com";      
$location = "/kd_xmlrpc/server.php";      
     
/* Function to give us back a nice date */      
function convert_date ( $date ) {      
   $date = date ( "D M y H:i:s",      
                 XMLRPC_convert_iso8601_to_timestamp ( $date ) );      
   return ( $date );      
}      
?>      
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">      
<html>      
<head>      
<title> KD XML RPC News Client </title>      
<meta name="Generator" content="EditPlus">      
<meta name="Author" content="HarryF">      
<meta name="Keywords" content="XML RPC">      
<meta name="Description" content="Gets news form server.php">      
</head>      
<body>      
<?php      
     
/* client.php */      
/* If user is viewing a single news item, do this */      
if ( ISSET ( $_GET['news_id'] ) ) {      
     
   /* $success is 0 (fail) / 1 ( succeeded ).      
      XMLPRC_request preforms the XML POST to      
      the server script, calling the method and      
      sending the correct parameters using      
      XMLRPC_prepare */      
   list($success, $response) = XMLRPC_request(      
       $site,      
       $location,      
       'news.viewNewsItem',      
       array(XMLRPC_prepare($_GET['news_id']),      
       'HarryFsXMLRPCClient')      
   );      
     
   /* If all went well, show the article */      
   if ($success) {      
?>      
<table align="center" width="600">      
<tr valign="top">      
<th colspan="2"><b><?php echo ( $response['title'] );?></b></th>      
</tr>      
<tr valign="top">      
<th><?php echo ( $response['author'] );?></th>      
<th><?php echo ( convert_date ( $response['date'] ) );?></th>      
</tr>      
<tr valign="top">      
<td colspan="2">      
<?php echo ( nl2br ( $response['full_desc'] ) );?>      
</th>      
</tr>      
</table>      
<?php      
     
   /* Else display the error */      
   } else {      
       echo ( "<p>Error: " . nl2br ( $response['faultString'] ) );      
   }      
} else {      
     
   /* Define the parameters to pass to the XML-RPC      
   method as a PHP array */      
   $query_info['limit'] = 10;      
   $query_info['order'] = "author";      
     
   /* XMLRPC_prepare works on an array and      
   converts it to XML-RPC parameters */      
   list($success, $response) = XMLRPC_request(      
       $site,      
       $location,      
       'news.getNewsList',      
       array(XMLRPC_prepare($query_info),      
       'HarryFsXMLRPCClient')      
   );      
     
   /* On success, display the list as HTML table */      
   if ($success) {      
       echo ( "<table align="center" width="600">n" );      
       $count = 0;      
       while ( list ( $key, $val ) = each ( $response ) ) {      
?>      
<tr valign="top">      
<td colspan="2">      
<a href="<?php echo ( $_SERVER['PHP_SELF'] );?>?news_id=<?php      
echo ( $response[$count]['news_id'] );      
?>">      
<?php echo ( $response[$count]['title'] ); ?>      
</a>      
</td>      
</tr>      
<tr valign="top">      
<td colspan="2">      
<?php echo ( $response[$count]['short_desc'] ); ?>      
</td>      
</tr>      
<tr valign="top">      
<td>      
<?php echo ( $response[$count]['author'] ); ?>      
</td>      
<td>      
<?php echo ( convert_date ( $response[$count]['date'] ) ); ?>      
</td>      
</tr>      
<?php      
           $count++;      
       }      
       echo ( "</table>n" );      
     
   /* Or error */      
   } else {      
       echo ( "<p>Error: " . nl2br ( $response['faultString'] ) );      
   }      
}      
;?>      
</body>      
</html>

What happened here? The PHP code itself is simply an if/else structure, so that if we decide to look at a single news item using the $_GET['news_id'] variable, we display that item. Otherwise, we display the list of news items.

For the news list, we use the news.getNewsList method. Notice that we've set the $query_info['limit'] to 10 (i.e. we want 10 news items maximum) and $query_info['order'] is set to "author" (sort the news items by author name). These will become parameters that will be sent in the XML-RPC client request.

When we view a single news item, we'll turn the $_GET['news_id'] into a parameter, by which the server will be able to identify the news item we want.

The special XML-RPC functions we used here were:

  • XMLRPC_prepare(): We've used this before in the server, again to turn PHP variables into XML-RPC parameters. This is where we provide the server with the data it needs for the particular method we're using.
  • MLRPC_request(): This function accepts five variables and returns an array. It's basically what makes your client a client. Accepted are:
    • $site - the domain name of the server
    • $location - the path on the server from the Web root to the server script
    • $methodName - the name of the XML-RPC method we're invoking
    • $params - any variable required for the method
    • $user_agent - which is optional and can be anything you like (e.g. HarryFsXMLRPCClient ).

    The returned array contains two variables. The first is either a 0 for a failure, or a 1 for a successful response to the request, which we use for error checking. The second is a multidimensional array that contains all the data from the response.

  • -XMLRPC_convert_iso8601_to_timestamp: with our convert_date() function at the top, we use this special function to change the XML-RPC date format into something that looks nice, via an intermediate PHP timestamp.

One tip: for writing XML-RPC clients, it can be helpful to think of the server in the same way that you'd think of a database -- it's just a source of data. In terms of your scripts, the code will be a similar structure to that which you'd use when querying MySQL.

So that's it! Point your browser at http://www.yourdomain.com/kd_xmlrpc/client.php, sit back and read the news.

Download the Code: the scripts used in this tutorial and the MySQL table are all in this zip file.

Wrap Up

As you've seen, using Keith Devens' XML-RPC code, it's pretty easy to create your own Web service. It makes an excellent starting place to come to grips with Web Services. Perhaps as "homework", you might want to try updating the client to allow visitors to choose how they want news items ordered (i.e. by author, title or date). And if you're feeling ambitious, perhaps you might even consider adding an interface to the client for INSERTing or UPDATEing news items.

If you've got big plans for Web Services, you may want to look at some of the other implementations, such as Userland's phpxmlrpc class, to optimise your code design. But you can definitely get by with Keith Devens' implementation, achieving the same end results as with any other solution.

There are one or two minor shortcomings in Keith's code that you should be aware of.

First, the XMLRPC_request() function used by a client removes the "knowledge" stored in the XML-RPC response of what type each variable is, which may lead extra coding for the client. For example, you might expect an array, but get back just a scalar (single valued) variable -- the client needs to deal with both circumstances.

Also, there's no extension for introspection, to allow other developers to see your API (i.e. what methods and parameters your server accepts). It shouldn't be too hard to add your own introspection though -- just use the array we defined in web_service_api.php.

Finally, there's only limited provision for debugging. If your only interface is the client, you can encounter a lot of headaches when it comes to working out what your XML-RPC server is doing. You'll probably want to think about generating some kind of log file for the server, using PHP's custom error handling -- have a look at this article to get started with custom error handlers.

But thanks to Keith Devens for some great code, and for getting us off to an excellent start!

There are a couple of issues we haven't looked at here: security and transaction processing.

If you'd like some fear and loathing, try this article. There are three main issues to be aware of in regard to security:

  1. How you authenticate XML-RPC clients. If you added the methods for INSERTing and UPDATEing the news above, you'll also need to find a way to authenticate the remote site and/or the users of that site. At the moment, how you do that is up to you. You might check the remote site using an IP-address or hostname. For remote users, one approach is to think of your XML-RPC server the same way as you'd check users against a database, and require a username and password combination before you llow access to sensitive methods, using a method purely for authentication. Ideally, if you're doing that, you'll encrypt the connection between client and server using SSL but that will require use of PHP's Curl functions to send and receive the data (you may need to re-write parts of the implementation you're using to do this).
  2. How will you control "denial of service" attacks? The more general question is: what happens when a client floods your server with requests and prevents it from responding to other clients?
  3. What controls do you place over publishing your API (introspection/WSDL) at the description layer? Is it a good idea to publish all methods for your XML-RPC server, so that everyone can "have a go"? How do you control who sees what? If your methods provide access to sensitive data that's usually kept safe behind your firewall, you need to think carefully.

In regard to transaction processing, what happens if you lose connection between client and server during an XML-RPC conversation? What mechanisms do you provide to handle retries (re-sending a request or response)? It may be that you can't afford to lose data for the Web Service you're running, in which case you need to consider some mechanism for queuing requests and responses. For an introduction to the principles of transaction processing, try this article.

Worries aside, you've now built your first Web Service and hopefully gained a good understanding of how the technology works. The question now is what to do with it? Publishing news is fine but it's been done before. What else is there? We'll look at that in the next section...

What can you do with XML-RPC?

This question's a bit like asking "what can I do with a Web page?" The only limit is your imagination. What you're capable of with XML-RPC and PHP in general is to extend your Website beyond the bounds of simply serving Web pages. Your site will be able to communicate data to any system you like, and by adopting and accepted standard, you provide an interface that other developers will be happy to work with.

Web Services in the broadest sense will make money for those who own "valuable data". For example, if you have direct access to currency exchange rates, you could use PHP and XML-RPC to deliver a currency converter to other Websites. But here are some other ideas and examples:

  • Provide an alternative client for updating your Website. Ever screamed in frustration as you typed a long message into an HTML textarea, clicked "Submit" -- and lost it all because your session timed out, or you lost connection to the Website? XML-RPC can:
    • provide you with an interface to update your site,
    • allow you to run a standard Windows client so you could save your input locally on your hard disk, and
    • update the Website when the time is right.

  • ez Systems have done exactly that, providing a Desktop Edition to their ezPublish content management system.
  • Correct spelling on your Website with Stuffed Dog's XML-RPC Spell Checker.
  • Distribute load and bandwidth use by running your database on one Web server, then have multiple servers providing the user interface, accessing the database over XML-RPC. For Massively Multiplayer Games like Planetarion, the possibilities are endless.
  • Get your PHP site talking to a CGI/Perl or ASP site using one of the many XML-RPC implementations, which allow you the option of preserving old code when you upgrade to PHP.
  • Make use of the Google Search Gateway, allowing search results to appear directly on your site.
  • POP your email from all over the place, then read it all from one XML-RPC source with xr2pop CGI server (OK -- Perl, not PHP -- but it's a nice example).
  • Get all kinds of news from News is Free using their XML-RPC Server.

The possibilities are endless...

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • ahad rafiee

    very good

  • Rahul

    very nice article..

  • mani

    POST /xmlrpcInterface HTTP/1.0
    User-Agent: Sitepoint XML-RPC Client 1.0
    Host: xmlrpc.sitepoint.com
    Content-type: text/xml
    Content-length: 195

    forums.getNumTodaysThreads

    PHP Development

  • http://www.thefivefinger.com/ fivefinger

    So nice tutorial. Nicely explained in simple word.

  • http://www.inemiliaromagna.net David

    I was looking for info about webservices and finally I found it!

    Thanks a lot,
    David