Tutoriál Web Services

Martin Kuba, Superpočítačové Centrum Brno, ÚVT MU, makub@ics.muni.cz
Tento tutoriál obsahuje základní vysvětlení pojmů z webservices a konkrétní příklady jak napsat klienta a server webservice v jazycích C, Java a JavaScript.
Novější verze tutoriálu je dostupná zde.

Obsah

Co jsou webservices

Dnes je World Wide Web souborem převážně stránek napsaných v jazyku HTML (Hyper Text Markup Language) srozumitelných pouze lidem. Web services - webové služby - mají za cíl zpřístupnění zdrojů snadno zpracovatelných strojově, tj. umožnit snadnou spolupráci programů bežících na libovolných platformách (prog. jazyk, CPU, OS). Proto využívají standardy World Wide Web Consortia (W3C).

Webservices je technologie pro vzdálené volání procedur (RPC - Remote Procedure Call) pomocí přenosu zpráv v jazyku XML (eXtensible Markup Language) protokolem HTTP (HyperText Transfer Protocol). Pro definici typů dat využívá standard XML Schema. Pro identifikaci objektů (dat, jmen operací, atd.) využívá standard XML Namespaces.

Technologii webových služeb tvoří tři části:

Rychlokurz XML

XML je značkovací jazyk, dokument v XML je stromová struktura, s práve jedním kořenem, uzly stromu jsou tagy, listy mohou být tagy, atributy a texty. Atributy typu ID umožňují odkazy na tagy a tedy vyjádřit obecný orientovaný graf.

