Java Servletek – II. rész

4. HTTP protokoll alapú szervletek

A HTTP protokollal elérhető Java szervleteket a javax.servlet.http.HttpServlet osztályból érdemes származtatni.

A javax.servlet.httpHttpServlet osztály a javax.servlet.GenericServlet osztály leszármazottja, de a service() metódusa alkalmas a HTTP protokoll elemeinek kezelésére. A szülőosztálytól örökölt, de felüldefiniált service() metódus úgy működik, hogy beolvassa a kliens HTTP-kérésének a HTTP-művelet azonosítóját tartalmazó sorát, és meghívja az illető HTTP-művelet feldolgozásáért felelős metódust, vagyis a HTTP klienskéréseket azok HTTP típusától függően a következő metódusokhoz továbbítja:

  • doDelete  - DELETE típusú HTTP kérések kezelésekor kerül meghívásra. Ilyen típusú kérésekkel dokumentumok törlését lehet kérni a webszerveren.
  • doGetGET típusú HTTP kérések kezelésekor kerül erre a vezérlés. Ilyen típusú kérésekkel a kért dokumentumnak paramétereket lehet megadni, melyek az URL végén levő paramétersztring formájában kerülnek átadásra.
  • doOptionsOPTIONS típusú HTTP kérések kezelésekor automatikusan meghívásra kerül. Alapértelmezés szerint a szervlet által támogatott HTTP kéréstípusokat adja vissza.
  • doPost - POST típusú HTTP kérések kezelésekor kerül meghívásra. Ilyen típusú kérésekkel a kért dokumentumnak paramétereket lehet megadni, melyek nem látható módon a HTTP fejlécben kerülnek átadásra.
  • doPut - PUT típusú HTTP kérések kezeléséhez tartozó automatikus meghívásra kerülő metódus. Ilyen típusú kérésekkel dokumentumok feltöltését lehet kérni a webszerverre.
  • doTrace - TRACE típusú HTTP kérések kezelésekor kerül  a vezérlés. Alapértelmezés szerint a kérés fejlécében található adatokat adja vissza.

 A HTTP protokoll alapú szervlet alkalmazások készítőinek az egyes HTTP protokoll műveletekhez tartozó szolgáltatásokat megvalósító metódusokat kell felüldefiniálniuk a leszármazott osztályokban, magát a service() metódust nem. Ez rendszerint a doGet és doPost metódusok implementálását jelenti, ráadásul úgy, hogy az egyik metódus tartalmazza a tényleges megvalósítást, míg a másik erre a metódusra irányítja át a kérés feldolgozását. A feldolgozás menete általában a kapott paraméterek kiolvasását, a válasz HTTP fejlécének beállítását, a válasz legenerálását, annak kiküldését a kliens adatfolyamra, majd az adatfolyam lezárásakor annak elküldését jelenti.

A javax.servlet.http csomagban találhatunk egy HttpServletRequest, valamint egy HttpServletResponse nevű interfészt is. A szerver ezen interfészeket implementáló osztályok megfelelő metódusai segítségével érheti el a kliensével felépített összeköttetésen érkező adatokat, illetve küldhet vissza adatokat a kliensnek. Ezek az interfészek a korábban már megismert ServletRequest, illetve ServletResponse interfészekhez képest annyi újat nyújtanak, hogy lehetővé teszik a HTTP-kérésekből a HTTP-fejlécek kinyerését és a HTTP-válaszokba a fejlécek elhelyezését.

A javax.servlet.http.HttpServlet osztály szerkezetét a következő programlista tartalmazza:

public abstract class HttpServlet extends GenericServlet{

      protected HttpServlet();

      protected void doGet (HttpServletRequest req, HttpServletResponse res)

                        throws ServletException, IOException;

      protected long getLastModified(HttpServletRequest req);

      protected void doPost (HttpServletRequest req, HttpServletResponse res)

                        throws ServletException, IOException;

      protected void service (HttpServletRequest req, HttpServletResponse res)

                        throws ServletException, IOException;

}

