Originally published on XML.COM
Copyright (c)2005 Jean-Luc David / Stormpixel.com
Rozwój usług sieciowych (webservices) jest szeroko wspierany przez wszystkich głównych działaczy i organizacje branży technologicznej, w tym przez IBM, Microsoft, Sun Microsystem oraz W3C. W tę gałąź zainwestowano do tej pory miliony dolarów. Usługi sieciowe pozwalają Ci wymieniać dane pomiędzy wieloma platformami i konfiguracjami sprzętowymi. Na przykład możesz stworzyć usługę sieciową w języku Java, a ktoś inny będzie w stanie skorzystać w niej, używając klienta pracującego na platformie .NET bez konieczności uczenia się choćby podstaw Javy.
Wiele rozmaitych języków skryptowych wspiera tworzenie usług WWW. PHP jest jednym z nich i posiadaja potężny arsenał całkowicie darmowych funkcji i narzędzi stworzonych specjalnie do tego celu.
W tym artykule porównamy trzy metody tworzenia usług sieciowych: XML-RPC, SOAP oraz REST. Artykuł wymaga od czytelnika podstawowej znajomości specyfikacji usług sieciowych i PHP. Musisz mieć także dostęp do serwera WWW udostępniającego parser PHP, aby móc korzystać z podanych tu przykładów. Jeżeli będziesz potrzebować więcej informacji, zajrzyj do spisu materiałów dodatkowych na końcu tekstu.
XML-RPC
Remote Procedure Calls (RPC) są używane do ustanawiania i obsługi transakcji pomiędzy dwoma odległymi systemami. Przykładami popularnych implementacji RPC są DCOM i CORBA. XML-RPC jest precyzyjną implementacją RPC zezwalającą na przesyłanie danych XML między dwoma serwerami przy pomocy protokołu HTTP. W poniższych przykładach skorzystamy z wersji XML-RPC stworzonej przez Edda Dumbilla (
http://xmlrpc.usefulinc.com/php.html).
Aby uaktywnić w PHP możliwość korzystania z XML-RPC, musisz ściągnąć XML-RPC Toolkit dostępny pod tym adresem:
http://sourceforge.net/project/showfiles.php?group_id=34455
Tworzenie usługi XML-RPC
Głównymi plikami, których będziemy używać, są
xmlrpc.inc (główna biblioteka) oraz
xmlrpcs.inc (biblioteka serwera). A oto opis, jak zakodować prosty serwer XML-RPC przy wykorzystaniu PHP. Na początku dołączymy obydwie biblioteki:
<?
include('xmlrpc.inc');
include('xmlrpcs.inc'); |
Następnie zdefiniujemy nową funkcję zwaną
onttax(). Będzie ona udostępniana przez naszą usługę, a posłuży do obliczania piętnastoprocentowego federalnego i prowincjonalnego podatku VAT dla Ontario w Kanadzie, bazując na przekazanej kwocie w dolarach. Parametr odpowiadający kwocie w dolarach przekazywany jest do funkcji, a następnie konwertowany do postaci skalarnej (liczba, tekst). Gdy obliczenia zostały wykonane, tworzona jest odpowiedź (przy użyciu klasy
xmlrpcresp) zawierająca wartość podatku VAT.
function onttax($par){
$amount=$par->getParam(0);
$amountval=$amount->scalarval();
$taxcalc=$amountval*.15;
return new xmlrpcresp(new xmlrpcval($taxcalc, 'string'));
} |
Teraz ustanawiamy serwer i oznaczamy naszą funkcję jako dostępną usługę WWW.
$server=new xmlrpc_server(array("taxcalc.onttax"=>array("function"=>"onttax")));
?> |
Korzystanie z usługi XML-RPC
Teraz, gdy stworzyliśmy już serwer, przyszedł czas na zaprojektowanie klienta potrafiącego obsługiwać naszą usługę sieciową. Na początku musimy załączyć bibliotekę bazową, zdefiniować skalarną zmienną zwaną
$amount i przypisać jej wartość
$15.00.
<?
include('xmlrpc.inc');
$amount='15.00'; |
Następnie tworzymy instancję klienta XML-RPC, który połączy się z naszym serwerem. Wartość
$amount jest do niego przesyłana przy pomocy obiektu
xmlrpcmsg:
$format=new xmlrpcmsg('taxcalc.onttax',
array(new xmlrpcval($amount, 'double')));
$client=new xmlrpc_client('/xmlrpc-server.php', 'localhost', 80); |
Gdy połączenie zostanie nawiązane, żądanie trafi na serwer do przetworzenia. Odpowiedź, czyli obliczona suma podatku, jest wtedy przekazywana z powrotem do
$value. Konwertujemy ją następnie na wartość skalarną i prezentujemy użytkownikowi. Oczywiście wcale nie trzeba się tutaj zatrzymywać. Możesz bez problemu stworzyć klienta, który jeszcze dodatkowo przetworzy otrzymane dane.
$request=$client->send($format);
$value=$request->value();
print $value->scalarval();
?> |
XML-RPC jest prostą i efektywną metodą transmisji danych XML. Został zaimplementowany w takich projektach, jak FreeNet, O'reilly's Meerkat, Syndic8, Google API i wielu innych aplikacjach. Jeżeli chcesz zdobyć o nim więcej wiadomości, odwiedź oficjalną stronę projektu:
http://www.xmlrpc.com lub przeczytaj książkę "Programming Web Services with XML-RPC" (na polskim rynku jest ona w chwili obecnej niedostępna. przyp. tłum).
NuSOAP i PHP
SOAP został zaprojektowany jako aplikacja XML dla usług sieciowych. Jego siła leży w wykorzystaniu przestrzeni nazw, typów danych XML Shema oraz elastyczności transportu. Niestety specyfikacje i implementacja SOAP'a jest bardziej złożona, szczególnie w porównaniu do XML-RPC. Jest wykorzystywany głównie przez korporacyjnych twórców usług sieciowych. Intensywnie korzystają z niego np. Microsoft .NET oraz IBM WebSphere. Google i Amazon.com także wykorzystały jego popularność i uruchomiły własne uługi SOAP.
NuSOAP jest potężnym API stworzonym dla platformy PHP. Pozwala Ci na tworzenie zarówno serwerów, jak i klientów. Jedną z jego najlepszych cech jest wbudowane wsparcie dla technologii WSDL. Instalacja NuSOAP jest prosta. Wszystko, czego potrzebujesz, to serwer z udostępnionym PHP. Wymagane biblioteki zawarte są w pliku
nusoap.php. Zarówno dokumentację, jak i ten plik możesz pobrać spod tego adresu:
http://deitrich.ganx4.com/nusoap/.
Tworzenie klienta NuSOAP w PHP
Stworzenie klienta SOAP przy pomocy tej biblioteki jest bardzo proste. Aby pokazać, jak to wygląda, zdecydowałem się łączyć z usługą sieciową udostępnioną przez XMethods.net (
http://www.xmethods.net). Znajduje się tam szeroka gama użytecznych usług, z których możesz korzystać za darmo. W naszym przykładzie skorzystamy z usługi konwertera walut, aby dowiedzieć się, jaki jest kurs wymiany między amerykańskimi, a kanadyjskimi dolarami.
Oto pełna analiza kodu PHP niezbędnego do połączenia się z konwerterem. Na początek musimy dołączyć plik biblioteki. Komenda
require_once() da nam pewność, iż stosowne funkcje zostaną dodane do skryptu tylko raz.
<?
require_once('nusoap.php'); |
Następnym krokiem jest zdefiniowanie lokalizacji WSDL'a i utworzenie obiektu klasy
soapclient, by otrzymać dostęp do usługi. WSDL oznacza
Web Services Description Language (Język Opisu Usług Sieciowych) będący aplikacją XML opisującą interfejs usługi:
// Pamietajmy, ze w normalnych warunkach tego typu zmiennych tymczasowych nie powinnismy uzywac!
$wsdl='http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl';
$client=new soapclient($wsdl, 'wsdl'); |
Teraz wyślemy poprzez utworzonego w ten sposób klienta dwa parametry zawierające nazwy dwóch krajów, których waluty chcemy porównać, aby otrzymać ich aktualny kurs. Użyjemy do tego funkcji
getRate(). Wynik jest zwracany użytkownikowi.
$param=array(
'country1'=>'usa',
'country2'=>'canada'
);
echo $client->call('getRate', $param);
?> |
Aby lepiej wyjaśnić, co się dzieje podczas komunikacji między klientem, a serwerem SOAP, przedstawię przykładowy pakiet danych transmitowany od jednego do drugiego i vice versa. Najpierw nasz klient wysyła żądanie otrzymania pliku WSDL z serwera XMethods:
GET /sd/2001/CurrencyExchangeService.wsdl HTTP/1.0
Host: www.xmethods.net |
Dokument ten jest przekazywany do naszego klienta SOAP. Zawiera definicje wszystkich metod dostępnych w usłudze
CurrencyExchangeService:
HTTP/1.1 200 OK
Date: Tue, 10 Feb 2004 11:45:24 GMT
Server: Apache/1.3.26 (Unix) Enhydra-Director/3 PHP/4.0.6 DAV/1.0.3 AuthNuSphere/1.0.0
Last-Modified: Tue, 23 Oct 2001 18:10:37 GMT
ETag: "1981a8-654-3bd5b29d"
Accept-Ranges: bytes
Content-Length: 1620
Connection: close
Content-Type: text/xml
<?xml version="1.0"?>
<definitions name="CurrencyExchangeService"
targetNamespace="http://www.xmethods.net/sd/CurrencyExchangeService.wsdl"
xmlns:tns="http://www.xmethods.net/sd/CurrencyExchangeService.wsdl"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<message name="getRateRequest">
<part name="country1" type="xsd:string"/>
<part name="country2" type="xsd:string"/>
</message>
<message name="getRateResponse">
<part name="Result" type="xsd:float"/>
</message>
<portType name="CurrencyExchangePortType">
<operation name="getRate">
<input message="tns:getRateRequest" />
<output message="tns:getRateResponse" />
</operation>
</portType>
<binding name="CurrencyExchangeBinding" type="tns:CurrencyExchangePortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="getRate">
<soap:operation soapAction=""/>
<input >
<soap:body use="encoded" namespace="urn:xmethods-CurrencyExchange"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
<output>
<soap:body use="encoded" namespace="urn:xmethods-CurrencyExchange"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
</binding>
<service name="CurrencyExchangeService">
<documentation>Returns the exchange rate between the two currencies</documentation>
<port name="CurrencyExchangePort" binding="tns:CurrencyExchangeBinding">
<soap:address location="http://services.xmethods.net:80/soap"/>
</port>
</service>
</definitions> |
Następnie klient przesyła dokładne żądanie użycia metody
getRate(). Parametry są wysyłane według specyfikacji usługi sieciowej:
POST /soap HTTP/1.0
User-Agent: NuSOAP v0.6
Host: services.xmethods.net
Content-Type: text/xml
Content-Length: 622
SOAPAction: ""
<?xml version="1.0"?>
<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"
xmlns:galactivism="urn:xmethods-CurrencyExchange">
<SOAP-ENV:Body>
<galactivism:getRate>
<country1 xsi:type="xsd:string">usa</country1>
<country2 xsi:type="xsd:string">canada</country2>
</galactivism:getRate>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope> |
W końcowym etapie usługa sieciowa odpowiada, przesyłając wynik żądania przekazanego w poprzedniej wiadomości. Nasz kurs walut między Kanadą a USA wynosi 1,3267. Jest on wyświetlany bez nagłówków SOAP.
HTTP/1.1 200 OK
Date: Tue, 10 Feb 2004 11:45:27 GMT
Server: Electric/1.0
Content-Type: text/xml
Content-Length: 492
X-Cache: MISS from www.xmethods.net
Connection: close
<?xml version='1.0' encoding='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/XMLSchema'
xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/'
soap:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
<soap:Body>
<n:getRateResponse xmlns:n='urn:xmethods-CurrencyExchange'>
<Result xsi:type='xsd:float'>1.3267</Result></n:getRateResponse>
</soap:Body>
</soap:Envelope> |
Tworzenie usługi sieciowej NuSOAP
Następny przykład pokaże, jak utworzyć serwer usług sieciowych w NuSOAP. Jedną z ciekawszych cech pakietu jest sieciowe zestawienie dostępnych usług oraz dynamicznie tworzone pliki WSDL dostępne z przeglądarki internetowej. Oto przykład takiego zestawienia:
Stworzymy teraz usługę
CanadaTaxCalculator, która (podobnie jak w przykładzie o XML-RPC) obliczy stawkę federalnego i prowincjonalnego podatku VAT dla Ontario w Kanadzie. Popatrzmy na kod:
<?
require_once('nusoap.php');
$ns='http://localhost/nusoap'; |
Na początku musimy dołączyć bibliotekę i zadeklarować przestrzeń nazw usługi. Zalecane jest wydzielenie osobnego adresu URI dla każdej z nich.
$server = new soap_server();
$server->configureWSDL('CanadaTaxCalculator',$ns);
$server->wsdl->schemaTargetNamespace=$ns; |
Teraz tworzymy serwer SOAP i definiujemy ustawienia naszego pliku WSDL takie, jak nazwa usługi oraz jej przestrzeń nazw.
$server->register('CalculateOntarioTax',
array('amount' => 'xsd:string'),
array('return' => 'xsd:string'),
$ns); |
Następnie rejestrujemy naszą funkcję do obliczania podatku. W ten sposób powiadomimy serwer o jej istnieniu oraz o danych, jakie należy do niej przekazać. Jeśli masz kilka funkcji, musisz zarejestrować każdą z nich z osobna.
function CalculateOntarioTax($amount){
$taxcalc=$amount*.15;
return new soapval('return','string',$taxcalc);
} |
Na końcu uruchamiamy usługę przy pomocy następującej linijki:
$server->service($HTTP_RAW_POST_DATA);
?> |
To wszystko. Zapisz ten kod w pliku
server.php, umieść go na serwerze i voila! Aby wykonać podgląd dynamicznie wygenerowanych danych WSDL, wpisz następujący URL (oczywiście zastąp "localhost" przez nazwę twego serwera):
http://localhost/server.php?wsdl
W praktyce dodanie parametru
?wsdl do jakiegokolwiek serwera NuSOAP spowoduje wygenerowanie zestawienia. Oto, jak wygląda ono u nas:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<definitions 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"
xmlns:tns="http://localhost/nusoap"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://localhost/nusoap">
<types>
<xsd:schema targetNamespace="http://localhost/nusoap">
<xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" />
<xsd:import namespace="http://schemas.xmlsoap.org/wsdl/" />
</xsd:schema>
</types>
<message name="CalculateOntarioTaxRequest">
<part name="amount" type="xsd:string" />
</message>
<message name="CalculateOntarioTaxResponse">
<part name="return" type="xsd:string" />
</message>
<portType name="CanadaTaxCalculatorPortType">
<operation name="CalculateOntarioTax">
<input message="tns:CalculateOntarioTaxRequest" />
<output message="tns:CalculateOntarioTaxResponse" />
</operation>
</portType>
<binding name="CanadaTaxCalculatorBinding" type="tns:CanadaTaxCalculatorPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
<operation name="CalculateOntarioTax">
<soap:operation
soapAction="http://localhost/nusoap/onttaxws.php/CalculateOntarioTax"
style="rpc" />
<input>
<soap:body use="encoded"
namespace="http://localhost/nusoap"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</input>
<output>
<soap:body use="encoded"
namespace="http://localhost/nusoap"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</output>
</operation>
</binding>
<service name="CanadaTaxCalculator">
<port
name="CanadaTaxCalculatorPort"
binding="tns:CanadaTaxCalculatorBinding">
<soap:address location="http://localhost/nusoap/onttaxws.php" />
</port>
</service>
</definitions> |
Korzystanie z naszej usługi
Co jednak, gdy zechcemy skorzystać z utworzonej przez nas usługi? Nie jest to problem. Stwórzmy jeszcze raz naszego klienta od zera. Po pierwsze, tworzymy obiekt klienta NuSOAP i przekazujemy adres do pliku WSDL z definicjami funkcji:
<?php
require_once('nusoap.php');
$wsdl='http://localhost/server.php?wsdl';
$client=new soapclient($wsdl, 'wsdl'); |
Następnie tworzymy zbiór parametrów i zdalnie wywołujemy funkcję
CalculateOntarioTax(). W tym przykładzie wysyłamy kwotę $15.00, aby dowiedzieć się, jaki podatek będziemy musieli od niej zapłacić w Ontario.
$param=array(
'amount'=>'15.00',
);
echo $client->call('CalculateOntarioTax', $param);
?> |
REST
REST znacząco różni się od SOAP i XML-RPC. Po pierwsze, nie jest to standard ani formalny, ani nieformalny. Nie istnieje do niego żadna ujednolicona biblioteka, czy też wbudowane klasy klienta i serwera dla PHP. W tym właśnie drzemie siła tego rozwiązania: nie potrzebujesz żadnych rozszerzeń i narzędzi do stworzenia usługi sieciowej. Protokół HTTP zawiera już wszystko, czego potrzebujesz do transmisji i odbierania wiadomości XML.
REST, będący skrótem od "Representational State Transfer" reprezentuje znacznie prostsze podejście do zagadnienia, niż XML-RPC i SOAP. Używa standardowych metod protokołu HTTP takich, jak GET, POST i PUT do transmisji danych XML. Do ich dalszego przetworzenia możesz użyć takich narzędzi, jak PHP DOM, SAX, SimpleXML (PHP 5), a nawet XSL. Wadą jest to, iż wszystko trzeba napisać własnoręcznie (mimo to w niektórych językach zaczynają się pojawiać zestawy narzędzi i frameworki REST). Jeżeli chcesz projektować tego typu usługi, musisz orientować się w tajnikach działania XML i HTTP oraz oczywiście być przygotowanym na samodzielne pisanie trochę większej ilości kodu.
Korzystanie z usług sieciowych REST
Do przesyłania danych XML z jednego serwera do drugiego użyjemy modułu PHP o nazwie cURL. Biblioteka
Client URL Request Library jest zbiorem klas umożliwiających transfer i przesyłanie plików protokołem HTTP. Dane są zapisane w formacie XML, zatem nie musimy tworzyć skomplikowanych serwerów. Nie są wymagane również żadne dodatkowe API, czy nakładki. REST zwiększa swą popularność: Amazon.com stworzył bardzo popularną usługę sieciową zbudowaną na bazie tej koncepcji.
Przyjrzyjmy się kodowi klienta usługi REST. Na początku definiujemy lokalizację pliku. Gdybyś chciał wygenerować go dynamicznie, musisz jedynie stworzyć jakiś plik PHP akceptujący parametry w adresie URL, który wygeneruje i wyśle jako rezultat działania właśnie dane XML.
<?
$rs='http://localhost/xmldata.xml';
$qs=''; |
Następnie tworzymy tablicę z parametrami do przesłania adresem URL. Wartość każdego z nich jest odpowiednio kodowana, dzięki czemu nie będzie problemów z przesłaniem ich w nagłówku HTTP:
$parray=array('amount'=>"15.00");
foreach($parray as $par=>$value){
$qs=$qs."$par=".urlencode($value)."&";
}
$uri="$rs?$qs"; |
Teraz tworzymy obiekt cURL. Parametr
CURLOPT_RETURNTRANSFER mówi, że chcemy przesłać dane nawet w przypadku wystąpienia błędów transmisji. Dalej wykonujemy całą operację, a odpowiedź przekazujemy do zmiennej zwanej
$xml:
$cobj=curl_init($uri);
curl_setopt($cobj,CURLOPT_RETURNTRANSFER,1);
$xml=curl_exec($cobj);
curl_close($cobj);
echo $xml;
?> |
Oto kod serwera REST. Skrypt pobiera z adresu URL kwotę pieniędzy, oblicza podatek, po czym odsyła wynik w postaci XML:
<?php
$taxcalc=$_GET["amount"]*.15;
echo "<?xml version=\"1.0\"?>";
echo "<taxinfo>";
echo "<result>".$taxcalc."</result>";
echo "</taxinfo>";
?> |
Zakończenie
Mamy trzy technologie oraz miliony pakietów narzędzi do tworzenia usług sieciowych. Nie ma zatem żadnych przeciwwskazań dla wykorzystania PHP do tworzenia aplikacji bazujących na XML'u. Upewnij się, że ściągnąłeś przykładowy kod i ruszaj do akcji!
Przykłady
REST Wiki
http://internet.conveyor.com/RESTwiki/
Specyfikacja SOAP
http://www.w3.org/TR/SOAP/
Skecyfikacje WDSL
http://www.w3.org/TR/wsdl
Tłum. Zyx