W końcu przyszedł czas na zabranie się za dokończenie tematu dotyczącego Web Services w PHP. Tym razem zabierzemy się za kolejny protokół, jeden z popularniejszych, czyli XML-RPC (XML-Remote Procedure Call).
Uzupełnieniem tego artykułu, dla początkujących, może być wcześniejszy wpis: Usługi sieciowe w PHP: REST. Znajduje się tam słowniczek pojęć, wspomniana jest instalacja pakietów PEAR, omówiona jest z lekka idea usług sieciowych i aplikacji typu Mashup.
Czym jest XML-RPC?
XML-RPC jest protokołem XML służącym do przesyłu danych między różnymi punktami końcowymi (serwerami). Umożliwia to wymianę danych dla zdalnych procedur.
Daje to wspaniałą możliwość komunikowania się różnych aplikacji przy minimalnym wysiłku, ponieważ usługi z wykorzystaniem tego protokołu są bardzo proste do definiowania.
Komunikacja odbywa się z wykorzystaniem protokołu HTTP, więc bez żadnych problemów można używać protokołu XML-RPC w web-developerce.
XML-RPC w praktyce
Gdy poznaliśmy już teoretyczne podstawy XML-RPC, możemy pokazać przykładowy kod przesyłany w żądaniu i odpowiedzi. Kod zaczerpnięty ze specyfikacji.
Request:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | POST /RPC2 HTTP/1.0 User-Agent: Frontier/5.1.2 (WinNT) Host: betty.userland.com Content-Type: text/xml Content-length: 181 <?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall> |
Response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | HTTP/1.1 200 OK Connection: close Content-Length: 158 Content-Type: text/xml Date: Fri, 17 Jul 1998 19:55:08 GMT Server: UserLand Frontier/5.1.2-WinNT <?xml version="1.0"?> <methodResponse> <params> <param> <value><string>South Dakota</string></value> </param> </params> </methodResponse> |
Jak więc widać powyżej, wysyłamy pewien pakiet danych o żądanej usłudze (nazwa metody, parametry) w formacie XML, w czego odpowiedzi dostajemy wynik – również jako XML.
Niestety, jest pewna dowolność w konstruowaniu odpowiedzi. Programista może napisać usługę w taki sposób, aby zwracała dane w formacie JSON czy PHP Array – nie jest to zgodne ze specyfikacją protokołu XML-RPC, jednak możliwe do wykonania, a co najgorsze – dość często stosowane.
XML-RPC w PHP
Protokół ten jest dość często wykorzystywany w PHP, w związku czym powstało multum bibliotek do jego obsługi.
Także i na potrzeby tego wpisu wykorzystamy jedną z takich bibliotek: XML_RPC2 z pakietu PEAR.
Aby biblioteka ta zadziałała konieczne jest także zainstalowane biblioteki cURL dla PHP. Ponadto na potrzeby poniższego przykładu wykorzystamy jeszcze jedną bibliotekę z pakietu PEAR odpowiedzialną za buforowanie wyników, czyli Cache_Lite, która jest zintegrowana z biblioteką XML_RPC2.
Warto korzystać z gotowych bibliotek tego typu, ponieważ bardzo skracają czas pracy. Zamiast samodzielnie tworzyć połączenie z wykorzystaniem cURL czy fsockopen, wystarczy napisać kilka linijek kodu z wykorzystaniem biblioteki.
W dodatku biblioteka potrafi buforować wyniki (zapisując na dysku wynikowe pliki), co bardzo skraca kolejne wywołania usługi. Jako, iż usługi sieciowe są najczęściej bardzo wolne, buforowanie jest wręcz wymagane!
To już wszystko, możemy zaczynać. Na początek napisałem prostą klasę XMLRPC, która jeszcze bardziej skraca odwoływanie się do zewnętrznych usług.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | require_once 'XML/RPC2/CachedClient.php'; class XMLRPC { // client xml-rpc private $client; /** * Magic method: constructor * * @param string web service url * @param string prefix * @param int cache time in seconds **/ public function __construct($url, $prefix='', $cache_time=3600) { $options = array( 'prefix' => $prefix, 'cacheDebug' => false, 'cacheOptions' => array( 'cacheDir' => './tmp/', 'lifetime' => $cache_time, 'cacheByDefault' => true ) ); // We make the XML_RPC2_CachedClient object // same syntax than XML_RPC2_Client $this->client = XML_RPC2_CachedClient::create($url, $options); } /** * Magic method: call * * @param string method name * @param mixed params * @return object response **/ public function __call($name, $arguments) { try { // run method from web service $result = call_user_func_array( array($this->client, $name), $arguments ); // response is array - return object if (is_array($result)) { return new ArrayObject($result); } // response is xml - return object $result_xml = simplexml_load_string( $result, 'SimpleXMLElement', LIBXML_NOERROR ); if ($result_xml !== false) { return $result_xml; } // response is string or other type return $result; } catch (XML_RPC2_FaultException $e) { // The XMLRPC server returns a XMLRPC error die('Exception #' . $e->getFaultCode() . ' : ' . $e->getFaultString()); } catch (Exception $e) { // Other errors (HTTP or networking problems...) die('Exception : ' . $e->getMessage()); } } } |
Krótkie omówienie klasy:
- Tworzymy folder „tmp”, w którym będzie przechowywany cache.
- Wywołujemy instancję klasy XMLRPC – w tym momencie jest utworzony obiekt klasy XML_RPC2_CachedClient z metodą create, który tworzy połączenie z usługą.
Jako parametr podajemy adres usługi i ustawienia, m. in. prefix dla metod, folder dla cache, czas życia buforu.
- Na utworzonym wcześniej obiekcie wywołujemy metody właściwej już usługi sieciowej. W wyniku otrzymujemy obiekt SimpleXMLElement ze zwróconej odpowiedzi w postaci XML.
W przypadku, gdyby usługa nie zwróciła dokumentu XML, a tablicę array to dokonywana jest konwersja na obiekt ArrayObject – jakoś przyjemniej mi się pracuje z wykorzystaniem tej klasy.
W przypadku zwrócenia przez usługę innego typu danych – dane te zostają zwrócone bezpośrednio do wywołanej metody.
W prawdziwej aplikacji warto by obsłużyć błędy w jakiś lepszy sposób, a nie zabijać od razu aplikację i pokazywać błędy userowi :-)
Przykładowe zastosowanie: UPC Database
UPC to takie amerykańskie kody kreskowe dla produktów :D powiedzmy więc, że mamy kilka takich numerów UPC i chcielibyśmy wiedzieć co to za produkty. W tym celu posłużymy się usługą UPC Database.
Oto przykładowe zastosowanie stworzonej klasy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | $client = new XMLRPC('http://www.upcdatabase.com/rpc'); $results = $client->lookupUPC('016600190027'); if (is_object($results)) { echo "Numer UPC: " . $results->offsetGet('upc'); echo "Produkt: " . $results->offsetGet('description'); } $results = $client->lookupUPC('011926112401'); if (is_object($results)) { echo "Numer UPC: " . $results->offsetGet('upc'); echo "Produkt: " . $results->offsetGet('description'); } |
W wyniku czego dostajemy:
1 2 3 4 5 | Numer UPC: 016600190027 Produkt: Schweppes Raspberry Gingerale Numer UPC: 011926112401 Produkt: Honey Nut Cheerios Cereal |
Prawda, że proste?
Przykładowe zastosowanie: Flickr
Drugim i ostatnim przykładem będzie również zagraniczny serwis, Flickr. Jest to chyba największy tego rodzaju internetowy manager zdjęć, w którym każdy użytkownik może publikować swoje zdjęcia, opisywać je, tworzyć albumy, itp itd.
Na potrzeby przykładu pobierzemy kilka adresów opatrzonych wybranym przez nas tagiem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // new XML RPC Client $client = new XMLRPC( 'http://api.flickr.com/services/xmlrpc/', 'flickr.photos.' ); // search tags $results = $client->search(array( 'api_key' => '[API_KEY_FROM_FLICKR]', 'tags' => 'money', 'per_page' => 5 )); if (is_object($results) && isset($results->photo)) { foreach ($results->photo as $id => $result) { echo 'http://farm' . $result['farm'] . '.static.flickr.com/' . $result['server'] . '/' . $result['id'] . '_' . $result['secret'] . ".jpg"; } } |
Na wyjściu otrzymujemy:
1 2 3 4 5 | http://farm5.static.flickr.com/4146/4842869683_984e815ecc.jpg http://farm5.static.flickr.com/4108/4843415934_901ba5db50.jpg http://farm5.static.flickr.com/4111/4843400822_542f815732.jpg http://farm5.static.flickr.com/4128/4842735689_14e7090041.jpg http://farm5.static.flickr.com/4109/4843254586_2c5d2030c1.jpg |
Wszystko więc działa jak należy i nie sprawia większych problemów. Podsumowując, protokół XML-RPC jest przyjaznym użytkownikowi protokołem i znacząco ułatwia wykorzystanie usług sieciowych w tworzonych aplikacjach.
W Zend Frameworku są już od jakiegoś czasu dostępne web services w postaci XML-RPC, SOAP i Json RPC, wszystkie trzy dostępne pod jednym interfejsem i dość nienajgorzej zaimplementowane…
W Zendzie i Symfony jest chyba wszystko, o czym tylko programista może pomyśleć :D niemniej jednak chciałem sprawdzić samemu jak to wszystko wygląda „od środka” – wpis ten popełniłem głównie w ramach nauki :-) bez korzystania z frameworków.
Wielkie dzięki!, właśnie tego potrzebowałem :)
ZendFfff? pffff…
Fajna ta klasa. Ale lepiej by było jakby zwracała ściśle określony rodzaj zmiennej np. zawsze tablicę. Po za tym bardzo zgrabna, to lubię.
@revenus: ta klasa była napisana ponad 3 lata temu. Dzisiaj napisałbym ją zupełnie inaczej – gdybym jeszcze tylko używał XML-RPC :-)