a.) GET HTTP műveletek

Ha a GET HTTP-műveleteket kezelni akarjuk, akkor felül kell definiálni a leszármazott osztály doGet() metódusát. A doGet() metódust definiálva egy szervlet képes lesz a HTTP protokoll GET, HEAD, valamint a feltételes GET műveletek kiszolgálására a HttpServlet ősosztályban található, ezt támogató programlogika segítségével. A doGet() metódus első paraméterén keresztül a klienstől érkező kérésben levő információkhoz férhetünk hozzá, a válaszadatokat pedig a második paraméterben kapott objektum megfelelő metódusaival juttathatjuk vissza a klienshez. A getLastModified() metódus egyetlen paraméterében a klienstől érkező kérésben kapott információkhoz férhetünk hozzá.

b.) POST HTTP műveletek

Ha  a POST HTTP-műveletet kezelni akarjuk, akkor a doPost() metódust kell felüldefiniálnunk. A doPost() metódus első paraméterén keresztül a klienstől érkező kérésben levő információkhoz férhetünk hozzá, válaszadatokat pedig a második paraméterben kapott objektum megfelelő metódusaival juttathatunk vissza. A POST művelet feldolgozása során lehetőleg először olvassuk be a klienstől érkező adatokat, és csak ezt követően állítsuk össze a válaszban visszaküldeni kívánt információkat.

A fentieken kívül szükség esetén felüldefiniálhatjuk az init(), a destroy(), vagy pedig a getServletInfo() metódusokat, ezeket a szervlet a szülőosztálytól örökli.

Ha szervletünkben a fenti műveleteken kívül más HTTP-műveleteket is támogatni szeretnénk, akkor a származtatott osztályban definiáljuk felül a service() metódust, és írjuk meg az új HTTP-műveletek támogatását. A fent említett HTTP-műveletek kiszolgálására elég meghívni a szülőosztály service() metódusát a super.service() kifejezést tartalmazó utasítással.

A klienstől HTTP protokollon keresztül érkező kérések absztrakciójára vezették be a javax.servlet.http.HttpServletRequest interfészt, míg a kliensnek visszaküldendő válasz absztrakciójára a javax.servlet.http.ServletResponse interfészt használható.

Szervletek szinkronizációja

Mivel a szervletek közönséges Java objektumpéldányok, és egy szervlet egyszerre akár több kliens kiszolgálását is végezheti, ezért a szervletek funkcionalitását implementáló osztályokat fel kell szerelni a megoldott feladat által megkövetelt szinkronizációs eszközökkel. Az alkalmazott szinkronizációs módszer kiválasztásánál meg kell gondolni, hogy a szervlet milyen konfigurációban fog működni, illetve milyen konfigurációban működhet, azaz például minden kliens-kérést önálló szervlet példánynak kell kezelnie, vagy egy szervlet példány több klienstől érkező kérésekkel is foglalkozhat.

Legegyszerűbb esetben a szinkronizációs mechanizmust definiálhatjuk úgy, hogy egy párhuzamos környezetben futó szervlet objektumnak nem lehetnek példányváltozói, illetve csak synchronized módosítóval ellátott állapotmódosító metódusai lehetnek. Ennél bonyolultabb esetben, ha egy szervlet implementációját tartalmazó osztály példányváltozókat (adattagokat), vagy osztályváltozókat tartalmaz, akkor gondoskodni kell egyrészt arról, hogy az illető változók módosítása megfelelően szinkronizálva legyen, másrészt arról is érdemes gondoskodni, hogy más osztályok a szükséges szinkronizációs mechanizmusok megkerülésével ne férjenek hozzá ezekhez az adattagokhoz. Ennek a szinkronizációnak a megvalósítására kézenfekvő eszközként használható például az illető adattagokhoz tartozó objektumzár kihasználása azzal, hogy a módosító műveleteket egy, az adattag köré felépített szinkronizációs utasítással védjük. Ha a klienskérések szinkronizációját a szervlet futtatókörnyezetre akarjuk bízni, akkor implementálnunk kell a SingleThreadModel interfészt. Ezen interfész nem tartalmaz egyetlen metódust sem, de ha implementáljuk, akkor a szervlet futtatókörnyezet biztosítja, hogy a kiszolgáló service metódust egy időben legfeljebb csak egy programszál fogja meghívni. Ez a legegyszerűbb módja a bejövő kliens kérések sorbaállításának.

