<?xml version="1.0" encoding="UTF-8"?><book> <chapter to jest element o nazwie "chapter"> www eee rrr 123 321 111 222 element zawiera tekst </chapter koniec elementu> <chapter ID="ch01" ID2="ch01_2" qqq="tra la la" to są atrybuty> qqqqqqqqqqqqqqqqqqqqqq </chapter> <qqq> <www1> qqq www eee </www1> <www2/> element ktory niczego nie zawiera może tak wygladać <www3/> </qqq></book>
# to jest komunikat soap z "żądaniem" wywołania metody pomnozRazy2 z argumentem {1 2} # widać jak jest zakodowana tablica (wg standardu soap1.1)<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="qqq"> <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <ns:pomnozRazy2> <par SOAP-ENC:arrayType="xsd:float[2]"> <item>1.0</item> <item>2.0</item> </par> </ns:pomnozRazy2> </SOAP-ENV:Body></SOAP-ENV:Envelope> # komunikat soap z wynikiem działania operacji pomnozRazy2<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="qqq"> <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <ns:pomnozRazy2Response> <return SOAP-ENC:arrayType="xsd:float[2]"> <item>2.0</item> <item>4.0</item> </return> </ns:pomnozRazy2Response> </SOAP-ENV:Body></SOAP-ENV:Envelope>
// --- to jest plik q.h ---// dyrektywy dla soapcpp2 zaczynaja się od //gsoap ...//gsoap ns service name: qqq//gsoap ns service namespace: qqq // to jest nazwa namespace XML w ktorej serwer soap definiuje operacje! // "ns" to prefix tego namespace-u//gsoap ns service style: rpc//gsoap ns service encoding: encodedstruct t_tab { // tak sie impl tablice pod gsoap !!! float *__ptr; int __size;};int ns__pomnozRazy2(struct t_tab par, struct t_tab *return_); // "ns" w "ns__" to prefix namespace XML !!! /* UWAGA 1: NAZWY PARAMETROW maja znaczenie !!! parametr wynikowy MUSI sie nazywac "return_" !!! nazwa tego parametru ZALEZY od serwera soap !!! UWAGA 2: konwencja gsoap: wynik jest zwracany przez ostatni paramter, który musi być przekazywany przez wsk (lub ref w C++) NALEŻY definiować specjalną strukturę, której nazwa kończy się na "Response" do zwracania wyniku ... (w tym przykładzie tego nie zrobiono) struktura taka może wyglądać tak: struct ns__pomnozRazy2Response {struct t_tab *return_}; */ // --- to jest plik q.c ---// ten plik przygotowujemy ręcznie !// klient soap ...#include "qqq.nsmap"#include <stdio.h>int main() { struct soap soap; soap_init(&soap); // initialize runtime environment struct t_tab ar; struct t_tab ar2; // przygotowanie danych wejsciowych ... const int dlugosc=55; ar.__ptr= soap_malloc(&soap, dlugosc*sizeof(float)); // dane tak alokowane beda automatycznie zwalniane przy soap_end() ar.__size= dlugosc; int i; for(i=0; i<dlugosc; i++) ar.__ptr[i]= i+1; // wywołanie operacji ... // - adres serwera to "http://localhost:8015/soap/qqq/go" if( soap_call_ns__pomnozRazy2(&soap, "http://localhost:8015/soap/qqq/go", "", ar, &ar2) !=SOAP_OK ) { soap_print_fault(&soap, stderr); } // pokazanie argumentu i wyniku: int d; printf("argument:\n"); d= ar.__size; for(i=0; i<d; i++) printf("%f ", ar.__ptr[i]); printf("\n"); printf("wynik:\n"); d= ar2.__size; for(i=0; i<d; i++) printf("%f ", ar2.__ptr[i]); printf("\n"); // zwalnianie pamiecie i inne ... soap_end(&soap); // clean up and remove deserialized data soap_done(&soap); // detach environment (last use and no longer in scope) return 0;}
// --- plik q.h ---// ten plik przygotowujemy ręcznie !//gsoap ns service name: qqq//gsoap ns service namespace: qqq//gsoap ns service style: rpc//gsoap ns service encoding: encoded// gsoap ns service style: document// gsoap ns service encoding: literalstruct t_tab { // bez tej struktury NIE MA tablicy!! float *__ptr; int __size;};int ns__pomnozRazy2(struct t_tab par, struct t_tab *return_);// --- plik q_serv.c ---// ten plik przygotowujemy ręcznie !// budujemy serwer 1-wątkowy ...#include "qqq.nsmap" // ten plik wlacza tez inne pliki nagłówkowe!int main() { struct soap soap; soap_init(&soap); int b=soap_bind(&soap, NULL, 10000, 100); // 10000 -nr portu na ktorym serwer czeka na klientow if(b<0) {soap_print_fault(&soap, stderr); soap_done(&soap); exit(1);} while (1) { soap_accept(&soap); // czeka na soap msg soap_serve(&soap); // obsluga klienta // UWAGA: można w tym miejscu tworzyc wątek POSIX i przekazywać mu obsługę klienta !!! soap_end(&soap); // zwalniani pamieci printf("."); fflush(stdout); } return 0;}// implementacja operacji "pomnozRazy2"int ns__pomnozRazy2(struct soap* soap, struct t_tab x, struct t_tab *return_) { int i; int d= x.__size; float *f1= x.__ptr; printf("serwer/dbg: argument:\n"); for(i=0; i<d; i++) printf("%f ", x.__ptr[i]); printf("\n"); // zakladam ze return_ wskazuje na istniejaca struct t_tab !!! return_->__size= d; float *f2= soap_malloc(soap, d*sizeof(float)); return_->__ptr= f2; for(i=0; i<d; i++) f2[i]= 2*f1[i]; return SOAP_OK;}// ---------------- koniec -------------------
import org.apache.axis.client.Call;import org.apache.axis.client.Service;import javax.xml.namespace.QName;public class TestClient2{ public static void main(String [] args) { try { String endpoint = "http://localhost:8015/soap"; Service service = new Service(); Call call = (Call) service.createCall(); call.setTargetEndpointAddress( new java.net.URL(endpoint) ); call.setOperationName(new QName("urn:tclsoap:Test1", "square") ); // uri serwisu (=namespace xml serwisu) + nazwa metody call.setOperationStyle("rpc"); call.setOperationUse("encoded"); // def nazwy i typy parametrów metody "square" oraz typ wyniku // Uwaga: nie musimy tego robic; axis sam zgadnie typy przy pomocy "introspekcji" !!! // Uwaga2: to jest proste tylko dla prostych typów !!! call.addParameter("num", org.apache.axis.Constants.XSD_INT, javax.xml.rpc.ParameterMode.IN); call.setReturnType(org.apache.axis.Constants.XSD_INT); // wywołanie operacji: // - parametry to tablica Object! każdy parametr jest osobnym elementem tej tablicy // w takiej kolejnosci w jakiej wywolywano addParameter() Integer ret = (Integer)call.invoke( new Object[] { 10 } ); System.out.println(ret); // 2 wywołanie operacji: ret = (Integer)call.invoke( new Object[] { 11 } ); System.out.println(ret); } catch (Exception e) { System.err.println(e.toString()); } }}
package re javaset service [java::new org.apache.axis.client.Service]set call [java::cast org.apache.axis.client.Call [$service createCall]]$call setTargetEndpointAddress "http://localhost:8015/soap/qqq/go"$call setOperationName [java::new javax.xml.namespace.QName \ "qqq" "pomnozRazy2"]$call setOperationStyle "rpc"$call setOperationUse "encoded"# przekazujemy tabl int... (parametry sa repr. przez tablice $q1)set q1 [java::new {Object[]} 1]$q1 set 0 [java::new {int[]} {} {1 2 3}]set q2 [$call invoke $q1][java::cast {float[]} $q2] getrange #% 1.0 2.0 3.0# przekazujemy tabl double...set q1 [java::new {Object[]} 1]$q1 set 0 [java::new {double[]} {} {1 2 3 4 5 6.111}]set q2 [$call invoke $q1][java::cast {float[]} $q2] getrange #% 1.0 2.0 3.0 4.0 5.0 6.11100006104
# najpierw wygenerowac pieniek (z pliku wsdl) przy pomocy :# java org.apache.axis.wsdl.WSDL2Java plik.wsdl# pieniek to klasy Javy ... należy je skompilować!# Uwaga: pieniek ukrywa wiele szczegółów, z którymi mamy do czynienia# programoując "czystego" klienta soap !!!package re javaset x [java::new www123.QqqLocator] # nazwa serwisu to "www123" # Uwaga 2: zdaje sie, ze axis wymaga aby "nazwa serwisu" = "namespace serwisu"set y [$x getqqqSoap]join [java::info methods $y] \n # to nam wyswietli m.in. operacje jakie można wywoływać!$y helloWorld # wywołanie operacji helloWorldset par [java::new {float[]} {} {1 2 3 4 5 6}]set y2 [$y pomnozRazy2 $par] $y2 getrange # wywołanie operacji pomnozRazy2 i odczytanie wyniku
lappend auto_path ./pkg ./tcllibpackage re SOAP# w razie potrzeby tez mozna definiowac typy złożone przy pomocy rpcvar::typedef !!!SOAP::create pomnozRazy2 \ -uri qqq \ -proxy http://localhost:8015/soap \ -params {x float()} # -uri to namespace xml, w którym są zdef operacje serwisu # -proxy do url serwera (adres serwera) # -params definiuje nazwy i typy parametrów procedury # "typ()" oznacza sekwencje elementów typu "typ" # aby przekazac sekwencję struktur trzeba zdef. typ strukturowy # przy pomocy rpcvar::typedefpomnozRazy2 {1 2 3} # powinno zwrocic: 2 4 6SOAP::dump -request pomnozRazy2SOAP::dump -reply pomnozRazy2 # wyświetla komunikaty SOAP !!!SOAP::create zwrocMojeDane \ -uri qqq \ -proxy http://localhost:8015/soap \ -params {} # powinno zwrocic strukture, chociaż jej typ nie zostal zdef u klienta!!! # xml jest samoopisujący, zatem tclsoap potrafi zinterpretować to co metoda zwróci...
## uruchamiamy serwer tclhttpd ---------------------------------------source ./pkg/tclhttpd3.5.1/bin/httpd_app.tcl # MUSI byc dostepna biblioteka tcllib!!! # (jesli nie ma to rozpakuj tcllib.tar.gz + "lappend auto_path ./tcllib") ## serwer SOAP -------------------------------------------------------lappend auto_path ./pkgpackage re SOAP::DomainSOAP::Domain::register -prefix /soap # rejestrujemy prefix url przeznaczony dla serwera soap# namespace tcl-a w którym definiujemy operacje to równocześnie# "abstrakcyjny" namespace xml tych operacji ...# (czyli klienci soap powinni podać "-uri qqq")namespace eval qqq { proc pomnozRazy2 x { set y {} foreach e $x {lappend y [expr {2*$e}]} return [rpcvar::rpcvar float() $y] # do zwracanej wartosci DODAJEMY typ tej wartości !!! # robi sie to przy pomocy komendy "rpcvar::rpcvar": rpcvar typ wartosc } SOAP::export pomnozRazy2 # eksportujemy procedure czyli czynimy ją operacją naszego serwisu # (robic to KONIECZNIE pod namespace eval)} # bardziej zlożony przykład ...package re rpcvarrpcvar::typedef -namespace ns { imie string wiek int lista_f float() lista_i int()} typMojeDane # definiujemy nowy typ (stukture) o nazwie "typMojeDane" # typ ten wystąpi w namespace o prefiksie "ns" w komunikatach soap!namespace eval qqq { proc zwrocMojeDane {} { puts [info level 0]; # informacje diagnostyczne o wywolaniu tej proc return [rpcvar::rpcvar typMojeDane { imie "Michal" wiek 123 lista_f {111.111 222.222} lista_i {1 2 3 4 5} }] } SOAP::export zwrocMojeDane}
# niskopoziomowy klient SOAPpackage re tdom; # pakiet typu DOM do przetwarzania XML# tworzymy zadanie/ komunikat soap ...# Uwaga: przykładowy komunikat soap można wyciągnąć z gsoap !!!set xml_req {<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="qqq"> <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <ns2:pomnozRazy2> <x SOAP-ENC:arrayType="xsd:float[5]"> <item>0.0</item> <item>1.0</item> <item>2.0</item> <item>3.0</item> <item>4.1111111111111</item> </x> </ns2:pomnozRazy2> </SOAP-ENV:Body></SOAP-ENV:Envelope>}# tworzymy komunikat http ...# Uwaga na prawidłowa długość w Content-Length !!!set len [expr {[string len $xml_req]+[regexp -all \n $xml_req]}]set http_req "POST /soap HTTP/1.0Accept: */*Host: localhost:8015User-Agent: TclSOAP/1.6.7 (Linux)Content-Type: text/xmlContent-Length: $len$xml_req"# wysylamy zadanie soap i odbieramy wynik soap ...set s1 [socket localhost 8015]puts $s1 $http_req; flush $s1set reply [read $s1]close $s1# interpretujemy wynik soap ...set i [string first "\n\n" $reply]set xml_reply [string range $reply [expr {$i+2}] end]dom parse $xml_reply q2set r2 [$q2 selectNodes //return/item/text()]puts ""foreach e $r2 {puts [$e nodeValue]}# --- koniec ---
// tak sie ustawia styl i encoding pod gsoap ... //gsoap ns service style: rpc //gsoap ns service encoding: encoded // gsoap ns service style: document // gsoap ns service encoding: literal // przyklad operacji zwrocCos2, zawracającej liste struktur ... struct t_mojaStruct2 { int a, b, c; struct t_mojaStruct2 *nast; }; struct ns__zwrocCos2Response { struct t_mojaStruct2 *return_; }; int ns__zwrocCos2(struct ns__zwrocCos2Response *r);
## uwierzytelnianie serwera --------------------------------------------# - klient upewnia się, że serwer jest tym za kogo się podaje ...lappend auto_path tls1.5package require tls # uwaga: jesli pakiet się nie ładuje to naprawdopodobniej # brakuje bibliotek libcrypto.so.0.9.7 i libssl.so.0.9.7 # kopie tych bibl. znajduja sie w katalogu tls1.5 # wystarczy ustawic zmienna LD_LIBRARY_PATH: # export LD_LIBRARY_PATH=...../tls1.5## serwertls::socket -server obsluga \ -keyfile nowy-private.pem -certfile nowy-public.pem \ -password haslo \ 10000 # -keyfile: klucz pryw serwera (moze byc zawarty w cert!) # -certfile: cert serwera (zawiera klucz pub)proc haslo {} {return "qwerty"}; # haslo do klucza pryw serweraproc obsluga {s args} { _puts "server socket $s"; # zakladam ze uruchamiamy to z konsola2c.tcl tls::handshake $s}gets sock???## klientset s [tls::socket -require 1 -cafile cacert.pem localhost 10000] # -require 1: klient żąda sprawdzenia certyfikatu serwera # -cafile: certyfikat CA (chodzi m.in. o klucz publiczny CA w tym pliku # dzięki któremu klient sprawdza podpis na certyfikacie serwera)tls::handshake $sputs $s "A ku ku !!!"; flush $s## uwierzytelnianie serwera ORAZ klientow ---------------------------# - serwer sprawdza swoich klientów# - jak widac, nic nie szkodzi ze ser i kli uzywaje tego samego cert!lappend auto_path tls1.5package require tls## serwerproc haslo {} {return "qwerty"}tls::socket -server obsluga -require 1 -cafile cacert.pem \ -password haslo -keyfile nowy-private.pem -certfile nowy-public.pem \ 10000proc obsluga {s args} { _puts "server socket $s" tls::handshake $s}# wyciąganie informacji o kliencie (z jego certyfikatu)tls::status sock???gets sock???## klientproc haslo {} {return "qwerty"}set s [tls::socket -require 1 -cafile cacert.pem \ -password haslo -keyfile nowy-private.pem -certfile nowy-public.pem \ localhost 10000]tls::handshake $sputs $s "A ku ku !!!"; flush $s
// ta klasa to imlp. naszego WS ...public class MojPrzyklad { // operacja WS o nazwie zwrocBeana zwracajac pojedynczą strukture MojBean public MojBean zwrocBeana() { MojBean mb= new MojBean(); mb.setA(123); mb.setB(321); return mb; }}// definicja Beana ...public class MojBean { private int a; private int b; public int getA() {return a;} public void setA(int pa) { a=pa; } public int getB() {return b;} public void setB(int pb) { b=pb; }}<!-- definicja serwisu w formacie WSDD: parametr className zawiera główną klasę naszego WS parametr allowedMethods zawiera listę dostępnych operacji (* oznacza wszystkie) każdy JavaBean musi byc zadeklarowany przy pomocy elem. beanMapping atrybut languageSpecificType zawiera "java:pakiet.klasa" (przedrostek java: jest konieczny!) UWAGA: prosze zwrocic uwage ze namespace MojBean nie moze sie nazywac tak samo jak serwis i glowna klasa!--><service name="MojPrzyklad" provider="java:RPC"> <parameter name="className" value="MojPrzyklad"/> <parameter name="allowedMethods" value="*"/> <beanMapping languageSpecificType="java:MojBean" qname="ns:MojBean" xmlns:ns="MojPrzyklad2" /></service>
package re SOAPpackage re rpcvarSOAP::create zwrocBeana \ -uri MojPrzyklad \ -action MojPrzyklad \ -proxy http://localhost:8080/axis/services/MojPrzyklad \ -params {}zwrocBeana #% {a 123 b 321} # gdyby pewna metoda także otrzymywała przez parametr MojBean# to należy zdefiniować typ tMojBean i użyć go w -params !!!rpcvar::typedef -namespace MojPrzyklad { a int b int} tMojBean
package re SOAP #% 1.6.7proc rpc2doc {v args} { soap_rpc_to_doc [eval SOAP::soap_request $v $args] # zakładam, że proc soap_rpc_to_doc modyfikuje komunikat soap # przekazany przez parametr, tak że ma styl document, a następnie go zwraca # SOAP::soap_request to std. procedura produkujaca komunikat soap w stylu rpc...}SOAP::create GetWeather \ -wrapProc rpc2doc \ -proxy "http://www.webservicex.net/globalweather.asmx" \ -uri "http://www.webserviceX.NET" \ -action "http://www.webserviceX.NET/GetWeather" \ -params {CityName string CountryName string} # opcja -wrapProc wymienia procedurę generującą komunikaty soapGetWeather "Poznan" "Poland" # powinno działać!!