Znaky menšítko, většítko, ampersand, uvozovky a apostrof jsou vyhrazeny, v textu se musí zapisovat pomocí tzv. entit &lt; &gt; &amp; &quot; &apos; nebo pomocí <![CDATA[]]>.

 <?xml version="1.0" encoding="UTF-8" ?>
 <!-- komentář -->
 <kořen atribut="text jako hodnota atributu" další="-1&lt;1" >
   <vnořený_tag id="tady"> 
     Text v těle tagu. 
   </vnořený_tag> 
   nebezpečné znaky nahrazené entitami: &lt; &gt; &amp; &quot; &apos;
   nebezpečné znaky přímo: <![CDATA[     <    >     &     "      '     ]]>
 </kořen>
 

Struktura tohoto dokumentu je:

kořen -----> @atribut
        |--> @další
        |--> vnořený_tag ----> @id
        |--> [text]       |--> [text]
 

XML Namespaces je norma, která definuje jak zabraňovat kolizím stejných jmen pro různé věci. Dělá to pomocí tzv. qualified names pro tagy a atributy, kde jméno tvoří prefix a local part oddělené dvojtečkou. Prefixy jsou mapovány na URI (Uniform Resource Identifier), což jsou URL (Uniform Resource Locator) nebo URN (Universal Resource Name). Významné je pouze URI, nikoliv řetězec prefixu, tj. dva různé prefixy mapované na stejné URI určují stejný namespace. Příklad:

 <?xml version="1.0" encoding="UTF-8" ?>
 <koren xmlns:umělecký="urn:Michelangelo"
        xmlns:pohlavní="http://www.sex.cz/"
        xmlns:divadelní="http://www.divadlo.cz/hra"
        xmlns:pietní="http://www.krematorium.cz/"
        xmlns:u="urn:Michelangelo"
        xmlns="http://urad.cz/akta"
 >

  Tohle jsou různé tagy:
  <umělecký:akt> obraz </umělecký:akt> 
  <pohlavní:akt>  styk </pohlavní:akt> 
  <divadelní:akt>  dějství </divadelní:akt> 
  <pietní:akt>  pohřeb </pietní:akt> 
  <akt>  úřední papír </akt> 

  Tohle jsou stejné tagy:
  <umělecký:akt> obraz </umělecký:akt> 
  <u:akt> zase obraz </u:akt> 
 </koren>
 

XML Schema je norma pro definici datových struktur a datových typů v XML. Kromě očekávatelných typů jako řetězec, číslo, čas, dokáže popsat i složené objekty nebo typy vzniklé omezením jiných typů, např. číselné a časové intervaly.

Předdefinované typy XML Schema - obrázek převzatý z XML Schema Part 2: Datatypes

XML Schema built-in types

Příklady - strukturu tagů

  <osoba>
   <jmeno>Martin</jmeno>
   <prijmeni>Kuba</prijmeni>
   <adresa>
    <ulice>Botanická</ulice>
    <cislo>68</cislo>
    <mesto>Brno</mesto>
   </adresa>
  </osoba>
 

lze definovat první definicí, omezený číselný interval druhou definicí a výčet hodnot třetí definicí:

 <schema xmlns="http://www.w3.org/2001/XMLSchema" 
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:impl="urn:mojedata"
     targetNamespace="urn:mojedata"
 >

   <!-- Objekty -->
   <complexType name="Adresa">
    <sequence>
     <element name="ulice" type="xsd:string"/>
     <element name="cislo" type="xsd:long"/>
     <element name="mesto" type="xsd:string"/>
    </sequence>
   </complexType>
   <complexType name="Osoba">
    <sequence>
     <element name="jmeno" type="xsd:string"/>
     <element name="prijmeni" type="xsd:string"/>
     <element name="adresa" type="impl:Adresa"/>
    </sequence>
   </complexType>
   <element name="osoba" type="impl:Osoba"/>

   <!-- číselný interval - integer omezený na 10000 až 99999 -->
   <simpleType name="myInteger">
    <restriction base="xsd:integer">
     <minInclusive value="10000"/>
     <maxInclusive value="99999"/>
    </restriction>
   </simpleType>

   <!-- výčet - string omezený na vyjmenované hodnoty -->
   <simpleType name="ovoce">
    <restriction base="xsd:string">
      <enumeration value="jablka"/>
      <enumeration value="hrušky"/>
      <enumeration value="banány"/>
    </restriction>
   </simpleType>
 </schema>
 

Rychlokurz HTTP

HTTP je protokol pro přenos libovolných dat pomocí páru požadavek-odpověď. Požadavek i odpověď jsou složeny z textových hlaviček, prázdného řádku a binárních dat. Typ dat je určen pomocí MIME (Multipurpose Intenet Mail Extension) typů v hlavičce Content-Type.

Požadavek:

 POST /nejaka/cesta?param=1 HTTP/1.0
 Host: www.neco.cz
 Content-Type: text/xml
 Content-Length: 30

 <?xml version="1.0" encoding="UTF-8"?>
 <neco/>
 

Odpověď:

 HTTP/1.1 200 OK
 Content-Type: text/xml
 Content-Length: 1184
 Server: Apache Coyote/1.0

 <?xml version="1.0" encoding="UTF-8"?>
 <neco_jineho/>
 

Provoz je proto snadné sledovat, popřípadě simulovat klienta telnetem.

SOAP - Simple Object Access Protocol

Není simple :-) Umožňuje volat vzdáleně funkce. Při volání přenese protokol HTTP zprávu tvořenou XML, která popisuje volanou funkci a její parametry. Jako odpověď protokol HTTP přenese opět XML reprezentující výsledná data.

Mějme např. funkci (operaci)

 boolean jePrvocislo(long cislo); 

Ta musí být v nějakém namespace, třeba urn:mojeURI, ten představuje příslušnost k nějakému objektu/interface. Volání této operace v protokolu SOAP vypadá následovně:

POST / HTTP/1.1
Content-Type: text/xml; charset=utf-8
Content-Length: 423
Connection: close
SOAPAction: ""

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope 
  xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 <env:Header/> 
 <env:Body>
  <jePrvocislo xmlns="urn:mojeURI">
    <cislo xsi:type="xsd:long">1987</cislo>
  </jePrvocislo>
 </env:Body>
</env:Envelope>
 

A odpověď od serveru je:

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 468
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope 
  xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 >
 <env:Body>
  <jePrvocisloResponse xmlns="urn:mojeURI">
   <vysledek xsi:type="xsd:boolean">true</vysledek>
  </jePrvocisloResponse>
 </env:Body>
</env:Envelope>
 

Název hlavního tagu ve volání je shodný s názvem volané operace. Název hlavního tagu v odpovědi je název operace s připojeným Response (požadováno ve WS-I Basic Profile 1.0a část 5.6.19). Názvy vnořených tagů se shodují s názvy vstupních popř. výstupních parametrů. Se SOAP zprávami se programátor obvykle nesetkává.

WSDL - Web Services Description Language

Abychom mohli protokolem SOAP něco volat, musíme nejřív vědět co a jak volat. Proto existuje jazyk WSDL pro popis rozhraní služby. Popisuje jména dostupných operací, typy jejich parametrů a návratových hodnot, a kde a jak je služba dostupná (HTTP/HTTPS/SMTP, port, stroj, URL). Vztah mezi SOAP službou a WSDL je asi jako mezi zkompilovanou C knihovnou a header souborem se seznamem funkcí v ní obsažených.

WSDL tedy nepopisuje sémantiku operací, pouze syntaxi jejich volání. Sémantiku je možné nanejvýš popsat lidským jazykem v tagu <documentation>, ale to není strojově zpracovatelný popis.

WSDL je jazyk založený na XML, obsahující tyto hlavní tagy:

WSDL pro předchozí SOAP příklad:

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="PrvniSluzba"
 targetNamespace="urn:mojeURI"
 xmlns:tns="urn:mojeURI"
 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:ns1="urn:mojeURI"
 xmlns:SOAP="http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:WSDL="http://schemas.xmlsoap.org/wsdl/"
 xmlns="http://schemas.xmlsoap.org/wsdl/">


<!-- definice typů -->
<types>
 <schema targetNamespace="urn:mojeURI"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns="http://www.w3.org/2001/XMLSchema"
     elementFormDefault="unqualified"
     attributeFormDefault="unqualified">
  <element name="cislo" type="xsd:long"/>
  <element name="vysledek" type="xsd:boolean"/>
 </schema>
</types>


<!-- komunikační zprávy -->
<message name="jePrvocisloRequest">
 <part name="cislo" element="ns1:cislo"/>
</message>
<message name="jePrvocisloResponse">
 <part name="vysledek" element="ns1:vysledek"/>
</message>


<!-- dostupné operace -->
<portType name="Cisilka">
 <operation name="jePrvocislo">
  <documentation>Operace jePrvocislo()</documentation>
  <input message="tns:jePrvocisloRequest"/>
  <output message="tns:jePrvocisloResponse"/>
 </operation>
</portType>


<!-- volatelné přes HTTP -->
<binding name="PrvniSluzba" type="tns:Cisilka">
 <SOAP:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
 <operation name="jePrvocislo">
  <SOAP:operation style="rpc" soapAction=""/>
  <input>
   <SOAP:body use="literal" namespace="urn:mojeURI"/>
  </input>
  <output>
   <SOAP:body use="literal" namespace="urn:mojeURI"/>
  </output>
 </operation>
</binding>


<!-- adresy komunikačních bodů -->
<service name="PrvniSluzba">
 <documentation>Sluzba pocitajici prvocisla</documentation>
 <port name="PrvniSluzba" binding="tns:PrvniSluzba">
  <SOAP:address location="http://localhost:10000"/>
 </port>
</service>
</definitions>
 

Základní webservice v jazyce C

Použijeme balík gSOAP. Stáhněte si balík gsoap-linux-2.6.tar.gz. Uložte si předchozí WSDL do souboru prvni.wsdl.

 tar xzvf gsoap-linux-2.6.tar.gz
 gsoap-linux-2.6/wsdl2h -c prvni.wsdl 
 

Tím vznikne soubor prvni.h s obsahem (bez komentářů):

// built-in type "xs:boolean":
enum xsd__boolean { false_, true_ };

//gsoap ns1 service name:       PrvniSluzba 
//gsoap ns1 service type:       Cisilka 
//gsoap ns1 service port:       http://localhost:10000 
//gsoap ns1 service namespace:  urn:mojeURI 
//gsoap ns1 service method-style:       jePrvocislo rpc
//gsoap ns1 service method-encoding:    jePrvocislo literal
//gsoap ns1 service method-action:      jePrvocislo ""
int ns1__jePrvocislo(
  LONG64 ns1__cislo,
  struct ns1__jePrvocisloResponse { enum xsd__boolean ns1__vysledek; } * 
  );
 

Z něj vygenerujeme pomocné soubory:

 gsoap-linux-2.6/soapcpp2 -c -pprvni prvni.h
 

Vznikne hejno souborů. Nyní naimplementujeme klienta služby v souboru klient.c:

#include "prvniH.h"
#include "PrvniSluzba.nsmap"

int main(int argc,char** argv) {
    LONG64 cislo = 1987;
    struct soap *mydlo = soap_new();
    struct ns1__jePrvocisloResponse out; 

    if(argc>1) sscanf(argv[1],"%lld",&cislo);

    if(soap_call_ns1__jePrvocislo(mydlo,"http://localhost:10000/","",cislo,&out) == SOAP_OK) {
      printf("vysledek: jePrvocislo(%lld) = %s\n", cislo, (out.ns1__vysledek==true_)?"ano":"ne");
    } else {// an error occurred 
      soap_print_fault(mydlo, stderr);
      exit(1);
    }
}

a server v souboru server.c

#include "prvniH.h"
#include "PrvniSluzba.nsmap"

int ns1__jePrvocislo(struct soap* mydlo,LONG64 cislo, struct ns1__jePrvocisloResponse *out) {
 LONG64 i;
 if(cislo%2==0) { out->ns1__vysledek = false_; return SOAP_OK; }
 for(i=3;i<cislo;i+=2) {
   if(cislo%i==0) { out->ns1__vysledek = false_; return SOAP_OK; }
 }
 out->ns1__vysledek = true_;
 return SOAP_OK;
}

int main() {
   struct soap soap;
   int i, m, s; // master and slave sockets
   soap_init(&soap);
   m = soap_bind(&soap, NULL, 10000, 100);
   if (m < 0)
      soap_print_fault(&soap, stderr);
   else
   {
      fprintf(stderr, "Socket connection successful: master socket = %d\n", m);
      for (i = 1; ; i++) {
         s = soap_accept(&soap);
         if (s < 0) {
            soap_print_fault(&soap, stderr);
            break;
         }
         soap_serve(&soap); // process RPC request
         soap_destroy(&soap); // clean up class instances
         soap_end(&soap); // clean up everything and close socket
      }
   }
   soap_done(&soap); // close master socket
}
 

Překompilujeme a spustíme:

 cp gsoap-linux-2.6/stdsoap2.h gsoap-linux-2.6/stdsoap2.c . 
 gcc -I. -O2 -o klient klient.c stdsoap2.c prvniC.c prvniClient.c
 gcc -I. -O2 -o server server.c stdsoap2.c prvniC.c prvniServer.c
 ./server &
 ./klient 

 vysledek: jePrvocislo(1987) = ano
 

Tak máme první webservice hotovou :-)

