15. SOAP

“Scrubba Dub Dub, PHP and Cocoa in the tub.”

—I'll admit it; I said this one.

A note to those of you reading this book in order: This chapter is very similar to Chapter 14, “XML-RPC,” with differences only where the XML-RPC and SOAP examples vary. The goal of these two chapters is to show the same project implemented in both XML-RPC and SOAP and to have them both stand alone should someone read them separately. If you already understand the concepts outlined in Chapter 14, you can skip ahead and just look at the source code explanations in this chapter.

SOAP, the Simple Object Access Protocol, is a Web Service similar to XML-RPC in function but different in form. Like XML-RPC, SOAP allows you to transmit complex data structures to remote computers for processing and response and uses XML to format its data. If you have a need for a reliable client/server architecture reaching across platforms, SOAP might be for you.

Although SOAP can, and often does, use the HTTP protocol as its transport mechanism, it need not be bound by this limitation. HTTP is, by far, the most widely implemented protocol across multiple platforms; however, SOAP allows you to use others as well, including SMTP. In this chapter, we will use HTTP as our example's transport mechanism.

SOAP can be implemented in any of a number of programming languages on just about every available platform. You can choose C/C++ (including Carbon), Java, Lisp, Objective-C (including Cocoa), Perl, PHP, Python, BASIC, AppleScript, 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 15.1 shows this concept.

Figure 15.1. A client and server via SOAP.

image

The official SOAP specification can be found at http://www.w3.org/2000/xp/Group/. 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 used in Chapter 14. 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 a SOAP 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.

Our 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 15.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 15.2. The PHP SOAP 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 15.3 shows our client displaying the original XML-format query—also called the request 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. There is much more resulting data than can be shown here, which will be covered in detail later in this chapter.

Figure 15.3. The PHP SOAP client after submitting a query and receiving the results.

image

The Cocoa client, shown in Figure 15.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, but does not display the results using HTML.

Figure 15.4. The Cocoa SOAP 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 it 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/soap/product_server.php. However, typing that URL into your Web browser will simply produce an error because SOAP servers require specifically formatted information passed by the client. Figure 15.5 shows the fault information returned by the server.

Figure 15.5. The PHP SOAP 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 Dietrich Ayala of NuSphere Corporation. The NuSOAP library and detailed information on its use can be found at http://dietrich.ganx4.com/nusoap/. This library contains one PHP file, nusoap.php, which is easily included in your PHP source. The distribution also contains documentation 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 15.1 contains the complete PHP SOAP server code. This simple server offers a concrete example of the protocol accepting multiple parameters and returning multiple parameters as an array, which is most likely what you will need in a real-life scenario.

Listing 15.1. product_server.php



<?php
include("nusoap.php");

// 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"
    );

// Create a new SOAP server instance
$server = new soap_server;

// Enable debugging, to be echoed via client
$server->debug_flag = true;

// Register the get_product_info function for publication
$server->register("get_product_info");

// Begin the HTTP listener service and exit
$server->service($HTTP_RAW_POST_DATA);

// Our published service
function get_product_info($product_name, $product_color)
{
    global $products;
    $err = "";

    // Verify values
    if (isset($product_name) && isset($product_color)) {

        // 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 soap_fault("Client", "product_server.php", $err, $err);
    } else {
        // Create an array and fill it with the data to return

        $result_array = array("product_name" => $product_name,
                    "product_color" => $product_color,
                    "product_price" => $product_price,
                    "product_stock" => $product_stock);

        return $result_array;
    }

}

exit();

?>


Let's look at the server code by section. First, in listing 15.2, you will notice the include statement for the nusoap.php file. This file is required by both clients and servers and provides much of the functionality to interface PHP with the underlying SOAP protocol. Using NuSOAP makes life as a SOAP developer much easier.

Listing 15.2. Initial Steps in product_server.php



<?php
include("nusoap.php");

// 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"
    );

