14. XML-RPC

“Does distributed computing have to be any harder than this? I don't think so.”

—Byte Magazine

Every once in a while, something comes along that changes the way you think about work. XML-RPC is one of the coolest technologies I've explored in a long time. XML-RPC is a specification to allow software on one computer to call remote procedures (RPCs) on another computer, regardless of platform. The transport mechanism is HTTP, and the encoding mechanism is XML.

XML-RPC was created by Dave Winer of UserLand Software, Inc. and is a simpler alternative to using SOAP, discussed in Chapter 15, “SOAP.” Both are considered “Web Services”—XML-RPC being the first. Although both enable complex data structures to be transmitted, processed, and responded to, using XML-RPC is just a tad simpler. XML-RPC is an excellent implementation to base your client/server project because there is no fluff. It does what it does very well.

XML-RPC can be implemented in any of a number of programming languages on just about every available platform. You can choose C/C++ (including Carbon), Frontier, Java, Lisp, Objective-C (including Cocoa), Perl, PHP, Python, BASIC, Tcl, and many more. This offers a lot of flexibility to the programmer looking for a robust solution with minimal limitations.

Both XML-RPC and SOAP are useful when you need to implement a data-based client/server architecture. Although XML-RPC and SOAP are “Web Services”, normally they will not return fully formatted HTML, but instead just the raw data. Remember, you don't know what format your client might be—it might be a Web browser, it might be a native application with a Mac OS X or Windows GUI, or it might be a cell phone. Therefore, your server should return data only, leaving the formatting of the data to the client. This is a perfect use for XML. Figure 14.1 shows this concept.

Figure 14.1. A client and server via XML-RPC.

image

The official XML-RPC Web site can be found at http://www.xmlrpc.org/. It has extremely detailed information on the protocol that we will use in this chapter.

The Result

The project in this chapter is the same project we will use in Chapter 15. One thing I found while learning about Web services is that it was very difficult to find concrete examples that accepted multiple parameters in and delivered multiple parameters out—like the real-world. By showing a single example using XML-RPC and SOAP as well as both PHP and Objective-C, I hope this will take some of the mystery out of these exciting protocols.

This project shows how to create an XML-RPC server written in PHP and running under the Apache Web server. We then create a PHP client as well as Cocoa Objective-C client. This shows that one server can handle multiple clients using multiple implementation mechanisms. Although the Cocoa client will only run under Mac OS X, the PHP client can be used on any computer with a Web browser.

My example is a product information system. We sell many different styles of bags in a rainbow of colors—everything from book bags to handbags to grocery bags and more. Using one of our clients, the user simply picks a type of bag and a color, and the server returns the price and number of bags currently in stock that matches those criteria. For the purposes of this demo, any bag in the color brown is out of stock, whereas all other colors return a random stock value. Normally, you would pull this information from a database, but this is left as an exercise to the reader.

The PHP client is served via Apache and is essentially a Web form. The client accepts data from the user, sends the request to the server, formats the resulting data appropriately, and displays it in a Web browser.

Figure 14.2 shows the PHP client running in Internet Explorer under Mac OS X although it could run in any modern Web browser. The user is presented with a form that allows her to choose which type of bag and which color she wants price and stock information for. Once chosen, she clicks the Get Product Information button, which passes the form data on to the server.

Figure 14.2. The PHP XML-RPC client before submitting a query.

image

Once the server receives and processes the data, it returns the resulting data (or an error) to the client. The client is then responsible for displaying the results to the user. Figure 14.3 shows our client displaying the original XML-format query, or message, the formatted results, and then the details of the response from the server that were parsed to produce the formatted results. Normally you wouldn't display the XML-formatted information to the user, but it is very helpful to see the complete picture when you're learning something new.

Figure 14.3. The PHP XML-RPC client after submitting a query and receiving the results.

image

The Cocoa client, shown in Figure 14.4, works similarly in that it collects data from the user and then contacts the server to perform the price and stock lookup. However, after it receives and parses the response, it displays it within its own window differently than the previous Web browser example. It still uses HTTP as the transport mechanism, which is standard for XML-RPC, but does not display the results using HTML.

Figure 14.4. The Cocoa XML-RPC client after submitting a query and receiving the results.

image

Let's look at how this is all put together.

The PHP Server

First, we will look at the server side. As mentioned, the server is written in PHP but can be written in almost any language. We chose PHP because it is reliable, straightforward, and powerful. Our product_server.php file is served by the Apache Web server running under Mac OS X just as any standard PHP file would be. The client accesses it via a standard HTTP URL. In this case, we have placed the file at http://www.triplesoft.com/xmlrpc/product_server.php. However, typing that URL into your Web browser will simply produce an error because XML-RPC servers require specifically formatted information passed by the client. Figure 14.5 shows the fault information returned by the server.

Figure 14.5. The PHP XML-RPC server being called directly from the Web browser.