Kérés átirányítása

HTTP protokoll használata esetén lehetőségünk van a klienskérést egy másik URL-hez átirányítani. Ha HttpServletResponse sendRedirect metódusával egy abszolút URL-t adunk meg, azzal ugyanazt a hatást érhetjük el, mintha a kliens nem is a szervletünktől kért volna választ, hanem egyből az általunk megadott URL-t tekintette volna meg. Természetesen a kérés átirányítása után már nem küldhetünk adatot a kliens felé.

Hiba jelzése

HTTP protokoll használata esetén lehetőségünk van előredefiniált HTTP státusz- és hibakódokat küldeni a kliensnek. A HttpServletResponse sendError metódusaival egy HTTP hibakódot adhatunk meg, ezen hibakódhoz tartozó hibaüzenetet fogja látni a kliens. Ilyenkor a metódus meghívása után már nem küldhetünk adatot a kliens felé. 

Ha csak a HTTP státuszt akarjuk beállítani, akkor használjuk a HttpServletResponse sendStatus metódusát.

HTTP fejléc direkt kezelése

Ha a HTTP fejléc mezőit valamilyen okból mi magunk szeretnénk kezelni, akkor a kliens-kérés HTTP fejléc mezőihez a HttpServletRequest interfész getHeader, getHeaderNames, getDateHeader és getIntHeader metódusaival férhetünk hozzá, míg a válasz fejlécmezőit a HttpServletResponse setHeader, setDateHeader és setIntHeader metódusaival állíthatjuk be. Megjegyzendő, hogy a válasz fejléceinek mezőit csak azelőtt szabad módosítani, mielőtt adatokat küldenénk a kliens felé.

Egy HTTP szervlet anatómiája

A javax.servlet egy környezetet biztosít szerverek létrehozására. A kérések feldolgozására és a kliensek kiszolgálására szolgáltat API-kat, melyeket kezelési struktúrákhoz, munkaszétosztó szálakhoz és egyéb közös szerverfeladatokba lehet beépíteni. (Java felsőfokon).  A javax.servlet.http csomag azon szervlet implementálását tartalmazza, amelyeket a webszervereknél használunk HTML oldalak generálására. Ahhoz, hogy egy HTTP szervlet el tudjon végezni egy bizonyos feladatot, újra kell definiálni a doGet() metódusát. A metódus annyiszor kerül meghívásra a webszerver által ahányszor a szerver egy HTTP kérést kap, amelyik egy GET <url>-t tartalmaz. A doGet() metódus a HttpServlet osztályban található a doDelete(), doOptions(), doPost(), doTrace() metódusok mellett. Ezek az osztályok két paraméterrel rendelkeznek: ServletRequest és ServletResponse, amelyek metódusokat és változókat tartalmaznak, amelyek biztosítják a szervlet kommunikációját a webszerverrel és ServletException, valamint IOException kivételeket váltanak ki.

A következő doGet metódus egy tipikus metódusfejlécet mutat:

public  void doGet (HttpServletRequest req, HttpServletResponse res)

            throws ServletException, IOException{...}

A ServletRequest osztály tartalmazza az összes információt ahhoz, hogy a szervlet megértse a kérést és tudjon válaszolni rá, információkat tartalmaz arról a számítógépről, amelyiknek továbbítania kell a választ.

A ServletResponse osztály olyan metódusokat tartalmaz, amelyek segítségével megvalósítható a válasz megfogalmazása és elküldése. Így a setContentType(String) segítségével a visszaküldendő adatok típusát adhatjuk meg. A lehetséges típusok közül megemlítem a text/html, illetve a text/plain-t. Egy másik fontos metódus a getWriter(). Ez egy PrintWriter típusú referenciát ad vissza, amely egy kimenő fluxus, amelyen el lehet küldeni a választ. Ezt használják text típusú válaszok, a getOutStream() metódust pedig bináris adatok esetén.