JavaScriptový klient v Mozille

Mozilla od verze 1.0 umí provádět SOAP volání z JavaScriptu. Nechte běžet server z minulého příkladu, uložte si na lokální disk následující HTML soubor (SHIFT+click) : javascript.html a zobrazte si ho. Zadejte číslo, klikněte na tlačítko a povolte prohlížeči přístup až se zeptá. Stránku je nutné mít na lokálním disku, protože skript není digitálně podepsaný.

Kde vzít WSDL ?

WSDL si mužeme napsat ručně, popřípadě s pomocí specializovaných editorů (např. Cape Clear SOA Editor), ale je to pracné. Proto se WSDL obvykle generují z kódu příslušného programovacího jazyka pomocí automatizovaných nástrojů.

Vyrobme soubor druha.h obsahující definici dvou operací v namespace http://scb.ics.muni.cz/druha se jmény zadej() a ziskej(), objektu Bumbrlik obsahujícího většinu základních typů, objektu Ukazovak pro demonstraci kolekce objektů a directivy //gsoap pro specifikaci dalších údajů o službě. Operace zadej() nemá žádné výstupní parametry, naopak operace ziskej() má dva výstupní parametry a žádný vstupní.


/* directiva pro definici namespace pro datove typy */
//gsoap scb schema namespace:   http://scb.ics.muni.cz/druha

