Servlettien käyttö Tietokantasovellusten harjoitustyössä

Harri Laine (28.5.2010)

Servletit

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:

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 HY/TKTL:lla

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:

Servlettien lähdekoodi

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.

Servlettien kääntäminen

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.

Viittaaminen servlettiin Tomcat-ympäristössä

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.

Servlettien olemus ja elinkaari

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.

Servlet API

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.

Palvelun toteutus

Alkutoimet palvelumetodeissa

Palvelumetodin alkutoimiin kuuluvat

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:
   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.

Toiminnan runko

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 
   }

Lopputoimet

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

Esimerkkejä