Web-sovellusten toteutukseen alunperin esitelty ohjelmointitekniikka oli CGI-tekniikka. CGI-tekniikassa pyydetty ohjelma käynnistettiin palvelimessa erillisenä prosessina. CGI-tekniikassa ohjelma voi olla kirjoitettu millä tahansa ohjelmointikielellä, myös Javalla. Tehokkaimmin CGI-liittymä toimii kun suoritettavat ohjelmat ovat konekielisiä, valmiiksi käännettyjä. Käytännössä kuitenkin suurimman suosion ovat saavuttaneet tulkattavat tai suorituksen esiprosessointina käännettävät kielet, tunnetuimpana esimerkkinä Perl. Näiden suoritusta hidastaa suoritusympäristön lataus. Muistiin on prosessia pystytettäessä ladattava paljon muutakin kuin vain ohjelmakoodi. Tämä pätee myös tulkattavalle Java-kielelle. Java-tulkin lataaminen ja käynnistys on aika raskas operaatio. Servlet-tekniikka esiteltiin ratkaisuna Javalla toteutettavien palvelinohjelmien latausongelman hoitamiseksi ja palvelinohjelmien käytön tehostamiseksi. Perusajatuksena tekniikassa on, että Java-ajoympäristö käynnistetään jatkuvakäyntiseksi. Ohjelmat (servletit) ladataan niitä ensimmäistä kertaa kutsuttaessa, ja ne pysyvät ladattuna ajoympäristöön ja ovat siten nopeasti käynnistettävissä, kun uusia palvelupyyntöjä tulee. Jatkuvakäyntinen ajoympäristö mahdollistaa myös tietojen säilyttämisen keskusmuistissa, ja siten nopeamman tiedonsiirron eri palvelinohjelmien välillä. Jatkuvakäyntisen ajoympäristön idea esiteltiin Javan yhteydessä, mutta on myöhemmin levinnyt siten, että nykyään www-palvelinohjelmistot saattavat pitää sisällään valmiiksi ladattuna esim. Perl- tai PHP-tulkkeja ja ladattua ohjelmakoodia mahdollistaen näin nopean käynnistyksen ja tietojen säilytyksen keskusmuistissa.
Servletit ovat Java-pohjainen perustekniikka web-sovellusten toteutukseen. Esimerkiksi JSP-tekniikka hyödyntää servlettejä siten, että JSP-sivut käännetään servleteiksi suoritusta varten. JSP-sivussa HTML:n ja ohjelmakoodin asema on käännetty päinvastaiseksi kuin normaaleissa servleteissä. Servleteissä HTML-kielistä sivua rakennetaan ohjelmointikielen tulostuslauseilla. JSP-tekniikassa sivun staattinen HTML-osuus on valmiina vakiotietona ja sen lomaan on upotettu Javalla kirjoitettuja ohjelmaosuuksia sivun dynaamisten osien tuottamiseksi ja muiden sivuun liittyvien tietojenkäsittelytehtävien hoitamiseksi.
Servleteistä löytyy runsaasti informaatiota esimerkiksi sivulla
olevan linkkikokoelman kautta.Hyviä johdatusartikkeleita ovat esimerkiksi:
- Stefan Zeiger : Servlet Essentials
- Hans Bergsten: An Introduction to Java Servlets
- Mark Andrews: STORY OF A SERVLET: AN INSTANT TUTORIAL
- MageLang Institute: Fundamentals of JavaTM Servlets Preface
- Dynamic Information Systems Corporation: Developing JDBC Servlet Applications
Servletti muodostuu yhdestä pääluokasta ja mahdollisista apuluokista. HTML-sivulta servlettiä kutsutaan oletusarvoisesti pääluokan nimellä (servletille voi konfigurointitiedostossa antaa jonkin muunkin nimen).
Servlettien ajoympäristönä toimii www-palvelinohjelmistoon kytketty servlettimoottori (servlet engine), joka pitää sisällään Java-ajoympäristön. Servletti ladataan ajoympäristöön, kun sitä ensimmäisen kerran kutsutaan. Latausta varten servlettiluokat, apuluokat, kirjastot ja sovelluksen www-sivut on asennettava tiettyihin standardin määrittelemiin hakemistoihin.
Servletit toimivat jatkuvakäyntisessä ajoympäristössä. Tällaisia ympäristöjä eli sovelluspalvelinalustoja on olemassa useilta. HY/TKTL:llä ajoympäristönä on tarjolla Apache Tomcat palvelin. Palvelimen voi pystyttää, ja servlettejä voi ajaa vain koneessa users.cs.helsinki.fi.
Tomcat -palvelimen pystytys
Tomcat ympäriston asennus edellyttää muutaman ympäristömuuttujan asetusta ja porttien määrittelyä. Servletit ja muut sovelluksen käyttämät resurssit on myös sijoitettava Tomcat-ympäristön edellyttämällä tavalla hakemistorakenteeseen. Tomcat-palvelun tilausskripti wanna-tomcat hoitaa tarvittavat asetukset. Skripti
Tomcat-palvelimen käynnistys ja sammutus
Tomcat-palvelin käynnistetään komennolla start-tomcat ja sammutetaan komennolla stop-tomcat. Ennen kuin näitä komentoja voi käyttää, on ympäristö pitänyt tilata komennolla wanna-tomcat. Ympäristö tilataan vain kerran. Käynnistetty tomcat-palvelin jää toistaiseksi käyntiin odottelemaan palvelupyyntöjä. Testatessasi sovellusta on turha jättää palvelinta käyntiin, jos et käytä sitä. Jos haluat varmistua siitä, että tekemäsi ohjelmamuutokset otetaan käyttöön on parasta sammuttaa tomcat palvelin ja käynnistää se uudelleen. Kun jätät harjoitustyön tarkastettavaksi, sovi ohjaajan kanssa kuinka pitkäksi aikaa palvelin jätetään käyntiin. Tilausskripti antaa lisäohjeita käynnistyksestä ja sammutuksesta.Sovelluksen asennus Tomcat-ympäristöön
Jotta Tomcat-palvelin löytäisi sovellukseen kuuluvan aineiston, pitää aineisto asentaa Tomcat:in vaatimusten mukaisesti asennushakemistoihin. Tilausskripti luo valmiiksi hakemistorakenteen mallin. Mallirakenteessa sovellushakemiston nimeksi on asetettu tsoha. Tämän voit vaihtaa.
Hakemistorakenne asennuksessa
Olkoon SOVELLUS sovelluksen päähakemisto. Polku sinne on muotoa <user>/tomcat/webapps/<sovelluksen_nimi>. Tilausskripti (wanna-tomcat) täsmentää polun alkuosan. Sovelluksen nimeät itse. Hakemiston mallirakenne on tehty 'tsoha'-nimiselle sovellukselle. Kun sovelluksen komponentit sijoitetaan alla kuvattavalla tavalla osaa Tomcat noutaa ne, käynnistää ohjelmat ja hakea kirjastoluokat:
- Hakemistoon SOVELLUS sijoitetaan sovelluksen staattiset HTML-sivut, tyylitiedostot, kuvat, JavaScript-tiedostot, sekä sovelluspolkujen määrittelyt, jne. Erityyppisille tiedostoille voi tehdä alihakemistoja. Esimerkiksi kuvat voisi sijoittaa hakemistoon SOVELLUS/kuvat/. Alihakemisto on tällöin otettava mukaan resurssipaikantimeen.
- Tiedosto SOVELLUS/WEB-INF/web.xml on Web-sovelluksen konfigurointitiedosto. Tässä tiedostossa voidaan esimerkiksi määritellä alustusparametreja ja nimetä servlettejä uudelleen. Tiedostossa on määriteltävä polku servlettien hakuun. Esimerkkinä testisovellusten web.xml. Jokaiselle servletille voi määritellä erikseen url:n rakenteen. Tässä web.xml:ssä kaikkien url:iin tulee lisäosa /servlet/ (tiedoston servlet-mapping osa)
- Hakemistoon SOVELLUS/WEB-INF/classes/ sijoitetaan servlettiluokat (*.class -tiedostot) ja sovelluksen apuluokat sekä hakemiston alihakemistoiksi käytettävät omat pakkaukset. Esimerkiksi oman pakkauksen 'box' luokat sijoitetaan hakemistoon
SOVELLUS/WEB-INF/classes/box/ .
- Hakemistoon SOVELLUS/WEB-INF/lib/ sijoitetaan ne sovelluksen käyttämät *.jar kirjastot, joita ei ole asennettu yleisesti tarjolle kaikille sovelluksille. Tänne suositellaan sijoitettavaksi esimerkiksi tietokanta-ajurin kirjasto. Yleisesti tarjolla olevia kirjastoja ovat servletti-API (servlet_api.jar) ja muut servlettiympäristön mukana tulevat kirjastot. Nämä on asennettu hakemistoihin $CATALINA_HOME/lib/ ja ne ovat siis automaattisesti mukana suoritusaikaisessa java-ympäristössä. Ne eivät kuitenkaan ole välttämättä mukana käännösaikaisessa ympäristössä.
Lähdekoodia suositellaan pidettäväksi erillään suoritusympäristöstä. Lähdekoodin sijainnilla ei ole kuitenkaan servlettien toiminnan kannalta merkitystä. Suoritusta varten on käännetyt *.class tiedostot kopioitava asennushakemistoon.
Käännettäessä Java-ohjelmaa täytyy kaikkien ohjelman käyttämien luokkien ja kirjastojen löytyä CLASSPATH:sta. Tietokanta-ajurin ei tarvitse olla CLASSPATH:ssa.
Käännösten hallintaan voi käyttää ant ohjelmistoa. Kurssisivulta löytyy esimerkki ant:n ohjaustiedostosta;. Ant-määrittelytiedosto (build.xml) kytkee mukaan kaikki käännöksessä tarvittavat tiedostot ja ohjaa käännöksen tuloksen paikkaan paikkaan.
Tomcat-ympäristössä toimivaan servlettiin viitataan, edellä kuvatun web.xml:n asetusten mukaan url:lla
http://t-<user>.users.cs.helsinki.fi/<sovellus_nimi>/servlet/<Servletin_nimi>
Hakemistoon SOVELLUS sijoitettuihin staattisiin sivuihin viitataan url.lla, joka on muotoa
http://t-<user>.users.cs.helsinki.fi/<sovellus_nimi>/<sivu_nimi.html>Esimerkiksi http://t-laine.users.cs.helsinki.fi/tsoha/servlet/PgTesti käynnistää käyttäjän laine sovellukseen tsoha kuuluvan esimerkkiservletin PgTesti.
Servletti muodostuu yhdestä Java-luokasta, joka perii kirjastoluokan HttpServlet. Luokka voi käyttää hyväkseen muita Java-luokkia. Varsinainen servletin tarjoama palvelu toteutetaan servlettiluokan palvelumetodeissa doPost tai doGet. Kumpi metodeista käynnistetään palvelupyyntöä hoitamaan riippuu kutsusta. Jos kutsu annetaan HTML-linkin kautta, tai html-lomakkeen method-attribuutin arvona on 'get', käynnistetään doGet-palvelu ja muutoin doPost -palvelu. Useissa tapauksissa kumpikin näistä kannattaa toteuttaa samansisältöisenä, eli varsinainen koodi kirjoitetaan vain toiseen näistä ja toinen määritellään kutsumaan sitä. Palvelu voidaan tällöin käynnistää sekä linkistä että lomakekäsittelijänä. Tilanteissa, joissa halutaan varmistua siitä, että kutsuparametrit eivät kulje resurssipaikantimen osana ja esimerkiksi näy selaimen osoite-kentässä, on syytä käyttää vain post-tekniikan lomakekäsittelijää.
Saadessaan servlettiin kohdistuvan palvelupyynnön servlettiympäristö käynnistää palvelumetodin erillisenä rinnakkaisena säikeenä, ts. samaa palvelumetodia voi samanaikaisesti olla käynnissä useita rinnakkain. Palvelumetodit pitää koodata säieturvallisiksi, eli niitä on kyettävä ajamaan rinnakkain ilman, että ne sotkevat toistensa tietoja. Yksinkertaisimmin tämän saa aikaan käyttämällä metodeissa vain metodin sisäisiä paikallisia muuttujia ja olioita sekä hoitamalla tarvittava tilatiedon välitys eri servlettien välillä www-sivujen ja selaimen kautta, esimerkiksi lomakkeen piilokenttinä tai evästeinä (cookie). Olio- ja luokkamuuttujiin kohdistuvat sijoitusoperaatiot palvelumetodeissa pitää suojata samanaikaiselta käytöllä tai välttää niitä kokonaan, mikäli se on mahdollista.
Servletin elinkaari muodostuu alustuksesta, palveluvaiheesta ja poistosta.
- Servletin alustus tapahtuu kun servlettiluokan koodi ladataan muistiin. Tämä tapahtuu joko ajoympäristöä käynnistettäessä tai kun servlettiluokan palvelua pyydetään ensimmäistä kertaa. Tällöin servlettiluokasta luodaan yksi olio ja suoritetaan sen init-metodi. Oman servlettiluokan init-metodiin voi lisätä esimerkiksi luokkamuuttujien asetuksia, tietokanta-ajurin latauksen yms. alustustoimintoja.
- Servletin palveluvaihe sisältää metodien doGet ja doPost suorituksia. Nämä hoitavat palvelunsa säikeinä (eräänlainen kevytprosessi). Kukin kutsu hoidetaan erillisenä säikeenä. Metodin sisäinen tila häviää metodin suorituksen päättyessä.
- Servletin poistossa servlettiluokka poistetaan ajoympäristöstä. Mahdollisesti tarpeellisia siivoustoimia varten suoritetaan destroy-metodi ennen poistoa.
Servlettien rajapinta on määritelty javax.servlet- ja javax.servlet.http-pakkauksissa, jotka on otettava mukaan ohjelmaan import:lla. Nämä rajapinnat sisältyvät J2EE-ympäristöön. Rajapintojen lisäksi tarvitaan ne toteuttava pakkaus. Tomcat-ympäristöön sisältyvä servlet.jar soveltuu tähän (= /usr/share/tomcat6/lib/servlet-api.jar). Tämä pakkaus pitää olla mukana käännösaikaisessa CLASSPATH:ssa).Kaikkien servlettiluokkien (servletin pääluokkien) tulee periä luokka HttpServlet. Tätä kautta periytyvät palvelumetodien doGet, doPost ja service hahmot, esimerkiksi
public void doPost( HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { }Kaikilla palvelumetodeista on kaksi parametria HttpServletRequest ja HttpServletResponse oliot. HttpServletRequest luokan olio tarjoaa palvelut servletin kutsun yhteydessä annettujen parametriarvojen käsittelyyn. HttpServletResponse-olio hoitaa servletin tulokset takaisin www-selaimelle.
Palvelumetodin alkutoimiin kuuluvat
- istunnon tilan palautus
- tulosten käsittelyyn liittyvät alkutoimet
- palvelupyynnön yhteydessä annettujen parametrien purkaminen ja
- mahdollisesti tietokantayhteyden muodostus
Istunnon tilan palaus
Servleteissä voi käyttää HttpSession -olioita säilyttämään istunnon tilaa. HttpSession olioon voidaan tallentaa nimettyjen attribuuttien arvoksi mitä tahansa olioita. HttpRequest olion getSession-metodilla joko luodaan uusi HttpSession olio tai haetaan samalle asiakkaalle aiemmin luotu olio palvelimen muistista. Servlettiympäristö hoitaa istunto-olion tunnuksen lähettämisen evästeenä selaimelle ja tunnuksen poimimisen evästeestä getSession hakua varten. HttpSession oliolla on metodit setAttribute ja getAttribute olioiden tallennusta ja hakua varten. Istunnon tilan palautus täytyy tehdä heti palveumetodin alussa, ennen kuin kirjoitetaan mitään tuloksia.
Tulosten käsittelyyn liittyvät alkutoimet
Tulosten käsittelyyn liittyvät alkutoimet suoritetaan ennen kuin tulostetaan mitään. Tähän liittyvät:Seuraavassa yksi tapa tehdä nämä alustukset:
- tuloksen tyypin määrittely ja
- kirjoitinolion kytkentä tulostusta hoitamaan.
resp.setContentType("text/html"); PrintWriter pw = res.getWriter();Näiden lisäksi voidaan alkuvaiheessa tulostaa www-sivun otsaketiedot. Jos usea servletti tuottaa samat otsaketiedot kannattaa määritellä joko luokkahierarkiassa HttpServletin ja varsinaisen servlettiluokan väliin tai luokkahierarkiasta erilleen apuluokka hoitamaan otsakkeet.
Palvelupyynnön yhteydessä annettujen parametrien purkaminen
Servletin kutsuparametrit annetaan HTML:ssä samalla tavoin kuin CGI-ohjelmia kutsuttaessa. Ohjelma saa parametrien arvot käyttöönsä palvelumetodin HttpRequest-tyyppisen parametrin kautta. Parametrin arvoa pyydetään HttpRequest-oliolta parametrin nimelläString arvo = req.getParameter("parametrin_nimi");Pyyntö tuottaa merkkijonomuotoisen arvon, joka tarvittaessa on erikseen munnettava vaikkapa kokonaisluvuksi. Ellei annetun nimistä parametria löydy on tuloksena arvo null. Moniarvoisten parametrien käsittelyyn on oma metodi getParameterValues, joka tuottaa tuloksenaan merkkijonotaulukon.Tietokantayhteyden avaus
Jos kyseessä on tietokantaa käyttävä servletti, liittyy alkutoimiin myös tietokantayhteyden muodostus. Jos käytössä on yhteysallas, jonne varastoidaan auki pidettäviä tietokantayhteyksiä, voidaan yhteys luoda varaamalla yhteys yhteysaltaasta. . Harjoitustöissä, kun suoritusnopeus ei ole tärkeää, voidaan jokaisen palvelumetodin alussa perustaa uusi yhteys. Auki jätettävien yhtyksien käyttö ei ole suotavaa myöskään harjoitustöiden suuren määrän vuoksi.
Yhteyden luomiseksi on ladattava tietokantakohtainen ajuri ja otettava se käyttöön sekä luotava sen avulla yhteys. Tietokantayhteyksien käsittelyn voi hoitaa esimerkiksi määrittelemällä luokan HttpServlet ja omien servlettiluokkien väliin tietokantayhteyksen hoidosta huolehtivan luokan. Tässä on esimerkkinä luokka DatabaseServlet, jota voi vapaasti käyttää tähän tarkoitukseen. Luokka edellyttää, että tietokantayhteyden määrittelytiedot annetaan erillisessä konfigurointitiedostossa (esimerkki sisällöstä). Konfigurointitiedoston osoite on määriteltävä web.xml tiedostossa (tarvittavat määrittelylauseet, jos tiedosto on SOVELLUS/WEB-INF/ hakemistossa).
Tietokantayhteydet tarvitsevat tietokanta-ajurin. Bodbackan Oracle kannan yhteydessä tarvittava ajuri löytyy osoitteesta /opt/oracle/jdbc/lib/ojdbc14.jar . Ajurin voi kopioida hakemistoon SOVELLUS/WEB-INF/lib/ , jolloin se liitetään automaattisesti Tomcatin suoritusaikaiseen CLASSPATH:iin. Ajurin ei tarvitse olla käännösaikaisessa CLASSPATH:ssa.
HUOM: komento setup oracle liittää yllä mainitun ajurin CLASSPATH:iin. Jos tämä komento on annettu ennen start-tomcat kutsua, kopioituu ajuripolku myös Tomcatin suoritusaikaiseen CLASSPATH:iin. Mikäli tomcat käynnistetään näin, ei ajuria saa kopioida SOVELLUS/WEB-INF/lib/ -hakemistoon. Jos siis kopioit ajurin, ei CLASSPATH:ssa saa tomcatia käynnistettäessä olla polkua toisesta paikasta löytyvään, vaikkakin täysin samaan, ajuriin (ohjelma kaatuu - Oracle internal error)
Yhteys koneella users.cs.helsinki.fi olevaan kayttäjän username postgeSQL-kantaan saadaan kutsulla:
conn = createConnection("org.postgresql.Driver", "jdbc:postgresql://localhost/username", "username","password");Tietokanta-ajuri PostgreSQL:lle on tiedostossa /usr/local/pgsql-7.4/jdbc.jar. Tämä pitäisi olla kopioituna SOVELLUS/WEB-INF/lib/ -hakemistoon.
Rungon toiminnallisuus riippuu siitä, mitä palvelinmetodin on tarkoitus tehdä. Yleensä palvelinmetodi ainakin tuottaa html-muotoisen tulossivun. Sivulle kirjoitetaan tekstiä käyttämällä alustustoimien yhteydessä HttpServletResponse-olioon kytkettyä kirjoitinoliota (yllä PrintWriter pw). Eräs suunnittelumalli servleteille on sellainen, että sama servletti, joka tuottaa tyhjän lomakkeen, myös hoitaa täytetyn lomakkeen käsittelyn.Tietokantaoperaatiot
Tietokantaoperaatioita varten luodaan tyypillisesti Statement- tai PreparedStatement-olio. CallableStatement oliota voi käyttää tietokantaproseduurien ja funktioiden käynnistykseen. PreparedStetement ja CallableStatement perivät Statement luokan. Jatkossa kaikkien näiden luokkien olioita kutsutaan Statement-olioiksi. Tietokantaoperaatioihin liittyvät Statement-oliot luodaan tietokantayhteysolion (Connection -olio) avulla. Ne on aina suljettava ohjelman lopuksi. Lähes kaikki tietokantaoperaatiot voivat aiheuttaa SQLException poikkeuksen, joka on pyydystettävä ohjelmassa. Tietokantaoperaatioita ei ole välttämätöntä upottaa suoraan palvelumetodien koodiin. Usein saattaa olla kätevämpää käyttää jotain apuluokkaa. Seuraavassa esimerkki tietokantasilmukasta:Statement stm=null; ResultSet rs=null; String kysely= "select nimi from opiskelija where hetu like \'0101%\' order by nimi"; // tammikuun 1. päivänä syntyneet try { stm= conn.createStatement(); rs= stm.executeQuery(kysely); while (rs.next()) { pw.println(rs.getString("nimi")+"<br>"); } } catch (SQLException e) { databaseError(e.getErrorCode(), e.getMessage(), pw); // databaseError on tässä itse tehty virheilmoitusmetodi } finally { // finally takaa, että tietokantaoperaatioihin liittyvä // close suoritetaan myös virhetilanteessa try { if (rs!=null) rs.close(); if (stm!=null) stm.close(); } catch (SQLException ee) {} // kursorin sulkemisen ei }
Palvelumetodin lopuksi on vapautettava tietokantaresurssit ja päätettävä HTML-sivu. Tietokantaresurssien vapauttamiseen sisältyy kaikkien ResultSet ja Statement (ja sen aliluokkien) olioiden close-metodien kutsuminen. JDBC-manuaalissa todetaan, 'ettei tämä ole yleensä välttämätöntä vaan java-roskienkerääjä hoitaa asian joskin jotkut tietokantapalvelimet vaativat, että resurssit vapautetaan eksplisiittisesti close metodeja kutsumalla'. Ainakin Oraclen kohdalla tilanne on tällainen ja close täytyy suorittaa jokaisessa ohjelman päättymishaarassa. Jos se jätetään suorittamatta resursseja jää palvelinpäässä auki ja koko palvelin saattaa hyytyä. Myös tietokantayhteys pitää sulkea lopuksi, esim erillisellä metodilla
Sovellus on toteutettu Tomcat-ympäristöön ja sen tiedostot on sijoitettu koneelle users.cs.helsinki.fi seuraavasti:
/home/laine/tomcat/webapps | |
/home/laine/tomcat/webapps/tsoha | |
esim.html | |
|
|
testing.gif -käytetään vain servletissä | |
/home/laine/tomcat/webapps/tsoha/WEB-INF | |
web.xml (huom selain saattaa näyttää tämän muunnettuna, tallenna ja tutki) | |
/home/laine/tomcat/webapps/tsoha/WEB-INF/classes | |
ServletTest.class SessionTest.class PgTesti.class |
|
/home/laine/tomcat/webapps/tsoha/WEB-INF/lib | |
ojdbc14..jar - Oracle JDBC-ajuri postgresql.jar -postgres ajuri |
|
/home/laine/tomcat/webapps/tsoha/src | |
SessionTest.java (ServletTest, jossa on yksinkertainen sessioiden toimintatesti doPost metodin alussa - tästä näkee miten sessioita luodaan ja kysytään, mutta muuten niitä ei käytetä mihinkään.) PgTesti.java (Postgres kannan testi ) Kaikissa ylläolevissa käyttäjätunnukset ja salasanat ovat toimimattomia. |