/* binarni data "xsd:base64Binary" */
struct xsd__base64Binary { unsigned char *__ptr; int __size; };
/* pravdivostni typ "xsd:boolean" */
enum xsd__boolean { false_, true_ };
/* nami definovany typ pro vycet "scb:denvtydnu" */
enum scb__denvtydnu {Pondeli, Utery, Streda, Ctvrtek, Patek, Sobota, Nedele};
/* casovy okamzik xsd:dateTime s presnosti na sekundy, prevod zajistuje gSOAP */
typedef time_t xsd__dateTime;

/* oznaceni externe definovaneho C typu */
volatile struct timeval { long tv_sec; long tv_usec; }; 

/* casovy okamzik xsd:dateTime s libovolnou presnosti.
   dve finty najednou - extern rika ze mame vlastni
   (de)serializer pro dany typ, podtrhnitko
   na konci xsd__dateTime_ umoznuje druhou
   vazbu stejneho XML Schema typu na jiny C typ */
extern typedef struct timeval* xsd__dateTime_;

/* definice typu pro pole retezcu */
struct scb__PoleRetezcu {
    int    __sizex;
    char*  *x    0;
};

/* nase typy "scb:Ukazovak" a "scb:Bumbrlik" */
struct scb__Ukazovak {
  char* jmeno 1:1;
  struct scb__Ukazovak* ukazatel1 1:1;//nillable
  struct scb__Ukazovak* ukazatel2 1:1;//nillable
};

struct scb__Bumbrlik {
  char* retezec 1:1;//nillable
  char  bajt 1:1;
  int   celecislo 1:1;
  short kratkecislo 1:1;
  LONG64 dlouhecislo 1:1;
  float plovoucicislo 1:1;
  double dvojitecislo 1:1;
  enum xsd__boolean pravdivost 1:1;
  struct xsd__base64Binary binarnidata 1:1; 
  struct scb__PoleRetezcu* pole 1:1;
  struct scb__Ukazovak* ukazatel 1:1;//nillable
  enum scb__denvtydnu den 1:1;
  xsd__dateTime casnasekundy 1:1;
  xsd__dateTime_ casnamikrosekundy 1:1;
};