image

An important thing to note is that both the PHP server and client require the use of a third-party PHP library by Edd Dumbill. The XML-RPC for PHP library and detailed information on its use can be found at http://www.usefulinc.com/. This library contains two include files, xmlrpc.inc and xmlrpcs.inc, that are easily included in your PHP source. The distribution also contains documentation and examples as well as licensing information.

Let's look at the complete PHP source code of the server and then I will explain it in sections.

Listing 14.1 contains the complete PHP XML-RPC server code. This simple server offers a concrete example of the protocol accepting multiple parameters and returning multiple parameters, which is most likely what you will need in a real-life scenario.

Listing 14.1. product_server.php



<?php
include("xmlrpc.inc");
include("xmlrpcs.inc");

// Array of products and prices, in real-life you might use a SQL database
$products = array (
    "Grocery Bag" => "9.00",
    "Book Bag" => "18.00",
    "Hand Bag" => "32.00",
    "Garment Bag" => "48.00",
    "Old Bag" => "0.31"
    );

// Signature of this RPC
$products_signature =
    array(array($xmlrpcArray,     // Returns a product price and quantity
    $xmlrpcString,                // Accepts a product name
    $xmlrpcString));              // Accepts a color

// Documentation string of this RPC
$products_docstring = "When passed valid product info, more info is returned.";

// RPC Main
function get_product_info($input)
{
    global $xmlrpcerruser, $products;
    $err = "";

    // Get the parameters
    $param0 = $input->getParam(0);
    $param1 = $input->getParam(1);

    // Verify value and type
    if ((isset($param0) && ($param0->scalartyp() == "string")) &&
           (isset($param1) && ($param1->scalartyp() == "string"))) {

        // Extract parameter values
        $product_name = $param0->scalarval();
        $product_color = $param1->scalarval();

        // Attempt to find the product and price
        foreach ($products as $key => $element) {
            if ($key == $product_name) {
                $product_price = $element;
            }
        }

        // For this demo, all Browns are out of stock and all other colors
        // produce a random in-stock quantity - probably wouldn't work like
        // this in real-life, you would probably use a SQL database instead
        if ($product_color == "Brown") {
            $product_stock = 0;
        } else {
            $product_stock = rand(1, 100);
        }

        // If not found, report error
        if (!isset($product_price)) {
            $err = "No product named '" . $product_name . "' found.";
        }

    } else {
        $err = "Two string parameters (product name and color) are required.";
    }

    // If an error was generated, return it, otherwise return the proper data
    if ($err) {
         return new xmlrpcresp(0, $xmlrpcerruser, $err);
    } else {
        // Create an array and fill it with the data to return
        $result_array = new xmlrpcval(array(), "array");
        $result_array->addScalar($product_name, "string");
        $result_array->addScalar($product_color, "string");
        $result_array->addScalar($product_price, "string");
        $result_array->addScalar($product_stock, "string");
        return new xmlrpcresp($result_array);
    }

}

// Create the server
$server = new xmlrpc_server(
    array("example.getProductInfo" =>
        array("function" => "get_product_info",
              "signature" => $products_signature,
              "docstring" => $products_docstring),
         )
     );
?>


Let's look at the server code by section. First, in Listing 14.2, you will notice the two include statements for the xmlrpc.inc and xmlrpcs.inc files. Servers will make use of both of these files; however, clients will only use xmlrpc.inc in most cases.

Listing 14.2. Initial Steps in product_server.php



include("xmlrpc.inc");
include("xmlrpcs.inc");

// Array of products and prices, in real-life you might use a SQL database
$products = array (
    "Grocery Bag" => "9.00",
    "Book Bag" => "18.00",
    "Hand Bag" => "32.00",
    "Garment Bag" => "48.00",
    "Old Bag" => "0.31"
    );

// Signature of this RPC
$products_signature =
    array(array($xmlrpcArray,     // Returns a product price and quantity
    $xmlrpcString,                // Accepts a product name
    $xmlrpcString));              // Accepts a color

// Documentation string of this RPC
$products_docstring = "When passed valid product info, more info is returned.";


Next, we create an array of products with prices, appropriately named $products. In a real-life scenario, you will most likely use some sort of database for this information. MySQL is a common choice when using PHP and Apache, but other options exist as well.

We initialize the $products_signature variable, which contains the signature for the RPC. The signature defines the parameters expected from and returned to the client. The signature is an array that contains a list of the data types in question. The first entry in the array is the return type—in this case, an array of items is returned by the server. Subsequent entries are the individual parameters expected of the client—in this case, a string representing the product name and a string representing the product color. You could just as easily make the second and third parameters part of an array as well.

Last, we initialize the $products_docstring variable, which contains the documentation string for the RPC. This is used to describe the RPC to a client in human-readable terms.

