Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced PHP Programming

.pdf
Скачиваний:
71
Добавлен:
14.04.2015
Размер:
7.82 Mб
Скачать
$entry

398 Chapter 16 RPC: Interacting with Remote Services

Element

Description

title

The title of the item

link

A URL that links to a formatted form of the item.

description

A summary of the item.

author

The name of the author of the item. In the RSS spec, this is speci-

 

fied to be an email address, although nicknames are more common-

 

ly used.

pubDate

The date the entry was published.

The specification also optionally allows for fields for links to comment threads, unique identifiers, and categories. In addition, many Web logs extend the RSS item definition to include a content:encoded element, which contains the full post, not just the post summary that is traditionally found in the RSS description element.

To implement the MetaWeblog API, you need to define functions to implement the three methods in question. First is the function to handle posting new entries:

function metaWeblog_newPost($message) { $username = $message->params[1]->getval(); $password = $message->params[2]->getval();

if(!serendipity_authenticate_author($username, $password)) { return new XML_RPC_Response(‘’, 4, Authentication Failed);

}

$item_struct = $message->params[3]->getval(); $publish = $message->params[4]->getval(); $entry[title] = $item_struct[title]; $entry[body] = $item_struct[description]; $entry[author] = $username;

$entry[isdraft] = ($publish == 0)?true:false; $id = serendipity_updertEntry($entry);

return new XML_RPC_Response( new XML_RPC_Value($id, string));

}

metaWeblog_newPost() extracts the username and password parameters from the request and deserializes their XML representations into PHP types by using the getval() method.Then metaWeblog_newPost() authenticates the specified user. If the user fails to authenticate, metaWeblog_newPost() returns an empty XML_RPC_Response object, with an “Authentication Failed” error message.

If the authentication is successful, metaWeblog_newPost() reads in the item_struct parameter and deserializes it into the PHP array $item_struct, using getval(). An array defining Serendipity’s internal entry representation is constructed from $item_struct, and that is passed to serendipity_updertEntry(). XML_RPC_Response, consisting of the ID of the new entry, is returned to the caller.

XML-RPC 399

The back end for MetaWeblog.editPost is very similar to MetaWeblog.newPost.

Here is the code:

function metaWeblog_editPost($message) { $postid = $message->params[0]->getval(); $username = $message->params[1]->getval(); $password = $message->params[2]->getval();

if(!serendipity_authenticate_author($username, $password)) { return new XML_RPC_Response(‘’, 4, Authentication Failed);

}

$item_struct = $message->params[3]->getval(); $publish = $message->params[4]->getval();

$entry[title]

= $item_struct[title];

$entry[body]

=

$item_struct[description];

$entry[author]

 

= $username;

$entry[id]

=

$postid;

$entry[isdraft] = ($publish == 0)?true:false; $id = serendipity_updertEntry($entry);

return new XML_RPC_Response( new XML_RPC_Value($id?true:false, boolean));

}

The same authentication is performed, and $entry is constructed and updated. If serendipity_updertEntry returns $id, then it was successful, and the response is set to true; otherwise, the response is set to false.

The final function to implement is the callback for MetaWeblog.getPost.This uses serendipity_fetchEntry() to get the details of the post, and then it formats an XML response containing item_struct. Here is the implementation:

function metaWeblog_getPost($message) { $postid = $message->params[0]->getval(); $username = $message->params[1]->getval(); $password = $message->params[2]->getval();

if(!serendipity_authenticate_author($username, $password)) { return new XML_RPC_Response(‘’, 4, Authentication Failed);

}

$entry = serendipity_fetchEntry(id, $postid);

$tmp = array(

pubDate=> new XML_RPC_Value( XML_RPC_iso8601_encode($entry[timestamp]), dateTime.iso8601),

postid=> new XML_RPC_Value($postid, string),

author=> new XML_RPC_Value($entry[author], string),

description=> new XML_RPC_Value($entry[body], string),

title=> new XML_RPC_Value($entry[title],string),

link=> new XML_RPC_Value(serendipity_url($postid), string)

);

400 Chapter 16 RPC: Interacting with Remote Services

$entry = new XML_RPC_Value($tmp, struct);

return new XML_RPC_Response($entry);

}

Notice that after the entry is fetched, an array of all the data in item is prepared. XML_RPC_iso8601() takes care of formatting the Unix timestamp that Serendipity uses into the ISO 8601-compliant format that the RSS item needs.The resulting array is then serialized as a struct XML_RPC_Value.This is the standard way of building an XML-RPC struct type from PHP base types.