/* direktivy platne pro celou sluzbu */
//gsoap scb service name:       VelkaSluzba
//gsoap scb service type:       Otesanek 
//gsoap scb service port:       http://localhost:10001/
//gsoap scb service namespace:  http://scb.ics.muni.cz/druha
//gsoap scb service documentation: Uzasna exemplarni sluzba
//gsoap scb service style: rpc
//gsoap scb service encoding: literal

/* operace s jednim vstupnim a zadnym vystupnim parametrem */
//gsoap scb service method-documentation: zadej Operace pro zadani dat.
int scb__zadej(struct scb__Bumbrlik* b,struct scb__zadejResponse { } *);

/* operace s zadnym vstupnim a dvema vystupnimi parametry */
//gsoap scb service method-documentation: ziskej Operace pro ziskani dat.
int scb__ziskej( struct scb__ziskejResponse { struct scb__Ukazovak* dvojiteLinkovanySeznam; char* poznamka; } *);
 

Tento soubor je určen pouze pro gSOAP preprocesor, který z něj vygeneruje WSDL a soubory s funkcemi pro obsluhu SOAP protokolu, nikdy se nekompiluje. Čísla tvaru 1:1 u položek struktur udávají minimální a maximální počet výskytů, použijí se ve WSDL v definici typů.

 gsoap-linux-2.6/soapcpp2 -c -pdruha druha.h
 

Vzniklý WSDL soubor se jmenuje VelkaSluzba.wsdl, podívejte se do něj.

 vi VelkaSluzba.wsdl
 

Plnotučná webservice v jazyce C

Ukážeme si co nejvíce rysů gSOAP v jedné službě, proto bude komplikovaná. Předvedeme přenos kolekce vzájemně se odkazujících objektů, konkrétně dvojitě linkovaného seznamu. Předvedeme jak psát vlastní převod mezi XML a nativními C typy pro vybraný XML Schema typ (struct timeval a xsd:dateTime).

Naimplementujeme klienta v souboru druhy_klient.c:


#include "druhaH.h"
#include "VelkaSluzba.nsmap"

void tiskni(struct scb__Ukazovak* u) {
	if(u==NULL) return;
	printf("jmeno=%s\n",u->jmeno);
	printf("ukazatel1=%s\n",(u->ukazatel1==NULL?"null":u->ukazatel1->jmeno));
	printf("ukazatel2=%s\n",(u->ukazatel2==NULL?"null":u->ukazatel2->jmeno));
	if(u->ukazatel1!=NULL) tiskni(u->ukazatel1);
}

int main(int argc,char** argv) {
    struct soap *mydlo = soap_new();
    struct scb__zadejResponse out1;
    struct scb__ziskejResponse out2;
    struct scb__Bumbrlik *b = malloc(sizeof(struct scb__Bumbrlik));
    struct scb__Ukazovak *u;
    char* retezce[3] = { "prvni", "druhy", "treti"};
    char* retezec = "cipisek";
    struct timeval ted;

    if(argc>1) retezec=argv[1];

    /* naplneni objektu nejakymi daty */
    b->retezec= retezec;
    b->bajt = (char)1;
    b->celecislo = (int)2;
    b->kratkecislo = (short)3;
    b->dlouhecislo = (LONG64)4;
    b->plovoucicislo = (float)5.0;
    b->dvojitecislo = (double)6.0;
    b->pravdivost = true_;

    b->binarnidata.__size = 5;
    b->binarnidata.__ptr = "ABCDE";

    b->pole = malloc(sizeof(struct scb__PoleRetezcu));
    b->pole->__sizex = 3;
    b->pole->x = retezce;

    b->ukazatel = NULL;
    b->den = Pondeli;
    b->casnasekundy = time(NULL);

    gettimeofday(&ted,NULL);
    b->casnamikrosekundy = &ted;

    if(soap_call_scb__zadej(mydlo,NULL,NULL,b,&out1) == SOAP_OK) {
      printf("zavolano zadej()\n");
    } else {// an error occurred 
      soap_print_fault(mydlo, stderr);
      exit(1);
    }

    if(soap_call_scb__ziskej(mydlo,NULL,NULL,&out2) == SOAP_OK) {
      char *poznamka;
      printf("zavolano ziskej()\n");
      poznamka = out2.poznamka;
      u = out2.dvojiteLinkovanySeznam;
      tiskni(u);
    } else {// an error occurred 
      soap_print_fault(mydlo, stderr);
      exit(1);
    }

}

 