The get_product_info function in Listing 14.3 is the main and only RPC of this server. Although servers can serve multiple RPCs, only one is implemented in this example. This function does all the work—parses the incoming parameters, performs any calculations or database lookups, and returns the results to the client. This function is automatically called when the server receives the request based on information within the XML-formatted data sent to the server. See Listing 14.4 for an example of the XML sent to the server that triggers this function call.

Listing 14.3. The get_product_info Method in product_server.php



function get_product_info($input)
{
    global $xmlrpcerruser, $products;
    $err = "";

    // Get the parameters
    $param0 = $input->getParam(0);
    $param1 = $input->getParam(1);

    // Verify value and type
    if ((isset($param0) && ($param0->scalartyp() == "string")) &&
           (isset($param1) && ($param1->scalartyp() == "string"))) {

        // Extract parameter values
        $product_name = $param0->scalarval();
        $product_color = $param1->scalarval();

        // Attempt to find the product and price
        foreach ($products as $key => $element) {
            if ($key == $product_name) {
                $product_price = $element;
            }
        }

        // For this demo, all Browns are out of stock and all other colors
        // produce a random in-stock quantity - probably wouldn't work like
        // this in real-life, you would probably use a SQL database instead
        if ($product_color == "Brown") {
            $product_stock = 0;
        } else {
            $product_stock = rand(1, 100);
        }

        // If not found, report error
        if (!isset($product_price)) {
            $err = "No product named '" . $product_name . "' found.";
        }

    } else {
        $err = "Two string parameters (product name and color) are required.";
    }

    // If an error was generated, return it, otherwise return the proper data
    if ($err) {
        return new xmlrpcresp(0, $xmlrpcerruser, $err);
    } else {
        // Create an array and fill it with the data to return
        $result_array = new xmlrpcval(array(), "array");
        $result_array->addScalar($product_name, "string");
        $result_array->addScalar($product_color, "string");
        $result_array->addScalar($product_price, "string");
        $result_array->addScalar($product_stock, "string");
        return new xmlrpcresp($result_array);
    }

}


Listing 14.4. The XML-Formatted Data Sent to product_server.php



<?xml version="1.0"?>
<methodCall>
       <methodName>example.getProductInfo</methodName>
       <params>
               <param>
                      <value><string>Book Bag</string></value>
               </param>
               <param>
                      <value><string>Black</string></value>
               </param>
       </params>
</methodCall>


Looking at Listing 14.3 again, first we allow access to two global variables—$xmlrpcerruser from xmlrpc.inc and the $products array we defined previously. $xmlrpcerruser will contain any error code that is generated during the execution of the server. We also initialize the $err string to "".

Next, we verify the parameter types and extract their values using the getParam function and scalartyp and scalarval methods. Remember, parameter 0 is the product name and parameter 1 is the color. The parameters must be sent to the server in that order. Note that Listing 14.4 contains the parameters in the correct (and expected) order. If both parameters do not exist or are not the correct type, we return a human-readable error in $err.

After the parameter values have parsed properly, we can then begin to use the data they contain. The first thing to do is peruse the $products array using foreach for the chosen product. Because we limit the users choices in the client, we should always get a match here. Once found, we set the $product_price variable accordingly. If the $product_price is not found, which should never happen in our simple example, we return a human-readable error in $err a few lines further down.

Next, we figure out the number of the product in the chosen color that is in stock. Remember, for the purposes of this demo, all products chosen in brown are out of stock. All other colors produce a random stock value. In reality, you might pull this information from a SQL database.

Last, if an error was returned, $err is set; we return the error using the xmlrpcresp function in xmlrpc.inc to create the XML-RPC response. You can see an example of an error returned in Figure 14.5.

However, if all is well, we create our result array. Remember, our server returns an array of data to the client. We first initialize the $result_array variable using the xmlrpcval function in xmlrpc.inc to create an xmlrpcval object. Once created, we simply call the addScalar method of that object to add the result data in the expected order. You can see that we return four strings. The product name and color are the same items passed to the server—we return them for convenience to the client because HTTP is a stateless protocol. The product price and stock are calculated by our server. We then finish it by passing the array to the xmlrpcresp function in xmlr pc.inc to create the XML-RPC response. Listing 14.5 shows an example of the data returned from the server, including the parameters in the correct (and expected) order.

Listing 14.5. The XML-Formatted Data Returned from product_server.php



<methodResponse>
       <params>
               <param>
                      <value><array><data>
                              <value><string>Book Bag</string></value>
                              <value><string>Black</string></value>
                              <value><string>18.00</string></value>
                              <value><string>82</string></value>
                      </data></array></value>
               </param>
       </params>
</methodResponse>


The last thing to do, actually the first thing that must occur when the server is called, is to create the server object itself. Listing 14.6 performs this task.

Listing 14.6. Creating the Server in product_server.php



// Create the server
$server = new xmlrpc_server(
    array("example.getProductInfo" =>
        array("function" => "get_product_info",
              "signature" => $products_signature,
              "docstring" => $products_docstring),
         )
     );