So far you have seen string, boolean, dateTime.iso8601, and struct identifiers, which can be passed as types into XML_RPC_Value.This is the complete list of possibilities:

Type

Description

i4/int

A 32-bit integer

boolean

A Boolean type

double

A floating-point number

string

A string

dateTime.iso8601

An ISO 8601-format timestamp

base64

A base 64-encoded string

struct

An associative array implementation

array

A nonassociative (indexed) array

structs and arrays can contain any type (including other struct and array elements) as their data. If no type is specified, string is used.While all PHP data can be represented as either a string, a struct, or an array, the other types are supported because remote applications written in other languages may require the data to be in a more specific type.

To register these functions, you create a dispatch, as follows:

$dispatches = array( metaWeblog.newPost=>

array(function=> metaWeblog_newPost),

metaWeblog.editPost=>

array(function=> metaWeblog_editPost),

metaWeblog.getPost=>

array(function=> metaWeblog_getPost));

$server = new XML_RPC_Server($dispatches,1);

Congratulations! Your software is now MetaWeblog API compatible!

XML-RPC 401

Auto-Discovery of XML-RPC Services

It is nice for a consumer of XML-RPC services to be able to ask the server for details on all the services it provides. XML-RPC defines three standard, built-in methods for this introspection:

n system.listMethods—Returns an array of all methods implemented by the server (all callbacks registered in the dispatch map).

n system.methodSignature—Takes one parameter—the name of a method—and returns an array of possible signatures (prototypes) for the method.

n system.methodHelp—Takes a method name and returns a documentation string for the method.

Because PHP is a dynamic language and does not enforce the number or type of arguments passed to a function, the data to be returned by system.methodSignature must be specified by the user. Methods in XML-RPC can have varying parameters, so the return set is an array of all possible signatures. Each signature is itself an array; the array’s first element is the return type of the method, and the remaining elements are the parameters of the method.

To provide this additional information, the server needs to augment its dispatch map to include the additional info, as shown here for the metaWeblog.newPost method:

$dispatches = array(

metaWeblog.newPost=>

array(function=> metaWeblog_newPost,

signature=> array( array($GLOBALS[XML_RPC_String],

$GLOBALS[XML_RPC_String], $GLOBALS[XML_RPC_String], $GLOBALS[XML_RPC_String], $GLOBALS[XML_RPC_Struct], $GLOBALS[XML_RPC_String]

)

),

docstring=> Takes blogid, username, password, item_struct .

publish_flag and returns the postid of the new entry),

/* ... */

);

You can use these three methods combined to get a complete picture of what an XMLRPC server implements. Here is a script that lists the documentation and signatures for every method on a given XML-RPC server:

<?php

require_once XML/RPC.php;

if($argc != 2) {

print Must specify a url.\n;

402 Chapter 16 RPC: Interacting with Remote Services

exit;

}

$url = parse_url($argv[1]);

$client = new XML_RPC_Client($url[path], $url[host]); $msg = new XML_RPC_Message(system.listMethods); $result = $client->send($msg);

if ($result->faultCode()) { echo Error\n;

}

$methods = XML_RPC_decode($result->value()); foreach($methods as $method) {

$message = new XML_RPC_Message(system.methodSignature,

array(new XML_RPC_Value($method)));

$response = $client->send($message)->value(); print Method $method:\n;

$docstring = XML_RPC_decode( $client->send(

new XML_RPC_Message(system.methodHelp,

array(new XML_RPC_Value($method))

)

)->value()

); if($docstring) {

print $docstring\n;

}

else {

print NO DOCSTRING\n;

}

$response = $client->send($message)->value(); if($response->kindOf() == array) {

$signatures = XML_RPC_decode($response); for($i = 0; $i < count($signatures); $i++) {

$return = array_shift($signatures[$i]); $params = implode(, , $signatures[$i]);

print Signature #$i: $return $method($params)\n;

}

}else {

print NO SIGNATURE\n;

}

print \n;

}

?>

SOAP 403

Running this against a Serendipity installation generates the following:

> xmlrpc-listmethods.php http://www.example.org/serendipity_xmlrpc.php

/* ... */

Method metaWeblog.newPost:

Takes blogid, username, password, item_struct, publish_flag and returns the postid of the new entry

Signature #0: string metaWeblog.newPost(string, string, string, struct, string)

/* ... */

Method system.listMethods:

This method lists all the methods that the XML-RPC server knows how to dispatch

Signature #0: array system.listMethods(string)

Signature #1: array system.listMethods()

Method system.methodHelp:

Returns help text if defined for the method passed, otherwise returns an empty string