a server v souboru druhy_server.c:


#include "druhaH.h"
#include "VelkaSluzba.nsmap"

extern char *tzname[2];
long int timezone;
extern int daylight;

static struct scb__Ukazovak* pamatuj = NULL;

void printCas(time_t secSinceEpoch) {
 struct tm * cas = localtime(&secSinceEpoch);
 printf("%d.%d.%d %02d:%02d:%02d %s\n",cas->tm_mday,1+cas->tm_mon,1900+cas->tm_year,cas->tm_hour,
                                       cas->tm_min,cas->tm_sec,tzname[daylight]);
}

int scb__zadej(struct soap* mydlo,struct scb__Bumbrlik* b,struct scb__zadejResponse *nic){
 char *den;
 struct scb__Ukazovak* nove = malloc(sizeof(struct scb__Ukazovak));
 int i;
 //vypis
 printf("\nb->retezec = %s\n",b->retezec);
 printf("b->bajt = %hhd\n",b->bajt);
 printf("b->celecislo = %d\n",b->celecislo);
 printf("b->kratkecislo = %hd\n",b->kratkecislo);
 printf("b->dlouhecislo = %lld\n",b->dlouhecislo);
 printf("b->plovoucicislo = %f\n",(double)b->plovoucicislo);
 printf("b->dvojitecislo = %f\n",b->dvojitecislo);
 printf("b->pravdivost = %d\n",b->pravdivost);
 printf("b->binarnidata.__size = %d\n",b->binarnidata.__size);
 for(i=0;i<b->binarnidata.__size;i++) {
  printf("b->binarnidata.__ptr[%d] = %c\n",i,b->binarnidata.__ptr[i]);
 }
 printf("b->pole.__sizex = %d\n",b->pole->__sizex);
 for(i=0;i<b->pole->__sizex;i++) {
  printf("b->pole.x[%d] = %s\n",i,b->pole->x[i]);
 }

 printf("b->casnasekundy: ");
 printCas(b->casnasekundy);

 printf("b->casnamikrosekundy:");
 printCas(b->casnamikrosekundy->tv_sec);
 printf("mikrosekund: %ld\n",b->casnamikrosekundy->tv_usec);

 switch (b->den) {
  case Pondeli: den = "Pondeli"; break;
  case Utery:   den = "Utery"; break;
  case Streda:  den = "Streda"; break;
  case Ctvrtek: den = "Ctvrtek"; break;
  case Patek:   den = "Patek"; break;
  case Sobota:  den = "Sobota"; break;
  case Nedele:  den = "Nedele"; break;
  default:      den = "Osmy den";
 }
 printf("b->den = %s\n",den);


 //zapamatuj jmeno
 nove->jmeno= strdup(b->retezec); 
 nove->ukazatel1=pamatuj;
 nove->ukazatel2=NULL;
 if(pamatuj!=NULL) { pamatuj->ukazatel2 = nove; }
 pamatuj=nove;

 if(strcmp(b->retezec,"chyba")==0) {
    return soap_receiver_fault(mydlo, "Umele vyvolana chyba",NULL);
 }

 return SOAP_OK;
}

int scb__ziskej(struct soap* mydlo,struct scb__ziskejResponse *out){
 out->dvojiteLinkovanySeznam = pamatuj;
 out->poznamka = "v pohode";
 return SOAP_OK;
}

int main() { 
   struct soap soap;
   int i, m, s; // master and slave sockets
   soap_init(&soap);
   m = soap_bind(&soap, NULL, 10001, 100);
   if (m < 0)
      soap_print_fault(&soap, stderr);
   else
   {
      fprintf(stderr, "Socket connection successful: master socket = %d\n", m);
      for (i = 1; ; i++) {
         s = soap_accept(&soap);
         if (s < 0) {
            soap_print_fault(&soap, stderr);
            break;
         }
         soap_serve(&soap); // process RPC request
         soap_destroy(&soap); // clean up class instances
         soap_end(&soap); // clean up everything and close socket
      }
   }
   soap_done(&soap); // close master socket
}
 

a vlastní serializer a deserializer v souboru dateTimeSerializer.c:


#include "druhaH.h"

//prevod timeval na xsd:dateTime
char* timeval2dateTime(struct soap* soap, struct timeval * presnycas) {
    char* dateTime = (char*)soap_malloc(soap,32);
    struct tm * c = gmtime(&(presnycas->tv_sec));
    sprintf(dateTime,"%04d-%02d-%02dT%02d:%02d:%02d.%06ldZ",c->tm_year+1900,c->tm_mon+1,c->tm_mday,c->tm_hour,
                     c->tm_min,c->tm_sec,presnycas->tv_usec);
    return dateTime;
}