When the URL of the server is loaded, this code must run to prepare the server to accept incoming requests. Essentially, the xmlrpc_server function in xmlrpcs.inc creates the server object. This function takes multiple parameters that define not only the names of the RPCs that the server implements—in this case, only getProductInfo—but also detailed information about each of those methods. In the example, this includes the actual name of the function in code, get_product_info; the signature, $products_signature; and the documentation string, $products_docstring.

Essentially, we register the get_product_info function as a method of the server object with the method name example.getProductInfo. Although we name our method example.getProductInfo, you might consider using the reverse domain method of naming your implementation—something like com.mydomainname.myprojectname.mymethodname.

That, my friends, is the server. Now let's look at the PHP client.

The PHP Client

Our first client is also written in PHP, just as the server. As you've seen, however, it could be written in just about any language. Later in this chapter, we will rewrite the client using Objective-C and Cocoa. The product_client.php file is served by the Apache Web server running under Mac OS X just as any standard PHP file would be. The client file is what the user will access via her Web browser. In this case, we have placed the file at http://www.triplesoft.com/xmlrpc/product_client.php. Typing that URL in your Web browser will produce the Web page shown in Figure 14.2.

When the user clicks the Get Product Information button in the Web form, the pop-up menu selections are sent to the server as XML-formatted data. The server then processes the selections and returns the price and stock information as previously discussed. Figure 14.3 shows what the user's Web browser looks like after the server responds.

Listing 14.7 contains the complete PHP XML-RPC client code. This simple client offers a concrete example of the protocol passing multiple parameters and parsing multiple parameters in return.

Listing 14.7. product_client.php



<html>
<head><title>PHP XML RPC Example</title></head>
<body>
<basefont size="2" face="Verdana,Arial">

<?php
include("xmlrpc.inc");

// Show the results of the previous query
// if the parameter there are results to show
if (($HTTP_POST_VARS["product_name"] != "") &&
    ($HTTP_POST_VARS["product_color"] != "")) {

    // Create the message object using the RPC name, parameter and its type
    $message = new xmlrpcmsg("example.getProductInfo",
        array(new xmlrpcval($HTTP_POST_VARS["product_name"], "string"),
              new xmlrpcval($HTTP_POST_VARS["product_color"], "string")));

    // Display the message detail
    print "<pre><b>Message Detail:<br /></b>" .
        htmlentities($message->serialize()) . "</pre>";

    // Create the client object which points to the server
    // and PHP script to contact
    $client = new xmlrpc_client("/xmlrpc/product_server.php",
        "www.triplesoft.com", 80);

    // Enable debug mode so we see all there is to see coming and going
//    $client->setDebug(1);
//    print "<pre><b>From Server:<br /></b></pre>";

    // Send the message to the server for processing
    $response = $client->send($message);

    // If no response was returned there was a fatal error
    // (no network connection, no server, etc.)
    if (!$response) { die("Send failed."); }

    // If no error, display the results
    if (!$response->faultCode()) {

        // Extract the values from the response
        $result_array = $response->value();
        if ($result_array->arraysize() == 4) {
            $product_name = $result_array->arraymem(0);
            $product_color = $result_array->arraymem(1);
            $product_price = $result_array->arraymem(2);
            $product_stock = $result_array->arraymem(3);

            print "<br />";
            print "<br />Product: <b>" . $product_name->scalarval() . "</b>";
            print "<br />Color: <b>" . $product_color->scalarval() . "</b>";
            print "<br />Price: <b>$" . $product_price->scalarval() . "</b>";
            print "<br />In-Stock: <b>" . $product_stock->scalarval() . "</b>";
            print "<br />";

        } else {

            print "<br />";
            print "<br />Incorrect number of items in array. " .
                              "Should be 4 but there are <b>" .
                              $result_array->arraysize() . "</b>.";
            print "<br />";
        }

        // Display the response detail
          print "<pre><b>Response Detail:<br /></b>" .
                       htmlentities($response->serialize()) . "</pre>";

    } else {
        print "<br />";
        print "<br /><b>Fault Code:</b> " . $response->faultCode();
        print "<br /><b>Fault String:</b> " . $response->faultString();
        print "<br />";
    }

    print "<hr>";

}
?>

    <form action="<?php echo "$PHP_SELF";?>" method="POST">
        <select name="product_name">
            <option label="Book Bag"
                value="Book Bag" selected>Book Bag</option>
            <option label="Garment Bag"
                value="Garment Bag">Garment Bag</option>
            <option label="Grocery Bag"
                value="Grocery Bag">Grocery Bag</option>
            <option label="Hand Bag"
                value="Hand Bag">Hand Bag</option>
            <option label="Old Bag"
                value="Old Bag">Old Bag</option>
        </select>
        in
        <select name="product_color">
            <option label="Black" value="Black" selected>Black</option>
            <option label="Blue" value="Blue">Blue</option>
            <option label="Brown" value="Brown">Brown</option>
            <option label="Green" value="Green">Green</option>
            <option label="Red" value="Red">Red</option>
            <option label="White" value="White">White</option>
        </select>
        <button type="submit" name="Get Product Information"
        value="Get Product Information">Get Product Information</button>
    </form>

