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