Információ a szervletről

A Servlet interfész getServletInfo metódusának felüldefiniálásával a felhasználók számára információt lehet adni magáról a szervletről, annak szerzőjéről és verziójáról. Ezen metódus eredményét webszerver-adminisztrációs programok használják az elérhető szervletek leírására.

5. Információk megőrzése

Információ megőrzése több klienskapcsolat alatt

Egy szervlet csak az alatt az idő alatt van kapcsolatban a klienssel, amíg kiszolgálja annak kérését és vissza nem adja a generált válaszoldalt. Előfordulhat azonban, hogy adott klienstől jövő kérések egymástól nem függetlenek, azaz információt szeretnénk átadni a klienskiszolgálások között. Például, ha egy szervlet csak jelszó megadása esetén végezhet el egy adott feladatot, a jelszót csak akkor kellene bekérni és feldolgozni, amikor a kliens először veszi igénybe a szervlet szolgáltatásait. Természetesen arra is ügyelni kell, hogy míg egy szervletből rendszerint csak egy példány szolgál ki minden kérést, addig a kliensek több példányban léteznek, teljesen különböző gépeken, azaz a szervletnek valahogy tudnia kell különbséget tenni a különböző kliensekhez tartozó éppen aktuális paraméterek között. A szervlet futtatókörnyezet ezen probléma megoldására két megoldást is ad:

·                     cookie-k használata

·                     klienskapcsolat követése a szerveren

a.) Cookie-k használata

A legegyszerűbb megoldás, ha a klienshez tartozó adatokat maga a kliens tárolja, és azokat minden kérésekor elküldi a szervletnek. Ehhez tehát az szükséges, hogy a szervlet információt tudjon tárolni a kliens gépen. Erre valók az úgynevezett cookie-k. Ezek tulajdonképpen szöveges információk, amelyeket a kliens böngészőprogram tárol el, majd a szervernek elküldi azok tartalmát a klienskéréssel együtt. Egy cookie tehát mindig a kliensoldalon tárolódik, egy adott webszerverhez tartozik és a kliens küldi őket automatikusan a webszervernek. A cookie tartalmára annak nevével lehet hivatkozni. Egy cookie-t a Cookie osztály képvisel. A klienstől kapott cookie-kat a HttpServletRequest getCookies metódusával lehet lekérdezni, amely azokat egy tömbben adja vissza. Megjegyzendő, hogy egy cookie-t újra el kell küldeni a kliensnek, ha annak valamely jellemzőjét megváltoztattuk.

b.) Klienskapcsolat követése szerveren

Egy másik megoldás, ha nem a kliens, hanem a szerver tárolja magát az információt. Ekkor minden klienskapcsolat, amely ugyanazt a klienskapcsolat környezetet használja, ugyanazon adatokat is fogja látni. Ezen klienkapcsolat környezetet tehát a szerver tartja nyilván, majd eljuttatja annak azonosítóját a klienshez, ezután a kliens a kapott azonosítóra hivatkozva lehetővé teszi, hogy a szervlet adatokat rendeljen a klienshez, melyek annak többszöri futása alatt is elérhetők lesznek.

Egy klienskapcsolat környezetet a HttpSession osztály reprezentál, amelynek a következő főbb jellemzői vannak:

  • Azonosító – a klienskapcsolat környezet szöveges azonosítója. Lekérdezni a getId metódussal lehet, beállítása pedig annak létrehozásakor automatikusan történik.
  • Létrehozás időpontja - a klienskapcsolat környezet létrehozásának időpontja. Lekérdezni a getCreationTime metódussal lehet, beállítása pedig annak létrehozásakor automatikusan történik.
  • Legutolsó hozzáférés időpontja - a klienskapcsolat környezet használatának legutolsó időpontja. Lekérdezni a getLastAccessedTime metódussal történhet, beállítása pedig annak létrehozásakor automatikusan történik.
  • Élettartam – a klienskapcsolat környezet érvényességi időtartama másodpercekben. Ha ezen időtartam alatt nem használja senki sem az adott környezet adatait, akkor a szerver megszünteti a környezetet.  Lekérdezni a getMaxInactiveInterval, beállítani pedig a setMaxInactiveInterval metódussal lehet.