</body>
</html>


Let's look at the client code by section. Unlike our server PHP code, our client PHP code contains both HTML and PHP intermixed. The first section does little more than implement the standard HTML tags required for a Web page (see Listing 14.8).

Listing 14.8. In the Beginning of product_client.php



<html>
<head><title>PHP XML RPC Example</title></head>
<body>
<basefont size="2" face="Verdana,Arial">


Listing 14.9, however, gets us into the code that handles the XML-RPC meat of our client.

Listing 14.9. In the Middle of product_client.php



<?php
include("xmlrpc.inc");

// Show the results of the previous query
// if the parameter there are results to show
if (($HTTP_POST_VARS["product_name"] != "") &&
    ($HTTP_POST_VARS["product_color"] != "")) {

    // Create the message object using the RPC name, parameter and its type
    $message = new xmlrpcmsg("example.getProductInfo",
        array(new xmlrpcval($HTTP_POST_VARS["product_name"], "string"),
              new xmlrpcval($HTTP_POST_VARS["product_color"], "string")));

    // Display the message detail
    print "<pre><b>Message Detail:<br /></b>" .
        htmlentities($message->serialize()) . "</pre>";

    // Create the client object which points to
    // the server and PHP script to contact
    $client = new xmlrpc_client("/xmlrpc/product_server.php",
        "www.triplesoft.com", 80);

    // Enable debug mode so we see all there is to see coming and going
//    $client->setDebug(1);
//    print "<pre><b>From Server:<br /></b></pre>";

    // Send the message to the server for processing
    $response = $client->send($message);

    // If no response was returned there was a fatal error
           // (no network connection, no server, etc.)
    if (!$response) { die("Send failed."); }

    // If no error, display the results
    if (!$response->faultCode()) {

        // Extract the values from the response
        $result_array = $response->value();
        if ($result_array->arraysize() == 4) {
            $product_name = $result_array->arraymem(0);
            $product_color = $result_array->arraymem(1);
            $product_price = $result_array->arraymem(2);
            $product_stock = $result_array->arraymem(3);

            print "<br />";
            print "<br />Product: <b>" . $product_name->scalarval() . "</b>";
            print "<br />Color: <b>" . $product_color->scalarval() . "</b>";
            print "<br />Price: <b>$" . $product_price->scalarval() . "</b>";
            print "<br />In-Stock: <b>" . $product_stock->scalarval() . "</b>";
            print "<br />";

        } else {

            print "<br />";
            print "<br />Incorrect number of items in array. " .
                              "Should be 4 but there are <b>" .
                              $result_array->arraysize() . "</b>.";
            print "<br />";
        }

        // Display the response detail
        print "<pre><b>Response Detail:<br /></b>" .
                      htmlentities($response->serialize()) . "</pre>";

    } else {
        print "<br />";
        print "<br /><b>Fault Code:</b> " . $response->faultCode();
        print "<br /><b>Fault String:</b> " . $response->faultString();
        print "<br />";
    }

    print "<hr>";

}
?>


Listing 14.9 begins by including the xmlrpc.inc file. We then verify that we have values for both of the $HTTP_POST_VARS that we are interested in. If both product_name and product_color are set, we continue creating the XML-RPC message.

Using the xmlrpcmsg function in xmlrpc.inc, we select the name of the RPC method that we intend to call on the remote server: example.getProductInfo. We also add the two expected parameters, in the correct order, to the array of parameters within the message: product_name and product_color as strings. We then display this message in its serialized form for debugging purposes. This is shown in Figure 14.3.

After our message is created, we can then put together the client object using the xmlrpc_client function in xmlrpc.inc. This function takes multiple parameters, which ultimately make up the URL to the server—in our case, a PHP script living at http://www.triplesoft.com/xmlrpc/product_server.php, on port 80, the default HTTP port. It returns a client object used to send the message to the server as the variable $client.

At this point, you can enable debug mode by uncommenting the setDebug method of the $client object. In either case, we send the message using the send method of the $client object. This single method handles locating the remote server, transmitting the message to the client via the HTTP protocol, and accepting any resulting data from the server, as the $response variable. If there is no response, the function dies with an error message; otherwise, it continues parsing the result.

If the $response object's faultCode method returns an empty result, there is no error and we can continue with the parsing. Otherwise, we display the faultCode and faultString to the user as the error message. These are sent from the server; faultString returns the $err string returned by the server.

