A HTTP protokollt
webes kliensek és szerverek beszélik egymással,
tipikusan böngészők és webszerverek. A HTTP egy
kapcsolat nélküli, kérés-válasz
protokol, amelyet egy megbízható transzportréteg,
tipikusan TCP felett használnak. A protokol
szolgáltatásai az OSI Session rétegének
felelnek meg.
A HTTP kérés a következő részekből áll:
- HTTP parancs és célcím
azonosító
- HTTP kérés fejlécek
- Kérés tartalom
A HTTP válasz ehhez eléggé hasonló
- HTTP státusz
- HTTP válasz fejléc
- Válasz tartalom
A HTTP parancs a célcím azonosítóval
és a HTTP státusz mindig jelen van, ezen felül a
HTTP válasz bizonyos fejlécei majdnem mindig szerepelnek.
A többi rész opcionális.
A HTTP-t legegyszerűbb példákon keresztül
megérteni (a
¶ karakter
a soremelés-kocsivissza karaktereket reprezentálja, a
túl hosszú sorok a következő sorban
folytatódnak). A kérés-válasz egy Netscape
7.1 böngésző és egy Apache webszerver
között zajlik.
GET
/test/example.html HTTP/1.1¶
Host: localhost¶
User-Agent: Mozilla/5.0 (Windows;U; Windows NT5.0; en-US; rv:1.4)
Gecko/20030624
Netscape/7.1 (ax)¶
Accept: application/x-shockwave-flash,text/xml,application/xml,
application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,video/x-mng,image/png,
image/jpeg,image/gif;q=0.2,*/*;q=0.1¶
Accept-Language: en-us,en;q=0.5¶
Accept-Encoding: gzip,deflate¶
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7¶
Keep-Alive: 300¶
Connection: keep-alive¶
¶
Érdemes odafigyelni az utolsó
fejléc után levő CR-LF jelre. A fejlécblokk
végét egy üres sor jelzi. Ezután
következne a kérés tartalom, de ebben a
kérésben ilyen nincsen, mert a GET parancshoz tartalom
nem tartozik. Lássuk ezek után a választ!
HTTP/1.1 200
OK¶
Date: Fri, 26 Sep 2003 12:11:49 GMT¶
Server: Apache¶
Last-Modified: Fri, 26 Sep 2003 12:08:03 GMT¶
ETag: "2041b-54-66790ec0"¶
Accept-Ranges: bytes¶
Content-Length: 84¶
Connection: close¶
Content-Type: text/html; charset=ISO-8859-1¶
¶
<html><head><title>Example</title></head>¶
<body><h1>Example</h1></body></html>¶
A válaszban már volt státusz, fejlécek
és tartalom is. A legfontosabb fejléc a Content-Type, ez
mondja meg a böngészőnek, mi módon kell
megjeleníteni a tartalmat. A Content-Type az u.n. MIME
(Multipurpose Internet Mail Extensions) típusra utal, ez ebben
az esetben text/html, tehát egy HTML oldalról van
szó. Fontos kiemelni, hogy a HTTP akármilyen tartalom
átvitelére alkalmas, tehát nem kell
szövegesnek lennie. Meglehetősen tipikus pl. HTTP felett
image/jpeg (JPEG kép) tartalmat átvinni. Másik
triviális, de fontos megállapítás, hogy a
tartalom típusát a szerver mondja meg, tehát
teljes szabadsága van abban, milyen célcímhez
milyen tartalmat rendel. A célcím a tartalom
típusát nem határozza meg, tehát pl. a
http://www.server.com/index.html cím mögött nyugodtan
lehet egy kép.
Nézzünk most egy másik példát, ahol
van kérés tartalom. Itt egy másik parancs, a POST
szerepel, amelyik a kéréssel tartalmat is küld. A
kérés tartalomnak ugyanúgy van típusa, mint
a válasznak.
POST
/cgi-bin/test-cgi HTTP/1.1¶
Referer: http://localhost/test/phbook1.html¶
Content-Type: application/x-www-form-urlencoded¶
Content-Length: 55¶
¶
firstname=John&familyname=Doe&phonenumber=%2B1777111111¶
A válasz szerkezete hasonló az előző
példában látotthoz. Habár ebben a
példában egy HTML form-ot küldtünk, ami
szöveges formátumú kérés tartalmat
jelent, a kérés tartalom lehetne akármilyen,
akár bináris típus is.
Messze a legtöbbet használt HTTP parancsok a GET és
a POST. Ezen felül még az OPTIONS, HEAD, PUT, DELETE, TRACE
és CONNECT parancsok léteznek.
A HTTP protokol megváltozik, ha proxy szerverekkel együtt
használják. A proxy szerver a tűzfalba van
építve és csak bizonyos protokoll (pl. HTTP)
átvitelére alkalmas. A kliensek a célszerver
helyett a proxyhoz kapcsolódnak, a proxy értelmezi a
kérést és a tűzfal túloldalán
kapcsolódik a valódi célszerverhez és
megismétli a kérést. Hogy ezt meg tudja
csinálni, a kérésnek tartalmaznia kell a teljes
célcímet (/test/example.html helyett
http://www.server.com/test/example.html).
Példa kérés-válasz proxyval.
GET
http://172.24.169.107/test/example.html
HTTP/1.1¶
Host: 172.24.169.107¶
Keep-Alive: 300¶
Proxy-Connection: keep-alive¶
¶
HTTP/1.1 200 OK¶
Date: Wed, 01 Oct 2003 12:27:56 GMT¶
Proxy-Connection: close¶
Via: 1.1 proxy1 (NetCache NetApp/5.3.1R4), 1.1 proxy2 (NetCache
NetApp/5.2.1R1D4)¶
¶
… válasz tartalom …
2. Jáva szervletek
A webszerverek a kezdetek óta tudnak dinamikus tartalmat
generálni. Ez azt jelenti, hogy egy kérés
érkezésekor a kiszolgált tartalmat nem egy
statikus fájlból veszik, hanem a kérés egy
programhoz kerül, ami dinamikusan generálja a
választ. A legősibb ilyen megoldás a Common Gateway
Interface (CGI), ami egy processzt indít az
operációs rendszerben és annak adja át a
kérést, de hasonló rendszerek
készültek pl. Perl-hez és a népszerű nagy
adatbázisokhoz. A Jáva válasza a
problémára a Jáva szervlet. A Jáva
szervletet a JSR-53 specifikálja.
A Jáva szervlet egy Jáva osztály, amely egy
bizonyos szülőosztályból
(javax.servlet.http.HttpServlet) származik és
regisztrálják a webszervernél. A webszerver ezek
után tudja, hogy bizonyos célcím
kérése esetén a szervletet kell meghívni. A
szervlet ilyenkor megkapja a kérést és a
kérés alapján generálhatja a
választ. A webszerver továbbra is elvégzi a
web-kiszolgálással kapcsolatos teendőket (HTTP
kezelés, TCP kapcsolat kezelés,
URL-leképezés, terhelésmegosztás, stb.) A
webszerver szervleteket futtató modulját szervlet
konténernek is nevezik, jelezvén, hogy a szervlet
konténer teremti meg a szervlet futási feltételeit.
A szervlet a következő állapotokon megy keresztül.
- Betöltődik és a szervlet konténer a
szervletosztályból egyedeket hoz létre. A szervlet
konténer maga választja meg, milyen
stratégiát követ a szervletosztályok
instanciálásakor, készíthet egy vagy
több egyedet is.
- Az első használat előtt inicializálódik.
Ekkor a szervlet init() metódusa meghívódik
és ez a metódus megkapja a szervlet
konfigurációját. A szervletegyed így
átveheti a konfigurációs paramétereit
és elvégezhet időigényes feladatokat, pl.
adatbázisokhoz kapcsolódhat, stb.
- A szervlet kérést dolgoz fel. Amikor a webszerver
HTTP kérést kap, amelynek célcíméhez
a szervlet van kapcsolva, a szervlet konténer meghívja a
szervlet service() metódusát. A service() egy
HttpServletRequest és egy HttpServletResponse objektumokra
mutató referenciát kap. Ezen objektumegyedeken
keresztül lehet a kérés adatait elhozni és a
választ generálni. A service() metódus
alapértelmezett implementációja a
javax.servlet.http.HttpServlet-ben szétválogatja a POST
és GET kéréseket és azokat a doGet
és doPost metódusokra küldi. Szokásos a
service() metódust nem felüldefiniálni és
csak doGet-et és doPost-ot implementálni.
Egy HTTP kérést egy végrehajtási
szál hajt végre. A végrehajtási
szálak és a szervlet egyedek viszonya nem
definiált, általában a szervlet konténer
egyszerre több kérést is küld egy
szervletegyednek. A szervlet implementációnak
tehát fel kell készülnie arra, hogy a szervlet
metódusaiban egyszerre több végrehajtási
szál is lehet.
- Életének végén a szervletet
elpusztítják a destroy() metódusának
meghívásával. Az elpusztítás oka a
konténer implementációjától
függ, pl. a webszervert leállítják vagy a
szervlet konténer adminisztrációs
felületén a szervletet eltávolítják.
A következő példa egy minimális szervletet mutat.
public class
MinimalServlet extends javax.servlet.http.HttpServlet {
// Init
metódus. Itt lehetne elhozni a konfigurácios
paramétereket
public
void init( ServletConfig conf ) throws ServletException {
super.init( conf );
}
// GET
kérések kezelése
public
void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType( "text/html" );
PrintWriter out = new PrintWriter( res.getWriter() );
out.println( "<body>Hello, GET!</body>" );
}
// POST
kérések kezelése
public
void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType( "text/html" );
PrintWriter out = new PrintWriter( res.getWriter() );
out.println( "<body>Hello, POST!</body>" );
}
// Szervlet
destroy. Meghívódik, amikor a szervletet elpusztitja a
webszerver
// (pl. a
webszerver leáll)
public
void destroy() {}
}
A
szervleteket lehet telepíteni direktben a szervlet
konténer megfelelő könyvtáraiba, de ez a
módszer ellenjavalt. Mindenekelőtt konténerenként,
sőt azok verzióiként változik, hova mit kell
tenni, hogy a szervlet regisztrálva legyen. Ezen
kínlódások elkerülése végett a
szervleteknek (pontosabban: szervleteket tartalmazó web
alkalmazásoknak) is van csomagolási formátuma, a
WAR formátum. A WAR egy közönséges JAR
formátum, aminek kötött könyvtárszerkezete
van, így a szervlet konténer megtalálhatja a
szervletek regisztrálásához szükséges
információkat. Mindenekelőtt a WAR-ba tetszőleges
fájl vagy könyvtár elhelyezhető, ezeket statikus
tartalomként fogja a webszerver kiszolgálni. Pl. a
WAR-ban levő dir/content.html fájl elérhető a
http://szervercím/webappcím/dir/content.html
címen. A WAR-ban levő WEB-INF könyvtárnak
(szigorúan nagybetűvel!) különleges szerepe van. A
struktúra így néz ki:
WEB-INF/
web.xml
classes/
lib/
A web.xml
leírófájl definiálja a szervleteket, erre
később visszatérek. A classes könyvtár alatt
vannak a szervletekhez tartozó osztályok (ha package-ben
vannak, akkor persze ennek megfelelő struktúrában), a lib
könyvtár alatt pedig a szervlet osztályok
által igényelt könyvtárak JAR
formában. Úgy is fel lehet fogni, hogy az
archívumban levő szervletek futása alatt a classes
könyvtár és a lib könyvtárban levő JAR
fájlok rajta vannak a CLASSPATH-en.
Nézzük most a web.xml fájlt! A web.xml
sokféle opciót leírhat, most csak a
legfontosabbakra, a szervlet osztály
meghatározására és a szervlet URL-hez
rendelését nézzük meg. Példa:
<?xml
version="1.0"?>
<!DOCTYPE
web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<description>Alkalmazasleiro az
MinimalServlet-hez</description>
<servlet>
<servlet-name>Servlet1</servlet-name>
<servlet-class>MinimalServlet</servlet-class>
<init-param>
<param-name>someConfigName</param-name>
<param-value>someConfigValue</param-value>
<description>Example configuration variable</description>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Servlet1</servlet-name>
<url-pattern>/MiniServletURL</url-pattern>
</servlet-mapping>
</web-app>
web.xml egy XML fájl, ennek megfelelő
formátumleíróval kezdődik. (megjegyzés: a
web.xml alapértelmezett kódolása - mint minden XML
fájlnak - UTF-8. Ez az ékezetes betűknél okoz
problémát. Ha nincs kéznél UTF-8-at
támogató szövegszerkesztő, inkább ne
tegyünk ékezetes betűt a web.xml-be.) Minden egyes
<servlet> elem egy szervletet definiál. A
<servlet-name> nevet ad a szervletnek, hogy hivatkozni lehessen
rá, a <servlet-class>
definiálja a szervletosztályt (a package-dzsel
együtt, de a példában nincs package), az <init-param>
elemben pedig egy konfigurációs paramétert lehet
definiálni, amit az init() kap meg a ServletConfig egyeden
keresztül. <init-param>-ból
is tetszőleges számú lehet.
A szervlet csak akkor elérhető, ha célcímhez van
rendelve. Korábbi vagy fejlesztési célú
konténerek megengedik szervletek automatikus
regisztrációját, amikor akármelyik
osztályt a konténer webalkalmazás
könyvtárából meg lehet próbálni
szervletként meghívni. Ez persze nagyon veszélyes
biztonsági szermpontból, ezért az elterjedt
konténerek csak címhez regisztrált szervlethez
adnak hozzáférést. Ez történt a
<servlet-mapping> elemben, ahol a szervletet a /MiniServletURL
címhez rendeltük. Megjegyzendő, hogy a
leképezés egyszerű reguláris kifejezés is
lehet, pl. *.jsp, ekkor a webalkalmazás címe alatt levő
összes illeszkedő címre (.jsp-vel végződő
célcímre) meghívódik a szervlet. A
HttpServletRequest-ből a szervlet kiveheti a kérés teljes
célcímét.
A konténer szabadon dönthet, milyen
stratégiát követ a teljes cím szervlethez
rendelésekor, szokásos például a
http://<szerver cím>/<webalkalmazás
név>/<url-pattern>, ahol a webalkalmazás
név alapértelmezésben a WAR neve, az url-pattern
pedig az <url-pattern> elemben megadott célcím
leképezés.
Minden webalkalmazáshoz egy ServletContext objektum van
rendelve. Ez lehetővé teszi, hogy egy webalkalmazásba
tartozó szervleteknek közös
konfigurációs paraméteri legyenek
(<context-param> elem a web-app elem alatt), a
ServletContext az egész webalkalmazásban (tehát a
webalkalmazás más szervletei által is
látható) változókat tárolhat
és a ServletContext-en keresztül elérhetők a
webalkalmazás statikus állományai.
A webalkalmazás állapotának
változásaihoz eseménykezelőket lehet rendelni.
Jelenleg 4 eseménykezelő létezik.
- A ServletContext létrejött vagy a konténer
elpusztítani készül a ServletContext-t
- ServletContext változó létrejött,
megváltozott vagy törlődött
- Egy HTTP session (több HTTP kérés
összekapcsolására képes szerkezet) jött
létre vagy szűnt meg
- Egy HTTP session változó létrejött,
megváltozott vagy törlődött
A konténer az eseménykezelő osztályokat az
általuk implementált interfész (pl.
javax.servlet.ServletContextListener) alapján azonosítja.
Az eseménykezelőket a web.xml-ben adjuk meg és a
konténer példányosítja őket.
<web-app>
<listener>
<listener-class>com.acme.MyConnectionManager</listener-class>
</listener>
....
</web-app>
3. Java Server Pages (JSP)
Amint azt minimális szervletünknél is
észrevehettük, ha a szervlet HTML lapot generál, a
szervlet kódja szükségszerűen tele lesz HTML
darabokkal. Ez egyfelől nehezen készíthető el,
másfelől nehezen tartható karban. A népszerű
template-nyelvek (PHP, ASP) mintájára
létrehozták a JSP nyelvet. A JSP a szervlet
technológiára épít.
A JSP kifordítja a szervletet: a JSP-nél a HTML (vagy
XML) oldalba ágyazzuk a Jáva kóddarabokat, amelyek
az oldalt dinamikussá teszik. Egy HTML oldal (.jsp
kiterjesztéssel) tehát érvényes JSP oldal.
A legfontosabb kifejezések, amelyeket az oldalba
helyezhetünk a következők:
- <%@direktíva
.... %> - direktíva a JSP feldolgozónak. Pl.:
<%@contentType type="text/plain" %>
- <%= Jáva
kifejezés %> - a Jáva kifejezés
kiértékelődik és az oldal adott pontján a
generált oldalba íródik
- <% Jáva
programrészlet %> - a Jáva
programrészlet az oldal generálásának adott
pontján végrehajtódik
A JSP feldolgozó a JSP lap első hivatkozásakor a lapot
Jáva szervletté fordítja, azt Jáva
bájtkódra fordítja és ez a szervlet
hajtódik végre. Ennek megfelelően a szervlet
szolgáltatásai elérhetőek, a JSP
fordító olyan kódot fordít, ami ismert nevű
objektumokon keresztül teszi elérhetővé pl. a
HttpServletRequest vagy HttpServletResponse objektumokat. A JSP lapban
tehát leírhatunk ilyet:
<% String param = request.getParameter( "parm" ) %>
vagy
<% out.println( "Date is: "+ new Date().toString() )
%>
mert biztosak
lehetünk benne, hogy a request vagy az out változó
ismert lesz a JSP fordító által generált
program adott pontján.
A JSP-t tartalmazó webalkalmazást ugyanúgy WAR-ba
csomagolhatjuk, mint a szervleteket (és persze a JSP-k és
a szervletek keverhetők). A JSP lapokat egyszerűen a többi
statikus fájl közé kell tenni a WAR-ban. A JSP lap
előfordítható, ekkor a JSP-ből generált szervlet
kerül a WAR-ba (a szokásos WEB-INF/classes
könyvtárba) és a <servlet-mapping> elemmel
biztosítjuk, hogy a JSP-re vonatkozó kérés
a szervletnél kössön ki. Pl.:
<web-app>
<servlet>
<servlet-name>HelloWorld </servlet-name>
<servlet-class> _jsp_HelloWorld_XXX_Impl.class
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorld </servlet-name>
<url-pattern>/HelloWorld.jsp </url-pattern>
</servlet-mapping>
</web-app>