Az aktuális klienskapcsolat környezetet a HttpServletRequest getSession metódusával lehet lekérdezni. Ez a metódus, ha még nem létezik az aktuális klienskapcsolathoz környezet objektum, akkor kérés esetén automatikusan létrehoz egy új objektumot. Ha már ismert a környezet azonosító a szerveroldal számára, akkor az azt reprezentáló objektumot fogjuk visszakapni. Egy klienskapcsolat környezetről annak isNew metódusával lehet megállapítani, hogy újonnan jött-e létre. A metódus egy hasznos felhasználási területe lehet annak ellenőrzése, hogy ismert-e már a kliens a szervlet számára. Ha nem, akkor például átirányíthatjuk a klienskérést egy üdvözlő, vagy éppen bejelentkezési adatokat kérő oldalhoz.  Miután megszereztük a HttpSession referenciát, objektumokat tárolhatunk benne a putValue metódussal, elérhetjük az abba már korábban berakott objektumokat a getValue metódussal, illetve törölhetjük azokat a removeValue meghívásával. A tárolandó objektumokra egy név alapján lehet hivatkozni. A kapcsolat környezetben tárolt összes objektum  nevét pedig a getValueNames metódussal lehet lekérdezni. A session objektum tartalmazhat bármilyen adatot, melyre alkalmazásunknak szüksége van. Amint az előbb bemutattam, az objektum kényelmes metódusaival adatot lehet az objektumból kivenni, illetve új adatot lehet benne tárolni. Ez egy nagyon egyszerű és természetes módot ad HTTP kérések közti állapot adminisztrálására. A session objektum és a benne lévő adatok a hely minden szervletje rendelkezésére állnak, ezenkívül lehetőséget ad arra is, hogy a különböző szervletek ezt az  állapotot közösen használhassák.

Ha a klienskapcsolat környezetben tárolt objektum szeretne értesülni arról, hogy mikor kerül be a környezet adatai közé, illetve mikor kerül ki onnan, akkor implementálnia kell a HttpSessionBindingListener interfészt. Ekkor az adott objektum értesítést kap egy HttpSessionBindingEvent formájában, ha a putValue, vagy a removeValue metódus paramétereként tárolásra, illetve törlésre került. 

Ha már nincs többé szükség egy klienskapcsolat környezetre, akkor mi magunk megszüntethetjük azt annak invalidate metódusával. A kapcsolat környezet automatikusan megszűnik, ha annak érvényességi időtartama alatt nem történik hivatkozás rá.

Bár érvényteleníthetünk egy ülésobjektumot egy tranzakció végén, ezt a lehetőséget nagy körültekintéssel kell használnunk. Az ülés érvénytelenítése nemcsak azt az adatot törli, amit a szervletek az ülés alatt eltároltak, hanem magát az ülést is teljes egészében. Ha a webhelyünkön vannak más szervletcsoportok is, melyek szintén használhatták az ülésobjektumot, lehetséges, hogy más rendszernek nagy problémát okozunk. A gyakorlatban legjobb, ha hagyjuk a Webszervert, hogy ő ügyeljen az ülések érvénytelenítésére. Ez alól a szabály alól van egy gyakorlati kivétel. Amikor ülést nyomon követő szervleteket tesztelünk, gyakran sok hulladékkal, használaton kívüli adattal találkozunk az ülésobjektumban. Ahelyett, hogy új felhasználóként bejelentkezünk, vagy leállítjuk és újraindítjuk a webszervert, vagy kerülünk egyet, amíg az ülés timeout-ja lejár, egyszerűbb csak az ülést érvényteleníteni és folytatni a munkát. Ebben az esetben kényelmes megoldás, ha van egy sevletünk, amely nem tesz mást, csak megöl minden olyan ülést, amelyikben megtalálja saját magát. Az invalidateSessionServlet egészen jól teljesíti ezt a funkciót.