// Create a new SOAP server instance
$server = new soap_server;

// Enable debugging, to be echoed via client
$server->debug_flag = true;

// Register the get_product_info function for publication
$server->register("get_product_info");

// Begin the HTTP listener service and exit
$server->service($HTTP_RAW_POST_DATA);


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 now create the SOAP server instance using the PHP new function to create a soap_server object. This functionality is provided by NuSOAP. Once the server is created, we use the $server variable to manage it. For debugging purposes, we set the debug_flag instance variable of the server object to true. This will provide a wealth of debug information in our Web browser when we access the server, and it is highly recommended during development.

After the server has been created, we need to register the functions that our server implements for publication. In this example, we only publish one function, get_product_info. At this stage, you need only register the name; the expected parameters to this function are handled automatically by NuSOAP and SOAP.

Last, we need to begin the process of listening to HTTP. By calling the service method of the server object, we begin the process of listening for requests for our published service. In reality, when the server is executed by means of a client, you are pretty much guaranteed that a request is forthcoming, so we won't be waiting for long.

The get_product_info function in Listing 15.3 is the main and only published service of this server. Although servers can publish multiple services, we only implement one in this example. This function does all the work—verifies 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. Listing 15.4 shows an example of the XML sent to the server that triggers this function call.

Listing 15.3. The get_product_info Method in product_server.php



// Our published service
function get_product_info($product_name, $product_color)
{
    global $products;
    $err = "";

    // Verify values
    if (isset($product_name) && isset($product_color)) {

        // 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 soap_fault("Client", "product_server.php", $err, $err);
    } else {
        // Create an array and fill it with the data to return

        $result_array = array("product_name" => $product_name,
                    "product_color" => $product_color,
                    "product_price" => $product_price,
                    "product_stock" => $product_stock);

        return $result_array;
    }

}


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



<?xml version="1.0" encoding="ISO-8859-1"?>
<SOAP-ENV:Envelope
       SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
       xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
       xmlns:si="http://soapinterop.org/xsd">
       <SOAP-ENV:Body>
               <ns1:get_product_info xmlns:ns1="http://testuri.org">
                   <product_name xsi:type="xsd:string">Book Bag</product_name>
                   <product_color xsi:type="xsd:string">Black</product_color>
               </ns1:get_product_info>
       </SOAP-ENV:Body>
</SOAP-ENV:Envelope>


Looking at Listing 15.3 again, we first allow access to the global $products array we defined previously. $err will contain any error code that is generated during the execution of the server, for now we initialize it to "".

Next, we verify that the expected variables are set. We explicitly expect the $product_name and $product_color parameters to be passed in from the client as shown in the body of the SOAP message in Listing 15.4. NuSOAP conveniently handles extracting the parameters from the SOAP XML message and passing them to our PHP function. If either of these parameters is not sent, it is considered an error and the user is alerted.

When 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, which is shown a few lines further down.

Next, we figure out the number of the product in the chosen color that is in stock. 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 using MySQL.

Last, if an error was returned, $err is set; we return the error by creating and returning a soap_fault object. The soap_fault object accepts up to four parameters. The first, faultcode, is either 'client' or 'server' and tells where the fault of the error occurred. In this case, the error is always considered one made by the client, such as passing the wrong parameters. If there were a specific server error, such as a database lookup failure, you would pass 'server' for this parameter. The second, faultactor, is the URL of the script that found the error. It can be helpful to fill this in when multiple actors (or servers) are involved in a process. The third, faultstring, is the human-readable error string and the fourth, faultdetail, can be an error code, and so on. You can see an example of an error returned in Figure 15.5.

However, if all is well, we create and return our result array. Remember, our server returns an array of data to the client. We do this by simply creating a standard PHP array and adding the items we want to return. 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. Listing 15.5 shows an example of the data returned from the server—note the parameters are listed by name.

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