Signature #0: string system.methodHelp(string)

Method system.methodSignature:

Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature) Signature #0: array system.methodSignature(string)

SOAP

SOAP originally stood for Simple Object Access Protocol, but as of Version 1.1, it is just a name and not an acronym. SOAP is a protocol for exchanging data in a heterogeneous environment. Unlike XML-RPC, which is specifically designed for handling RPCs, SOAP is designed for generic messaging, and RPCs are just one of SOAP’s applications. That having been said, this chapter is about RPCs and focuses only on the subset of SOAP 1.1 used to implement them.

So what does SOAP look like? Here is a sample SOAP envelope that uses the xmethods.net sample stock-quote SOAP service to implement the canonical SOAP RPC example of fetching the stock price for IBM (it’s the canonical example because it is the example from the SOAP proposal document):

<?xml version=1.0encoding=UTF-8?> <soap:Envelope

xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/

404 Chapter 16 RPC: Interacting with Remote Services

xmlns:xsd=http://www.w3.org/2001/XMLSchemaxmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:soap-enc=http://schemas.xmlsoap.org/soap/encoding/ soap:encodingStyle=http://schemas.xmlsoap.org/soap/encoding/>

<soap:Body>

<getQuote xmlns=

http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote.StockQuote/

>

<symbol xsi:type=xsd:string>ibm</symbol>

</getQuote>

</soap:Body>

</soap:Envelope>

This is the response:

<?xml version=1.0encoding=UTF-8?>

<soap:Envelope xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd=http://www.w3.org/2001/XMLSchemaxmlns:soapenc=http://schemas.xmlsoap.org/soap/encoding/soap:encodingStyle=http://schemas.xmlsoap.org/soap/encoding/>

<soap:Body>

<n:getQuoteResponse xmlns:n=urn:xmethods-delayed-quotes> <Result xsi:type=xsd:float>90.25</Result>

</n:getQuoteResponse>

</soap:Body>

</soap:Envelope>

SOAP is a perfect example of the fact that simple in concept does not always yield simple in implementation. A SOAP message consists of an envelope, which contains a header and a body. Everything in SOAP is namespaced, which in theory is a good thing, although it makes the XML hard to read.

The topmost node is Envelope, which is the container for the SOAP message.This element is in the xmlsoap namespace, as is indicated by its fully qualified name <soap:Envelope> and this namespace declaration:

xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/which creates the association between soap and the namespace URI

http://schemas.xmlsoap.org/soap/envelope/.

SOAP and Schema

