XML and PHP [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

XML and PHP [Electronic resources] - نسخه متنی

Vikram Vaswani

نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
افزودن یادداشت
افزودن یادداشت جدید

















A Few Examples




Over the preceding sections, you''ve seen quite a few examples of how the RPC library works and should now have a fairly clear understanding of the theory. This section puts it into practice with a few real-life demonstrations of how this technology can be used across the web.




Retrieving Meteorological Data with XML-RPC




I''ll begin with something simplea basic RPC client-server implementation for information delivery over HTTP. I''ll be using a MySQL database as the data source, XML-RPC as the expression language for the procedure calls and responses, and PHP for the actual mechanics.




Requirements




Let''s assume the existence of a fictional government department that is charged with the task of obtaining meteorological information and charting it for statistical purposes. In a rare fit of generosity, this department has decided to make some of its meteorological readings available to the general public, in the hope of encouraging amateur meteorologists (and interested science students) to take an interest in the subject. Consequently, this department needs some way to broadcast its raw data (at the moment, sets of temperature readings for different cities) over the web, to be available to any connecting client.



Here''s a snippet of the MySQL database table that contains the temperature data (you may assume that this table is automatically updated on a regular basis by an independent process).




+------+-------+------+-------+
| city | t1 | t2 | t3 |
+------+-------+------+-------+
| BOM | 27.6 | 26.9 | 26.1 |
| LON | 10.5 | 11.9 | 10.8 |
| NYC | 17.69 | 15.8 | 14.99 |
+------+-------+------+-------+



All that is required is an interface to this database so that users can connect to the system and obtain readings for any city (keyed against each city''s unique three-letter city code).



RPC''s client-server architecture provides a suitable framework for this kind of application. An RPC server can be used at one end of the connection to accept XML-encoded remote procedure calls (containing arguments such as the city code and the format in which data is required), and execute appropriate functions to retrieve the required data from the database. The results of invoking the procedure can then be sent back to the client for further processing or display.



Obviously, there are a number of different ways to implement this application (Perl, Java, WDDX, and even a plain-vanilla PHP script with MySQL hooks), so my usage of XML-RPC here is purely illustrative. In the long run, however, using XML-RPC might well turn out to be a good decisionbecause implementations of the protocol are available for different platforms, it would be possible for the fictional government organization to create other, non-web-based clients (for example, a Windows COM-based application) without any changes required to its RPC server.




The Server




The server is fairly easy to implement (and, in fact, hardly changes across subsequent examples), so I''ll demonstrate that first. Listing 6.27 has the code.



Listing 6.27 A Simple RPC Server



<?php
// get the request
$request = $HTTP_RAW_POST_DATA;
// create server
$rpc_server = xmlrpc_server_create()
or die("Could not create RPC server");
// register methods
xmlrpc_server_register_method($rpc_server,
"getWeatherData", "phpWeather") or die("Could
not register method");
// call method
$response = xmlrpc_server_call_method
($rpc_server, $request, NULL, array("verbosity" =>
"no_white_space"));
// print response
echo $response;
// clean up
xmlrpc_server_destroy($rpc_server);
// function to return weather data
// $args include a city code and a parameter indicating whether
// all three readings (raw) or a composite calculation (avg)
// are required
function phpWeather($method, $args, $add_args)
{
// open connection to database
$connection = mysql_connect
("localhost", "rpc_agent", "secret") or die ("Unable to
connect!");
mysql_select_db("weather_data") or die ("Unable to select database!");
// get data
$city = mysql_escape_string($args[0]["city"]);
$query = "SELECT t1, t2, t3 FROM weather WHERE city = ''$city''";
$result = mysql_query($query) or die
("Error in query: $query. " . mysql_error());
// if a result is returned
if (mysql_num_rows($result) > 0)
{
// iterate through resultset
list($t1, $t2, $t3) = mysql_fetch_row($result);
// process data depending on requested output format
if ($args[0]["format"] == "raw")
{
// return raw data
return array("format" => "raw", "data" => array($t1, $t2, $t3));
}
else if ($args[0]["format"] == "avg")
{
// total and average readings
// return average
$total = $t1 + $t2 + $t3;
// do this to avoid division by zero errors
if ($total != 0)
{
$avg = $total/3;
}
else
{
$avg = 0;
}
return array("format" => "avg", "data" => $avg);
}
}
else
{
// return a fault
return array("faultCode" => 33, "faultString" =
> "No dataavailable for that
city");
}
// close database connection
mysql_close($connection);
}
?>



Most of this should be familiar to you from the previous sections of this chapter. The procedure getWeatherData(), which maps internally to the PHP function phpWeather(), is set up to accept two arguments: the three-letter city code and the data format (raw readings or arithmetic mean of raw readings) required by the client. This procedure is registered with the RPC server via the xmlrpc_server_register_method() function, and it is invoked when the server receives a POST request containing the XML-encoded RPC request.




Going POST-al




In case you''re wondering, the special PHP variable $HTTP_RAW_POST_DATA returns the raw data sent to the server via the POST method (or, if you''re using a command-line script, the data entered via the standard input). This data can then be parsed and processed by your script.





Internally, the phpWeather() function is pretty simple. It uses the arguments provided to it to connect to a MySQL database, retrieve the data from it, and then return it to the caller as an array containing either the average or the raw data. This returned array is then encoded into XML and sent back to the client as a POST response.




The Client




The RPC client needs to do a few important things:






Accept user input as to the city and output format required.






Encode this user input into an XML-RPC request, and POST it to the server.






Receive the XML-encoded POST response and decode it.






Display the received data as an HTML page.







Listing 6.28 has the complete code.



Listing 6.28 A Simple XML-RPC Client



<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
if(!$_POST[''submit''])
{
?>
<form action="<? echo $_SERVER[''PHP_SELF'']; ?>" method="POST">
<b>City code:</b>
<br>
input type="text" name="city" size="4" maxlength="3">
<p>
<b>Data format:</b>
<br>
<input type="Radio" name="format" value="avg" checked>Average only
<br>
<input type="radio" name="format" value="raw">Raw data
<p>
<input type="submit" name="submit" value="Go!">
</form>
<?php
}
else
{
// where is the RPC server?
$server = "weather.domain.com";
$url = "/rpc/server.php";
$port = 80;
// RPC arguments
$city = strtoupper($_POST[''city'']);
$params = array("city" => $city,
"format" => $_POST[''format'']);
// encode XML-RPC request
$request = xmlrpc_encode_request
("getWeatherData", $params, array("verbosity" =>
"no_white_space"));
// open socket
$fp = fsockopen($server, $port);
if(!$fp)
{
echo "Could not open socket";
}
else
{
// create POST data string
$post_data = "POST $url HTTP/1.0\r\nUser-Agent: PHP-RPC
Client\r\nContent-Type: text/xml\r\n
Content-Length: " .strlen($request) . "\r\n\r\n" .
$request;
// send POST data
fwrite($fp, $post_data);
// read the response
$post_response = fread($fp, 14096);
// close the socket
fclose($fp);
}
// strip out HTTP headers in response
// look for <?xml and get
everything from there to the end of the response
$response = substr
($post_response, strpos($post_response, "<?xml"));
// decode response
$output = xmlrpc_decode($response);
// more stringent error checks would be good here
if(is_array($output))
{
// check to see if a fault was returned
if (!isset($output[0]["faultCode"]))
{
// no? this means valid data was returned
// format and display
if ($output["format"] == "avg")
{
echo "Average temperature reading for city $city is " .
$output["data"] . " (based on three readings)";
}
else if ($output["format"] == "raw")
{
echo "Last three temperature readings for city $city (8-hour
intervals) are:<br><ul>";
echo "<li>" . $output["data"][0];
echo "<li>" . $output["data"][1];
echo "<li>" . $output["data"][2];
echo "</ul>";
}
}
else
{
// a fault occurred
// format and display fault information
echo "The following fault occurred:<br>";
echo $output[0]["faultString"] . " (fault code " .
$output[0]["faultCode"] . ")";
}
}
else
{
// no array returned
echo "Unrecognized response from server";
}
}
?>
</body>
</html>



This entire script is split into two main sections. The first section merely displays an HTML form for user input; this form contains a text field for the city code and a pair of radio buttons to choose the data format required.



After the form is submitted, the script calls itself again; this time around, however, the $submit variable will exist, so the second half of the script will be executed. At this stage, the first order of business is to create an array to hold the remote procedure arguments, and create an XML-encoded RPC request with xmlrpc_encode_request().



Next, a socket is opened to the remote RPC server (actually a web server with PHP''s RPC extension compiled in), and the XML data is sent to it as a POST request (note the various HTTP headers that must accompany the data packet). After the server has received the request, decoded it, and executed the remote procedure, the response is sent back (through the same socket connection) to the client as a POST response.



This POST response contains a bunch of HTTP headers, in addition to the XMLRPC response string. These need to be stripped out so that the remaining XML can be decoded into a native PHP data type with xmlrpc_decode(). Finally, depending on the data format requested (this format is included in the RPC response), the temperature reading(s) are displayed with an appropriate message.



Figure 6.3 and Figure 6.4 demonstrate what the client looks like, both before and after sending an RPC request.




Figure 6.3. The XML-RPC client from Listing 6.28, prior to requesting weather data.








Figure 6.4. The XML-RPC client from Listing 6.28, with the weather data requested.










The HTTP Files




If you want to learn more about the HTTP protocol and the POST method, you should take a look at the HTTP specification on the W3C''s Protocols web page, at http://www.w3.org/Protocols/.





Now that was fairly complicated, especially the part involving sockets and HTTP headerswhich is why it makes sense to package those bits of code into functions and then separate them from the main body of your script. And, if you look at the official web site for PHP''s RPC extension, you''ll see that the library author, Dan Libby, has done just this: He''s packaged a number of network transmission functions as separate utilities that can be included and used in your RPC client.



Listing 6.29 demonstrates an alternative implementation of the script in Listing 6.28, using these packaged functions. As you will see, this listing is far simpler to read and understand.



Listing 6.29 An Alternative XML-RPC Client



<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
if($_POST[''submit''])
{
?>
<form action="<? echo $_SERVER[''PHP_SELF'']; ?>" method="POST">
<b>City code:</b>
<br>
<input type="text" name="city" size="4" maxlength="3">
<p>
<b>Data format:</b>
<br>
<input type="Radio" name="format" value="avg" checked>Average only
<br>
<input type="radio" name="format" value="raw">Raw data
<p>
<input type="submit" name="submit" value="Go!">
</form>
<?php
}
else
{
include("utils.php");
// where is the RPC server?
$server = "weather.domain.com";
$url = "/rpc/server.php";
$port = 80;
// RPC arguments
$city = strtoupper($_POST[''city'']);
$params = array("city" => $city, "format" => $_POST[''format'']);
// encode, transmit request and receive
, decode response
$output = xu_rpc_http_concise
(array("host" => $server, "uri" => $url,"port" =>
$port, "method" =>
"getWeatherData", "args" => $params));
if(is_array($output))
{
// check for faults
if (!xu_is_fault($output))
{
// no fault!
// data has been returned
// format and display
if ($output["format"] == "avg")
{
echo "Average temperature reading for city $city is "
.$output["data"] . " (based on three readings)";
}
else if ($output["format"] == "raw")
{
echo "Last three temperature readings for city $city (8-hour
intervals) are:<br><ul>";
echo "<li>" . $output["data"][0];
echo "<li>" . $output["data"][1];
echo "<li>" . $output["data"][2];
echo "</ul>";
}
}
else
{
// fault! format and display
echo "The following fault occurred:<br>";
echo $output[0]["faultString"] . " (fault code " .
$output[0]["faultCode"] . ")";
}
}
else
{
// no array returned
echo "Unrecognized response from server";
}
}
?>
</body>
</html>



In this case, everything related to HTTP header transmission and response translation has been extracted into the function xu_rpc_http_concise(), which accepts a number of arguments identifying the RPC server, remote procedure name and arguments, and response format. This function is defined in the file "utils.php", which you can download from the official site (http://xmlrpc-epi.sourceforge.net/). If you examine its internals, you''ll see that it performs almost all the tasks so lovingly detailed in Listing 6.28, albeit with more stringent error checks.



The same holds true for the xu_is_fault() function, which merely checks the server response for a fault identifier, and returns true if it finds one.




Building an RPC-Based Mail Service




The previous example examined remote database access using the RPC framework. However, this is just one application of many; RPC makes it possible to expose and execute some very useful services over a network. The following example demonstrates one such service: an RPC-accessible mail service that checks a POP3 server for email and returns the number of messages in the specified mailbox.




Requirements




The requirements here are fairly simple: a server capable of accepting RPC requests and making POP3 (or IMAP) connections, and a client capable of encoding and decoding XML-encoded RPC data. Both client and server use SOAP as the encoding mechanism.



Note that it''s not necessary that both client and server be implemented using PHP (although that''s the way I''ve done it here); either client or server (or both) can be implemented in any SOAP-supported platform.




The Server




Listing 6.30 has the code for the server.



Listing 6.30 A SOAP Server



<?php
include("utils.php");
$request = $HTTP_RAW_POST_DATA;
// create server
$rpc_server = xmlrpc_server_create()
or die("Could not create RPC server");
// register methods
xmlrpc_server_register_method
($rpc_server, "getTotalPOP3Messages",
"getTotalPOP3Messages") or die("Could not register method");
// call method
$response = xmlrpc_server_call_method
($rpc_server, $request, NULL,array("verbosity" =>
"no_white_space", "version" => "soap 1.1"));
// print response
echo $response;
// clean up
xmlrpc_server_destroy($rpc_server);
// function to return number of messages
function getTotalPOP3Messages
($method, $args, $add_args)
{
// check to see if the server supports POP3
// you may need to recompile your PHP build
// to enable this support
if (function_exists(''imap_open''))
{
// open connection to mail server
$inbox = imap_open ("{". $args[0]["pop_host"] . "/pop3:110}",
$args[0]["pop_user"], $args[0]["pop_pass"]);
if ($inbox)
{
// get number of messages
$total = imap_num_msg($inbox);
imap_close($inbox);
return $total;
}
else
{
return xu_fault_code(2, "Could not connect to POP3 server");
}
}
else
{
return xu_fault_code(1, "POP3 support not available on RPC server");
}
}
?>



Again, the server code here is not very different from that seen in Teething Trouble" for a few caveats related to this), switching between the two is a fairly simple matter, and one that is quickly accomplished.




The Client




In its internals, this client also resembles the previous ones. Take a look at Listing 6.31.



Listing 6.31 A SOAP Client



<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
if(!$_POST[''submit''])
{
// display form
?>
<table border="0" cellspacing="5" cellpadding="5">
<form action="<? echo $_SERVER[''PHP_SELF'']; ?>" method="POST">
<tr>
<td><b>Username:</b></td>
<td><input type="text" name="pop_user"></td>
</tr>
<tr>
<td><b>Password:</b></td>
<td><input type="password" name="pop_pass"></td>
</tr>
<tr>
<td><b>POP Server:</b></td>
<td><input type="text" name="pop_host"></td>
</tr>
<tr>
<td colspan="2" align="center"><input
type="submit" name="submit" value="Get Total
Messages!"></td>
</tr>
</form>
</table>
<?php
}
else
{
// include useful network connection functions
include("utils.php");
// where is the RPC server
$server = "mail.service";
$url = "/rpc/server.php";
$port = 80;
// RPC arguments
$params = array("pop_user" =
> $_POST[''pop_user''], "pop_pass" =
> $_POST[''pop_pass''],
"pop_host" => $_POST[''pop_host'']);
// encode, transmit request and
receive, decode response
$result = xu_rpc_http_concise
(array("host" => $server, "uri" => $url, "port" =>
$port, "method" => "getTotalPOP3Messages",
"args" => $params, "output" => array("version"
=> "soap 1.1")));
// check for faults
if(is_array($result) && $result["faultcode"])
{
echo "The following error occurred
: " . $result["faultstring"] . " (error
code " . $result["faultcode"] . ")";
}
// if none present, display message count
else
{
echo "$result messages in mailbox";
}
}
?>
</body>
</html>



Again, the first half of the script sets up an HTML form for the user to enter mail account details; these details are then encoded into a SOAP envelope and sent to the SOAP server using the provided xu_rpc_http_concise() function. The decoded return value is then analyzed, and an appropriate message is displayed, depending on whether a fault was recorded or not.



Figure 6.5 and Figure 6.6 demonstrate what the client looks like, both before and after sending an RPC request.




Figure 6.5. The SOAP client from Listing 6.31, prior to retrieving mailbox information.








Figure 6.6. The SOAP client from Listing 6.31, with the mailbox information requested.








Executing Shell Commands via RPC




Finally, an example that demonstrates the power inherent in RPC, and illustrates what a double-edged sword that power can be. The next (and final) example creates an RPC server that can accept any system or shell command from a client and execute this command on the server.




Requirements




Again, fairly simple: a client who accepts a command through user input, encodes it, and transmits it to a server that executes this command and returns the resulting output to the client. Both client and server communicate using XML-RPC, with HTTP as the network transport layer.




The Server




Chapter 2, "PHP and the Simple API for XML (SAX)") to execute the command received and return the output to the calling script.



Listing 6.32 An XML-RPC Server



<?php
$request = $HTTP_RAW_POST_DATA;
// create server
$rpc_server = xmlrpc_server_create()
or die("Could not create RPC server");
// register methods
xmlrpc_server_register_method($rpc_server, "execR", "execR")
or die("Could not register
method");
// call method
$response = xmlrpc_server_call_method
($rpc_server, $request, NULL);
// print response
echo $response;
// clean up
xmlrpc_server_destroy($rpc_server);
// execute command
function execR($method, $args, $add_args)
{
$output = `$args[0]`;
return $output;
}
?>


The Client




As you might guess, the client is equally simplea form consisting of one text box, into which the user can enter the command to be executed on the remote server. This data is then encoded and sent to the server as an XML-RPC request with the command output coming back as an XML-RPC response.



Listing 6.33 has the complete code listing.



Listing 6.33 An XML-RPC Client



<html>
<head>
<basefont face="Arial">
</head>
<body>
<?php
if(!$_POST[''submit''])
{
?>
<form action="<? echo $_SERVER[''PHP_SELF'']; ?>" method="POST">
<b>Remote command:</b>
<br>
<input type="text" name="command">
<input type="submit" name="submit" value="Go!">
</form>
<?php
}
else
{
include("utils.php");
// where is the RPC server?
$server = "remote.server";
$url = "/rpc/server.php";
$port = 80;
// encode,
transmit request and receive
, decode response
output = xu_rpc_http_concise
(array("host" =>
$server, "uri" => $url, "port" =>
$port, "method" => "execR",
"args" => $_POST[''command'']));
echo $output;
}
?>
</body>
</html>



Figure 6.7 and Figure 6.8 demonstrate what the client looks like, both before and after sending an RPC request.




Figure 6.7. The XML-RPC client from Listing 6.33, prior to executing a remote command.








Figure 6.8. The XML-RPC client from Listing 6.33, after receiving the output of the uname -a command from the XML-RPC server.










Red Alert




It cannot be stressed enough that an RPC server, such as the one discussed in Listing 6.32, should never, ever be implemented in a production environment. By allowing any client to execute any command on a remote system, you''re immediately opening up a security hole of gaping proportionsone that can be used by malicious users to seriously damage your network or system.



The example is included here only for illustrative purposes, to demonstrate the power that RPC provides; it''s fitting to remember that this power can be easily turned against you if used improperly.







/ 84