<?xml version="1.0" encoding="ISO-8859-1"?>
<SOAP-ENV:Envelope
       SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
       xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
       xmlns:si="http://soapinterop.org/xsd">
       <SOAP-ENV:Body>
               <get_product_infoResponse>
               <soapVal>
                      <product_name xsi:type="xsd:string">Book Bag</product_name>
                      <product_color xsi:type="xsd:string">Black</product_color>
                      <product_price xsi:type="xsd:string">18.00</product_price>
                      <product_stock xsi:type="xsd:int">51</product_stock>
               </soapVal>
               </get_product_infoResponse>
       </SOAP-ENV:Body>
</SOAP-ENV:Envelope>


The very last thing to do is exit and end the PHP code. Listing 15.6 performs this task.

Listing 15.6. Last but not Least in product_server.php



exit();

?>


That is all there is to 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/soap/product_client.php. Typing that URL in your Web browser will produce the Web page shown in Figure 15.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 15.3 shows what the users Web browser looks like after the server responds.

Listing 15.7 contains the complete PHP SOAP client code. This simple client offers a concrete example of the protocol passing multiple parameters and parsing multiple parameters in return.

Listing 15.7. product_client.php



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

<?php
include("nusoap.php");

// 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 parameters array
    $params = array("product_name" => $HTTP_POST_VARS["product_name"],
             "product_color" => $HTTP_POST_VARS["product_color"]);

    // Create a new soap client endpoint specifying the server location
    $client = new soapclient("http://www.triplesoft.com/soap/product_server.php");

    // Enable debugging
    $client->debug_flag = true;

    // Call the function, passing the parameter array
    $response = $client->call("get_product_info", $params);

    // Echo request detail
    echo "<pre><b>Request Detail:<br /></b><xmp>".$client->request."</xmp></pre>";

    // If no response was returned there was a fatal
    // error (no network conn, no server, etc.)
    // You might prefer to use the $client->getError()
    // method for more detailed information.
    if (!$response) { die("Send failed."); }

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

        // Extract the values from the response
        if (count($response) == 4) {

            foreach ($response as $key => $element) {
                $$key = $element;
            }

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

        } else {

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

        // Echo response detail
        echo "<pre><b>Response Detail:<br /></b><xmp>".
            $client->response."</xmp></pre>";

    } else {
        print "<br />";
        print "<br /><b>Fault Code:</b> " . $client->faultcode;
        print "<br /><b>Fault Actor:</b> " . $client->faultactor;
        print "<br /><b>Fault String:</b> " . $client->faultstring;
        print "<br /><b>Fault Detail:</b> " . $client->faultdetail; // serialized?
        print "<br />";
    }

    // Echo debug log at the bottom since it can be large
    echo "<pre><b>Debug log:<br /></b>".$client->debug_str."</pre>";

    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, shown in Listing 15.8, does little more than implement the standard HTML tags required for a Web page.

Listing 15.8. In the Beginning of product_client.php



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


Listing 15.9, however, gets us into the code that handles the SOAP fat of our client.

Listing 15.9. In the Middle of product_client.php



<?php
include("nusoap.php");

// 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 parameters array
    $params = array("product_name" => $HTTP_POST_VARS["product_name"],
             "product_color" => $HTTP_POST_VARS["product_color"]);

    // Create a new soap client endpoint specifying the server location
    $client =
        new soapclient("http://www.triplesoft.com/soap/product_server.php");

    // Enable debugging
    $client->debug_flag = true;

    // Call the function, passing the parameter array
    $response = $client->call("get_product_info", $params);

    // Echo request detail
    echo "<pre><b>Request Detail:<br /></b><xmp>".$client->request."</xmp></pre>";

    // If no response was returned there was a
    // fatal error (no network conn, no server, etc.)
    // You might prefer to use the $client->getError()
    // method for more detailed information.
    if (!$response) { die("Send failed."); }

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

        // Extract the values from the response
        if (count($response) == 4) {

            foreach ($response as $key => $element) {
                $$key = $element;
            }

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

        } else {

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

        // Echo response detail
        echo "<pre><b>Response Detail:<br /></b><xmp>".
            $client->response."</xmp></pre>";

    } else {
        print "<br />";
        print "<br /><b>Fault Code:</b> " . $client->faultcode;
        print "<br /><b>Fault Actor:</b> " . $client->faultactor;
        print "<br /><b>Fault String:</b> " . $client->faultstring;
        print "<br /><b>Fault Detail:</b> " . $client->faultdetail; // serialized?
        print "<br />";
    }

    // Echo debug log at the bottom since it can be large
    echo "<pre><b>Debug log:<br /></b>".$client->debug_str."</pre>";

    print "<hr>";

}
?>