//prevod xsd:dateTime na timeval
struct timeval *dateTime2timeval(struct soap* soap, const char *dateTime) {
 struct timeval* presnycas = (struct timeval*)soap_malloc(soap,sizeof(*presnycas));
 long zlomky;
 time_t celycas;
 char *dot;
 soap_s2dateTime(soap,dateTime,&celycas);
 presnycas->tv_sec = celycas;
 if((dot=strchr(dateTime,'.'))==NULL) {
   //zadne zlomky sekund
   presnycas->tv_usec = 0;
 } else {
   //zlomky sekund nutno najit mezi teckou a casovou zonou
   size_t delka;
   int i;
   char *s,*ms;
   ms = strdup(dot + 1);
   for (s = ms; *s; s++) { if (*s < '0' || *s > '9') { *s = 0; break; } }
   delka = strlen(ms);
   sscanf(ms,"%ld",&zlomky);
   free(ms);
   if(delka>6) {
     for(i=0;i<delka-6;i++) zlomky/=10;
   } else if(delka<6) {
     for(i=0;i<6-delka;i++) zlomky*=10;
   }
   presnycas->tv_usec = zlomky;
 }
 return presnycas;
}

void soap_mark_xsd__dateTime_(struct soap* soap, struct timeval *const* a)
{ } // no need to mark this node (for multi-ref and cycle detection)

void soap_default_xsd__dateTime_(struct soap* soap, struct timeval ** a)
{ *a = NULL; }

int soap_out_xsd__dateTime_(struct soap *soap, const char *tag, int id, struct timeval *const* a, const char *type)
{
   soap_element_begin_out(soap, tag, id, type); // print XML beginning tag
   soap_send(soap, timeval2dateTime(soap,*a)); // print the string 
   soap_element_end_out(soap, tag); // print XML ending tag
}

xsd__dateTime_ *soap_in_xsd__dateTime_(struct soap *soap, const char *tag, struct timeval **a, const char *type)
{
   if (soap_element_begin_in(soap, tag))
      return NULL;
   if (!a)
      a = (xsd__dateTime_*)soap_malloc(soap, sizeof(xsd__dateTime_));
   if (soap->null)
      *a = NULL; // xsi:nil element
   if (*soap->type && soap_match_tag(soap, soap->type, type))
   {
      soap->error = SOAP_TYPE;
      return NULL; // type mismatch
   }
   if (*soap->href)
      a = (xsd__dateTime_*)soap_id_forward(soap, soap->href, a, SOAP_TYPE_xsd__dateTime_ , sizeof(xsd__dateTime_));
   else if (soap->body)
   {
      char *s = soap_value(soap); // fill buffer
      *a = (struct timeval*)soap_malloc(soap, sizeof(struct timeval));
      *a = dateTime2timeval(soap,s); //prevod
   }
   if (soap->body && soap_element_end_in(soap, tag))
      return NULL;
   return a;
}
 

Teď spustíme server a několikrát klienta:

 gcc -I. -O2 -o druhy_klient druhy_klient.c stdsoap2.c druhaC.c druhaClient.c dateTimeSerializer.c
 gcc -DDEBUG -I. -O2 -o druhy_server druhy_server.c stdsoap2.c druhaC.c druhaServer.c dateTimeSerializer.c
 ./druhy_server &
 ./druhy_klient rumcajs
 ./druhy_klient cipisek
 ./druhy_klient manka
 ./druhy_klient chyba
 

Prohlédněte si soubory SENT.log a RECV.log obsahující záznam komunikačních SOAP zpráv serveru.

Klient v jazyce Java