Mint már említettem, a klienskapcsolat környezetre annak azonosítójával hivatkozik a kliens. Ezt az azonosítót tehát a szervernek valahogy el kell juttatnia a klienshez, majd a kliensnek minden kérésével együtt el kell küldenie azt. Mivel a cookie-k pontosan erre használhatók, ezért alapértelmezés szerint a környezet azonosítója cookie-k segítségével kerül automatikus átvitelre a kliens és szervlet között. Abban az esetben, ha a kliensoldalon nem engedélyezett a cookie-k használata, magunknak kell gondoskodni a környezet azonosító átviteléről. Ezt például úgy tehetjük meg, hogy a szervletünk által generált HTML oldal minden hivatkozásához és űrlapjához megadjuk a környezetazonosítót.

A HttpServletRequest interfész isRequestSessionIdFromCookie, valamint isRequestSessionIdFromURL metódusaival lehet megtudni, hogy a klienstől hogyan kapta vissza a szervlet a kért klienskapcsolat környezet azonosítóját.

Szervletközi kommunikáció

Azonos webszerveren futó szervletek kommunikálhatnak egymással a szervletfuttató környezeten keresztül, amelyet egy ServletContext objektum reprezentál. Minden szervlethez tartozik egy ilyen objektum, melyet a szervlet inicializációjakor kapott ServletConfig objektum getContext metódusával lehet lekérdezni. A kommunikáció egyik formája azon alapszik, hogy minden szervlet névvel ellátott objektumokat tárolhat a futtató környezetben annak setAttribute metódusával, illetve ezen név alapján lekérdezheti a hozzá tartozó objektumot a getAttribute metódus segítségével. A tárolt objektumok nevének listáját a getAttributeNames metódus adja vissza, objektumot törölni pedig a removeAttribute metódussal lehet. Az így tárolt objektumok függetlenek a klienskapcsolat élettartamától, viszont csak a szervletfuttató környezet futása alatt érhetők el, nem kerülnek perzisztensen is tárolásra. Ugyanazon szervletfuttató környezet objektum több szervlethez is tartozhat, ekkor ezen szervletek látják egymás objektumait. 

A kommunikáció másik formája, hogy adott szervlet teljesen vagy csak ideiglenesen átirányíthatja a klienskérést egy másik szervlethez. Ehhez azonban valahogy el kell érni azt. Erre célra használható a getRequestDispatcher metódus, amely a paramétereként kapott webszerveren belüli lokális erőforrás URL-hez ad egy reprezentáló RequestDispatcher objetkumot. Ezen objektum forward metódusát kell meghívni, miután a klienskérés kiszolgálásáért a megcímzett objektum lesz a felelős. Ilyenkor a hívó szervlet egyáltalán nem küldhet adatot a kliens felé, különben kivétel fog fellépni.

Ha a válasz generálása közben annak adott pontjától kezdve egy másik szervlet eredményét szeretnénk megjeleníteni, akkor használjuk a RequestDispatcher include metódusát. Ilyenkor a külső erőforrás tartalma egyszerűen csak belekerül a mi szervletünk válaszába, azaz előtte és utána is küldhetünk még adatot a kliens felé. Ha a beszúrt tartalmat egy másik szervlet állítja elő, akkor ügyeljünk arra, hogy a meghívott szervlet már nem módosíthatja a HTTP fejlécet, és hogy az nehogy lezárja a kliens felé az adatátviteli csatornát.

Mivel a szervlet sosem hagyja el a hoszt webszerverét, nincs kitéve visszafejtésnek. A kliens számára csak a szervlet outputja látható, sohasem az a folyamat, amellyel ezt az outputot kiszámították.

Finta Annamária