Listing 15.9 begins by including the nusoap.php 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 SOAP message by first adding them to the $params array by name.

We then create the soapclient object using PHP new function. The soapclient takes one parameter, the URL to the server—in our case, our PHP server script living at http://www.triplesoft.com/soap/product_server.php. 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 setting the debug_flag variable of the $client object to true. In either case, we send the message using the send method of the client object. This method takes two parameters, the name of the published service, get_product_info, and the parameters to pass to it. It single-handedly locates the remote server, transmits the message to the client via the chosen protocol, and accepts any resulting data from the server as the $response variable. For debugging purposes, we then echo the request detail by calling the client's request method. If there is no response, the function dies with an error message; otherwise, it continues parsing the result.

If the client object's fault method returns an empty result, there is no error and we can continue with the parsing. Otherwise, we display the faultcode, faultactor, faultstring, and faultdetail results to the user as the error. As you recall, these are sent from the server.

Assuming that there is no error, we verify the size of the array is four; otherwise, there is an error, and we display it accordingly. Remember, the server returns four values in the array of results. We then use foreach to extract the keys and elements in the array for display back to the user. For debugging purposes, we then echo the response detail by calling the client's response method. We also show the debug log by calling the client's debug_str method.

That ends the PHP portion of our file.

Listing 15.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 Figure 15.2. 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 15.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 15.6 shows the MySOAPClient 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 15.4.

Figure 15.6. The MySOAPClient project.

image

One thing to note is the addition of the 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.

The Project Settings

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

Figure 15.7. The MySOAPClient Target settings.

image

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

Figure 15.8. The MySOAPClient InfoPlist entries.

image

The Nib File

The MainMenu.nib file is shown in Interface Builder in Figure 15.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: IBAction method. The AppController also contains IBOutlets for the appropriate items in the main window.

Figure 15.9. The MySOAPClient MainMenu.nib file.

image

The Source Code

The interface of AppController is shown in Listing 15.11.

Listing 15.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 IBOutlets of our user interface for convenience. One IBAction is also connected to the Get Product Information button.