Assuming that there is no error, we pull the $response object's array of parameters as $result_array by calling its value method. If the size of the array is anything other than four, there is an error and we display it accordingly. Remember, the server returns four values in the array of results in a specific order. We then use the arraymem method of the $result_array and scalarval method of each array member to display the results back to the user.

That ends the PHP portion of our file.

Listing 14.10 is nothing more than the HTML form portion of our client file. This portion appears each time the page is displayed as seen in Figures 14.2 and 14.3. It simply contains the pop-up menus and Get Product Information button and calls back to itself as the form action. Although this example hard-codes the product names and colors into the form, a real-life application might query the server for the list of products and list of available colors. See the “Try This” section of this chapter for your assignment to do just that!

Listing 14.10. In the End of product_client.php



    <form action="<?php echo "$PHP_SELF";?>" method="POST">
        <select name="product_name">
            <option label="Book Bag"
                value="Book Bag" selected>Book Bag</option>
            <option label="Garment Bag"
                value="Garment Bag">Garment Bag</option>
            <option label="Grocery Bag"
                value="Grocery Bag">Grocery Bag</option>
            <option label="Hand Bag"
                value="Hand Bag">Hand Bag</option>
            <option label="Old Bag"
                value="Old Bag">Old Bag</option>
        </select>
        in
        <select name="product_color">
            <option label="Black" value="Black" selected>Black</option>
            <option label="Blue" value="Blue">Blue</option>
            <option label="Brown" value="Brown">Brown</option>
            <option label="Green" value="Green">Green</option>
            <option label="Red" value="Red">Red</option>
            <option label="White" value="White">White</option>
        </select>
        <button type="submit" name="Get Product Information"
            value="Get Product Information">Get Product Information</button>
    </form>

</body>
</html>


Now let's see how we can implement this client using Objective-C.

The Cocoa Client

Figure 14.6 shows the MyXMLRPCClient project in Project Builder. The project is a simple Cocoa application. We use an AppController to implement the –applicationShouldTerminateAfterLastWindowClosed: delegate method. For the sake of convenience, we also implement the -getProductInformation: method in the AppController and link it to the Get Product Information button in our main application window shown in Figure 14.4. We could have created a window controller to handle this, but decided against it for the sake of brevity.

Figure 14.6. The MyXMLRPCClient project.

image

One thing to note is the addition of CoreServices.framework in the project. This framework contains functionality that allows us to access Web Services such as XML-RPC and SOAP. You'll see more of what this brings us when we look at the source code later.

Note

Speaking of frameworks, exploring the /System/Library/Frameworks/ directory can open the doors to you as an informed programmer. Take a peek, and see what's in there.

The Project Settings

The Target settings in Figure 14.7 are straightforward for a simple Cocoa application.

Figure 14.7. The MyXMLRPCClient Target settings.

image

The InfoPlist entries in Figure 14.8 are also straightforward. Because this is a simple Cocoa application, there are no surprises here.

Figure 14.8. The MyXMLRPCClient InfoPlist entries.

image

The Nib File

The MainMenu.nib file is shown in Interface Builder in Figure 14.9. The AppController has been instantiated and is the NSApplication's delegate, as you would expect. The Get Product Information button is linked to the AppController's -getProductInformation: action method. The AppController also contains outlets for the appropriate items in the main window.

Figure 14.9. The MyXMLRPCClient MainMenu.nib file.

image

The Source Code

The interface of AppController is shown in Listing 14.11.

Listing 14.11. AppController Interface in AppController.h



#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    IBOutlet NSPopUpButton *m_product;
    IBOutlet NSPopUpButton *m_color;
    IBOutlet NSTextField *m_price;
    IBOutlet NSTextField *m_stock;
    IBOutlet NSTextField *m_result;
}
- (IBAction)getProductInformation:(id)sender;
@end


The AppController manages the outlets of our user interface for convenience. One action is also connected to the Get Product Information button.

Listing 14.12 contains the –getProductInformation: method that is called when the Get Product Information button is clicked. The user has already made her choice of product and color. This method alone is the equivalent of our PHP version of the client.

Listing 14.12. -getProductInformation: in AppController.m