Pro demonstraci interoperability předvedeme klienta v jazyce Java. Použijeme Apache Axis. Stáhněte si axis-1_1.tar.gz a nainstalujte:

 tar xzvf axis-1_1.tar.gz
 module add jdk-1.4.2
 export CLASSPATH=.
 for i in $PWD/axis-1_1/lib/*.jar; do CLASSPATH=$i:$CLASSPATH; done
 

Z WSDL souboru vygenerujeme třídy:

 java  org.apache.axis.wsdl.WSDL2Java -v \
      --NStoPkg http://scb.ics.muni.cz/druha=druha \
      --output classes \
      VelkaSluzba.wsdl
 cd classes
 

Naimplementujeme stejného klienta v souboru VolejVelkouSluzbu.java:

import druha.*;
import druha.holders.*;
import javax.xml.rpc.holders.StringHolder;
import java.util.Calendar;

public class VolejVelkouSluzbu {

 public static void main(String [] args) throws Exception {
  String retezec = "cipisek";
  if(args.length>0) { retezec = args[0]; }

  Otesanek o = new VelkaSluzbaLocator().getVelkaSluzba();

  Bumbrlik b = new Bumbrlik();
  b.setRetezec(retezec);
  b.setBajt((byte)11);
  b.setCelecislo(12);
  b.setKratkecislo((short)13);
  b.setDlouhecislo(14L);
  b.setPlovoucicislo(15f);
  b.setDvojitecislo(16d);
  b.setPravdivost(true);
  b.setBinarnidata(new byte[] { 'K', 'L', 'M' });
  PoleRetezcu p = new PoleRetezcu();
  p.setX(new String[] { "slepice", "kurata" });
  b.setPole(p);
  b.setDen(Denvtydnu.Pondeli);
  Calendar c = Calendar.getInstance();
  b.setCasnasekundy(c);
  b.setCasnamikrosekundy(c);

  o.zadej(b);

  UkazovakHolder uh = new UkazovakHolder();
  StringHolder   sh = new StringHolder();
  o.ziskej(uh,sh);

  System.out.println("poznamka"+sh.value);
  Ukazovak u = uh.value;
  while(u!=null) {
      System.out.println("jmeno="+u.getJmeno());

      System.out.println("ukazatel1="+(u.getUkazatel1()==null?"null":u.getUkazatel1().getJmeno()));
      System.out.println("ukazatel2="+(u.getUkazatel2()==null?"null":u.getUkazatel2().getJmeno()));
      u = u.getUkazatel1();
  }
 }
}

 

Teď stačí přeložit a spustit:

 javac VolejVelkouSluzbu.java 
 java VolejVelkouSluzbu raholec
 

Tak máme i druhou, složitější službu se dvěma klienty hotovou :-)

Adresace webservice

Jak server nalezne kterou webservice zavolat ? U gSOAP je to triviální - jeden TCP port = jedna webservice. U složitějších serverů může být více webservice najednou pohromadě. Např. Apache Axis to dělá následovně:

Morální ponaučení - nespoléhejte na to že webservice je určena portem jako v gSOAP. Měli byste ji volat tak jak je určeno ve WSDL.

Styl WSDL

Historicky vznikly čtyři styly WSDL:

Rozdíly mezi nimi jsou pěkně popsány v článku Which style of WSDL should I use? RPC/encoded, RPC/literal, document/literal?. Stručně řečeno, dnes se preferuje wrapped document/literal, protože tak to dělá MS .NET, a validace XML ve zprávě je možné snadno odvodit přímo z definice typů ve WSDL. Takže dnes i gSOAP a Axis podporují tento styl, gSOAP ho má jako default. Ale nehodí se vždycky, pokud je třeba přenášet grafy objektů i s typovou informací, je třeba použít RPC/encoded.

Bezpečnost a webservices

Zatím webservices bezpečnost moc neřeší. Jsou dva přístupy - bezpečnost na úrovni přenosu a bezpečnost na úrovni XML zpráv.

Transport level security

Bezpečnost na úrovni přenosu se zajišťuje prostým použitím SSL pod HTTP, tj. https protokolem pro přenos SOAP zpráv. Nevýhoda je, že bezpečnost je zajištěna pouze mezi komunikujícími konci, a nelze například zpětně dokázat že druhý konec poslal určitou zprávu, nebo u vícevrstvých aplikací nelze zajistit že zpráva nebyla změněna mezilehlými vrstvami. Ale funguje to už teď a je to jednoduché.

Existují pluginy do gSOAP i do Apache Axis, které umožňují HTTP-nad-GSI, tzv. httpg protokol. Úspěšně používáme v GridLabu.

Message level security

Existují pokusy využít standardy XML Encryption a XML Signature pro šifrování a podepisování SOAP zpráv, protože SOAP zprávy jsou nakonec XML dokumenty. IBM si vydala vlastní WS-Security specifikaci. Microsoft si vydal vlastní WS-SecureConversation specifikaci. Globus projekt se snaží tohle všechno nějak poskládat dohromady v GT3 Security. Ale reálně řečeno, je to zatím v plenkách.

Výhody bezpečnosti na úrovni zpráv by byly právě možnost zpětného prokazování co kdo poslal, a vyloučení mezilehlých vrstev z bezpečnosti komunikace. Nevýhody jsou že to zatím nefunguje, a režie zpracování bude vysoká.

Verze specifikací

Odkazy