Listing 15.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 15.12. -getProductInformation: in AppController.m



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

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

    // Create the URL to the SOAP service
    soapURL = [NSURL URLWithString:
        @"http://www.triplesoft.com/soap/product_server.php"];

    // Assign the method name to call on the SOAP service
    methodName = @"get_product_info";

    // Create a method invocation
    // First parameter is the URL to the SOAP service
    // Second parameter is the name of the SOAP method to call
    // Third parameter is a constant to specify the SOAP2001 protocol
    soapCall = WSMethodInvocationCreate((CFURLRef)soapURL,
                   (CFStringRef)methodName, kWSSOAP2001Protocol);

    //
    //     2. Set up the parameters to be passed to the SOAP 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:@"product_name"];
    [params setObject:selectedColor forKey:@"product_color"];

    // Create the array to specify the order of the parameter values
    paramsOrder = [NSArray arrayWithObjects:@"product_name",
        @"product_color", 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,
    // sometimes optional for SOAP
    WSMethodInvocationSetParameters(soapCall, (CFDictionaryRef)params,
        (CFArrayRef)paramsOrder);

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

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

    // 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,
        // held as another dictionary named "soapVal"
        NSDictionary *dictionary = [result objectForKey:
            (NSString*)kWSMethodInvocationResult];
        NSDictionary *soapVal = [dictionary objectForKey:@"soapVal"];

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

        // Display the specific fields we are interested in (price, stock)
        [m_price setStringValue: [soapVal objectForKey:@"product_price"]];
        [m_stock setStringValue: [soapVal objectForKey:@"product_stock"]];
    }

    // 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 15.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 SOAP call itself. You'll see how this works shortly.

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



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

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

    // Create the URL to the SOAP service
    soapURL = [NSURL URLWithString:
        @"http://www.triplesoft.com/soap/product_server.php"];

    // Assign the method name to call on the SOAP service
    methodName = @"get_product_info";

    // Create a method invocation
    // First parameter is the URL to the SOAP service
    // Second parameter is the name of the SOAP method to call
    // Third parameter is a constant to specify the SOAP2001 protocol
    soapCall = WSMethodInvocationCreate((CFURLRef)soapURL,
        (CFStringRef)methodName, kWSSOAP2001Protocol);


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 published service we are interested in, get_product_info. Last, we create the WSMethodInvocation by calling WSMethodInvocationCreate, part of the Web Services functionality. This method takes three parameters. The first is the URL of the server. The second is the name of the published service. The third is a constant to specify which protocol to use—in our case, kWSSOAP2001Protocol. Other protocol options include XML-RPC. These are all defined in WSMethodInvocation.h.

In Listing 15.14, we set up the parameters to be passed to the published service based on the user's 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 15.14. -getProductInformation:—Part 2 in AppController.m



//
//     2. Set up the parameters to be passed to the SOAP 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:@"product_name"];
[params setObject:selectedColor forKey:@"product_color"];

// Create the array to specify the order of the parameter values
paramsOrder = [NSArray arrayWithObjects:@"product_name",
    @"product_color", 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,
// sometimes optional for SOAP
WSMethodInvocationSetParameters(soapCall, (CFDictionaryRef)params,
    (CFArrayRef)paramsOrder);


Although it has been said that SOAP parameters are based on names and this parameter order isn't required, I've found that this isn't necessarily true; therefore, we must create an NSArray to specify the parameters and their expected 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. I am unsure at the time of this writing whether this is a NuSOAP issue or something else—for now, include this information.

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 15.15, we make the call to the published service by calling the WSMethodInvocationInvoke function and passing the WSMethodInvocationRef. This is equivalent to the $client->call("get_product_info", $params) 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 15.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(soapCall);

    // 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,
        // held as another dictionary named "soapVal"
        NSDictionary *dictionary = [result objectForKey:
            (NSString*)kWSMethodInvocationResult];
        NSDictionary *soapVal = [dictionary objectForKey:@"soapVal"];

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

        // Display the specific fields we are interested in (price, stock)
        [m_price setStringValue: [soapVal objectForKey:@"product_price"]];
        [m_stock setStringValue: [soapVal objectForKey:@"product_stock"]];
    }

    // 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. If the function returns TRUE, we pull the 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 a dictionary of results from the WSMethodResultIsFault return value using the -objectForKey: method and passing kWSMethodInvocationResult. We must then pull a sub-dictionary of the actual parameters, named soapVal, from this dictionary, also using –objectForKey:.

After we have completely dereferenced the results, the first thing we do is display the entire dictionary (for debugging purposes) by calling the –description method of NSMutableDictionary. This allows us to quickly see all fields in the result and display them to the user. Next, we pull just the price and stock information from the sub-dictionary, by name, to display to the user as well.

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? Using NuSOAP, SOAP isn't entirely complex after all!

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 as your data source in the server. If you don't want to explore MySQL, 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

SOAP includes many more advanced options than the simple example shown here. If you plan to do any serious SOAP development, be sure to review the SOAP specification online—the URL is listed earlier in this chapter.

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

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