- (IBAction)getProductInformation:(id)sender
{
    WSMethodInvocationRef rpcCall;
    NSURL                *rpcURL;
    NSString             *methodName;
    NSMutableDictionary  *params;
    NSArray              *paramsOrder;
    NSDictionary         *result;
    NSString             *selectedProduct;
    NSString             *selectedColor;

    //
    //    1. Define the location of the RPC service and method
    //

    // Create the URL to the RPC service
    rpcURL = [NSURL URLWithString:
        @"http://www.triplesoft.com/xmlrpc/product_server.php"];

    // Assign the method name to call on the RPC service
    methodName = @"example.getProductInfo";

    // Create a method invocation
    // First parameter is the URL to the RPC service
    // Second parameter is the name of the RPC method to call
    // Third parameter is a constant to specify the XML-RPC protocol
    rpcCall = WSMethodInvocationCreate((CFURLRef)rpcURL,
                  (CFStringRef)methodName, kWSXMLRPCProtocol);

    //
    //     2. Set up the parameters to be passed to the RPC method
    //

    // Get the users choices
    selectedProduct = [m_product titleOfSelectedItem];
    selectedColor = [m_color titleOfSelectedItem];

    // Add the users choices to the dictionary to be passed as parameters
    params = [NSMutableDictionary dictionaryWithCapacity:2];
    [params setObject:selectedProduct forKey:selectedProduct];
    [params setObject:selectedColor forKey:selectedColor];

    // Create the array to specify the order of the parameter values
    paramsOrder = [NSArray arrayWithObjects:selectedProduct,
        selectedColor, nil];

    // Set the method invocation parameters
    // First parameter is the method invocation created above
    // Second parameter is a dictionary containing the parameters themselves
    // Third parameter is an array specifying the order of the parameters
    WSMethodInvocationSetParameters(rpcCall, (CFDictionaryRef)params,
        (CFArrayRef)paramsOrder);

    //
    //    3. Make the call and parse the results!
    //

    // Invoke the method which returns a dictionary of results
    result = (NSDictionary*)WSMethodInvocationInvoke(rpcCall);

    // If the results are a fault, display an error to the user with the
    // fault code and descriptive string
    if (WSMethodResultIsFault((CFDictionaryRef)result)) {
        NSRunAlertPanel([NSString stringWithFormat:@"Error %@",
            [result objectForKey: (NSString*)kWSFaultCode]],
            [result objectForKey: (NSString*)kWSFaultString],
            @"OK", @"", @"");
    } else {
        // Otherwise, pull the results from the dictionary as an array
        NSArray *array = [result objectForKey:
            (NSString*)kWSMethodInvocationResult];

        // Display the entire array result from the server
        [m_result setStringValue: [array description]];

        // Display the specific fields we are interested in (price, stock)
        [m_price setStringValue: [array objectAtIndex: 2]];
        [m_stock setStringValue: [array objectAtIndex: 3]];
    }

    // Release those items that need to be released
    [params release];
    params = nil;
    [paramsOrder release];
    paramsOrder = nil;
    [result release];
    result = nil;
}


Let's look at the method in stages. The first thing we do in Listing 14.13 is to define all the variables we will be using in the method. A lot are listed here, but most are straightforward: NSArrays, NSStrings, and NSDictionarys (mutable and otherwise). However, one item is new. The WSMethodInvocationRef variable is used to track and manage the object that handles our XML-RPC call. You'll see how this works shortly.

Listing 14.13. -getProductInformation:—Part 1 in AppController.m



- (IBAction)getProductInformation:(id)sender
{
    WSMethodInvocationRef rpcCall;
    NSURL                *rpcURL;
    NSString             *methodName;
    NSMutableDictionary  *params;
    NSArray              *paramsOrder;
    NSDictionary         *result;
    NSString             *selectedProduct;
    NSString             *selectedColor;

    //
    //    1. Define the location of the RPC service and method
    //

    // Create the URL to the RPC service
    rpcURL = [NSURL URLWithString:
        @"http://www.triplesoft.com/xmlrpc/product_server.php"];

    // Assign the method name to call on the RPC service
    methodName = @"example.getProductInfo";

    // Create a method invocation
    // First parameter is the URL to the RPC service
    // Second parameter is the name of the RPC method to call
    // Third parameter is a constant to specify the XML-RPC protocol
    rpcCall = WSMethodInvocationCreate((CFURLRef)rpcURL,
                  (CFStringRef)methodName, kWSXMLRPCProtocol);


Next, we want to create an NSURL that identifies the server that we will be calling. We also want to assign the methodName variable to contain the name of the RPC method. Last, we create the WSMethodInvocation by calling WSMethodInvocationCreate, part of the Web Services functionality. This method takes three parameters. The first is the URL to the RPC service. The second is the name of the RPC method. The third is a constant to specify which protocol to use—in our case, kWSXMLRPCProtocol. Other protocol options include SOAP. These are all defined in WSMethodInvocation.h.

In Listing 14.14, we set up the parameters to be passed to the RPC based on the users choices. First, we obtain the values of the pop-up buttons for product and color as strings. We then create an NSMutableDictionary capable of storing two items using the +dictionaryWithCapacity: class method. Then, using the –setObject:forKey: method of NSMutableDictionary, we add the strings to the dictionary.

Listing 14.14. -getProductInformation:—Part 2 in AppController.m



//
//     2. Set up the parameters to be passed to the RPC method
//

// Get the users choices
selectedProduct = [m_product titleOfSelectedItem];
selectedColor = [m_color titleOfSelectedItem];

// Add the users choices to the dictionary to be passed as parameters
params = [NSMutableDictionary dictionaryWithCapacity:2];
[params setObject:selectedProduct forKey:selectedProduct];
[params setObject:selectedColor forKey:selectedColor];

// Create the array to specify the order of the parameter values
paramsOrder = [NSArray arrayWithObjects:selectedProduct, selectedColor, nil];

// Set the method invocation parameters
// First parameter is the method invocation created above
// Second parameter is a dictionary containing the parameters themselves
// Third parameter is an array specifying the order of the parameters
WSMethodInvocationSetParameters(rpcCall, (CFDictionaryRef)params,
    (CFArrayRef)paramsOrder);


Note

Another way to have done this would be to use an NSDictionary as opposed to an NSMutableDictionary. I chose to use an NSMutableDictionary in case I wanted to add more parameters later on—one less thing to change. Using an NSDictionary, you might consider the following:

NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:     selectedProduct, selectedProduct,     selectedColor, selectedColor,     nil];

