Luku 7 RPC - TODO: write as text (not just list of items) Seuraavassa käsitellään RPC:tä (Remote Procedure Call), joka on ilmeisesti vanhin sokettien päälle rakennettu tapa tehdä hajautettuja sovelluksia. Käsittely on kohtuullisen pinnallinen ja se tehdään olettaen RMI tutuksi. 7.1 Taustaa Birrell & Nelson (1984) (vaikka keskusteltu jo aiemmin kirjallisuudessa). RFC 1057 (vähän ympäripyöreä). RPC:n (Remote Procedure Call) idea on välittää (TCP avulla) verkon yli proseduurin kutsu(ja) argumentteineen ja palauttaa takaisin etäällä suoritetun proseduurin tulos. Syy irrottaa proseduuri etäproseduuriksi liittyy yleensä työmäärään tai keskitettyyn suureen tietomäärään (jota ei ole järkevä kopioida moneen paikkaan). RPC:ssä, etäproseduurin kutsu näyttää ohjelmassa tavalliselta metodin kutsulta. Toteutus (kommunikointi verkon yli) on näkymätön ohjelmoijan kannalta. C-pohjainen ei olioita kuten RMI:ssä. 7.2 Sovelluksen rakentaminen Paikallinen RPC-kutsu näyttää normaalilta kutsulta. Jotta tämä olisi mahdollista, todellista proseduuria vastaten pitää määrittää rajapinta ja sille toteutus paikalliseen koneeseen. Jokainen paikallinen RPC-kutsu on edustaproseduurin client stub kutsu, joka hoitaa kommunikoinnin etäkoneen kanssa. Etäkoneessa (palvelimessa) on etäkutsupalvelin (server stub), joka odottaa etäkutsun tiedot sisältäviä viestejä. Palvelin tulkitsee viestin ja suorittaa muistiavaruudessaan vastaavaan etäproseduurin palauttaen tuloksen takaisin. 65
66 LUKU 7. RPC (Stub = tynkä) 1. Kutsuva ohjelma kutsuu edustaproseduuria kuten muitakin paikallisia proseduureja. 2. Edustaproseduuri koodaa parametrit viestiin ja lähettää viestin. 3. Etäkutsupalvelin ottaa viestin vastaan, purkaa sen ja kutsuu vastaavaa etäproseduuria. 4. Etäkutsupalvelin koodaa tuloksen viestiin ja lähettää sen takaisin. 5. Paikallinen edustaproseduuri ottaa viestin vastaan, purkaa tuloksen ja palauttaa sen paikalliselle ohjelmalle. 6. (Edellisessä: synkroninen RPC-kutsu.) Parametriongelmia Etäproseduurikutsujen toteuttaminen olisi helppoa, jos kaikki parametriarvot olisivat skalaariarvoja ja molemmat RPC-järjestelmät olisivat samoja. RPC-toteutukset perustuvat usein alla olevaan käyttöjärjestelmään ja siinä tiedon esitysmuoto voi vaihdella. Parametrien koodauksen (mashalling) täytyy ottaa tämä huomioon. (XDR) Jotkin paikalliset parametrit voivat olla viiteparametrejä. Jos sallittua, marshallointi tarkoittaa viitatun rakenteen kopiointia. Tulisiko muutosten heijastua takaisin? Mahdollista! Copy/restoreparametrit! Koneessa pitää olla RPC:n toteutus. Useita saatavilla. (Kokeile esim. Unixissa man rpcgen.) Helpoin tapa tehdä RPC-sovellus on käyttää rajapintakieltä Interface Definition Language (IDL), jonka avulla määritellään kutsuttavien etäproseduurien muoto. Järkevää, koska IDL kuvausta käyttäen voidaan generoida edustaproseduuri ja etäkutsupalvelin (+muuta)!! rpcgen: ei IDL vaan RPC Language kuvaus rajapinnasta. 7.3 Esimerkki Esimerkki: DCE RPC http://www.opengroup.org/dce/ Open Software Foundation (OSF) on tehnyt RPC-toteutuksen nimeltä Distributed Computing Environment useille käyttöjärjestelmille. DCE RPC tarjoaa välikerroksen käyttöjärjestelmän ja hajautettujen sovellusten välille. Seuraavan kalvon kuva näyttää, miten tehdä DCE RPC-sovellus käytännössä (asiakas- ja palvelinpää). DCE RPC-sovellusten tekeminen
7.3. ESIMERKKI 67 IDL description + client code + server code = app. Rajapintakuvaus on kirjoitettu IDL-kielellä ja kuvaus on kaikkia yhteensitova liima. Jokaisella kuvauksella tulee olla yksikäsitteinen tunnistenumero. Asiakas sidotaan etäkutsupalvelimeen hakemistopalvelun avulla. Palvelimessa on käynnissä DCE daemon, joka tunnistaa sisääntulevat RPC-viestit ja välittää ne oikealle prosessille. Varsinainen RPC-kutsun toteutus on normaali: koodaus, viestin lähettäminen, purkaminen, prosessointi, (tuloksen) koodaus, lähettäminen, purkaminen. Sun RPC-esimerkki Ei ilmeisesti ole aivan samanlainen kuin DCE RPC. Esimerkki (dice.x, dice_prog.c, rdice.c) on kurssin kotisivulla. Miten käyttää esimerkkiä Generoi rajapinnat rpcgen dice.x Käännä client gcc rdice.c dice_clnt.c -o dice_client -lnsl Käännä server gcc dice_prog.c dice_svc.c -o dice_server -lnsl Serverin käynnistys./dice_server Clientin käynnistys./dice_client staff.cs.utu.fi 10 dice.x /* dice.x: Remote random dice protocol */ program DICEPROG { version DICEVERS { int GETDICE(string) = 1; } = 1; } = 0x20000001; Palvelimeen: dice_prog.c
68 LUKU 7. RPC /* * dice_proc.c: implementation of the * remote procedure "getdice" */ #include <stdio.h> #include "dice.h" /* dice.h generated by rpcgen */ #define RANDOM random long random(); int * getdice_1_svc(char **who, struct svc_req *req) { static int result; /* must be static! */ } result = (unsigned long int)(random()%6) + 1; printf("sending %d back to %s\n", result, *who); return (&result); Asiakas: rdice.c 1/3 /* * rdice.c: main program for getting random dice numbers */ #include <stdio.h> #include "dice.h" /* dice.h generated by rpcgen */ main(int argc, char **argv) { CLIENT *clnt; int *result; char *server; int howmanytimes,i; if (argc!= 3) { fprintf(stderr, "usage: %s host number\n", argv[0]); exit(1); } Asiakas: rdice.c 2/3 server = argv[1]; sscanf(argv[2],"%d",&howmanytimes); if (howmanytimes < 1) howmanytimes = 1; // Create client "handle".
7.4. HUOMIOITA JA YHTEENVETO 69 clnt = clnt_create(server, DICEPROG, DICEVERS, "tcp"); if (clnt == (CLIENT *)NULL) { // No connection established clnt_pcreateerror(server); exit(1); } Asiakas: rdice.c 3/3 // Call the remote procedure for (i=0; i<howmanytimes; i++) { result = getdice_1(&server, clnt); printf("got %d \n", *result); } } printf("dice asking from %s done.\n", server); clnt_destroy( clnt ); exit(0); 7.4 Huomioita ja yhteenveto RPC:n viestin voi tehdä itse. RPC:n kirjasto tarjoaa suuren määrään proseduureja asiakkaita ja palvelimia varten. Parametrien koodausstandardi: XDR (external Data Representation). Asiakkaan ja palvelimen voi tehdä alusta alkaen itse tai ne voi toteutusympäristöstä riippuen osittain generoida. IDL-kieli on kohtuullisen rikas. DCE RPC:n IDL-kieli on toiminut pohjana Microsoftin COM:n (ja DCOM:n) IDL-kielelle (lähes identtinen). Kutsun ei tarvitse olla synkroninen kutsuja voi jatkaa suoritustaan ja kun kutsu on valmis, kutsujalle ilmoitetaan. TCP:n tilalla voi olla jokin muu kuljetusmekanismi (XML-RPC: HTTP). RPC ei ole vain C-kielisille ohjelmille esim. XML-RPC toteutuksia on useille kielille: Java, C/C++, Perl, PHP, Python. RPC on esitelty välitystekniikkana. Sovellusten tekemisen problematiikka samanlaista kuin RMI:n kohdallakin. Etäproseduurit eivät ole mitään aktiivista, vaan passiivisia palveluita. Ja yleisemmin monisäiesovellusten ongelmat koskien tiedon samanaikaista käsittelyä ovat myös RPC-sovellusten ongelmia.
70 LUKU 7. RPC
Luku 8 Verkkoprotokollat ja XML Tässä luvussa perehdytään hiemaan XML:ään (extensible Markup Language) asiakas-palvelin sovellusten ohjelmoinnin ja ylipäänsä Java-kielen kannalta. Luvussa 8.2 pyritään selvittämään lukijalla, mikä XML on, mitä siihen liittyvät monet lyhenteet/käsitteet (XSL, DTD, skeema,... ) tarkoittavat, ja miten laajan kokonaisuuden XML itse asiassa muodostaa. Tämän oppimateriaalin puitteissa käsittely jää hyvin pinnalliseksi lukijan tulee hakea lisätietoja esim. W3C:n sivuilta (http://www.w3c.org). XML:n idea tämän oppimateriaalin ja luvun kannalta on toimia asiakas-palvelin sovellusten välisessä keskustelussa välitettävän datan esitysmuotona. Itse asiassa XML soveltuu myös hyvin tilanteisiin, joissa ohjelman eri suorituskertojen välillä pitää taltioida jotain tietoa tiedostoihin. Tämä on hyvin perusteltua sillä, vaikka Java-ohjelmien kesken onkin helppo välittää Java-olioita (ja Java-olioita voidaan tallettaa tiedostoihin), niin sellainen menettely rajaa toiminnan Java-kielisiin ohjelmiin. Lisäksi tällaiseen sarjallistamiseen liittyvä ongelma on, että sarjallistamisen purkamisen yhteydessä käytettävien luokkien täytyy olla muuttumattomia siihen nähden millaisia ne olivat sarjallistamisen tapahtuessa. Usein esiintyvä tarve muuttaa ohjelmia ja niiden luokkia käytännössä laskee sarjallistamisen hyödyllisyyttä. XML:n merkitys tässä mielessä on toimia (ohjelmointi)kielestä ja suoritusalustasta riippumattomana yleisenä datan esitysmuotona 1. Luvussa 8.3 tarkastellaan hieman XML:n käyttötilanteita vaikka XML ei olekaan ohjelmointikieli, niin osoittautuu, että sillä on Javankin yhteydessä käyttöä. Tämän materiaalin yhteydessä XML:ää tarkastellaan vain rakenteisen tiedon esittämisen välineenä. Tätä XML:n osa-aluetta valoitetaan luvussa 8.4. Oliot edustavat rakenteista tietoa ja ohjelmointikieliessä tiedolla on jokin tyyppi tilanne on samanlainen myös XML:n kohdalla. Luvussa 8.4 tarkastellaan tiedon esittämisen lisäksi tyypin, DTD = Document Type Definition, esittämistä. Tämän jälkeen luvussa 8.5 tarkastellaan hieman XML:ää Javan kannalta perehtymällä erityisesti DOM:iin (Document Object Model), jonka avulla XML-dokumentteja voidaan esittää Javan olioina. DOM on suunniteltu esittämisen lisäksi XML-tiedostojen jäsentämistä ajatellen (samalla myös käänteistä toimenpidettä, antijäsentämistä, ajatellen). DOM on itse asiassa pelkkä ohjelmointirajapinta XML-dokumenttien käsittelemiseksi on myös kaksi muuta ohjelmointirajapintaa SAX (Simple API for XML) StAX (Streaming API for XML), mutta niitä ei tämän materiaalin puitteissa käsitellä. Luvussa 8.5 esitetään myös laajahko esimerkki DOM:n soveltamisesta. Tässä luvussa on ollut myös tarkoitus käsitellä verkkoprotokollien muodostamiseen liittyviä asioita asiakkaan ja palvelimen kannalta. Erityisesti tarkoituksena oli pohtia, miten protokollan tilan tulisi näkyä asiakas- ja palvelinsovelluksissa, ja mitä luokkia protokollan muodostamisen yhteydessä tulisi 1 Asiakkaan ja palvelimen välisiä viestejä suunniteltaessa niille voi toki keksiä itse täysin vapaamuotoisen tekstipohjaisen muodon, mutta miksi tuolloin ei käyttäisi XML:ää? 71
72 LUKU 8. VERKKOPROTOKOLLAT JA XML muodostaa. Tässä oppimateriaalin versiossa nämä asiat ovat vain listamaisesti kalvojen pohjalta (ei kirjoitettu auki). 8.1 Verkkoprotokollat Yleistä Protokolla asiakkaan ja palvelimen kannalta; tilat Kuvaaminen, Muodostaminen Protokollasta luokiksi Viime aikaisia suuntauksia 8.1.1 Taustaa Protokollia määritelty pilvin pimein. RFC (Request For Comments) 1 http://www.ietf.org/rfc.html Yli 3500 olemassa. Esim. RFC 821: Simple Mail Transfer Protocol. Tyypillisesti Internetin alkuaikoina kuvattiin useiden tekstipohjaisten protokollien kaikkien viestien tarkka muoto sekä keskustelun etenemistavat. Hajautetun sovelluksen voidaan ajatella olevan jossain tilassa. Viesti(e)n lähettämisen seurauksena sovellus siirtyy tilasta toiseen. Vuoropohjaisissa protokollissa usein vähän vaihtoehtoja seuraavalle viestille (& tilasiirtymälle). Keskustelevien osapuolten pitää mieltää hajautetun sovelluksen olevan jossain tilassa ja hyväksyä vain tilan kannalta lailliset viestit. 8.1.2 Protokollan muodostaminen ja kuvaaminen Muodostaminen: Pitää miettiä läpi kaikki asiakkaan ja palvelimen väliset keskustelut. Kirjataan keskustelun vaiheet viesteiksi; vältä turhia viestejä. Pitäisi pyrkiä minimoimaan erilaisten viestien määrää. Tärkeää huomioida erilaiset kuittaukset sekä virhetilanteista ilmoittaminen. Todellisuudessa keskustelu ei aina etene niin kuin toivotaan: virheistä toipuminen eräs keskustelun muoto (viestejä). Viestien koodaus:
8.1. VERKKOPROTOKOLLAT 73 Binäärisessä muodossa, esim. kirjoitetaan olioita TCP-sokettien tiedostovirran päällä. Tekstimuodossa (perinteinen vaihtoehto): suunnitellaan itse viestien muoto ja koodaus. Moderni variaatio: esitetään viestit XML:llä. Kaikkien edellisten vaihtoehtojen yhteydessä on hyvinkin järkevää esittää viestit ohjelmassa erilaisilla Java-olioilla. Muodostetaan sovellukselle tiloja. Tilat ovat hajautetun järjestelmän kokonaistiloja, mutta usein liittyvät johonkin osapuoleen. Tilakaavio: Tilasta toiseen siirrytään vaihtamalla viestejä tekemällä keskustelu. Yksittäiset tilasiirtymät (keskustelu) voidaan kuvata UML:n sekvenssikaaviolla. Yleisemmin sekvenssikaaviolla voidaan kuvata tyypillisiä keskusteluja. tee yhteys odot. yhteyd.otto yhteys tehty odotetaan autentikointia login ok kyselyjen palvelu logout lopetustila yhteys tehty login fail login login ok kyselyn tulos tee kysely logout logout ok close conn. close ok autentikointi kyselytila logout tehdään logout lopetustila Kuva 8.1: Eräs mahdollinen tilakaavio. 8.1.3 Protokollista luokiksi Perusajatus: Luetellaan kaikki järjestelmässä tarvittavat viestit ja tehdään jokaista vastaten yksi luokka. Tehdään yksi yliluokka, joka määrittää yleiset ominaisuudet. Niitä lähinnä decode/encode (tai read/write tai parse/unparse). Yksittäisiä viestejä vastaavat aliluokat toteuttavat lisäksi havainnointi- ja modifiointimetodeja. Pohditaan esimerkkiä, jossa henkilöiden tietoja haetaan palvelimelta. Ensin pitää onnistuneesti autentikoida. Sitten voidaan tehdä useita kyselyjä. Lopuksi suljetaan yhteys palvelimeen. Tarkoitus linkittää IT-laitoksen henkilötietokantaan.
74 LUKU 8. VERKKOPROTOKOLLAT JA XML tee yhteys epäonnistunut login autentikointi onnistunut login epäonnistuva kysely onnistunut kysely kyselytila logout tehdään logout logout lopetustila yhteyden sulkeminen Kuva 8.2: Tilakaavio asiakkaan kannalta. A P: Tee autentikointiyritys P A: Autentikointi onnistui P A: Autentikointi epäonnistui P A: Autentikointi suoritettu jo A P: Anna ilmoitetun henkilön tiedot P A: Palautetaan halutut tiedot P A: Kyseistä henkilöä ei ole A P: Sulje yhteys P A: Yhteys sulkeutuu... Kustakin luokka + yliluokka XXX_App_Message. import org.w3c.dom. ; public abstract class ITinfo_Message { public abstract ITinfo_Message parse(document doc); public abstract void unparse(document doc); } // class ITinfo_Message import org.w3c.dom. ; public class ITinfo_Login_Message {
8.1. VERKKOPROTOKOLLAT 75 odot. yhteyd.otto yhteys tehty odotetaan autentikointia login ok kyselyjen palvelu palvellaan kyselypyyntö logout lopetustila Kuva 8.3: Tilakaavio palvelimen kannalta. private String user; // tunnus private String passwd; // salasana public ITinfo_Login_Message(String u, String p) { // tarkistuksia... (poissa) user = u; passwd = p; } // ITinfo_Login_Message public String getuser() { return user; } public String getpasswd() { return passwd; } public void setuser(string u) { user = u; } public void setpasswd(string p) { passwd = p; } public ITinfo_Message parse(document doc) { // jäsennetään DOM:sta ITinfo_Login_Message String p = ; String u = ; //... luetaan arvot u:lle ja p:lle return new ITinfo_Login_Message(u,p); } // parse public abstract void unparse(document doc) { // muodostetaan rakenne tyhjään dokumenttiin... } // unparse } // class ITinfo_Login_Message Mahdollisesti uloin kuori pois + pitäisi tehdä DTD-määrittely. 8.1.4 Uusia painotuksia: REST- ja ohut käyttöliittymä-tekniikat Protokollaa ja ylipäänsä järjestelmän toimintaa suunniteltaessa olennaista päättää missä tilatietoa ylläpidetään.
76 LUKU 8. VERKKOPROTOKOLLAT JA XML : asiakas onnistunut : palvelin login autentikointi tila LOGIN viesti autentikointi tila kyselytila LOGINOK viesti kyselytila Kuva 8.4: Keskustelu sekvenssikaaviona. Perinteinen ratkaisu: pidetään tilatietoa sekä asiakkaissa että palvelimissa. Thin client: pidetään kaikki tieto palvelimessa. Rest = representational state transfer: pidetään kaikki tilatieto yhteydestä asiakkaassa. Ohut asiakas -tekniikka: Lähinnä käyttöliittymätekniikka, koska koko sovellus on oikeastaan palvelimessa. Ajax on yksi toteutuskeino. Toimii web-alustan yhteydessä. Useita toteutuksia saatavilla. Perustuu web-sivun sisällön dynaamiseen muuttamiseen ilman sivun uudelleenlataamista. Esim. Vaadin (IT Mill Oy). Ohut asiakas -tekniikkaa täydentää pilvilaskenta. REST-tekniikka: Palvelimella hajautetun järjestelmän tilatietoa, mutta kaikki asiakasyhteyteen liittyvä tieto asiakkaalla (toimitetaan pyyntöjen mukana palvelimelle). Asiakas-palvelin-suhde on tilaton: palvelin on aina levossa ja palvelee yksittäisen pyynnön kerrallaan. Tuloksiin voidaan soveltaa välimuistiin taltiointia. Muita periaatteita: layered design, uniform interfaces ja code-on-demand. 8.2 Johdatus XML:ään Mikä XML oikein on? XML on alunperin kehitetty tuomaan rakennetta WWW:n sisältöön, rakennetta HTML-sivujen latteuden tilalle. HTML-kielen tapaan varsinainen XML-kieli (joskin XML on
8.2. JOHDATUS XML:ÄÄN 77 kytkeytyminen laiton toimenpide autentikointitila epäonnistunut autentikointi logout login laiton toimenpide kyselytila logout kyselyn tekeminen lopetustila lopettaminen Kuva 8.5: ITinfo: tilakaavio asiakkaasta. <?xml version="1.0" standalone="yes"?> <itinfo_message> <itinfo_login_message> <user> villep </user> <passwd> salainen </passwd> </itinfo_login_message> </itinfo_message> Kuva 8.6: ITinfo_Login_Message XML:nä. myös paljon muutakin) on myös ns. merkkauskieli eli pelkistetysti ilmaistuna tekstin osiin, osakokonaisuuksiin, on mahdollista liittää tietynlainen merkitys merkkauksen avulla. HTML:ssä merkkauksen tarkoituksena on lähinnä muotoilun ilmaiseminen (samantapainen tilanne on mm. LaTeX:n kohdalla). XML on johdettu yleisestä SGML:stä (Standard Generalized Markup Language). XML on SGML:ään yksinkertaisempi, spesifisempi. XML-kieli on rakenteisen tiedon kuvauskieli. XML-dokumentti kuvaa yhden rakenteisen tietoalkion. XML pyrkii myös antamaan merkityksen kuvatulle tiedolle joskaan tässä ei tarkoiteta mitään yrityksiä ilmaista kuvattavien asioiden semantiikka formaalisti, vaan ideana on, että voidaan itse luoda tietynlainen sanasto (jonka sanoihin ajatellaan liittyvän semanttinen merkitys) ja tuota sanastoa käyttäen dokumentin osille annetaan merkitys. Yleisesti XML on joukko tekniikkoja tiedon kuvaamiseksi, käsittelemiseksi ja esittämiseksi. XML:ään liittyy useita laajoja kokonaisuuksia varsinainen XML-kieli muodostaa hyvin pienen osan. Esimerkiksi XSL, extensible Style Language, on tarkoitettu XML-dokumenttien ulkoasun kuvaamiseksi. Yhdelle dokumentille voidaan eri julkaisualustoja varten määritellä XSL-kielinen kuvaus, jonka avulla XML-dokumentista johdetaan kyseisen alustan mukainen (sen rajoituksiin sopeutuva) esitys. XSL:n on itse asiassa menetelmä muuttaa dokumentteja yhdestä rakenteisesta esityksestä toiseen. XSL:ää ei tämän materiaalin puitteissa käsitellä (vaikka Javalla onkin mahdollista lukea ja käsitellä myös XSL-kuvauksia XML-dokumenttien yhteydessä), sillä XSL suuntautuu dokumenttien muotoi-
78 LUKU 8. VERKKOPROTOKOLLAT JA XML luun. XML on suunniteltu WWW:stä lähtöisin, mutta kuten todettu, se soveltuu muuallekin. XML:n kehittämistä johtaa W3C (World Wide Web Consortium, www.w3c.org). W3C:hen kuuluu n. 500 jäsenorganisaatiota, ja sen perusti Tim Berners-Lee vuonna 1994. W3C:n takana on paljon isoja organisaatioita ja yrityksiä (mm. MIT, CERN, DARPA, EU, Nokia, Sun, Microsoft, HUT, HY,... ). XML:n kehitystyö alkoi 1990-luvun puolivälissä. XML 1.0 on W3C:n suositus (helmikuu 1998; korjattu versio, lokakuu 2000). Itse asiassa XML 1.1 on jo julkaistu, mutta siitä ei ole vielä tehty suositusta. Vaikka suositus (engl. recommendation) kuulostaa hieman vähäpätöiseltä, niin se on W3C:n asteikolla korkein status ja sen voi rinnastaa standardin määrittelyyn. XML-kielen suosituksen lisäksi W3C on antanut suosituksen useista kymmenistä XML:ään liittyvistä osakokonaisuuksista. Lisäksi W3C:llä on useita työryhmiä XML:stä tälläkin hetkellä ilmeisesti asioiden laajuudesta johtuen kehitystyö ainakin vaikuttaa hieman hitaalta. Lopuksi W3C:n oma näkemys XML:n olemuksesta, XML in 10 points : 1. Tiedon strukturointi. Strukturointia on nykyään kaikkialla: tietokannan tietue, OO-kielen olio,... 2. XML-kieli on HTML-kielen tapainen kuvauskieli. 3. XML-dokumentti on tekstipohjainen kuvaus, jota ei ole tarkoitettu luettavaksi ihmisten toimesta. 4. Havainnollisuus: Kuvauksia voi tehdä käsin (ja XML-dokumentteja voi jopa lukea). Tekstuaalisesta esityksestä seuraavaan havainnollisuuden haittapuoli on dokumenttien suuri koko, mutta siihen voi välittämisen yhteydessä vaikuttaa tiivistyksellä. 5. Joukko teknologioita kuvauskielen lisäksi. 6. XML on suhteellisen uusi asia; SGML on ISO-standardi, 1980-luvun alkupuoli. 7. XML: HTML XHTML (uusi kieli WWW-sivuille). 8. XML-dokumentilla voi olla tyyppi; tyypin voi koostaa modulaarisesti. 9. XML toimii semanttisen webin perustana. 10. XML on lisenssivapaa, alustariippumaton ja hyvin tuettu. 8.3 XML:n käyttötilanteista Seuraavassa luonnehdintoja XML-dokumenttien esittämisestä, välittämisestä, jäsentämisestä ja tuottamisesta. XML-dokumenttien esittäminen XML-dokumentteja halutaan tyypillisesti esittää useilla erilaisilla alustoilla. Alustoista WWW-selain on luonnollisesti tällä hetkellä kiinnostavin. Esittäminen vaatii tuekseen XSL-määrityksen. Kuvassa 8.7 on havainnollistettu kahta tapaa katsoa XML-dokumentteja WWW-selaimen avulla: joko suoraan tai XML/HTML-konversion kautta. Nykyään monet selaimet tukevat jo suoraa XML-dokumenttien esittämistä
8.3. XML:N KÄYTTÖTILANTEISTA 79 XML konversio ohjelma HTML + muuta (CSS) www selain XSL XML XML pohj. verkkoselain XSL Kuva 8.7: XML-dokumenttien esittämisestä. XML-dokumenttien välittäminen Välittämistilanne liittyy suoraviivaisesti asiakas-palvelin sovelluksiin. XML-dokumentti toimii tiedon esittämismenetelmänä. Osapuolten pitää sopia dokumenttien muodosta ja merkityksestä. Tässä yhteydessä on luonnollista määrittää DTD-kuvaus välitettävälle tiedolle. Valitettavasti XML-kielen mukaisten dokumenttien ei tarvitse noudattaa mitään tyyppikuvausta (DTD:tä tai skeemaa), mutta ilman sopimusta (siis tyyppi-ilmausta) välitettävän tiedon muodosta, välitettävän tiedon käyttökelpoisuus on hyvin kyseenalaista. sovellus A XML dokumentti sovellus B DTD Kuva 8.8: XML-dokumenttien välittämisestä.
80 LUKU 8. VERKKOPROTOKOLLAT JA XML XML-dokumenttien jäsentäminen Kun XML-dokumentteja käsitellään ohjelmallisesti (kuva 8.9), niin vastaanottavan ohjelman pitää pystyä jäsentämään XML-dokumentti, Javan tapauksessa olioiksi ohjelman muistiavaruuteen. Javan yhteydessä tarjolla on useita jäsentäjiä. Luvussa 8.5 perehdytään Apache XML-projektin tuloksena syntyneeseen Xerces-jäsentäjään. Sen avulla voi myös tehdä jäsentämisen käänteistoiminnon ( antijäsentäminen ), eli oliomuotoisen XML-dokumentin esityksen purkamisen tekstimuotoon. Xerces perustuu DOM:iin (ja SAX:iin) tietyssä mielessä DOM on tapa käydä läpi oliomuotoinen XMLdokumentti. sovellus XML dokumentti XML jäsentäjä XML olioita Kuva 8.9: XML-dokumenttien jäsentäminen. XML-dokumenttien tuottaminen XML-dokumenttien tuottamiseksi on useita tapoja. On jopa erityisiä editoreita, jotka tukevat dokumenttien tekemistä käsin. Yleisempi XML-dokumentin muodostumistapa on kuitenkin itsenäinen ohjelma, joka käyttäjä tietojen perusteella tekee ohjelmallisesti XML-dokumentin (DOM, Java). XML-dokumentteja voidaan helposti muodostaa esimerkiksi applettien, servlettien, JSP:n, HTMLlomakkeet + CGI-ohjelmien, jne yhteydessä. Usein XML-dokumentti generoidaan osittain tietokannan tietojen perusteella. Tietokannan rooli XML:n yhteydessä on merkittävä, koska tietokantojen tarkoitushan on samantapainen: tiedon esittäminen. Tietokantojen (erityisesti myös relaatiotietokantojen) valmistajat tukevat nykyään hyvin XML:ää ja saatavilla on jopa erityisiä XML-tietokantoja, joihin voi taltioida XMLdokumentteja. XML ja verkkoviestintä XML liittyy dynaamiseen sisällön tuottamiseen kuten servletit, JSP, ASP, jne. XML:n rooli on tiedon sisällön esittämisessä ei niinkään ulkoasun esittämisessä (vaikka siihen on toki myös panostettu). XML:n alkuperäinen tarkoitus ei ole ilmaista, mistä tieto koostetaan, vaan XML-dokumentti toimii itse tiedon koosteena. XML ei ole verkkolomake, vaan pikemminkin verkkolomakkeen täyttämisen tulos.
8.4. TIEDON ESITTÄMINEN XML:LLÄ 81 8.4 Tiedon esittäminen XML:llä XML-dokumentin rakennetta voi lähestyä tarkastelemalla HTML-dokumentin tagi-rakennetta: <html> <title> Sivun otsikko </title> <body> <h1> Pääotsikko </h1> Tekijä on <b>l. Auri</b>... <p>... </body> </html> HTML-dokumentissa tageilla on sulkumainen rakenne, sama sulkurakenne on XML-dokumenteilla. HTML:n ongelmaksi XML:n kannalta nähdään esimerkiksi tagiparin <b>... </b> sisällä olevan tiedon merkitys <b>... </b> on vain tiedon muotoiluun liittyvä määritys (bold face), se ei ota kantaa sisältöön. XML-dokumenteilla on myös hierarkinen sulkumainen tagi-rakenne, mutta tageilla ajatellaan nyt olevan semanttinen merkitys erityisesti tällä ymmärretään sitä, että tagien merkitys ei liity muotoiluun. Seuraavassa esimerkissä 8.1 on esitetty eräänlainen sähköpostiviesti rakenteisena XML-dokumenttina. Aluksi on muutamia XML-dokumenttiin liittyviä määrityksiä, mutta sen jälkeen tiedoston sisältönä on vain hierarkinen sulkumaista järjestystä noudattava tagi-rakenne.
82 LUKU 8. VERKKOPROTOKOLLAT JA XML Esimerkki 8.1 Eräänlainen sähköpostiviesti XML-dokumenttina, sposti.xml. <?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="sposti.xsl"?> <!DOCTYPE sposti SYSTEM "./sposti.dtd"> <sposti> <lahettaja> <email> Ville.Leppanen@it.utu.fi </email> <nimi> Ville Leppanen </nimi> </lahettaja> <vastaanottajat> <vastaanottaja> <email> hannu@utu.fi </email> </vastaanottaja> <vastaanottaja> <email> heikki@utu.fi </email> </vastaanottaja> </vastaanottajat> <otsikko> Koeviesti </otsikko> <runko> <kappale> Heips! </kappale> <kappale> Olin muuten... </kappale> </runko> </sposti> Kuvassa 8.10 on esitetty esimerkin 8.1 dokumentti puurakenteena. Kuva esittää dokumenttia vain osittain, jotkin solmut puuttuvat kuvasta. dokumentti sähköposti lähettäjä vastaaottajat otsikko runko email vastaanottaja kappale kappale nimi email Heips! Olin... Ville.L... hannu@.. Ville heikki@.. Kuva 8.10: Esimerkkidokumentti puurakenteena.
8.4. TIEDON ESITTÄMINEN XML:LLÄ 83 XML-dokumentti koostuu yhdestä (juuri)elementistä (tagi-pari), jolla on tyyppinimi". Elementit koostuvat hierarkisesti XML-elementeistä ja/tai tekstistä. Samannimisiä elementtejä voi yhden elementin sisällä olla useita. Elementti olion/tietokannan kenttä (pikemminkin kentän tyyppi). XMLdokumentti oliokielen olio Pascalin tietue relaatiotietokannan tietue. Periaatteessa XML-dokumentissa olevien elementtien nimet voidaan valita täysin vapaasti. Jos tagit noudattavat hierarkista rakennetta ja dokumentissa on vain yksi juurielementti, niin dokumentti on oikeanmuotoinen tai hyvinmuodostettu (well-formed) XML-dokumentti. Nimien on tarkoitus liittää tagi-parin sisällä olevalle tiedolle merkitys tapahtuuko näin, kun kerran elementtien nimet voidaan valita vapaasti? Ratkaisun tähän pyrkii muodostamaan dokumentin DTD-kuvaus; huomaa, miten esimerkin 8.1 alussa on viittaus DTD-kuvaukseen (sposti.dtd). Elementeillä voi olla myös tekstiarvoisia nimettyjä attribuutteja, esimerkiksi seuraavaan tapaan: <kappale tasaus="both"> Tämä tasataan molempiin reunoihin... </kappale> Attribuuttien nimet voi myös itse määrittää. Niiden arvona on jokin merkkijono. Elementillä ei ole pakko olla sisältöä, jolloin ainoa mahdollinen elementtiin liittyvä tieto on elementin attribuuteissa: <graphic source="kissa.gif"/> 8.4.1 DTD = Document Type Definition DTD on dokumentin rakennemääritys ja se on verrattavissa ohjelmointikielissä tyyppiin. DTD:n tekemiseksi on oma kieli, joka on osa XML:ää. DTD on niin laaja, ettei sitä tässä kuvata yksityiskohtaisesti toisaalta DTD on XML:n kannalta osoittautunut kuvausvoimaltaan hieman liian rajoittuneeksi ja sen takia XML:n yhteyteen on määritelty ns. skeemat (engl. scheme). Skeemoja voisi luonnehtia dokumenttien luokkakuvauksiksi skeemoihin liittyvä kuvauskieli on DTD:n vastaavaa rikkaampi. Vaikka DTD-kuvaus onkin tärkeä, niin on hyvä muistaa, että XML-dokumentin ei tarvitse olla minkään DTD:n mukainen! DTD:n tarkoitus on määritellä, millaisia elementtejä XML-dokumentissa voi olla, ja mikä täsmällisesti ilmaistuna on niiden rakenne. DTD-kuvaus kuvaa myös elementteihin liittyvät mahdolliset attribuuttimääritykset. Jos dokumentin rakenne vastaa DTD:tä, XML-dokumenttia sanotaan validiksi (engl. valid). DTD-määreitä on kahdenlaisia: ulkoisia ja sisäisiä. XML-dokumentin alussa voi olla ulkoisen DTD:n määre (DOCTYPE-kohta): <?xml version="1.0"> <!DOCTYPE sähköposti <sähköposti>... </sähköposti> SYSTEM "/home/dtd/sposti.dtd"> Toisaalta XML-dokumentin alussa voi olla myös sisäinen DTD-määre (samoin DOCTYPE-kohdassa): <?xml version="1.0"> <!DOCTYPE sähköposti [ <!ELEMENT sähköposti (lähettäjä, vastaanottajat, otsikko, runko)> <!ELEMENT lähettäjä (email, nimi?)>
84 LUKU 8. VERKKOPROTOKOLLAT JA XML... ]> <sähköposti>... </sähköposti> DTD-määritys on pääasiallisesti luettelo ELEMENT-määrittelyitä. ELEMENT-määrittelyiden apuna käytetään ENTITY-määreitä, jotka ovat vakioita / makroja / viittauksia ulkoisiin tiedostoihin (esimerkiksi toisiin XML-dokumentteihin). ENTITY-määreitä on myös kahdenlaisia, mutta niiden idea on toimia tietynlaisena vakioarvoisena lausekkeena. DTD:ssä lauseke määritellään ja XMLdokumentissa sitä voidaan käyttää tuolloin kyseiseen kohtaan XML-dokumenttia laajennetaan ENTITY-viittauksen arvo. Esimerkki 8.2 Sähköpostiviestiä vastaava DTD-määrittely, sposti.dtd. <?xml version="1.0"?> <!ELEMENT sposti (lahettaja, vastaanottajat, otsikko, runko)> <!ELEMENT lahettaja (email, nimi?)> <!ELEMENT email (#PCDATA)> <!ELEMENT nimi (#PCDATA)> <!ELEMENT vastaanottajat (vastaanottaja)+> <!ELEMENT vastaanottaja (email nimi)> <!ELEMENT otsikko (#PCDATA)> <!ELEMENT runko (kappale)*> <!ELEMENT kappale (#PCDATA)> Esimerkissä 8.2 on esitetty esimerkkiä 8.1 vastaava DTD-määritys (ulkoisena kuvauksena). DTDkuvauksessa ELEMENT-osat ovat tavallaan kielioppikuvauksia ajatellen nonterminaaleja, joille annetaan kuvauksessa korvaussääntö. Määreissä merkki tarkoittaa tai :ta; merkki? tarkoittaa, että kyseinen elementti voi olla ko. kohdassa tasan kerran tai puuttua kokonaan; merkki + puolestaan tarkoittaa, että kyseinen elementti esiintyy mahdollisesti useita kertoja, mutta kuitenkin vähintään kerran; merkki * kertoo vastaavasti toistosta, mutta nyt toistojen määrä on 0. Peräkkäin esiintyvien elmenttien listassa (A, B, C) järjestys on sidottu. Kentän tyyppi voi olla monikko (nonterminaaleja), EMPTY (elementillä ei sisältöä), ANY (mikä tahansa kelpaa sisällöksi) tai #PCDATA (merkkijono). Edellisten lisäksi DTD-kuvauksessa on <!ATTLIST...>-määreitä, joiden avulla määritellään elementteihin liittyvien attribuuttien muoto, esimerkiksi seuraavasti: <!ATTLIST book id ID #REQUIRED> Jolloin määritellään, että elementtiin nimeltä book liittyy id -niminen attribuutti, jonka arvona on yksikäsitteinen identifioiva merkkijono. Määreistä #REQUIRED tarkoittaa, että attribuutti vaaditaan, mm. #IMPLIED tarkoittaa, että sovellus päättää arvon, ja #FIXED tarkoittaa, että XMLdokumenteissa kyseisellä attribuutilla on kiinteä arvo, joka ilmoitetaan DTD-kuvauksen yhteydessä. Muita tyyppejä ID:n lisäksi ovat mm. IDREF viittaa ID:hen (täytyy olla olemassa) ja ENTITY DTD:ssä määrätty entiteetti. Entiteetit voivat olla ulkoisia jäsentämättömia entiteettejä: <!ENTITY kustantajanlogo SYSTEM "../pics/logo.gif" NDATA gif>
8.5. XML JA JAVA 85 jotka viittaavat johonkin ulkoiseen tiedostoon tai yleisentiteettejä: <!ENTITY XML "Extensible Markup Language"> joihin XML-dokumentissa voidaan viitata ilmauksella &XML;. 8.5 XML ja Java DOM, Document Object Model, on W3C:n toimesta määritetty API XML-dokumenttien käsittelemiseksi. Sunin JDK:n osana on myös Java-kielinen API DOM:lle (JDK 1.4: org.w3c.dom-paketti). Javan kannalta DOM on lähinnä joukko rajapintaluokkia, joiden avulla kuvataan XML-dokumenttien puurakenne ja mahdollistetaan sen käyminen läpi. DOM ei siis oikeastaan ole jäsennin, vaan jäsennyksen tulos. DOM:a voidaan käyttää ohjelmalliseen XML-dokumenttien muodostamiseen & muokkaamiseen. DOM:n haittapuolena on, että sen yhteydessä dokumentti ajatellaan muodostettavan kokonaan muistiin ennen käsittelyä. Toinen rajapinta SAX, Simple API for XML (paketti org.xml.sax), toimii hieman toisella ajatuksella. Siinä jäsentäminen / läpikäynti etenee tapahtumapohjaisesti ja tarkoituksena ei ole muodostaa koko XML-dokumenttia vastaavaa oliorakennetta ohjelman muistiin. 8.5.1 DOM:n luokista DOM:iin liittyy joukko luokkia, joista seuraavassa lyhyt luonnehdinta: Element XML-dokumentin elementti. Attr attribuutti. Comment <!-- Kommentti... --> Entity entiteettiviittaus (ei määrittely). Document koko XML-dokumentti. DocumentType DTD-määrittely. Text tekstitietoa. CDATASection tekstiä (CDATA). Node puurakenne edellisille (yliluokka). Edellisistä luokka Node lienee keskeisin, sillä muut ovat sen aliluokkia se määrittelee siis puumuodon ja samalla yleisen toiminnallisuuden erityyppisille XML-dokumenttia esitävän puun solmuille. Taulukossa 8.1 on lueteltu joitakin luokan Node metodeja. 8.5.2 Jäsentäminen Xercesillä Xerces on XML Apache-projektin tulos; se on DOM 1.0:n mukainen jäsentäjä. Xercesin avulla tuotetaan ajonaikainen esitys XML-dokumentista Java-olioina. Jotta tämä olisi mahdollista, Xerces:n mukana tulee toteutus DOM:n rajapintaluokille antaa siis toteutuksen JDK 1.4:n paketille org.w3c.dom (ja javax.xml.parsers). Jäsennyksen tuloksena syntyy org.w3c.dom.document-tyyppinen olio. Xerces tukee XML-dokumentin validointia, vaikka sitä ei esimerkeissä 8.3 8.5 hyödynnetäkään. XML Apache:een liittyy myös Xalan, jolla voidaan käsitellä XSL-määrityksiä. Xercesjäsentäjän täyttäminen näkyy ohjelmakoodissa esimerkiksi seuraavasti:
86 LUKU 8. VERKKOPROTOKOLLAT JA XML Node getfirstchild() Puun solmun ensimmäinen lapsisolmu (solmuilla dokumentin mukainen järjestys; null, jos ei ole). Node getnextsibling() Nykyisen solmun seuraava sisarus, eli isäsolmun kannalta seuraava lapsisolmu (null, jos ei ole). Node getprevioussibling() Nykyisen solmua edeltävä sisarus, eli isäsolmun kannalta edeltävä lapsisolmu (null, jos ei ole). Node getlastchild() Solmun viimeinen lapsisolmu (null, jos ei ole). Node getparentnode() Solmun isäsolmu. NodeList getchildnodes() Solmun kaikki lapsisolmut listamuodossa. String getnodename() Solmun nimi; nimi vaihtelee esitettävän asian mukaan. Esim. elementeillä se on elementin nimi. String getnodevalue() Solmun arvo; vaihtelee esitettävän asian mukaan. Esim. elementeillä tämä on aina null, sillä jos niiden arvona XML-dokumentissa tekstiä, niin kyseinen teksti on solmun lapsisolmussa, joka on Text-tyyppinen. NamedNodeMap getattributes() Vain elementeille; palauttaa attribuutit. boolean hasattributes() Onko elementillä attribuutteja? boolean haschildnodes() Onko solmulla lapsisolmuja? Lisäksi monia muita metodeja; esimerkiksi muuttaminen: appendchild, removechild, replacechild, setnodevalue,... Taulukko 8.1: Joitakin luokan Node metodeja. import org.apache.xerces.parsers.domparser; import org.w3c.dom.document; import org.xml.sax.saxexception; import java.io.ioexception;... String xmlfile = "file:///omatdatat/sposti.xml"; DOMParser parser = new DOMParser(); try { parser.parse(xmlfile);} catch (SAXException se) { se.printstacktrace(); } catch (IOException ioe) { ioe.printstacktrace();}... Document document = parser.getdocument(); Seuraavissa esimerkeissä 8.3 8.5 näytetään, miten palvelinsovellus voi ottaa vastaan XMLpohjaisen viestin, jäsentää sen ja selvittää jäsennyksen tuloksesta erilaisia tietoja. Asiakassovellus (esimerkki 8.4) lukee tiedostosta XML-dokumentin (sposti.xml) jäsentämällä sen olioiksi. Kyseisen esityksen se lähettää palvelimelle purkamalla sen taas tekstimuotoon. Luokka XMLsposti (esimerkki 8.5) on aika yllätyksetön kuvaus siitä, miten XML-dokumentti jäsennetään kuvauksen lopussa olevien luokkien mukaisiksi olioiksi (DOM-olioista) ja vastaavasti puretaan ko. oliomuodosta DOM:n mukaiseksi puurakenteeksi.
8.5. XML JA JAVA 87 Esimerkki 8.3 Palvelin, joka ottaa vastaan viestin XML-muodossa. import java.io. ; import java.net. ; import java.util. ; public class ViestiPalvelin { public static final int VIESTIPORTTI = 8203; public static void main(string[] args) throws Exception { ServerSocket ss = new ServerSocket(VIESTIPORTTI); while (true) { Socket cs = ss.accept(); System.out.println("Connection from " + cs.getinetaddress() + "port " + cs.getport()); InputStream is = cs.getinputstream(); Sposti sp = XMLsposti.parse(iS); System.out.println("Message from " + sp.lähettäjä.email); is.close(); cs.close(); } // while } // main } // class ViestiPalvelin Esimerkki 8.4 Asiakassovellus, joka jäsentää tiedostosta XML-muotoisen viestin ja lähettää sen palvelimelle. import java.net. ; import java.io. ; public class Viestittaja { public static void main(string[] args) throws Exception { if (args.length 2) { System.out.println("Usage: java Viestittaja <host> <xml-message>"); System.exit(0); } InputStream in = new FileInputStream(args[1]); Sposti sp = XMLsposti.parse(in); System.out.println("Sending message from " + sp.lähettäjä.email + "to mailserver at " + args[0]); Socket soc = new Socket(args[0], ViestiPalvelin.VIESTIPORTTI); OutputStream os = soc.getoutputstream(); XMLsposti.unparse(sp, os); os.flush(); os.close(); soc.close(); } // main } // class Viestittaja
88 LUKU 8. VERKKOPROTOKOLLAT JA XML Esimerkki 8.5 Lähetettävän viestin käsittely: XML-muotoinen sähköposti XMLsposti.java 1/3. import org.apache.xerces.parsers.domparser; import org.apache.xerces.dom.documentimpl; import org.apache.xml.serialize. ; import org.xml.sax.inputsource; import org.w3c.dom. ; import java.io. ; import java.util. ; public class XMLsposti { private static final String SPOSTI = "sposti"; private static final String LÄHETTÄJÄ = "lahettaja"; private static final String EMAIL = "email"; private static final String NIMI = "nimi"; private static final String VASTAANOTTAJAT = "vastaanottajat"; private static final String VASTAANOTTAJA = "vastaanottaja"; private static final String OTSIKKO = "otsikko"; private static final String RUNKO = "runko"; private static final String KAPPALE = "kappale"; public static Sposti parse(inputstream xmlin) throws Exception { DOMParser parser = new DOMParser(); parser.parse(new InputSource(new InputStreamReader(xmlIn))); Document doc = parser.getdocument(); // Navigoidaan dokumenttia. Element root = doc.getdocumentelement(); if (!root.getnodename().equals(sposti)) throw new Exception("Uloimman alkion tulee olla " + SPOSTI); Node ch = root.getfirstchild(); Lahettaja lähettäjä = null; Vector vastaanottajat= new Vector(); String aihe = null; Vector kappaleet = new Vector(); ch = seekfor(ch, Node.ELEMENT_NODE); while (ch null) { String n = ch.getnodename(); if (n.equals(lähettäjä)) lähettäjä = parselähettäjä(ch); else if (n.equals(vastaanottajat)) vastaanottajat = parsevastaanottajat(ch); else if (n.equals(otsikko)) aihe = parseotsikko(ch); else if (n.equals(runko)) kappaleet = parserunko(ch); // else throw new Exception("Outo elementti: " + n); ch = seekfor(ch.getnextsibling(), Node.ELEMENT_NODE); } // while // Tuloksen muodostus int vnum = vastaanottajat.size(); Vastaanottaja[] va = new Vastaanottaja[vnum]; for (int i=0; i<vnum; i++) va[i] = (Vastaanottaja)vastaanottajat.elementAt(i); int knum = kappaleet.size(); String[] ka = new String[knum]; for (int i=0; i<knum; i++) ka[i] = (String)kappaleet.elementAt(i); Sposti sp = new Sposti(); sp.lähettäjä = lähettäjä; sp.otsikko = aihe; sp.kappaleet = ka; sp.vastaanottajat= va; return sp; } // unparse private static Node seekfor(node n, short t) { while ((n null) && (n.getnodetype() t)) n = n.getnextsibling(); return n; } // seekfor private static String parseotsikko(node n) { return n.getfirstchild().getnodevalue(); }... jatkuu
8.5. XML JA JAVA 89 Esimerkki 8.5. Luokka XMLsposti (jatkoa 2/3).... jatkoa private static Lahettaja parselähettäjä(node n) throws Exception { Node child = seekfor(n.getfirstchild(), Node.ELEMENT_NODE); String email, nimi; if (child == null) throw new Exception("Lähettäjällä ei osia."); if (!child.getnodename().equals(email)) throw new Exception("Lähettäjän ensimmäinen osa tulee olla " + EMAIL + "se oli " + child.getnodename()); email = child.getfirstchild().getnodevalue(); child = seekfor(child.getnextsibling(),node.element_node); if (child == null) nimi = null; else { if (!child.getnodename().equals(nimi)) throw new Exception("Lähettäjän toinen elementti on " + NIMI); nimi = child.getfirstchild().getnodevalue(); child = seekfor(child.getnextsibling(), Node.ELEMENT_NODE); if (child null) throw new Exception("Lähettäjässä kolmas elementti."); } Lahettaja l = new Lahettaja(); l.email = email; l. nimi = nimi; return l; } // parselähettäjä private static Vector parsevastaanottajat(node n) throws Exception { Node child = seekfor(n.getfirstchild(), Node.ELEMENT_NODE); if (child == null) throw new Exception("Ei vastaanottaja-elementtejä."); Vector vec = new Vector(); while (child null) { if (!child.getnodename().equals(vastaanottaja)) throw new Exception("Vieras elementti: " + child.getnodename()); vec.add(parsevastaanottaja(child)); child = seekfor(child.getnextsibling(), Node.ELEMENT_NODE); } // while return vec; } // parsevastaanottajat private static Vastaanottaja parsevastaanottaja(node n) throws Exception { Node child = seekfor(n.getfirstchild(), Node.ELEMENT_NODE); String email = null, nimi = null; if (child == null) throw new Exception("Vastaanottaja: email tai nimi."); String nn = child.getnodename(); if (nn.equals(email)) email = child.getfirstchild().getnodevalue(); else if (nn.equals(nimi)) nimi = child.getfirstchild().getnodevalue(); else throw new Exception("Vastaanottajassa vieras elementti: " + nn); Vastaanottaja v = new Vastaanottaja(); v.email = email; v.nimi = nimi; return v; } // parsevastaanottaja private static Vector parserunko(node n) throws Exception { if (!n.getnodename().equals(runko)) throw new Exception(RUNKO + ":a odotettiin."); Node child = seekfor(n.getfirstchild(), Node.ELEMENT_NODE); Vector vec = new Vector(); while (child null) { if(!child.getnodename().equals(kappale)) throw new Exception("Odotettiin elementtiä: "+ KAPPALE); vec.add(child.getfirstchild().getnodevalue()); child = seekfor(child.getnextsibling(), Node.ELEMENT_NODE); } // while return vec; } // parserunko... jatkuu
90 LUKU 8. VERKKOPROTOKOLLAT JA XML Esimerkki 8.5. Luokka XMLsposti (jatkoa 3/3).... jatkuu public static void unparse(sposti sp, OutputStream xmlout) throws Exception { // Tehdään juurisolmu + muut solmut. Document doc = new DocumentImpl(); Element rootelement = doc.createelement(sposti); doc.appendchild(rootelement); Element lähettäjä = doc.createelement(lähettäjä); rootelement.appendchild(lähettäjä); Element vastaanottajat = doc.createelement(vastaanottajat); rootelement.appendchild(vastaanottajat); Element otsikko = doc.createelement(otsikko); rootelement.appendchild(otsikko); Element runko = doc.createelement(runko); rootelement.appendchild(runko); // lähettäjä Element email = doc.createelement(email); lähettäjä.appendchild(email); email.appendchild(doc.createtextnode(sp.lähettäjä.email)); Element nimi = doc.createelement(nimi); lähettäjä.appendchild(nimi); nimi.appendchild(doc.createtextnode(sp.lähettäjä.nimi)); // Vastaanottajat for (int i=0; i<sp.vastaanottajat.length; i++) { Vastaanottaja v = sp.vastaanottajat[i]; Element e; if (v.email == null) { e = doc.createelement(nimi); e.appendchild(doc.createtextnode(v.nimi)); } else { e = doc.createelement(email); e.appendchild(doc.createtextnode(v.email)); } Element e1 = doc.createelement(vastaanottaja); e1.appendchild(e); vastaanottajat.appendchild(e1); } // for // Otsikko otsikko.appendchild(doc.createtextnode(sp.otsikko)); // Runko for (int i=0; i<sp.kappaleet.length; i++) { Element e = doc.createelement(kappale); e.appendchild(doc.createtextnode(sp.kappaleet[i])); runko.appendchild(e); } // for // Puretaan tiedostoon. OutputFormat of = new OutputFormat(Method.XML, "utf-8", false); XMLSerializer unparser = new XMLSerializer(xmlOut, of); unparser.asdomserializer(); unparser.serialize(doc.getdocumentelement()); } // unparse } // XMLsposti class Sposti { public Lahettaja lähettäjä; public Vastaanottaja[] vastaanottajat; public String otsikko; public String[] kappaleet; } // Sposti class Lahettaja { public String email; public String nimi; } // Lahettaja class Vastaanottaja { // Toinen aina null. public String email; public String nimi; } // Vastaanottaja
Luku 9 WWW-tekniikoista old content - rather old content Servletti (engl. servlet) eli palvelinsovelma on Java-kielinen olio, joka toimii WWW-palvelimen yhteydessä olevassa virtuaalikoneessa. Kaikkiin WWW-palvelimiin ei kuitenkaan ole upotettu virtuaalikonetta, joten servlettejäkään ei voida suorittaa kaikkien WWW-palvelimien yhteydessä. Seuraavaksi luvussa 9.1 kerrotaan servlettien taustasta, roolista ja yleisestä toimintaideasta. Luvussa 9.2 kerrotaan mahdollisuudesta määritellä HTML-kielisille WWW-sivuille lomakkeita, joiden avulla voidaan kutsua mm. servlettejä toimittaen niille samalla käyttäjän antamia arvoja. Tämän jälkeen luvussa 9.3 tehdään katsaus servletteihin liittyviin luokkiin (mm. Servlet, HttpServlet, Http- ServletRequest, HttpServletResponse ja Cookie) ja niiden ominaisuuksiin. Servletteihin liittyvät luokat ovat paketeissa javax.servlet ja javax.servlet.http. Luvussa 9.4 esitetään tämän jälkeen kolme esimerkkiä palvelinsovelmien käytöstä. Loppukommentteja esitetään luvussa 9.5. Servlettejä on käsitelty monissa kirjoissa, mutta useimmissa käsittely on pinnallista tai puutteellista. Tätä kirjoitettaessa paras kirja lienee edelleen Java Servlet Programming, Jason Hunter & William Crawford, 1998. Kyseinen kirja käsittelee jopa tiedostojen siirtämistä HTTP-protokollalla servlettioliolle. 9.1 Johdanto Servletti eli palvelinsovelma on siis WWW-palvelimessa suoritettavaksi tarkoitettu (tietynlainen) Javaohjelma. Servletti kuten nimikin antaa ymmärtää on tietyssä mielessä samanlainen kuin applettikin. Servlettiin ei appletin tapaan liity main -metodia, mutta appleteista poiketen sillä ei voi olla edes graafista käyttöliittymää. Servleteillä on hyvin tarkkaan määrätty rooli WWW-palvelimien yhteydessä: niiden tehtävänä on dynaamisesti generoida WWW-sivuja. Kaikki palvelimet eivät tue servlettejä. Itse asiassa servletit eivät ole edes osa JDK:ta (ei edes versiossa 1.4), vaan ne on itse asennettava kääntäjän (ja mahdollisesti WWW-palvelimen) yhteyteen. Sun on tehnyt servleteistä JSDK-kokonaisuuden, jonka voi asentaa JDK:n yhteyteen ja jonka mukana tulee jopa pienimuotoinen servlettejä tukeva WWW-palvelin. Servlettien käyttämiseen liittyvät asiat ovat valitettavasti hieman WWW-palvelinkohtaisia asioita. Servlettejä suoritetaan palvelimen oikeuksilla. Servletti voidaan luoda uudelleen jokaisen WWWpalvelimeen kohdistuvan servletin kutsun seurauksena, mutta yleensä servletit ovat luonnin jälkeen pysyvä osa palvelinta. Tämä tarkoittaa, että servletit muistavat tilansa kutsukerrasta toiseen vaikka kutsuva taho onkin palvelimen sisällä olevan virtuaalikoneen ulkopuolella. Tällainen ominaisuus 91
92 LUKU 9. WWW-TEKNIIKOISTA OLD CONTENT on erittäin hyödyllinen raskaissa sovelluksissa servletti voi esimerkiksi käsitellä tietokantaa niin, että tietokantayhteyttä pidetään koko ajan auki. Palvelinkohtainen asia on myös servlettien luonti ja alustus yleensä luonti tapahtuu sellaisella konstruktorilla, johon ei liity parametrejä ja luomisen jälkeen niitä käyttävä taho kutsuu niiden init()-metodia (parametrien määräämistapa on palvelinkohtainen asia). WWW-palvelimen tehtävänä on välittää HTML-sivuja palvelinta käyttäville WWW-selaimille. HTML-sivujen ideahan, että ne ovat tekstimuotoisia sisältäen muotoilukomentoja ja esim. viittauksia muihin dokumentteihin. Viittaukset muihin dokumentteihin ovat johonkin palvelimeen kohdistuvia URL-osoitteita kuljettaessa linkkiä pitkin kyseinen URL-välitetään URL:ssä mainitulle palvelimelle ja WWW-palvelimen tapauksessa palvelin lähettää selaimelle takaisin HTML-sivun. Tällainen dokumenttien noutaminen tapahtuu HTTP-protokollalla käyttäen GET, POST, PUT tai muutamaa muuta komentoa. Huomaa, että HTTP-protokolla on yhteydetön tässä mielessä on merkittävää, että servletit ovat pysyviä olioita. Toinen tärkeä huomio tässä yhteydessä on, että nykyään WWW-selaimet eivät ole passiivisten HTML-sivujen varastoja, vaan sivuja usein tuotetaan eri menetelmillä lennosta, dynaamisesti. Servlettien idea on juuri dynaamisessa WWW-sivujen generoinnissa. Kuvassa 9.1 havainnollistetaan servlettien toimintaympäristöä. www palvelin kone www selain GET komento HTML sivu verkkoyhteys www palvelin prosessi POST komento HTML sivu doget() dopost() servletti olioita Kuva 9.1: Palvelinsovelmien suorittaminen. Toinen servlettihin liittyvä idea on, että sivujen tuottamiseen voi liittyä raskasta laskentaa, joka tällä tavalla annetaan palvelimen tehtäväksi. Servletit ovat omimmillaan ns. kolmitasoarkkitehtuurien (3-tier) toteuttamisen yhteydessä: servletit ovat välitaso selaimen ja tietokannan välissä. Servletit on osin tehty korvaamaan ns. CGI-ohjelmointi. Koska servlettejä kutsutaan HTML-sivuilta WWW-selaimen toimesta, niin herää kysymyksiä: (1) miten kutsun yhteydessä voidaan välittää tietoja ja (2) voiko servletti toimia yhteen WWWselaimessa suoritettavan appletin kanssa? Eräs tapa välittää tietoa WWW-palvelimelle ja sitä kautta servleteille ovat HTML-kielen FORM-määritykset, eli ns. lomakkeet. Näitä käsitellään tarkemmin kohdassa 9.2. Idea on kuitenkin se, että voidaan antaa käyttäjän täyttää joitakin lomakkeen kohtia ja samalla määritellä, mitä URL:ää kutsutaan kun lomake on täytetty. Kutsuminen tarkoittaa mm. HTTP-protokollan mukaisen GET- tai POST-komennon lähettämistä lomakkeessa täytetyt tiedot koodautuvat automaattisesti tällaisen viestin yhteyteen ja ne voidaan servletissä saada helposti selvil-
9.2. LOMAKKEISTA 93 le. Toisen kysymyksen suhteen tilanne on valitettavasti sellainen, että servlettejä ja appletteja ei ole tehty kommunikoimaan keskenään! Perusajatus on, että servlettiä kutsutaan HTTP-protokollaa käyttäen. Voidaan toimia niin, että appletti avaa HTTP-yhteyden WWW-palvelimeen (ja jäsentää tuloksen vastauksena saamastaa HTML-sivusta), mutta tuolloin appletti tavallaan itse toimii selaimena selaimen sisällä vaikkakin mahdollista, niin kovin järkevää tällainen toiminta ei ole. Itse asiassa servletin ja appletin välille on mahdollista luoda myös soketti- ja/tai RMI-yhteys, mutta tämä ei ole kovin luonnollista ajatellen servlettien toimintaympäristöä. Tuolloin servlettejä käytettäisiin ohi niiden normaalin rajapinnan. Soketti- ja RMI-yhteyksiä ei käsitellä tässä yhteydessä. 9.2 Lomakkeista HTML-kielen lomakkeet 1 (engl. forms) ovat tapa välittää tietoa servleteille HTTP-protokollan yli. Servlettejä kutsuvat lomakkeet näyttävät HTML-koodissa seuraavalta: <FORM METHOD="POST" ACTION="http://kohdekone/servlets/OmaServletti">... määrityksiä </FORM> METHOD-kohdassa määritellään käytettävä HTTP-protokollan komento. Arvo POST ei ole ainoa mahdollinen itse asiassa GET :n merkitys on sama kuin POST :kin. Niiden ero on lähinnä tekninen ja syy osittain vanhentunut GET :n yhteydessä on aikoinaan ajateltu lähetettävän vain hyvin vähän muuta tietoa ja POST :n yhteydessä voidaan lähettää vaikka tiedostoja asiakkaalta WWWpalvelimelle. 9.2.1 FORM:n määrityksistä FORM-määrityksen sisällä on lisää määrityksiä, joiden avulla voidaan muodostaa HTML-sivulle jo tutuksi tulleita yksinkertaisia GUI-komponentteja. Näistä määrityksistä tehdään seuraavaksi selkoa. Tarkemmin asiat on määritelty HTML 4.01:n määrittelyssä (www.w3c.org). Kenttä Kenttä on määre, joka on muotoa <INPUT TYPE="tyyppi" NAME="nimi" VALUE="arvo"> Idea on, että määritellään nimi -niminen resurssi, jolla on alkuarvo arvo. Edellisessä tyypillä voi olla useita arvoja ja sen mukaan määräytyy WWW-sivulle syntyvä GUI-komponentti. Komponentin tyypin mukaan määräytyy tapa, jolla arvo voi muuttua. Mahdollisia arvoja tyypille ovat TYPE="text" Esittää tekstikenttäkomponenttia, johon voidaan kirjoittaa tekstiä. 1 Ks. http://www.w3c.org/tr/html4/interact/forms.html.
94 LUKU 9. WWW-TEKNIIKOISTA OLD CONTENT TYPE="radio" Esittää radionappulaa ja NAME:n arvo kertoo ryhmän. VALUE:lla voidaan välittää tieto siitä, mikä saman ryhmän radionappuloista painettu pohjaan. TYPE="checkbox" Esittää valintaruutua. Toiminta on radionappuloiden tapaista, mutta NAME:a vastaava arvo esiintyy resurssina vain jos jokin kyseisen NAME-arvon omaavista valintaruuduista on painettu pohjaan. Resurssin VALUE:n arvona ovat kaikkien niiden valintaruutujen VALUE-osien arvot, joita vastaava kyseisen niminen valintaruutu on painettu pohjaan. TYPE="submit" Erityinen nappula, jota painamalla FORM:n ACTION osa käynnistyy. VALUEosalla voidaan asettaa nappulan teksti (oletusarvoisesti submit ). TYPE="reset" Tämä muodostaa myös nappulan kuten submit, mutta tarkoituksena on palauttaa painamisen seurauksena kaikkien kenttien arvo niiden alkuarvoiksi. TYPE="image" Kuten submit, mutta nappulan sisältönä kuva. TYPE="password" Kuten text, mutta esittää salasanakenttää. TYPE="file" Esittää tiedoston nimen valintatyökalua. Valittava tiedosto lähetetään HTTP-komennon mukana MIME-koodattuna, jos lomakkeen otsikossa on on seuraava määritys: ENCTYPE="multipart/mime". TYPE="hidden" Piilokenttä vakioarvon välittämiseksi. Esimerkiksi seuraava FORM-määrittely <FORM action="http://koneen-nimi/adduserservlet" method="post"> <P> First name: <INPUT type="text" name="firstname"><br> Last name: <INPUT type="text" name="lastname"><br> email: <INPUT type="text" name="email"><br> <INPUT type="radio" name="sex" value="male"> Male<BR> <INPUT type="radio" name="sex" value="female"> Female<BR> <INPUT type="submit" value="send"> <INPUT type="reset"> </P> </FORM> näyttää selaimessa seuraavalta
9.3. SERVLETTIEN TEKEMISESSÄ TARVITTAVIA LUOKKIA 95 Valintalista Valintalista on myös jo tuttu GUI-komponentti. HTML:n FORM:ien yhteydessä se määritellään seuraavasti <SELECT NAME="nimi"> <OPTION>Vaihtoehto nro1</option> <OPTION SELECTED>Vaihtoehto nro2</option> <OPTION>Vaihtoehto...</OPTION> </SELECT> Eli, kutakin vaihtoehtoa kohti määritellään <OPTION>-rivi. NAME on samassa roolissa kuin kentän yhteydessä. Tavallaan tämä on kenttä, jonka arvo valitaan annetusta joukosta. SELECTED tarkoittaa, että yksi on jo valittuna 2. Tekstialue Tekstikentän yleistys, tekstialue, on myös osana FORM:a. Sen määrittelyn muoto on pelkistetysti alla olevan muotoinen. ROWS ja COLS saavat arvokseen positiivisia kokonaislukuja. Muita <TEXTAREA NAME="nimi" ROWS="rivit" COLS="sarakkeet"> Tässä voi antaa tekstin oletusarvon. </TEXTAREA> Itse asiassa muitakin määreitä on mm. nappula, mutta niistä ei tehdä selkoa tässä yhteydessä. 9.3 Servlettien tekemisessä tarvittavia luokkia Servlettien tekemissä tarvittavat luokat eivät ole osa JDK:ta (ei edes versiossa 1.4), vaan ne löytyvät mm. Sunin JSDK-paketista 3. JSDK koostuu kahdesta paketista: javax.servlet ja javax.servlet.http. Niissä on n. 40 luokkaa tai rajapintaa. Olennaisimmat näistä ja niiden väliset perimyssuhteet on esitetty kuvassa 9.2 (italics-tyyliset ovat rajapintoja). Luokka Servlet on rajapinta, jonka toteuttavat abstraktit luokat GenericServlet ja HttpServlet. Servlet-luokalla ei ole juurikaan metodeja erityisesti ei yhtään suoraan HTTP:hen liittyvää metodia; ks. taulukko 9.1. Luokalla GenericServlet on jo aika runsaasti metodeja. Ne liittyvät lähes poikkeuksetta servlettiä kutsuvan tahon tietojen havainnointiin ja ylipäänsä hallinnollisiin asioihin. Tässäkään luokassa ei ole mitään HTTP:hen liittyvää luokan GenericServlet metodeja ei tässä yhteydessä esitetä. Taulukkoon 9.2 on kerätty joitakin luokan HttpServlet metodeja itse asiassa kaikki muut hyödylliset metodit on peritty luokan GenericServlet kautta. Jokaista HTTP-protokollan mukaista komentoa kohti on luokassa metodi. Metodien muoto on täsmälleen sama metodien semantiikasta vastaa niiden toteuttaja. Oletusarvoisesti metodit eivät tee mitään. Huomaa, että periaatteessa voisi toimia niin, että lomakkeessa kutsuu DELETE-komennolla servlettiä, mutta (tiedoston) tuhoamisen sijaan voi vapaasti määritellä, miten servlettiolio reagoi kyseiseen komentoon. Metodien semantiikan tulisi kuitenkin olla HTTP-protokollan komentojen semantiikan mukaista. 2 Itse asiassa useitakin voidaan valita; katso tarkempi SELECT:n kuvaus HTML 4.01:n määrittelystä. 3 Ks. http://java.sun.com/products/servlet/.
96 LUKU 9. WWW-TEKNIIKOISTA OLD CONTENT ServletResponse Servlet ServletRequest GenericServlet HttpServletResponse HttpServlet HttpServletRequest Cookie Kuva 9.2: Muutamia servletteihin liittyviä luokkia ja niiden väliset perimyssuhteet. void init(servletconfig c) Servlettiolion alustus; kutsutaan vain kerran; c tulee palvelimelta. void service(servletrequest rq, ServletResponse rs) Servletin kutsu ohjataan ensin tähän metodiin. Taulukko 9.1: Joitakin rajapinnan Servlet metodeja. Seuraavaksi esittelyvuorossa on rajapinta HttpServletRequest (taulukko 9.3). Sillä on varsin runsaasti havainnointimetodeja, joiden kautta voidaan havainnoida sitä, mitä tietoa HTTP-komennon yhteydessä on servlettioliolle välitetty. Rajapinnan HttpServletResponse metodeita on lueteltu taulukossa 9.4. Lähinnä hyödyllisiä ovat muodostettavan sivun tyypin asettaminen, piparien liittäminen palautettavaan sivuun ja getwriter, jolla otetaan käyttöön tiedostovirta, jonne muodostettava sivu tulee kirjoittaa. Edellisten lisäksi hyödyllinen luokka on myös Cookie (taulukko 9.5), jolla on lähinnä konstruktori sekä metodeja pipariin (engl. Cookie) liittyvien ominaisuuksien asettamiseksi ja havainnoimiseksi. Piparin avulla palvelin voi tallettaa asiakkaaseen (siis asiakkaana olevan selainohjelman yhteyteen) tietoa itsestään ja yhteydestään tähän asiakkaaseen. Piparit ovat erittäin hyödyllisiä servlettien yhteydessä piparien tarkoitus on tarjota palvelimelle selaimesta muistipaikka, jonne se voi taltioida haluamaansa tietoa. Piparien olennaiset ominaisuudet on selitetty taulukon 9.5 yhteydessä. 9.4 Esimerkkejä 9.4.1 Esimerkki HelloServlet Esimerkissä 9.1 on esitetty HTML-tiedosto, jolla on vain yksi nappula. Nappulan painamisen seurauksena kutsutaan (bg.cs.utu.fi-koneen) servlettiä HelloServlet (ja esitetään tuloksena saatava HTMLsivu).
9.4. ESIMERKKEJÄ 97 protected void dodelete(httpservletrequest rq, HttpServletResponse rs) Käsittelee HTTPprotokollan mukaisen DELETE-komennon. protected void doget(httpservletrequest rq, HttpServletResponse rs) Käsittelee HTTPprotokollan mukaisen GET-komennon. protected void dohead(httpservletrequest rq, HttpServletResponse rs) Käsittelee HTTPprotokollan mukaisen HEAD-komennon. protected void dooptions(httpservletrequest rq, HttpServletResponse rs) Käsittelee HTTP-protokollan mukaisen OPTIONS-komennon. protected void dopost(httpservletrequest rq, HttpServletResponse rs) Käsittelee HTTPprotokollan mukaisen POST-komennon. protected void doput(httpservletrequest rq, HttpServletResponse rs) Käsittelee HTTPprotokollan mukaisen PUT-komennon. protected void dotrace(httpservletrequest rq, HttpServletResponse rs) Käsittelee HTTPprotokollan mukaisen TRACE-komennon. protected void service(httpservletrequest rq, HttpServletResponse rs) Tätä metodia varsinaisesti kutsutaan WWW-selaimen toimesta; komennon mukaan tämä kutsuu edellisiä. Taulukko 9.2: Joitakin luokan HttpServlet metodeja. String getparameter(string p) Palauttaa lomakkeelta lähetetyn parametrin p arvon. Enumeration getparameternames() Kaikkien komennon yhteydessä välittyjen parametrien nimet. Cookie[] getcookies() Palauttaa sisääntulevat piparit! ServletInputStream getinputstream() Binäärinen lukuvirta lähetettyyn dataan. BufferedReader getreader() Sama tekstipohjaisena. String getcontenttype() Kertoo missä MIME-muodossa komennon yhteydessä välitettävä data tulee. int getcontentlength() Datan pituus. getremotehost, getremoteaddr, getremoteuser, getmethod, getservername, getserverport,... Taulukko 9.3: Joitakin rajapinnan HttpServletRequest metodeja. Esimerkki 9.1 HelloServlet.html HTML-tiedosto, joka käynnistää yksinkertaisen servletin. <html> <head><title> HelloServlet </title></head> <body> <h2> HelloServlet:n käynnistävä sivu </h2> <form method="get" action="http://bg.cs.utu.fi:8080/servlet/helloservlet"> <input type=submit value="push"> </form> </body> </html>
98 LUKU 9. WWW-TEKNIIKOISTA OLD CONTENT ServletOutputStream getoutputstream() Palauttaa binäärisen lähtevän virran. PrintWriter getwriter() Sama merkkipohjaisena; tällä toteutetaan sivujen kirjoittaminen. void setcontenttype(string c) Asettaa ulosmenevän sivun koodaukseksi c:n; esimerkiksi text/html. void senderror(int code, String msg) Palauttaa virheilmoitussivun takaisin virhekoodilla code ja viestillä msg. void addcookie(cookie c) Asettaa piparin lähtevään virtaan. Taulukko 9.4: Joitakin rajapinnan HttpServletResponse metodeja. Kuva 9.3: HelloServlet.html:n suorittaminen. Kuvassa 9.3 on esitetty tilanne, jossa selaimella suoritetaan lomakkeen sisältävää HTML-tiedostoa HelloServlet.html. Kuvassa 9.4 on vastaavasti tilanne, jossa HTML-sivun push -nappulan painamisen seurauksena on suoritettu esimerkissä 9.2 oleva servletti. Servletti on tuottanut kuvassa olevan HTMLsivun. Kuva 9.4: Tilanne HelloServlet.html:n push -nappulan painamisen jälkeen.
9.4. ESIMERKKEJÄ 99 Cookie(String name, String value) Konstruktori, joka luo piparin, jolla nimi name ja arvo value (RFC 2109). boolean getsecure() Onko turvallinen yhteys käytössä? HTTPS tai SSL. Voidaan asettaa (setxxx-metodi) ja havainnoida (getxxx-metodi) useita ominaisuuksia: kommentti (Comment) mikä on tämän piparin merkitys; voimassaoloaika (MaxAge) selain ei lähetä piparia enää takaisin, jos voimassaoloaika on mennyt umpeen; URL-polku (Path) määrittää polun palvelimen sisältä; aina kun selain pyytää jotain sivua, jonka etuliitteenä on tämä polku, lähetetään tämä pipari komennon mukana; oletusarvoisesti piparit liittyvät vain siihen sivuun, jonka yhteydessä ne ensimmäisen kerran välitettiin selaimelle; domain-nimi (Domain) edellisen laajennos, pipari voidaan välittää nyt URL:ään liittyvän domain nimen mukaan; arvo (Value) merkkijono, joka sisältää sen datan, jonka palvelin haluaa piparin mukana taltioida selaimeen; nimi (Name) piparin nimi; versionumero (Version) tuetaan versioita 0 & 1 (RFC 2109); ja turvallisuus (Secure) onko pipari välitetty turvallisen yhteyden yli. Taulukko 9.5: Joitakin luokan Cookie metodeja. Esimerkki 9.2 HelloServlet.java yksinkertainen servlettiohjelma. import javax.servlet. ; import javax.servlet.http. ; import java.io. ; public class HelloServlet extends HttpServlet { public void doget(httpservletrequest rq, HttpServletResponse rs) throws IOException, ServletException { rs.setcontenttype("text/html"); PrintWriter out = rs.getwriter(); out.println("<html>"); out.println("<title> Hello there! </title>"); out.println("<body>"); out.println("<img SRC=http://www2.cs.utu.fi/icons/slidbar.gif ALT=>"); out.println("<h1> Tervehdys! </h1>"); out.println("<img SRC=http://www2.cs.utu.fi/icons/slidbar.gif ALT=>"); out.println("</body></html>"); } // doget } // class HelloServlet
100 LUKU 9. WWW-TEKNIIKOISTA OLD CONTENT Esimerkissä 9.2 oleva ohjelma perii HttpServlet luokalta kaikkiin HTTP-protokollan komentoihin reagoivat metodit, mutta antaa uuden toteutuksen vain yhdelle niistä: doget :lle. Huomaa, miten metodin toteutuksessa ei välitetä lainkaan siitä, mitä servlettioliolle annetaan HttpServletRequesttyyppisen parametrin kautta, vaan servletti vain vakiomuotoisesti kirjoittaa vastauksena HTML-sivun ServletResponse-tyyppiseen parametriin liittyvän tiedostovirran kautta. 9.4.2 Esimerkki hieman monimutkaisemmasta servletistä Esimerkissä 9.3 on esitetty HTML-tiedosto, jossa käytetään useita kohdassa 9.2 kuvattuja lomakkeen komponentteja.
9.4. ESIMERKKEJÄ 101 Esimerkki 9.3 LomakeServlet.html HTML-tiedosto, joka käynnistää lomakkeen tiedot vastaanottavan servletin. <html> <head> <title> Lomakkeen välittämistä... </title> </head> <body> <p> Täytä seuraavat ja paina lopuksi lähetä. </p> <form method=post ENCTYPE="multipart/mime" ACTION=http://bg.cs.utu.fi:8080/servlet/LomakeServlet> <input type=hidden name="piiloarvo" value="oletusarvo"> <p> Nimi: <input type=text name="kentta1" value=""> </p> <p> Osoite: <input type=text name="kentta2" value="osoite"> </p> <p> Salasana: <input type=password name="kentta3" value=""> </p> <p> Opinnot aloitettu: <select name="opinnot"> <option> muu </option> <option selected> 1999 </option> <option> 2000 </option> <option> 2001 </option> </select> <p> Valitse jokin: <input type=radio name="valinta" value="1999"> <input type=radio name="valinta" value="2000"> <input type=radio name="valinta" value="2001"> </p> <p> Valitse tiedosto: <input type=file name="tiedosto" accept="html"> </p> <p> <input type=reset value="resetoi"> <input type=submit value="lähetä"> </p> </form> </body> </html> Kuvassa 9.5 on esitetty tilanne, jossa selaimella suoritetaan LomakeServlet.html -tiedostoa. Kuvassa 9.6 on vastaavasti tilanne, jossa HTML-sivun lähetä -nappulan painamisen seurauksena on suoritettu esimerkissä 9.4 oleva servletti. Servletti on tuottanut kuvassa olevan HTML-sivun.
102 LUKU 9. WWW-TEKNIIKOISTA OLD CONTENT Kuva 9.5: LomakeServlet.html:n suorittaminen. Esimerkki 9.4 Servletti, joka vain selvittää, mitä parametrien kautta sille on lähetetty. import javax.servlet. ; import javax.servlet.http. ; import java.io. ; public class LomakeServlet extends HttpServlet { public void dopost(httpservletrequest rq, HttpServletResponse rs) throws IOException, ServletException { String[] q = { "PiiloArvo", "Kentta1", "Kentta2", "Kentta3", "Opinnot", "Valinta", "Tiedosto" }; rs.setcontenttype("text/html"); PrintWriter out = rs.getwriter(); out.println("<html>"); out.println("<title> Vastaanotettiin seuraavaa </title>"); out.println("<body>"); for (int i=0; i<q.length; i++) { out.print("<p> " + q[i] + "= "); out.print(rq.getparameter(q[i])); out.println("</p>"); } out.println("</body></html>"); } // dopost } // class LomakeServlet Esimerkin 9.4 ohjelma muodostaa myös HTML-sivun, mutta esimerkistä 9.2 poiketen käytetään
9.5. LOPUKSI 103 Kuva 9.6: Tilanne LomakeServlet.html:n lähetä -nappulan painamisen jälkeen. hyväksi HttpServletRequest-tyyppisen parametrin kautta välitettyjen parametrien arvoja. Arvojen sisältö vain tulostetaan sivun sisällöksi. 9.5 Lopuksi Servletit ovat WWW-palvelimessa olevia olioita, joita voidaan HTTP-protokollaa käyttäen kutsua ne säilyttävät tilansa kutsusta toiseen. Koska servlettiä käytetään HTTP:n kautta, servlettien pääasiallinen toimintaidea on dynaaminen HTML-sivujen generointi ja toisaalta työn osittainen siirtäminen palvelimelle. Servlet applet -keskustelu on periaatteessa mahdollinen, esimerkiksi soketeilla ja RMI:llä (käytetään säikeitä apuna), mutta sellainen keskustelu ohittaa HTTP-protokollan. Keskustelun saa toki myös aikaan HTTP-protokollan avulla, mutta tuolloin appletti joutuu esimerkiksi jäsentämään tuloksen servletin lähettämästä HTML-sivusta (hyvin epäluonnollista). HTTP-protokollan yhteydettömyyteen saadaan ratkaisuja pipareista ja siitä, että servlettioliot säilyttävät tilansa (niin kauan kun WWW-palvelin on pystyssä). Piparit tuovat selkeästi erään ratkaisu useiden samanaikaisten käyttäjien erotteluun servletin kannalta. Usein servlettien avulla halutaan tallettaa (lomakkeen kautta syötettyjä) tietoja tietokantaan tai vastaavasti lukea joitakin tietoja tietokannasta. Tähän tarkoitukseen servletit soveltuvat erittäin hyvin. Eräs näppärä tapa toteuttaa sivusto servlettien avulla on olla kirjoittamatta lainkaan HTML-sivuja. HTML-sivut voidaan nimittäin koodata osaksi servlettiä. Voidaan toimia niin, että servletin doget - metodi palauttaa HTML-sivun, jossa on mukana lomake, joka kutsuu samaa servlettiä POST-menetelmällä, käyttäen siis sen dopost -metodia. Servletteihin liittyy läheisesti myös esimerkiksi JSP (Java Server Pages), mutta sitä ei tämä oppimateriaalin puitteissa käsitellä. Lopuksi pitää hieman harmillisesti todeta, että vaikka HTML-sivujen