SOAP makes heavy implicit use of Schema, which is an XML-based language for defining and validating data structures. By convention, the full namespace for an element (for example, http:// schemas.xmlsoap.org/soap/envelope/) is a Schema document that describes the namespace. This is not necessary—the namespace need not even be a URL—but is done for completeness.

TEAM

FLY

SOAP

405

 

Namespaces serve the same purpose in XML as they do in any programming language: They prevent possible collisions of two implementers’ names. Consider the top-level node <soap-env:Envelope>.The attribute name Envelope is in the soap-env namespace.Thus, if for some reason FedEX were to define an XML format that used Envelope as an attribute, it could be <FedEX:Envelope>, and everyone would be happy.

There are four namespaces declared in the SOAP Envelope:

n xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/—The SOAP envelope Schema definition describes the basic SOAP objects and is a standard namespace included in every SOAP request.

n xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”—The xsi:type element attribute is used extensively for specifying types of elements.

n xmlns:xsd=”http://www.w3.org/2001/XMLSchema”—Schema declares a number of base data types that can be used for specification and validation.

n xmlns:soapenc=”http://schemas.xmlsoap.org/soap/encoding/”—This is the specification for type encodings used in standard SOAP requests.

The <GetQuote> element is also namespaced—in this case, with the following ultra-long name:

http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote.StockQuote

Notice the use of Schema to specify the type and disposition of the stock symbol being passed in:

<symbol xsi:type=xsd:string>ibm</symbol>

<symbol> is of type string.

Similarly, in the response you see specific typing of the stock price:

<Result xsi:type=xsd:float>90.25</Result>

This specifies that the result must be a floating-point number.This is usefulness because there are Schema validation toolsets that allow you to verify your document.They could tell you that a response in this form is invalid because foo is not a valid representation of a floating-point number:

<Result xsi:type=xsd:float>foo</Result>

WSDL

SOAP is complemented by Web Services Description Language (WSDL).WSDL is an XML-based language for describing the capabilities and methods of interacting with Web services (more often than not, SOAP). Here is the WSDL file that describes the stock quote service for which requests are crafted in the preceding section:

<?xml version=1.0encoding=UTF-8 ?>

<definitions name=net.xmethods.services.stockquote.StockQuote

406 Chapter 16 RPC: Interacting with Remote Services

targetNamespace=

http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote.StockQuote/

xmlns:tns=

http://www.themindelectric.com/wsdl/net.xmethods.services.stockquote.StockQuote/xmlns:electric=http://www.themindelectric.com/xmlns:soap=http://schemas.xmlsoap.org/wsdl/soap/xmlns:xsd=http://www.w3.org/2001/XMLSchemaxmlns:soapenc=http://schemas.xmlsoap.org/soap/encoding/xmlns:wsdl=http://schemas.xmlsoap.org/wsdl/xmlns=http://schemas.xmlsoap.org/wsdl/>

<message name=getQuoteResponse1>

<part name=Resulttype=xsd:float/> </message>

<message name=getQuoteRequest1>

<part name=symboltype=xsd:string/> </message>

<portType name=net.xmethods.services.stockquote.StockQuotePortType> <operation name=getQuoteparameterOrder=symbol>

<input message=tns:getQuoteRequest1/> <output message=tns:getQuoteResponse1/>

</operation>

</portType>

<binding name=net.xmethods.services.stockquote.StockQuoteBindingtype=tns:net.xmethods.services.stockquote.StockQuotePortType>

<soap:binding style=rpctransport=http://schemas.xmlsoap.org/soap/http/> <operation name=getQuote>

<soap:operation soapAction=urn:xmethods-delayed-quotes#getQuote/> <input>

<soap:body use=encodednamespace=urn:xmethods-delayed-quotes encodingStyle=http://schemas.xmlsoap.org/soap/encoding//>

</input>

<output>

<soap:body use=encodednamespace=urn:xmethods-delayed-quotes encodingStyle=http://schemas.xmlsoap.org/soap/encoding//>

</output>

</operation>

</binding>

<service name=net.xmethods.services.stockquote.StockQuoteService> <documentation>

net.xmethods.services.stockquote.StockQuote web service </documentation>

<port name=net.xmethods.services.stockquote.StockQuotePortbinding=tns:net.xmethods.services.stockquote.StockQuoteBinding>

<soap:address location=http://66.28.98.121:9090/soap/>

</port>

</service>

</definitions>

SOAP 407

WSDL clearly also engages in heavy use of namespaces and is organized somewhat out of logical order.

The first part of this code to note is the <portType> node. <portType> specifies the operations that can be performed and the messages they input and output. Here it defines getQuote, which takes getQuoteRequest1 and responds with getQuoteResponse1.

The <message> nodes for getQuoteResponse1 specify that it contains a single element Result of type float. Similarly, getQuoteRequest1 must contain a single element symbol of type string.

Next is the <binding> node. A binding is associated with <portType> via the type attribute, which matches the name of <portType>. Bindings specify the protocol and transport details (for example, any encoding specifications for including data in the SOAP body) but not actual addresses. A binding is associated with a single protocol, in this case HTTP, as specified by the following:

<soap:binding style=rpctransport=http://schemas.xmlsoap.org/soap/http/>

Finally, the <service> node aggregates a group of ports and specifies addresses for them. Because in this example there is a single port, it is referenced and bound to http:/66.28.98.121:9090/soap with the following:

<port name=net.xmethods.services.stockquote.StockQuotePortbinding=tns:net.xmethods.services.stockquote.StockQuoteBinding>

<soap:address location=http://66.28.98.121:9090/soap/>

</port>

It’s worth noting that nothing binds SOAP to only working over HTTP, nor do responses have to be returned. SOAP is designed to be a flexible general-purpose messaging protocol, and RPC over HTTP is just one implementation.The WSDL file tells you what services are available and how and where to access them. SOAP then implements the request and response itself.

Fortunately, the PEAR SOAP classes handle almost all this work for you.To initiate a SOAP request, you first create a new SOAP_Client object and pass in the WSDL file for the services you want to access. SOAP_Client then generates all the necessary proxy code for requests to be executed directly, at least in the case where inputs are all simple Schema types.The following is a complete client request to the xmethods.net demo stock quote service:

require_once SOAP/Client.php;

$url = http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl; $soapclient = new SOAP_Client($url, true);

$price = $soapclient->getQuote(ibm)->deserializeBody(); print Current price of IBM is $price\n;

SOAP_Client does all the magic of creating a proxy object that allows for direct execution of methods specified in WSDL. After the call to getQuote() is made, the result is

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]