Remember that XML-RPC requires parameters to be in the correct order; therefore, we must create an NSArray to specify that order. We simply add the items in the proper order using the NSArray +arrayWithObject: class method. Failure to include this parameter will cause your server to become confused because the parameter order is unspecified.

We use the WSMethodInvocationSetParameters function to attach the parameters to the WSMethodInvocationRef. This function takes three parameters. The first is the reference to the method invocation created earlier. The second is the dictionary containing the parameters themselves. The third is the array specifying the order of the parameters.

In Listing 14.15, we make the call to the RPC by calling the WSMethodInvocationInvoke function and passing the WSMethodInvocationRef. This is equivalent to the $client->send($message) method in the PHP client version. This single function handles locating the remote server, transmitting the message to the client via the HTTP protocol, and accepting any resulting data from the server as an NSDictionary.

Listing 14.15. -getProductInformation:—Part 3 in AppController.m



    //
    //    3. Make the call and parse the results!
    //

    // Invoke the method which returns a dictionary of results
    result = (NSDictionary*)WSMethodInvocationInvoke(rpcCall);

    // If the results are a fault, display an error to the user with the
    // fault code and descriptive string
    if (WSMethodResultIsFault((CFDictionaryRef)result)) {
        NSRunAlertPanel([NSString stringWithFormat:@"Error %@",
            [result objectForKey: (NSString*)kWSFaultCode]],
            [result objectForKey: (NSString*)kWSFaultString], @"OK", @"", @"");
    } else {
        // Otherwise, pull the results from the dictionary as an array
        NSArray *array = [result objectForKey:
            (NSString*)kWSMethodInvocationResult];

        // Display the entire array result from the server
        [m_result setStringValue: [array description]];

        // Display the specific fields we are interested in (price, stock)
        [m_price setStringValue: [array objectAtIndex: 2]];
        [m_stock setStringValue: [array objectAtIndex: 3]];
    }

    // Release those items that need to be released
    [params release];
    params = nil;
    [paramsOrder release];
    paramsOrder = nil;
    [result release];
    result = nil;
}


We then check the results for an error by calling the WSMethodResultIsFault function, passing the NSDictionary typecast as a CFDictionaryRef—toll free bridging between CoreFoundation and Foundation makes this simple. If the function returns TRUE, we pull kWSFaultCode and kWSFaultString from the result and display them to the user. The kWSFaultString is the error returned from the server in the $err variable.

Assuming that there are no errors, we pull the array of results from the dictionary as kWSMethodInvocationResult. When we have the array, we know what it should contain and in what order. The first thing we do is display the entire array (for debugging purposes) by calling the –description method of NSArray. This allows us to quickly see all fields in the result array and display them to the user. Next, we pull just the price and stock information as array objects 2 and 3 to display to the user as well. Remember that array objects 0 and 1 are an echo of the product name and color that we passed to the server.

At the end of the line, we merely clean up after ourselves and release all the memory that needs to be released.

So, what do you think? It's not so hard to harness the power of distributed computing across languages and platforms!

Try This

A client/server architecture is a great place to experiment with different implementations. Here are some ideas to keep you busy with this one.

Collect the list of product names and colors used in the HTML form in the PHP client from the server itself. Assume that the client knows nothing about what products are available. The server should implement functions named getProductNames and getProductColors to be listed to the user. Once you get it working in the PHP client, implement it in the Cocoa client as well.

Use MySQL (or another database) as your data source in the server. If you don't want to explore MySQL (or another database), check out the Flat File DataBase (FFDB) PHP Library by John Papandriopoulos. John's library offers a simple way to use database functionality quickly and easily on your server using PHP:

http://ffdb-php.sourceforge.net/

Conclusion

I hope that XML-RPC excites you as much as it does me. I find PHP and Web-based implementations to be an incredible way to open up your creativity to users of all platforms via a Web browser. You can implement so many excellent technologies using these methods. Explore the possibilities of XML-RPC and create something new for the world to explore!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.45.92