Sunteți pe pagina 1din 82

7.

TEHNOLOGIA JAVA SERVLET

7.1. Conceptul de servlet; containere de servlet-uri


7.1.1. Locul servlet ntre tehnologiile Web
Foarte simplu spus, un servlet [49] este un program Java care ruleaz pe un server Web i care construiete (dinamic) pagini Web. Din punct de vedere funcional, un servlet face acelai lucru ca i un program CGI, descris n 4.7.1. S vedem mai nti unde se afl servlet n panoplia tehnologiilor Web actuale. Exist mai multe lucrri care trateaz acest subiect. Prerea noastr este c lucrarea [51] face o analiz pertinent a subiectului, motiv pentru care ne-o nsuim. Mai mult, ceea ce vom prezenta n aceast seciune este n mare msur preluat din [51]. Pentru realizarea de pagini Web dinamice i pentru asigurarea unei interaciuni bidirecionale ntre browser i server-ul Web, ultimii 10 ani au consacrat mai multe tehnologii. Urmtoarele sunt cele mai rspndite i le enumerm aproximativ n ordinea apariiei lor. 1. Common Gateway Interface (CGI) Este prima tehnologie de acest tip. n 4.7.1 am prezentat aceast tehnologie. 2. ColdFusion [51] Este un produs al corporaiei Allaire. n esen este vorba de extinderea HTML cu o serie de tag-uri noi pentru operaii speciale, mai ales pentru interogarea bazelor de date. Tehnologia i-a avut locul ei n istorie i a fost atractiv (doar) pn cnd au aprut i alte tehnologii Web. 3. Server-Side Includes (SSI) [23] Ofer posibilitatea execuiei unor script-uri sau a altor aciuni n partea de server, prin specificarea unor comenzi incluse direct n codul HTML. Din cauza multor slbiciuni, mai ales pe partea de securitate, utilizarea SSI este din ce n ce mai rar. 325

4.

Server-Side JavaScript (SSJS) [51] Limbajul JavaScript este destinat n principal aciunilor la nivel de client. SSJS este o extensie a limbajului JavaScript pentru a descrie clase care s acioneze la nivel de server.

5.

PHP (Personal Home Page, sau mai recent Professional Hipertext Processor) Este o tehnologie open-source extrem de atractiv i de rspndit n prezent. PHP ofer faciliti simple de descrierea unor aciuni la nivel de server, opernd inclusiv cu conceptul de sesiune i oferind o serie de funciuni interne legate de acces la fiiere i baze de date.

6.

Servlet Este o tehnologie introdus de Sun n 1996. O vom trata n acest capitol.

7.

Java Server Pages (JSP) Este o extensie a tehnologiei servlet, bazat pe script-uri, de care ne vom ocupa n capitolul urmtor.

8.

Active Server Pages (ASP) Este o tehnologie bazat pe script-uri, promovat de Microsoft pentru a opera pe platforme Windows. Lucreaz n principal n tandem cu server-ele Web produse de Microsoft, adic cele de tip IIS. Se ncearc adaptarea i pentru server-ele Web de tip Apache. Urmtoarea tehnologie o include.

9.

Active Server Pages .NET (ASP.NET) Face parte din iniiativa .NET a Microsoft. Introduce elemente noi fa de ASP. Este interesant faptul c la baza .NET st limbajul C#, care este extrem de asemntor cu limbajul Java. De asemenea, executivul .NET - numit Common Language Runtime (CLR) este extrem de asemntor cu Java Virtual Machine (JVM)!

n ultima vreme, piaa programrii Web a fost dominat pe de o parte de servlet/JSP, iar pe de alt parte de ASP. Apariia ASP.NET introduce n competiie un element nou. n sfrit, PHP vine puternic din urm. Care va fi mine realitatea? Greu de anticipat.

326

n spiritul prezentei lucrri ne simim obligai s subliniem cteva dintre calitile oferite de servlet/JSP, caliti care nu se regsesc la celelalte tehnologii: Performana Tehnologia servlet este superioar CGI, deoarece nu este necesar crearea unui nou proces la fiecare cerere, ci numai un thread ntreinut de containerul de servlet-uri. Portabilitate Aplicaiile servlet sunt portabile, deoarece att servlet-ul, ct i containerul de servlet sunt scrise n Java. Dezvoltare rapid a aplicaiilor Fiind o thnologie Java, servlet are acces la o bibliotec bogat de pachete specializate, sporind astfel viteza de proiectare. Robustee Fiind vorba de Java, JVM controleaz gestiunea memoriei, garbage collection etc. Astfel proiectantul este scutit de o serie de gestiuni care de regul conduc la pene de program greu de depistat. Acceptare larg Din ce n ce mai multe case de soft preiau Java ca i principal platform de dezvoltare a proiectelor lor. Astfel, aplicaiile servlet/JSP se pot integra uor n proiecte de anvergur.

7.1.2. Arhitecturile aplicaiilor servlet


Formal vorbind, un servlet este o clas care poate fi ncrcat dinamic i apoi rulat ntr-un server web de tip special, numit container de servlet-uri (servlet engine). Un servlet interacioneaz cu clienii dup modelul HTTP bazat pe cerere - rspuns. Din aceast cauz, containerul de servlet-uri permite comunicarea prin protocolul HTTP pentru cereri de la clieni i rspuns al servlet-ului. n plus, n special din raiuni de securitate [71], containerul suport i protocoale nrudite, ca de exemplu HTTPS (HTTP securizat prin SSL). La aceast or sunt cunoscute dou tipuri de arhitecturi ale aplicaiilor servlet: aplicaii prin container de sine stttor (standalone); aplicaii cu container cascadat dintr-un server web. 327

n fig. 7.1 este prezentat arhitectura cu container standalone.


Servlet Container servleturi Raspuns HTTP Continut static

Cerere HTTP Client (browser)

Figura 7.1. Servlet cu container standalone

O aplicaie servlet conine, pe lng partea dinamic condus de servlet i o parte static specific web, ca de exemplu pagini HTML i fiiere cu imagini. Containerul de servlet avnd i funcii de server web, va trebui, pe lng execuia servlet-ului, s gestioneze i aceste coninuturi statice. Pentru aplicaii mai serioase este de preferat ca partea static s fie gestionat de ctre un server web robust i destinat special, cum ar fi de exemplu Apache sau Microsoft IIS. Prin aceasta, containerul va fi mai puin ncrcat, iar serviciile web relative la aplicaie vor fi mai bine i mai sigur gestionate. Astfel apare necesitatea celei de-a doua arhitecturi, cea din fig. 7.2.
Container servleturi Server Web Raspuns HTTP Continut static

Cerere HTTP Client (browser)

Servlet

Figura 7.2. Servlet mpreun cu un server web

7.1.3. Containere de servlet


7.1.3.1. Probleme generale Un container de servlet-uri este deci o aplicaie Java cu rol de main virtual n care s ruleze servlet-urile. Funcionalitatea unui container nu se reduce numai la simpla execuie a unui servlet, ci el trebuie s gestioneze acest servlet pe toat durata de via a acestuia. La ora actual exist numeroase implementri de containere, unele dintre ele comerciale, altele dezvoltate ca proiecte open-source, care pot fi liber 328

distribuite. Exist n prezent, conform Sun Microsystem, aproximativ 36 de implementri de containere, lista complet poate fi consultat la adresa: http://java.sun.com/products/servlet/industry.html. Fie c sunt comerciale, fie c sunt open-source, toate implementrile trebuie sa respecte specificaiile Sun Microsystem cu privire la servlet-uri. Recomandm cititorilor interesai parcurgerea acestor specificaii, care sunt disponibile pe Internet la adresa: http://jcp.org/aboutJava/communityprocess/first/jsr053. Dintre implementrile comerciale de containere, s-au impus pe pia Macromedia Jrun, Borland AppServer i IBM WebSphere Application Server. Aceste produse exceleaz n primul rnd prin uurina n instalare i configurare. Dintre containerele open-source, dou dintre cele mai cunoscute sunt Tomcat i Jetty. Primul dintre ele este dezvoltat n paralel cu server-ul web Apache i s-a impus ca model de referin, recomandat de Sun Microsystem. Pentru a putea rula un servlet trebuie instalat i configurat un container de servlet-uri. Unele containere, dezvoltate pentru platformele Microsoft Windows, se instaleaz cu ajutorul unui program de setup. Alte containere pot fi descrcate de pe Internet ntr-un singur fiier arhiv, de obicei zip Instalarea acestor containere se reduce la simpla dezarhivare a acestui fiier ntr-un anumit director. Fiind scris n Java, un container de servlet-uri este n general independent de platform. Dou versiuni ale aceluiai container, pentru dou platforme diferite, difer prin fiierele script de lansare. Spre exemplu un container pentru Linux poate prezenta script-uri care s permit lansarea n execuie a containerului respectiv ntr-un mod compatibil System V, n timp ce un container pentru Windows 2000 poate fi nsoit de fiiere binare care s permit lansarea containerului ca serviciu. Configurarea unui container de servlet-uri difer de la caz la caz. Unele containere, de obicei cele comerciale, au diferite componente grafice pentru administrare i configurare. Altele pot prezenta o interfa web-based pentru a efectua aceste operaii. Implementrile open-souce prezint opiunile de configurare n fiiere text, de obicei cu extensia .conf, sau mai noi n fiiere xml. 7.1.3.2. Instalarea i configurarea Tomcat n continuare ne vom referi numai la containerul Tomcat. Pentru fixarea ideilor, n cele ce urmeaz vom utiliza dou variabile de mediu: CATALINA_HOME prin care vom marca locul n structura de directori unde se afl instalat (urmeaz a se instala) containerul Tomcat. Dup caz, este fie un director sistem, fie un director al utilizatorului. De exemplu, pe o staie Windows2000 aceast variabil poate fi: 329

o C:\jakarta-tomcat-4.0.1 JAVA_HOME prin care vom marca locul n structura de directori unde se afl instalat pachetul Java. Dup caz, este fie un director sistem, fie un director al utilizatorului. De exemplu, pe o staie Windows 2000 aceast variabil poate fi: o C:\j2sdk1.4.0 Setarea variabilelor de mediu se face n conformitate cu cerinele specifice ale sistemului de operare pe care se lucreaz. De exemplu, setarea unei variabile VAR se face: o Pentru Windows 9x, acest lucru se face adugnd n fiierul autoexec.bat linia:
set VAR=valoare

o Pentru sistemele de operare Windows NT sau Windows 2000 aceast variabil se seteaz apelnd pe rnd: My Computer / click dreapta / Properties / Users Profiles / User defined Variables. o Pentru Unix se depun, dup caz, fie n /etc/profiles, fie n ~/.profile fie n ~/.bash_profile urmtoarele dou linii:
VAR=valoare export VAR

Pentru instalarea Tomcat sunt necesari urmtorii pai: Descrcarea containerului Tomcat Se face de pe Internet de la adresa http://jakarta.apache.org. Containerul Tomcat poate fi descrcat n mai multe formate: .exe pentru platformele Windows, .rpm pentru platformele Linux, .zip sau tar.gz pentru ambele platforme. Instalarea containerului Tomcat n funcie de formatul n care a fost descrcat containerul Tomcat, instalarea poate s difere. Daca s-a optat pentru formatul .exe se execut fiierul exe descrcat. Dac s-a optat pentru formatul .zip sau tar.gz se dezarhiveaz acest fiier n $CATALINA_HOME. Dac s-a optat pentru formatul .rpm, atunci containerul poate fi instalat cu urmtoarea comanda Linux, executat ca root: o rpm Uhv $CATALINA_HOME

330

Configurarea containerului Mai nti trebuie setat variabila JAVA_HOME. Setarea se face n conformitate cu cerinele sistemului de operare gazd. Dac se dorete, se poate seta i variabila CATALINA_HOME. Dac este cazul, se vor modifica unele dintre numerele de port cu care opereaz Tomcat. Restul elementelor de configurare le vom prezenta n seciunile urmtoare. Lansarea containerului Tomcat n $CATALINA_HOME exist un subdirector bin care conine script-urile de lansare ale lui Tomcat. Pentru platformele Unix se va rula script-ul startup.sh, iar pentru Windows script-ul startup.bat, adic: ${CATALINA_HOME}/bin/startup.sh %CATALINA_HOME%\bin\startup.bat Dup instalarea Tomcat, n $CATALINA_HOME apare urmtoarea structur de directori: bin Este directorul n care se gsesc programele execuabile i scripturile de baz Tomcat. clases Este directorul cu clasele globale necesare aplicaiilor web. conf Este directorul pentru fiierele de configurare. Dintre acestea, cele mai importante sunt server.xml care este fiierul principal de configurare Tomcat i web.xml care este fiierul machet pentru descriptorii aplicaiilor. server Conine fiierele arhiv (tip .jar) ale Tomcat. lib Conine o serie de clase pentru folosin comun, grupate n arhive .jar.

331

logs Este directorul n care Tomcat ntreine o serie de fiiere jurnal (log-uri). Consultarea acestora este uneori util, mai ales cnd apar o serie de erori n funcionarea unui servlet. common Conine o serie de clase de folosin att pentru Tomcat, ct i pentru aplicaiile web. webapps Este directorul n ale crui subdirectoare pot fi gzduite diverse aplicaii web. Aici se afl, printre altele exemplele demonstrative de aplicaii livrate odat cu distribuia Tomcat. Tot aici pot fi plasate noi aplicaii. Pentru fiecare aplicaie se va depune ori structura complet de directori a ei, ori o arhiv .war a acestei structuri, care va fi desfcut automat de ctre Tomcat. Odat cu distribuia Tomcat, acest director conine cel puin subdirectoarele ROOT, manager i examples. work Este spaiul n care sunt depuse servlet-urile generate automat din paginile JSP. Recomandm ca n prim faz, instalarea i configurarea unui container de servlet s o fac programatorul. Acesta trebuie s se familiarizeze cu toate opiunile de configurare i cu modalitile de instalare a unui astfel de container, pentru a putea folosi i exploata la maxim serviciile oferite. De asemenea, recomandm ca n faza de dezvoltare i testare a unei aplicaii, containerul s fie de asemenea ntreinut de ctre programator i repornit ori de cte ori este nevoie. Astfel, pentru testare fiecare programator i va ntreine propria instan Tomcat. n faza de exploatare se va folosi, eventual, un Tomcat unic, ntreinut i securizat de ctre un administrator. 7.1.3.3. Gestiunea porturilor Un container servlet are un port de ascultare cereri HTTP. De obicei acesta este portul 8080, dar el poate diferi de la container la container. Specificarea acestui numr de port se face n fiierul de configurare $CATALINA_HOME/conf/server.xml unde se gsete secvena urmtoare:

332

<!-- Define a non-SSL HTTP/1.1 Connector on port 8080 --> <Connector className="org.apache.catalina.connector.http.HttpConnector" port="8080" minProcessors="5" maxProcessors="75 enableLookups="true" redirectPort="8443" acceptCount="10" debug="0" connectionTimeout="60000"/>

Dac dorete, utilizatorul poate s modifice acest numr de port dup care trebuie s relanseze Tomcat. Aceast modificare este absolut necesar pentru evitarea conflictelor de ateptare la porturi, atunci cnd pe aceeai main mai muli utilizatori i lanseaz propriile containere Tomcat. Pe lng acest port, containerul mai folosete nc cel puin dou porturi. Unul dintre acestea este portul de oprire Tomcat. La acest port containerul ateapt comanda de oprire. n fiierul de configurare $CATALINA_HOME/conf/server.xml apare:
<Server port="8005" shutdown="SHUTDOWN" debug="0">

Printr-o comand:
TELNET adresaMainaTomcat 8005

se realizeaz conectarea la acest port, dup care se tasteaz o linie cu coninutul irului specificat prin atributul shutdown (n cazul de mai sus este irul SHUTDOWN). Astfel se poate comanda, eventual de la distan, oprirea Tomcat. Dac se dorete, se poate schimba portul sau irul de comand a opririi. Pe lng aceste dou porturi, Tomcat mai utilizeaz i urmtoarele numere de port:
Numr port 8008 8443 8082 8081 8009 Rol Conexiune warp cu serverul Apache conexiune SSL HTTP/1.1 Conexiune non SSL HTTP/1.0 Conexiune serviciu proxy Conexiune AJP

Pe moment este suficient ct am prezentat despre gestiunea porturilor. ntr-o seciune urmtoare o s revenim pentru conexiunea cu Apache. Dup fixarea numerelor de porturi, practic containerul este instalat. Pentru a verifica c instalarea este bun, mai nti se va porni containerul, dup caz sub Unix sau sub Windows: ${CATALINA_HOME}/bin/startup.sh %CATALINA_HOME%\bin\startup.bat

333

Apoi se va lansa, pe maina pe care s-a pornit containerul, http://localhost:8080, iar dac s-a modificat portul atunci n loc de 8080 se va pune portul respectiv. Efectul trebuie s fie afiarea paginii din fig. 7.3.

Figura 7.3. Pagina de start Tomcat

Pagina cuprinde printre altele i o documentaie complet despre container, o documentaie complet despre Servlet API, precum i cteva exemple de servlet-uri nsoite de codurile lor surs. ATENIE Asigurai-v c navigatorul nu are setat proxy. n caz contrar, navigatorul va trimite cererea http la server-ul proxy, care va interpreta localhost ca o main pe care ruleaz server-ul proxy i nu maina pe care ruleaz containerul! 7.1.3.4. Contextul unei aplicaii Servlet: definire i creeare ntr-o aplicaie web pot s apar servlet-uri, clase Java, pagini JSP, documente html statice, applet-uri Java etc. Toate aceste resurse trebuie plasate ntr-un director, numit rdcina aplicaiei i n subdirectoare ale acestuia, respectnd o anumit structur. Pentru fixarea ideilor, vom defini o variabil de 334

mediu cu numele WEBAPP_HOME. Valoarea ei va fi calea de la rdcina sistemului de fiiere i pn la directorul rdcin al aplicaiei. De exemplu, WEBAPP_HOME poate avea una dintre valorile: WEBAPP=${CATALINA_HOME}/webapps/OAplicatie WEBAPP=d:/Florin/Carti/Servlet/OAplicatie n primul caz aplicaia OAplicatie este plasat n spaiul rezervat de Tomcat pentru aplicaiile utilizatorilor, iar n al doilea caz ea este plasat undeva n sistemul de directori. (NB - Tomcat folosete separatorul de directoare /, indiferent dac este pe Unix sau pe Windows). Din pcate (sau poate din fericire), aplicaia web din $WEBAPP_HOME nu poate fi invocat prin specificare absolut:
http://adresamasina[:port]/$WEBAPP_HOME...

Adic nici una dintre specificrile:


http://localhost:8080/C:/jakarta-tomcat-4.0.1/webapps/OAplicatie... http://localhost:8080/d:/Florin/Carti/Servlet/OAplicatie...

nu este acceptat de ctre Tomcat! Tomcat, ca de altfel orice container de servlet, folosete pentru specificri un mecanism de alias, prin intermediul conceptului de context. El poate fi privit ca un domeniu de vizibilitate, care poate fi accesat din resursele coninute n cadrul aplicaiei web. Fiecare aplicaie web are un singur context, iar containerul trebuie s-i asigure acestuia securitatea necesar. De exemplu, dou aplicaii web distincte nu i vor putea accesa una alteia contextul. Pentru fiecare aplicaie se va defini un context care va avea asociat un nume. Pentru fixarea ideilor, vom defini o variabil de mediu WEBAPP_NAME care va conine numele asociat contextului. O prim ntrebare se impune: Cum se definete un context? Definirea contextelor i a parametrilor de configurare a acestora se face prin intermediul tag-urilor <HOST i <CONTEXT din fiierul $CATALINA_HOME/conf/server.xml. O a doua ntrebare: Cum se leag directorul aplicaiei de context? O astfel de ntrebare apare n mod firesc, deoarece aplicaia se afl n $WEBAPP_HOME, iar containerul (Tomcat) o vede ca $WEBAPP_NAME. Vom rspunde deodat la ambele ntrebri.

335

Tag-ul <HOST definete pentru container un host virtual. Fr s intrm n detalii (utilizatorul poate consulta [87] sau comentariile din fiierul de configurare), ne vom opri numai la dou atribute principale: name i appBase. n interiorul tag-ului <HOST se definesc tag-urile <CONTEXT. Din tag-ul <CONTEXT ne intereseaz numai atributele path i docBase. Maniera de specificare n cele dou tag-uri este specific limbajului XML [87, 38, 84]. Schematic, partea legat de definirea contextelor Tomcat este:
- - <HOST name="adresaMasina" appBase="dirAplicatii" ... alteAtr ... > - - <CONTEXT path="/nume" docBase="caleReala" ... alteAtr ... />

sau
<CONTEXT path="/nume" docBase="caleReala" ... alteAtribute ... > - - definire caracteristici context prin taguri interioare - - </CONTEXT> - - </HOST> - - -

Un tag <HOST definete un host virtual. Atributul name al lui specific prin adresaMasina adresa real a mainii care gzduiete host-ul virtual. Atributul appBase definete prin dirAplicatii subdirectorul lui $CATALINA_HOME destinat s gzduiasc aplicaiile web. Pe lng aceste atribute mai sunt i altele, iar ntr-un tag <HOST apar mai multe tag-uri interioare lui. Distribuia Tomcat are definit n fiierul de configurare numai un singur host virtual, definit prin:
<HOST name="localhost" appBase="webapps" ... >

Aa cum am artat mai sus, directorul de aplicaii indicat de acest host implicit este:
$CATALINA_HOME/webapps

O prim regul relativ la crearea de contexte este aceea c toate subdirectoarele directorului de aplicaii devin n mod automat contexte, numele contextului fiind acelai cu numele directorului. Mai jos prezentm i alte modaliti de creare de contexte.

336

Din interiorul unui tag <HOST ne intereseaz doar tag-urile <CONTEXT cu ajutorul crora se pot defini contexte pentru diferite aplicaii. Dac pentru definirea unui context sunt suficiente atributele din cadrul tag-ului, atunci se foloseta un tag vid (n terminologia XML [84]):
<CONTEXT path="/nume" docBase="caleReala" ... alteAtr ... />

Pentru definiri de tag-uri mai complexe, se folosete o structur XML cu tag de nceput i de sfrit:
<CONTEXT path="/nume" docBase="caleReala" ... alteAtribute ... > - - </CONTEXT>

i acum rspunsul la ambele ntrebri. Valoarea nume din atributul path al unui context este numele contextului. Deci, pentru o anumit aplicaie vom avea WEBAPP_NAME=nume. Prin atributul docBase se specific caleReala din sistemul de directori unde se afl aplicaia. Specificarea caleReala poate fi fcut relativ la appBase sau absolut. n cazul specificrii relative valoarea variabilei WEBAPP_HOME se obine concatennd trei elemente: valoarea variabilei CATALINA_HOME, urmat de valoarea atributului appBase din <HOST, ncheiat cu valoarea atributului docBase din <CONTEXT, adic:
WEBAPP_HOME=$CATALINA_HOME/dirAplicatii/caleReala

n cazul specificrii absolute, valoarea variabilei WEBAPP_HOME este valoarea atributului docBase din <CONTEXT, adic:
WEBAPP_HOME=caleReala

Referirea la aplicaie se face prin intermediul contextului, astfel:


http://adresamasinaTomcat:portTomcat/$WEBAPP_NAME/...

n continuarea URL-ului se pot specifica resurse din orice subdirectoare ale contextului. Exemple de creare a unor contexte: 1. Distribuia Tomcat vine cu trei contexte predefinite, toate specificate relativ, ca mai jos: Contextul vid, implicit cnd nu se specific nici un context. El se definete prin <CONTEXT path="" docBase="ROOT ... Contextul manager, destinat gestiunii Tomcat, definit prin <Context path="/manager" docBase="manager" ... 337

Contextul examples, care conine n el mai multe exemple demonstrative de lucru cu servlet i cu JSP, definit prin <Context path="/examples" docBase="examples" ... De exemplu, referirea de pe maina local prin portul 8080 la o resurs din contextul examples, se face prin:
http://localhost:8080/examples/...

2.

Se dorete crearea unui context cu numele OAplicatie, gzduit n directorul standard de aplicaii. Pentru aceasta este suficient s se creeze, n $CATALINA_HOME/webapps directorul OAplicatie care s conin structura de directori i fiierele necesare aplicaiei, aa cum pretinde Tomcat. Apoi, referirea, spre exemplu de pe maina local, la o resurs din contextul OAplicatie, se face prin:
http://localhost:8080/OAplicatie/...

3.

La fel ca i la punctul 2, numai c structura de directoare i fiiere pentru aplicaie poate fi pregtit n alt parte. Apoi se creeaz din ntreaga structur o arhiv zip cu numele OAplicatie.zip. n sfrit, munele acesteia va fi schimbat n OAplicatie.war i aceast arhiv se va depune n $CATALINA_HOME/webapps. Tomcat va avea grij ca la prima ncrcare a lui s desfac automat arhiva n webapps/OAplicatie i s creeze contextul cu numele OAplicatie. Se dorete, pe o main Windows, crearea unui context cu numele TfJaW (acrostih de la titlul prezentei cri) care s se refere la o aplicaie gzduit n subdirectorul Carti al directorului Florin din rdcina discului D:. Pentru aceasta, contextul se definete prin:
<Context path="/TfJdW" docBase="d:/Florin/Carti" ... />

4.

Cu notaiile de mai sus vom avea


EBAPP_HOME=d:/Florin/Carti

WEBAPP_NAME=TfJaW

Referirea la o resurs din acest context, spre exemplu de pe maina local, se face prin:
http://localhost:8080/TfJaW/...

De pe o alt main, prin adresa IP, referirea este:


http://193.226.40.130:8080/TfJaW/...

338

Rezumnd problema contextului, exist patru posibiliti prin care se leag spaiul fizic al unei aplicaii WEBAPP_HOME, cu numele de context WEBAPP_NAME pentru acces la container: 1. O anumit resurs dintr-o aplicaie este gzduit n interiorul unui context deja definit. n acest caz, referirea la resurs se face din contextul gazd completat cu calea relativ pn la resurs. n $CATALINA_HOME/webapps se creeaz un subdirector pentru aplicaia respectiv, caz n care contextul este creat automat, iar numele contextului este chiar numele directorului respectiv. n $CATALINA_HOME/webapps se depune o arhiv nume.war cu numele aplicaiei. Tomcat va crea automat un subdirector pentru aplicaia respectiv, iar numele contextului va fi chiar numele directorului respectiv. Dac aplicaia web este coninut n alt loc n sistemul de fiiere, atunci se definete un context nou, al crui nume este dat de atributul path al tag-ului <CONTEXT, iar locul fizic este indicat prin atributul docBase al tag-ului.

2.

3.

4.

Contextul unei aplicaii devine operaional odat cu pornirea containerului dup configurare. 7.1.3.5. Structura de subdirectoare a unui context i invocarea de resurse Structura de subdirectoare i fiiere a unui context, n conformitate cu specificaiile Sun, este urmtoarea:
$WEBAPP_HOME $WEBAPP_HOME/WEB-INF/web.xml $WEBAPP_HOME/WEB-INF/classes/ $WEBAPP_HOME/WEB-INF/lib/

n directorul $WEBAPP_HOME se plaseaz resursele statice i paginile JSP ale aplicaiei. Directorul $WEBAPP_HOME/WEB-INF/ este folosit n aplicaie numai prin intermediul containerului. n interiorul acestuia apar cteva fiiere i directoare. Fiierul $WEBAPP_HOME/WEB-INF/web.xml se numete descriptor al aplicaiei i prin intermediul lui se pot fixa o serie de configurri pentru aplicaia web respectiv: parametri de iniialzare, 339

elemente de depanare etc. n multe aplicaii acest fiier coincide cu cel implicit livrat de distribuia Tomcat. Directorul $WEBAPP_HOME/WEB-INF/classes/ conine fiierele de tip .class pentru toate servlet-urile i bean-urile folosite de aplicaie. Directorul $WEBAPP_HOME/WEB-INF/lib/ conine clasele adiionale, arhivele JAR, driver-ele JDBC etc. necesare rulrii aplicaiei web. Accesul la o resurs static Accesul la o resurs static a aplicaiei web poate fi invocat prin container folosind un URL de forma:
http://masinaTomcat:port/$WEBAPP_NAME/numeResursa

unde n loc de $WEBAPP_NAME apare numele contextului aplicaiei. Structura de directoare a unui aplicaii web nu este rigid. Programatorul poate s extind aceast structur de directoare dup cum dorete. De exemplu poate plasa imaginile aplicaiei ntr-un director numit $WEBAPP_HOME/images/, dup care referirea se poate face prin:
http://masinaTomcat:port/$WEBAPP_NAME/images/poza.jpg

Accesarea unui servlet Aa cum am precizat mai sus, un servlet trebuie plasat n directorul $WEBAPP_HOME/WEB-INF/classes/. Accesul la acest servlet se face folosind un alias intermediar care substituie construcia WEBINF/classes. Presupunnd c avem un servlet ClasaServlet.class, acesta va fi depus n $WEBAPP_HOME/WEBINF/classes/, repornit containerul, dup care poate fi invocat prin:
http://masinaTomcat:port/$WEBAPP_NAME/servlet/ClasaServlet

Pentru a lansa, spre exemplu, servlet-ul SessionExample.class livrat odat cu distribuia Tomcat, acesta poate fi invocat, de pe maina local, prin:
http://localhost:8080/examples/servlet/SessionExample

Pentru multe dintre exemplele care urmeaz n acest capitol, am definit un context numit pdpj (acrostih dup Programare Distribuit pe Platforme 340

Java). Mai nti am creat directorul $CATALINA/webapps/pdpj, n interiorul acestuia am definit subdirectorul WEB-INF iar n acesta am creat directoarele classes, lib i am copiat fiierul web.xml din $CATALINA_HOME/ROOT/web.xml. 7.1.3.6. Tomcat conectat la Apache Containerul Tomcat are definite dou servicii. Serviciul TomcatStandalone funcioneaz aa cum am artat n fig. 7.1. Serviciul Tomcat-Apache funcioneaz aa cum am artat n fig. 7.2. Cele dou moduri de lucru sunt descrise n fiierul de configurare $CATALINA_HOME/conf/server.xml prin intermediul tag-urilor <SERVICE:
<SERVICE name="Tomcat-Standalone"> - - -</SERVICE> <SERVICE name="Tomcat-Apache"> - - - </SERVICE>

La pornirea Tomcat, acesta anun pornirea celor dou servicii ca n fig. 7.4.

Figura 7.4. Fereastra de pornire Tomcat

Serviciul Tomcat-Standalone ofer containerului funcionaliti relativ reduse i puin performante ca i server web. El este recomandat mai ales n faza de testare a unei aplicaii. Pentru a reduce consumul de resurse Tomcat, utilizatorul poate dezactiva complet serviciul Tomcat-Apache. Pentru aceasta, este suficient s se tearg din fiierul de configurare liniile dintre <Service name="Tomcat-Apache"> i </Service>. Evident, se recomand nainte salvarea fiierului de configurare ntr-o copie de siguran, ca la nevoie s poat fi refcut. Pentru aplicaii de anvergur, n care pot s apar muli clieni, practica a demonstrat c Tomcat-Standalone nu face fa rezonabil. De aceea se recomand ca el s se ocupe doar de gestiunea servlet-urilor i s fie degrevat total de partea static (vezi fig. 7.1 i 7.2). Sarcinile de natur static vor fi transmise unui server web profesional, ca de exemplu Apache. n acest scop se folosete serviciului oferit de Tomcat-Apache. Containerul Tomcat, integrat mpreun cu

341

server-ul Apache ofer performane mai bune pentru aplicaii i ofer aplicaiilor web un mediu mai sigur de rulare. Astfel, cnd Apache primete din exterior o cerere pentru o resurs care nu solicit containerul Tomcat (vezi fig. 7.2), atunci cererea va fi deservit direct de Apache. Dac cererea invoc containerul, atunci server-ul web trimite cererea containerului. Acesta o proceseaz, d rspunsul ctre Apache, iar acesta la rndul lui rspunde clientului. Pentru integrarea Tomcat cu Apache, sunt necesare mici configurri att la Tomcat, ct i la Apache. Pentru partea Tomcat, este suficient s se pstreze n fiierul de configurare liniile prin care se definete serviciul Tomcat-Apache. n interiorul acestui tag se definete conectorul Warp, care la rndul lui definete un port pe care ascult cereri de la server-ul web. Partea aceasta din configurare este:
<Service name="Tomcat-Apache"> - - <Connector className="org.apache.catalina.connector.warp.WarpConnector" port="8008".... appBase="webapps" .../> - - </Service>

n fiierul de configurare httpd.conf al server-ului Apache trebuie adugate cteva linii, pentru ca acesta s poat comunica cu conectorul Warp:
LoadModule webapp_module libexec/mod_webapp.so WebAppConnection numeConexiune masinaTomcat:8008 WebAppDeploy $WEBAPP_NAME numeConexiune /$WEBAPP_NAME/

Din punct de vedere formal, exist o singur linie LoadModule. Pentru fiecare main Tomcat la care se dorete conexiune, trebuie dat cte o linie WebAppConnection, cu nume de conexiuni diferite. Pentru fiecare aplicaie conectabil la un Tomcat, trebuie dat cte o linie WebAppDeploy. La nivel de server web, exist un modul adiional numit mod_webapp.so (cazul Apache) sau mod_webapp.dll (cazul Microsoft IIS), de comunicare cu Tomcat. Prin intermediul primei linii se comand serverului web s ncarce n memorie modulul adiional corespunztor. Fiecare linie WebAppConnection definete o conexiune Warp (numeConexiune) la o anumit main Tomcat. Dac este cazul, numrul de port (8008) va fi schimbat n cele dou fiiere de configurare. Urmeaz cte o line WebAppDeploy pentru fiecare aplicaie web conectabil la Tomcat. S numin $WEBAPP_NAME numele contextului aplicaiei. Server-ul web caut n URL apariia substring-ului "/$WEBAPP_NAME/". n caz c n URL apare acest substring imediat dup specificarea mainii i a portului, cererea este trimis la Tomcat spre a fi 342

executat. Selectarea containerului Tomcat se face prin numeConexiune. Aplicaia pe care o va rula acest Tomcat va fi $WEBAPP_NAME, rezident n directorul lui de aplicaii webapps (indicat prin atributul appBase n <Connector). Dac string-ul "/$WEBAPP_NAME/" nu apare, atunci cererea este executat de ctre Apache.

7.1.4. Funcionarea unui servlet


7.1.4.1. Cum arat un servlet? Aa cum am mai spus, un servlet reprezint n programarea web o alternativ la CGI. Asta nseamn c trebuie s preia o cerere HTTP printr-una din metodele GET, POST etc. Apoi trebuie s ntoarc un rspuns cu respectarea protocolului HTTP. Formal, un servlet este o clas care extinde clasa Servlet i utilizeaz metoda service a acestei clase pentru tratarea cererii i a rspunsului. Din punct de vedere practic, marea majoritate a servlet-urilor extind subclasa HttpServlet a clasei Servlet, ea fiind aliniat complet la protocolul HTTP. Practic, toate exemplele noastre folosesc clasa HttpServlet pentru proiectarea servlet-urilor. Fragmentul de program surs de mai jos prezint schema cadru a prii eseniale a unui servlet, primirea unei cereri i ntoarcerea rspunsului.
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class UnServlet extends HttpServlet { public void doGet ( // Metoda GET // public void doPost( // Metoda POST HttpServletRequest cerere, HttpServletResponse raspuns) throws ServletException, IOException { - - // Obtinerea elementelor cererii, eventual prin: // cerere.getParameter(...) la cerere text // cerere.getInputStream() la cerere binara - - // Obtinerea unui obiect pentru trimiterea raspunsului: // raspuns.getWriter() pentru raspuns text // raspuns.getOutputStream() pentru raspuns binar - - // Tratarea cererii si pregatirea raspunsului - - -

343

// Fixarea si trimiterea unor parametri din antetul raspunsului // Creearea si trimiterea corpului raspunsului - - // Inchiderea obiectelor } // UnServlet.doGet } // UnServlet

Lipsete de aici partea de iniializare, n fapt nu ntotdeauna prezent, ca i partea de distrugere a servlet-ului, opional i ea. n seciunile urmtoare vom relua i detalia elementele constitutive ale unui servlet. 7.1.4.2. Interaciunea cu clienii Un servlet gestioneaz cererile clienilor cu ajutorul metodei service. n funcie de metoda (GET sau POST) cu care a fost formulat cererea clientului, metoda service transmite cererea spre procesare, unor metode precum doGet sau doPost. Comunicarea este fcut cu respectarea protocolului HTTP [86, 24, 51]. Metodele din clasa HttpServlet, care proceseaz cererile clienilor, au dou argumente: un obiect HttpServletRequest care ncapsuleaz datele transmise de la client; un obiect HttpServletResponse care ncapsuleaz rspunsul server-ului spre clieni. Accesarea datelor transmise prin cererea clientului Pentru accesarea datelor transmise prin cererea trimis de client, obiectul cerere (de tip HttpServletRequest) permite obinerea elementelor constitutive ale cererii. De cele mai multe ori, cererea este dat printr-un string de cerere coninnd nume de cmpuri i valori ale acestora, sub forma:
nume1=valoare1&nume2=valoare2...

Acest string de cerere poate sosi fie ca opiune adugat la URL-ul cererii, n cazul metodei GET, fie ca i corp al cererii n cadrul metodei POST. String-ul de cerere nu trebuie parsat, valorile cmpurilor se obin cu ajutorul metodei getParameter. Ea primete la intrare numele cmpului i d la ieire valoarea acestuia. La nevoie mai pot fi folosite i alte metode ale obiectului cerere, cum ar fi getParameterNames pentru obinerea numelor cmpurilor, sau getParameterValues pentrua obine toi parametrii. De asemenea, 344

exist mai multe metode prin care se pot accesa valori din antetul (header-ul) cererii. n cazul metodei POST este posibil ca n corpul cererii s fie cod binar. Pentru accesarea acestuia, obiectul cerere permite obinerea unui obiect IO de acces, de tip ServletInputStream, obinut de la metoda getInputStream. Tot metoda POST permite, ca alternativ la codul binar, s aib n corp linii text. Pentru accesarea acestora, obiectul cerere permite obinerea unui obiect IO de acces, de tip BufferedReader, obinut de la metoda getReader. Transmiterea rspunsului Obiectul rspuns (de tip HttpServletResponse parametru al metodei doGet sau doPost), nglobeaz conform protocolului HTTP, rspunsul ctre client. Mai nti, servlet-ul fixeaz header-ul HTTP al mesajului de rspuns. De exemplu, metoda setContentType seteaz tipul coninutului rspunsului. Pentru trimiterea corpului rspunsului, obiectul HttpServletResponse ofer unul dintre urmtoarele dou moduri de a transmite date: folosind metoda getWriter, care ntoarce un obiect Writer, pentru a scrie date de tip text; folosind metoda getOutputStream, care ntoarce un obiect ServletOutputStream, pentru a scrie date binare. 7.1.4.3. Primul exemplu i compilarea unui servlet Cel mai simplu servlet este unul de tip HelloWorld, care nu primete nici o cerere i d ca rspuns un text static. Pentru a fi n ton cu schema cadru (7.1.4.1) vom presupune pentru primul servlet c ar putea eventual primi i un string de cerere. Sursa acestuia este dat n programul 7.1.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class Primul extends HttpServlet { public void doGet(HttpServletRequest cerere,

345

HttpServletResponse raspuns) throws ServletException, IOException { raspuns.setContentType("text/html"); PrintWriter out = raspuns.getWriter(); out.println("<HTML>"); out.println("<HEAD>"); out.println("<TITLE>Primul servlet</TITLE>"); out.println("</HEAD>"); out.println("<BODY>"); out.println("<CENTER><H1>Salutare!</H1></CENTER>"); out.println("Am primit: "+cerere.getQueryString()); out.println("</BODY>"); out.println("</HTML>"); out.close(); } // Primul.doGet } // Primul Programul 7.1. Primul.java

Pentru a putea compila un servlet, compilatorul trebuie s poat accesa arhiva servlet.jar. n cazul Tomcat, aceast arhiv se afl n $CATALINA_HOME/common/lib. Utilizatorul poate: s seteze permanent variabila de mediu CLASSPATH ca s includ aceast arhiv; s copieze arhiva ntr-un subdirector vizibil prin CLASSPATH; s compileze cu opiunea -classpath, eventual prin intermediul unui fiier de comenzi. pentru situaii ocazionale, ce poate folosi, spre exemplu, urmtorul fiier de comenzi:
set CPATH=.;c:\%CATALINA_HOME%\common\lib\servlet.jar javac -classpath "%CPATH%" *.java

El este scris pentru platforma Windows, dar se poate adapta uor la Unix. Pentru exemplele (simple) care urmeaz, am creat un director cu numele test n %CATALINA_HOME%\webapps, mpreun cu structura descris n 7.1.3.5. Acesta va fi contextul pentru cteva dintre servlet-urile care urmeaz. Dup compilare, fiierul Primul.class al servlet-ului se depune n:
%CATALINA_HOME%\webapps\test\WEB-INF\classes

Dup restartarea Tomcat, servlet-ul se poate apela sub diverse forme. Mai nti vom apela: http://localhost:8080/test/servlet/Primul. Imaginea afiat de navigator va fi cea din fig. 7.5. 346

Figura 7.5. Apel al servlet-ului Primul

Apelm apoi servlet-ul de pe o alt main i n plus i transmitem i ceva parametri. Imaginea la client va fi cea din fig. 7.6, unde se poate vedea i URL-ul de apel.

Figura 7.6. Al doilea apel al servlet-ului Primul

S nlocuim n sursa servlet-ului Primul (programul 7.1) "text/html" cu "text/plain". Prin aceasta, navigatorul va interpreta rspunsul ca un text simplu i va afia imaginea din fig. 7.7.

Figura 7.7. Apel servlet Primul cu text/plain

347

Acelai servlet se poate apela i prin intermediul unui formular. Documentul HTML din programul 7.2 este un exemplu de apel al acestui servlet. Programul servlet se poate lansa i dintr-un formular. S crem fiierul:
<html> <head> <title>Inainte de servlet</title> </head> <body> <form method=get action=http://florin.cs.ubbcluj.ro/florin/servlet/Primul> <input type=submit value= "Apelul servletului Primul"> </form> </body> </html> Programul 7.2. Primul.html

Depunem fiierul Primul.html n


%CATALINA_HOME%\webapps\test\

relansm Tomcat, dup care apare imaginea din fig. 7.8.

Figura 7.8. Apel Primul prin formular

Dup ce se d click pe butonul "Apelul servletului Primul", va apare imaginea cu salutul servlet-ului Primul. 7.1.4.4. Interaciunea cu containerul Relativ la ciclul de via al unui servlet n cadrul containerului, distingem cinci momente eseniale: 1. 2. Containerul creeaz o instan a servlet-ului. Containerul apeleaz metoda init a instanei.

348

3.

La apariia unei cereri, containerul creeaz un thread nou (se alimenteaz dintr-un pool intern de thread-uri) i din acesta apeleaz metoda service a instanei servlet-ului pentru tratarea cererii. nainte de distrugerea instanei, containerul apeleaz metoda destroy. Instana este distrus i pus la dispoziia garbage collection.

4. 5.

Cnd containerul ncarc un servlet, el apeleaz metoda init, pentru a realiza diferite operaii de iniializare specifice acestuia. Aceast metod este oferit de clasa HttpServlet i poate fi suprascris n servlet-ul curent. Iniializarea se termin nainte ca servlet-ul s rspund la cererile clienilor i nainte ca servlet-ul s fie distrus. ntr-o seciune viitoare vom reveni asupra iniializrilor unui servlet. Servlet-urile sunt capabile s deserveasc simultan mai muli clieni, deoarece fiecare cerere este tratat ntr-un thread separat. n cazul n care procesarea cererilor acceseaz o resurs partajat, n servlet trebuie prevzute secvene de sincronizare. Exist posibilitatea de procesare a unei singure cereri la un moment dat. Pentru aceasta, servlet-ul trebuie s implementeze interfaa SingleThreadModel, care nu presupune descrierea unor metode suplimentare. La terminarea ciclului de via a unui servlet, de exemplu la cererea administratorului server-ului, se apeleaz metoda destroy. Aceast funcie este oferit de clasa HttpServlet i poate fi suprascris n servlet-ul curent, pentru eliberarea unor resurse, pentru nchiderea unor fiiere etc. nainte de apelul lui destroy, containerul ateapt, eventual un interval maxim de timp, ca toate instanele ce deservesc cereri ale clienilor, s se ncheie. n cazul unor cereri care necesit un timp mai mare de procesare, se pot folosi urmtoarele tehnici: Server-ul menine un contor care indic cte thread-uri ruleaz metoda service n mod curent. Metoda destroy este apelat cnd acest contor ajunge la valoarea 0. Metoda destroy anun thread-urile care proceseaz aceste cereri c trebuie s-i ncheie activitatea, dup care ateapt terminarea lor. Un thread care proceseaz timp mai ndelungat verific periodic dac server-ul a primit o cerere de shutdown, i n caz afirmativ i ncheie activitatea. Un servlet poate fi rencrcat numai dup ce a fost distrus de ctre destroy. 349

Dac pentru servlet au fost specificai parametri de iniializare, numele acestora poate fi obinut apelnd n funcia init metoda getParameterNames(). Pentru a obine valoarea unui anumit parametru se apeleaz metoda getInitParameter, avnd ca argument numele parametrului. Accesul unui servlet la resurse locale se face prin intermediul containerului. Cele mai importante resurse locale sunt fiierele, iar pentru accesul la ele este indicat una dintre urmtoarele trei metode: 1. acces la directorul implicit al containerului; 2. accesul la directorul implicit al aplicaiei; 3. acces n orice loc al sistemului de fiiere prin specificare absolut. 1. Directorul implicit al containerului este $CATALINA_HOME/bin. Toate fiierele invocate de servlet prin numele lor, sau printr-o cale relativ, sunt cutate n acest director. Metoda este foarte bun, dar n cazul unui container utilizat de ctre mai muli useri pot s apar dificulti legate de suprapunere de nume, de lipsa unor drepturi de acces sau de prea multe drepturi de acces etc. Este metoda cea mai recomandat, deoarece este normal ca fiierele locale ale aplicaiei s se afle n interiorul contextului. Cel mai simplu este s se depun fiierele n interiorul contextului aplicaiei, servlet-ul s i determine mai nti calea real pn la context, dup care s citeze fiierele prefixate cu aceast cale. De exemplu, s presupunem c am creat n contextul aplicaiei un director $WEBAPP_HOME/FisiereLocale/, destinat special fiierelor locale ale aplicaiei. n faza de iniializare servlet-ul fixeaz calea absolut spre aceste fiiere astfel:
String cale = getServletContext().getRealPath(""); cale = cale+File.separatorChar+ "FisiereLocale"+File.separatorChar;

2.

Dup aceea, citarea oricrui fiier din acest director se poate face prin:
cale+"numeFisier"

3.

Accesul la un fiier prin specificare absolut, adic prin indicarea ntregii ci de la rdcin i pn la fiier, este foarte simpl. Din pcate, la orice mutare a unui fiier dintre cele folosite de aplicaie trebuie s se intervin n textul surs. Acest neajuns poate fi evitat dac aceste specificri absolute sunt transmise la iniializare ca i parametri de configurare a aplicaiei.

350

7.1.4.5. Configurarea i iniializarea servlet-urilor. Exemple Am vzut cum se face configurarea containerului prin modificarea fiierului lui de configurare: $CATALINA_HOME\conf\server.xml. n mod analog, fiecare aplicaie NU fiecare servlet, are un fiier de configurare:
$WEBAPP_HOME/WEB-INF/web.xml

Acest fiier mai poart numele de "Deployment Descriptor" al aplicaiei. n el sunt trecute, folosind standardul XML, o serie de informaii specifice pentru servlet-urile sau fiierele .jsp din cadrul aplicaiei. Este vorba de o serie de parametri de iniializare furnizai prin perechi nume - valoare, de specificare a utilizrii unor filtre, de specificare a unor elemente de securizare etc. Nu vom intra n detalii, ci lsm cititorul s consulte [87, 51] pentru amnunte. n cele ce urmeaz vom prezenta doar cteva elemente de configurare i iniializare. Trebuie s precizm din capul locului c la crearea structurii de directori a contextului (7.1.3.5) fiierul web.xml care se depune n structur se recomand s fie unul fr coninut semnificativ. Un astfel de fiier poate fi preluat din distribuia Tomcat i el are coninutul:
<?xml version="1.0" encoding="ISO-8859-1"?> <!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> </web-app>

Acest fiier se gsete n distribuie la:


$CATALINA_HOME/webapps/ROOT/WEB-INF

n funcie de necesitile de configurare, ntre tag-urile <web-app> i </web-app> vor fi introduse construciile XML necesare. Dac nu se introduce nici o informaie de configurare, atunci n loc de perechea <web-app> </web-app> se poate scrie un tag XML vid: <web-app/> n cele ce urmeaz, presupunem c avem, n contextul test dou servlet-uri. Unul este servlet-ul Prim descris mai sus n programul 7.1 i servlet-ul care are sursa ServletCuInit.java ce se poate vedea mai jos n programul 7.4. Ne propunem ca prin configurare s asociem nite nume aliate celor dou servlet-uri. Primul va avea n plus numele First i Unu, iar al doilea va mai avea i numele CuInit. Fiierele class ale celor dou servlet-uri rmn cele iniiale. Ne propunem, de asemenea, ca servlet-ul CuInit s preia un parametru de iniializare numit MAXIM cu valoarea 12. Fiierul de configurare pentru exemplul nostru va fi: 351

%CATALINA_HOME%\webapps\test\WEB-INF\web.xml

Coninutul lui este prezentat n programul 7.3.


<?xml version="1.0" encoding="ISO-8859-1"?> <!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> <servlet> <servlet-name>First</servlet-name> <servlet-class>Primul</servlet-class> </servlet> <servlet> <servlet-name>Unu</servlet-name> <servlet-class>Primul</servlet-class> </servlet> <servlet> <servlet-name>CuInit</servlet-name> <servlet-class>ServletCuInit</servlet-class> <init-param> <param-name>MAXIM</param-name> <param-value>12</param-value> </init-param> </servlet> </web-app> Programul 7.3. web.xml (aplicaia test)

Toate tag-urile XML din acest fiier au semnificaie precis pentru Tomcat. Informaiile concrete de configurare sunt date ca i coninuturi ale celor mai interioare tag-uri. Considerm c fiierul are un coninut foarte sugestiv, motiv pentru care nu l mai comentm. n programul 7.4 prezentm sursa ServletCuInit.java.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class ServletCuInit extends HttpServlet { int numarApeluri=0; int numarMaxim; public void init(ServletConfig config) { numarMaxim = Integer.parseInt(config.getInitParameter("MAXIM")); System.out.println("Initializare. Numarul maxim de "+ "afisari la Tomcat este: "+numarMaxim); } // ServletCuInit.init public void doGet(HttpServletRequest cerere, HttpServletResponse raspuns)

352

throws ServletException, IOException { raspuns.setContentType("text/html"); PrintWriter out = raspuns.getWriter(); out.println("<HTML>"); out.println("<HEAD>"); out.println("<TITLE>Primul servlet</TITLE>"); out.println("</HEAD>"); out.println("<BODY>"); out.println("<CENTER><H1>Salutare!</H1></CENTER>"); out.println("Am primit: "+cerere.getQueryString()); out.println("</BODY>"); out.println("</HTML>"); out.close(); numarApeluri++; if (numarApeluri <=numarMaxim) System.out.println("Apelul nr: "+numarApeluri); destroy(); } // ServletCuInit.doGet } // SevletCuInit Programul 7.4. ServletCuInit.java

Acest servlet este obinut prin adaptarea programului 7.1. Scopul lui este de a demonstra folosirea metodei init. Cititorul poate fi surprins de prezena unor tipriri System.out.println ntr-o aplicaie web! De fapt, toate aceste tipriri sunt efectuate pe consola de lansare Tomcat. Afirile pe aceast consol sunt prezentate n fig. 7.9.

Figura 7.9. Fereastra Tomcat la apelurile CuInit

Dup liniile de pornire a Tomcat, la primul apel al servlet-ului CuInit se va afia linia de iniializare i linia cu apelul nr: 1. n apelurile urmtoare se vor afia, pn la 12 inclusiv, liniile de apel. ncepnd cu cel de-al 13-lea apel, aa dup cum prevede programul, nu se mai afieaz linii pe consola Tomcat. Acest exemplu arat c metoda init este apelat doar o singur dat. 353

Din cauza schimbrilor de nume din aceast aplicaie, se pot folosi att numele atribuite prin configurare, ct i numele de clas ale servlet-urilor, cu o observaie: Comportarea din fig. 7.9 se obine doar dac n URL-ul de apel este de forma:
- - -/servlet/CuInit

Dac n locul lui se apeleaz:


- - -/servlet/ServletCuInit

atunci Tomcat va arunca o excepie n prima linie a metodei init, deoarece metoda getInitParameter nu va gsi parametrul MAXIM!

7.1.5. Servlet vis-a-vis cu CGI


7.1.5.1. Avantajele Servlet fa de CGI Utilizarea tehnologiei servlet este mai avantajoas dect utilizarea CGIurilor clasice, cel puin din urmtoarele motive: Eficiena: Pentru fiecare apel de CGI, server-ul Web lanseaz un nou proces. n cazul apelurilor de servlet, JVM creeaz doar un nou thread, ceea ce, evident, nseamn consum mult mai mic de resurse. Este evident c servlet-urile sunt mult mai uor de proiectat dect CGI. Dezvoltarea unui CGI n Perl sau C reclam respectarea unui numr mare de restricii. n cazul servlet-urilor, acestea fac ntreaga regie n locul programatorului. Servlet-urile Java pot s comunice uor cu servlet-ul Web, n timp ce CGI-urile nu prea. Parametrii de cerere i rspuns din servlet-uri ofer numeroase posibiliti de colaborare cu serverul Web.

Uurina proiectrii:

Putere:

Portabilitate: Java este un limbaj perfect portabil. n consecin, toate containerele sunt perfect compatibile ntre ele, deci aplicaiile pot fi transportate fr probleme ntre platforme. Soluii ieftine: Java, cu tot ce ine de ea, inclusiv Apache, sunt produse free. n schimb, platformele tradiionale, sunt pe baz de licen etc.

354

7.1.5.2. Acces la variabilele de mediu CGI Pentru pstrarea complet a compatibilitii cu CGI, clasele de definire i utilizare a servlet-urilor dein instrumentele necesare accesului la toate variabilele de mediu oferite CGI-urilor. n tabelul urmtor am presupus c request este parametrul cerere pentru service, doGet sau doPost din servlet. Acest tabel d funciile i corespondenele necesare accesului la aceste variabile.
Variabil CGI AUTH_TYPE CONTENT_LENGTH CONTENT_TYPE DOCUMENT_ROOT HTTP_XXX_YYY Semnificaie Acces din doGet sau doPost

Furnizeaz tipul de autorizare, request.getAuthType() dac exist Numai pentru cereri POST, d request.getContentLength() numrul de octei trimii Tipul MIME ataat datelor request.getContentType() Calea spre director, corespun- getServletContext(). ztoare lui http://host/ getRealPath("/") Acces la un antet oarecare HTTP request.getHeader( "Xxx-Yyy")

PATH_INFO

Informaia path ataat unui URL. Deoarece servlet-ul, spre deosebire de CGI, poate discuta request.getPathInfo() cu server-ul web, nu este necesar tratarea separat a acestei informaii

Map-area real a unei informaii PATH_TRANSLATED path. Ca mai sus, nu este request.getPathTranslated() necesar la servlet Pentru cereri GET, ntoarce irul cerere codificat de client. Rar este necesar parsarea irului, request.getQueryString() deoarece cererea are metoda request.getParameter Adresa IP a clientului Adresa Internet a clientului request.getRemoteAddr() request.getRemoteHost()

QUERY_STRING

REMOTE_ADDR REMOTE_HOST REMOTE_USER

Partea user, dac se folosete request.getRemoteUser() autorizarea Intoarce metoda de cerere; uzual GET sau POST, dar ocazional request.getMethod() HEAD, PUT, DELETE, OPTIONS, TRACE. Calea spre servlet request.getServletPath()

REQUEST_METHOD SCRIPT_NAME

355

Variabil CGI SERVER_NAME SERVER_PORT

Semnificaie Numele server-ului Web Portul la care ateapt server-ul

Acces din doGet sau doPost request.getServerName() request.getServerPort()

Name i versiunea protocolului SERVER_PROTOCOL de cerere request.getProtocol() ( HTTP/1.0 sau HTTP/1.1). SERVER_SOFTWARE Informaii despre server-ul Web getServletContext(). getServerInfo()

Programul 7.5 afieaz coninutul variabilelor CGI dintr-un servlet.


import import import import java.io.*; javax.servlet.*; javax.servlet.http.*; java.util.*;

public class ShowCGIVariables extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); String[][] variables = { { "AUTH_TYPE", request.getAuthType() }, { "CONTENT_LENGTH", String.valueOf( request.getContentLength()) }, { "CONTENT_TYPE", request.getContentType() }, { "DOCUMENT_ROOT", getServletContext().getRealPath("/") }, { "PATH_INFO", request.getPathInfo() }, { "PATH_TRANSLATED", request.getPathTranslated() }, { "QUERY_STRING", request.getQueryString() }, { "REMOTE_ADDR", request.getRemoteAddr() }, { "REMOTE_HOST", request.getRemoteHost() }, { "REMOTE_USER", request.getRemoteUser() }, { "REQUEST_METHOD", request.getMethod() }, { "SCRIPT_NAME", request.getServletPath() }, { "SERVER_NAME", request.getServerName() }, { "SERVER_PORT", String.valueOf(request.getServerPort()) }, { "SERVER_PROTOCOL", request.getProtocol() }, { "SERVER_SOFTWARE", getServletContext().getServerInfo() } }; out.println("<html><head><title>"+ "Variabilele CGI</title></head>" + "<BODY>\n" + "<H1 ALIGN=CENTER>Variabilele CGI</H1>\n" + "<TABLE BORDER=1 ALIGN=CENTER>\n" + "<TR>\n" + "<TH>Variabla CGI<TH>Valoare");

356

for(int i=0; i<variables.length; i++) { String varName = variables[i][0]; String varValue = variables[i][1]; if (varValue == null) varValue = "<I>(NESPECIFICATA)</I>"; out.println("<TR><TD>" + varName + "<TD>" + varValue); } // for out.println("</TABLE></BODY></HTML>"); } // ShowCGIVariables.doGet public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } // ShowCGIVariables.doPost } // ShowCGIVariables Programul 7.5. ShowCGIVariables.java

Execuia servlet-ului va avea ca rezultat imaginea din fig. 7.10.

Figura 7.10. Lista variabilelor CGI

357

7.2. Faciliti specifice servlet


n acest seciune vom prezenta exemple de lucru cu metoda GET i POST, facilitatea forward pentru servlet-uri, cookies, session etc. Toate exemplele sunt rulate ntr-o aplicaie cu numele pdpj, creat prin crearea directorului:
%CATALINA_HOME%\webapps\pdpj

Odat cu el s-a creat ntreaga structur de sub el, inclusiv fiierul web.xml vid.

7.2.1. Exemple de aplicare GET, POST, forward


7.2.1.1. Servlet-ul Maxim, apelat prin GET i prin POST Vom construi un servlet simplu care primete de la un formular client dou numere (reprezentate prin string-uri) i ntoarce cel mai mare dintre acestea. Formularul prezentat de navigator este prezentat n fig. 7.11.

Figura 7.11. Formular pentru Maxim

Textul HTML al formularului este prezentat n programul 7.6.


<html><head><title>Maximul a doua numere</title></head> <body><form action="http://localhost:8080/pdpj/servlet/Maxim" method="get" > <p>Primul numar:<input type="text" name="unu"> <p>Al doilea:<input type="text" name="doi">

358

<p><input type=submit value= "Maxim"> </form></body></html> Programul 7.6. Maxim.html

Dup apsarea butonului <Maxim>, rspunsul servlet-ului ajunge la client aa cum se vede n fig. 7.12.

Figura 7.12. Rezultatul apelului Maxim

Metoda de cerere este GET. De aceea se observ n URL-ul cererii ctre servlet c, ncepnd cu semnul de ntrebare, apare string-ul care reprezint cererea. Textul surs al servlet-ului este prezentat n programul 7.7.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class Maxim extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { int unu, doi; unu = Integer.parseInt(req.getParameter("unu")); doi = Integer.parseInt(req.getParameter("doi")); res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.println("<HTML><HEAD>"); out.println("<TITLE>Maximul a doua numere</TITLE>"); out.println("</HEAD><BODY>"); out.println("<CENTER><H1>Maxim("+unu+","+doi+ ")= "+((unu>doi)?unu:doi)+"</H1></CENTER>"); out.println("</BODY></HTML>");

359

out.close(); } // Maxim.doGet public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doGet(req, res); } // Maximum.doPost } // Maxim Programul 7.7. Maxim.java

Pentru apelul prin metoda GET, penultimele patru linii ale textului surs al servlet-ului nu sunt necesare! Dac utilizatorul dorete ca n acest exemplu s comunice prin metoda POST, atunci sunt necesare doar dou modificri: 1. n linia a doua a formularului HTML trebuie nlocuit method=GET cu method=POST. 2. n linia a cincea a sursei servlet se va nlocui doGet cu doPost. Fcnd aceste modificri, rspunsul de pe navigator va coincide cu cel de la GET, mai puin string-ul de cerere care nu va apare n URL. Cu penultimele patru linii (definirea lui doPost prin doGet sau este posibil i invers) prezente n sursa servlet-ului, servlet-ul accept oricare dintre metodele de cerere, n funcie de cum i se transmite de la client. 7.2.1.2. Servlet-ul DisplayQueryString Servlet-ul care urmeaz este unul simplu: el primete prin metoda GET un ir de cerere oarecare i l afieaz ca rspuns pe navigatorul clientului. Textul surs al servlet-ului este dat n programul 7.8.
import import import import javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*;

public class DisplayQueryString extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { StringTokenizer st = new StringTokenizer(req.getQueryString(), "&"); String nume;

360

res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.println("<HTML><HEAD>"); out.println("<TITLE>Argumentele de apel</TITLE>"); out.println("</HEAD><BODY>"); out.println("<CENTER><H1>Urmeaza "+st.countTokens()+ " parametri</H1></CENTER>"); for ( ; st.hasMoreTokens(); ) { nume = st.nextToken(); out.println("<P>"+nume+"</P>"); } // for out.println("</BODY></HTML>"); out.close(); } // DisplayQueryString.doGet } // DisplayQueryString Programul 7.8. DisplayQueryString.java

n fig. 7.13 este prezentat un exemplu de apel al acestui servlet:

Figura 7.13. Rezultatul DisplayQueryString

7.2.1.3. Servlet-ul MaximForward Servlet-ul care urmeaz exemplific una dintre facilitile oferite de ctre tehnologia servlet. Este vorba de posibilitatea ca dintr-un servlet s se apeleze alt servlet. Pentru a exemplifica, ne inspirm dup servlet-ul Maxim prezentat

361

mai sus i vom crea un nou servlet. Noul servlet va purta numele MaximForward i sursa lui este prezentat n programul 7.9.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class MaximForward extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { ServletContext sc = getServletContext(); RequestDispatcher rd; String sunu, sdoi; int unu, doi; sunu = req.getParameter("unu"); sdoi = req.getParameter("doi"); if ((sunu==null)||(sdoi==null)) { rd = sc.getRequestDispatcher("/MaximError.html"); rd.forward(req,res); } else { unu = Integer.parseInt(sunu); doi = Integer.parseInt(sdoi); unu = (unu>doi)?unu:doi; rd = sc.getRequestDispatcher( "/servlet/DisplayQueryString?maxim="+unu); rd.forward(req,res); } // if } // MaximForward.doGet } // MaximForward Programul 7.9. MaximForward.java

Prezentul servlet primete parametrii cererii, iar pentru rspuns, care va fi unul din dou posibile, va transfera spre tratare altei componente. Pentru aceasta, MaximForward va folosi dou obiecte speciale: Un obiect de tip ServletContext care deine elementele necesare realizrii aciunii de forward. Un obiect de tip RequestDispatcher, furnizat de obiectul ServletContext, care transmite cererea de forward ori spre o resurs static a server-ului web, ori spre un server. Pentru realizarea de forward, servlet-ul cere obiectului su context o instan a dispecerului de cereri (gestionat de container). Apoi cere acestuia din urm retransmiterea unei cereri i a unui rspuns ctre noua resurs. n exemplul nostru, vom introduce n 362

%CATALINA_HOME%\webapps\pdpj\

resursa static constituit din documentul MaximError.html, prezentat n programul 7.10.


<html><head><title>Eroare Maxim</title></head> <body> <h3>Ne para rau, trebuie sa dati doi parametri</h3> </body> </html> Programul 7.10. MaximError.html

Apoi servlet-ul MaximForward verific dac string-ul cerere venit de la client este corect. Dac acesta nu este corect, atunci servlet-ul face forward ctre resursa MaximError.html de mai sus. n cazul n care string-ul de cerere este corect, atunci determin valoarea maxim, dup care lanseaz o cerere GET cu rspunsul ctre servlet-ul DisplayQueryString prezentat n seciunea precedent. Lsm pe seama cititorului s testeze modul de comportare a acestui ansamblu de resurse statice i de servlet-uri.

7.2.2. Comunicare applet - servlet


Dei este cel mai natural mod de comunicare Java peste web, literatura trateaz prea puin acest subiect [31] 7.2.2.1. Elemente specifice de comunicare Este natural ca ntre un client applet i un servlet s existe o comunicare de o factur mai deosebit. n mod natural, aa cum am artat la CGI, un applet poate contacta un servlet folosind URLConnection, cu tot ce am prezentat acolo. n plus, deoarece este vorba de doi protagoniti Java, acetia pot s-i construiasc pentru comunicare obiecte ObjectInputStream i ObjectOutputStream. Cu acestea din urm, protagonitii i pot transmite unul altuia obiecte Java! Evident, transmiterea de obiecte este posibil doar folosind metoda de cerere POST. n plus, conform rigorilor protocolului URLConnection (parte din HTTP), clientul poate s fac, eventual o scriere, dup care o citire i att! Dac se doresc schimburi multiple, atunci acestea vor fi fcute prin intermediul unor URLConnection diferite, care vor comunica cu instane thread diferite ale servlet-ului. 363

n mai multe dintre exemplele care urmeaz am preferat schimbul de obiecte ntre applet i servlet, datorit eleganei cu care se lucreaz. Vom ncepe printr-un exemplu simplu, reluare a exemplului prezentat n 4.7.2 (transformarea unui ir prin CGI). 7.2.2.2. Clientul Applet2Servlet Elementele acestui client sunt documentul HTML din programul 7.11 i applet-ul a crui surs este prezentat n programul 7.12. Att documentul HTML, ct i fiierul class al applet-ului trebuie depuse (n cazul nostru) n:
%CATALINA_HOME%\webapps\pdpj\

Prin acest exemplu mai ilustrm nc un element, cel puin insolit, de programare Java. Artm o modalitate simpl prin care un applet citete din documentul HTML invocator un numr variabil de parametri.
<html><head><title>Aplet ce apeleaza un Servlet</title></head> <body><APPLET code="Applet2Servlet.class" width=500 height=300> <param name="Linia1" value="Aceasta este linia 1"> <param name="Linia2" value="Aceasta este linia 2"> <param name="Linia3" value="Aceasta este linia 3"> <param name="Linia4" value="Aceasta este linia 4"> <param name="Linia5" value="Aceasta este linia 5"> <param name="Linia6" value="Aceasta este linia 6"> <param name="Linia7" value="Aceasta este linia 7"> <param name="Linia8" value="Aceasta este linia 8"> <param name="Linia9" value="Aceasta este linia 9"> <param name="Linia10" value="Aceasta este linia 10"> <param name="Linia11" value="Aceasta este linia 11"> <param name="Linia12" value="Aceasta este linia 12"> </APPLET></body></html> Programul 7.11. Applet2Servlet.html import import import import java.net.*; java.io.*; java.applet.*; java.awt.*; Applet2Servlet extends Applet {

public class

public void init() { URL url; URLConnection urlCon; ObjectInputStream in; ObjectOutputStream out; TextArea raspuns = new TextArea(); int i;

364

String s="", l; try { for (i=1; ((l = getParameter("Linia" + i))!= null); i++) s += l+"\n"; url = new URL("http://"+getCodeBase().getHost()+ ":8080/pdpj/servlet/Servlet2Applet"); urlCon = url.openConnection(); // Deschide conexiunea urlCon.setUseCaches(false); urlCon.setDefaultUseCaches(false); urlCon.setDoOutput(true); // Permite si scrierea in resursa out = new ObjectOutputStream(urlCon.getOutputStream()); out.writeObject(s); in = new ObjectInputStream(urlCon.getInputStream()); s = (String)in.readObject(); in.close(); out.close(); this.add(raspuns); // Adauga obiectul la applet this.setVisible(true); // Fa applet-ul vizibil raspuns.setText(s); // Depune liniile de raspuns } // try catch(Exception e) { e.printStackTrace(); } // catch } // Applet2Server.init } // Applet2Servlet Programul 7.12. Applet2Servlet.java

Mai nti, clientul applet i extrage din tag-urile <PARAM existente n a.html liniile pe care le va transmite servlet-ului spre a le transforma n litere mari. Din toate aceste linii la applet se formeaz un singur string. Apoi se deschide conexiunea URL, se configureaz aa nct pe ea s se poat citi i scrie, iar transmisia s se fac direct, fr staionri prin zone catch. Se creeaz un obiect out de tip ObjectOutputStream prin intermediul cruia se trimite obiectul String la servlet. Pentru partea a doua receptarea rspunsului, applet-ul citete de la servlet, folosind obiectul in de tip ObjectInputStream, un obiect de tip String. Acesta este apoi depus pentru a fi afiat pe navigator. 7.2.2.3. Servlet-ul Servlet2Applet Perechea servlet pentru clientul prezentat mai sus este extrem de simpl. El suprascrie metoda doPost. n ea i creeaz din obiectul cerere un obiect ObjectInputStream, iar din obiectul rspuns un obiect ObjectOutputStream. Prin intermediul acestora, pereche cu cele duale din applet, va primi un obiect String i va ntoarce obiectul String cu toate 365

literele transformate n litere mari. Sursa servlet-ului este prezentat n programul 7.13.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class Servlet2Applet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { ObjectInputStream in = new ObjectInputStream(req.getInputStream()); ObjectOutputStream out = new ObjectOutputStream(res.getOutputStream()); String s = (String)in.readObject(); s = s.toUpperCase(); out.writeObject(s); out.flush(); in.close(); out.close(); } // try catch (ClassNotFoundException e) { e.printStackTrace(); } // catch } // Servlet2Applet.doPost } // Servlet2Applet Programul 7.13. Servlet2Applet.java

Dup depunerea fiierului Servlet2Applet.class n:


%CATALINA_HOME%\webapps\pdpj\WEB-INF\classes

i restartarea Tomcat, efectul execuiei este cel din fig. 7.14. n mod firesc, el se aseamn foarte mult cu cel din fig. 4.12.

366

Figura 7.14. Efectul apelului applet <--> servlet

367

7.2.3. Utilizare cookies


7.2.3.1. Noiunea de cookie Cookies sunt mici informaii textuale pe care un server web le genereaz i le trimite la un browser spre a fi accesate ulterior. La un nou apel al browserului spre server-ul web, browser-ul le returneaz neschimbate server-ului generator. Prin acest mecanism, server-ul Web se poate folosi de aceste informaii. De fapt, mecanismul este folosit adesea pentru a depi restricia comunicrii fr stare (stateless client/server communication) ntre browser i server-ul web. Din punct de vedere formal, un cookie este o pereche de forma (nume, valoare), reprezentate ASCII i fr spaii n interiorul lor. Printre sarcinile pe care informaiile cookie le pot ndeplini enumerm: identificarea unui user n timpul unei sesiuni e-commerce; controlul username password n doi pai, dei comunicarea web este fr stare; ajustarea parametrilor unui site n funcie de clientul care l consult; optimizarea cutrii frecvente a unor informaii reinerea unor parametri mai des invocai; controlul unor elemente de protecie i securitate a comunicaiei ntre browser i server-ul web. Din pcate, spaiul rezervat pentru cookies este limitat. Ca i cifre aproximative, unele browsere accept maximum 20 cookies/site i 300 total, spaiul total limitat la 4Ko etc. Trebuie reinut faptul c dei navigatorul gzduiete cookies, titularul lor este server-ul generator. Mai mult, unele browsere nici mcar nu permit vizualizarea acestor cookies. Altele, ca de exemplu Mozilla, permit vizualizarea acestora. Un element de mare nesiguran privitor la cookies este faptul c orice utilizator al unui navigator poate s-i comande acestuia tergerea cookies! Pentru informaii privind cookies la Internet Explorer, utilizatorul poate consulta: Tools / Internet options / Delete cookies. Cookies nu se pot vizualiza n IE. Pentru Mozilla, se poate consulta: Edit / Preferences / Privacy & Security / Cookies / manager / Cookies manager. Prelucrarea cookies n servlet trebuie s respecte anumite cerine. Deoarece cookies sunt transmise prin header-ul cererii, acestea trebuie citite primele, nainte de citirea parametrilor. Analog, atunci cnd servlet-ul transmite cookies spre navigator, acesta trebuie s trimit mai nti cookies i abia apoi rspunsul efectiv. 368

Nu intenionm s prezentm API cookies. n schimb, vom prezenta n seciunea urmtoare un exemplu de utilizare cookies care acoper multe dintre aspectele de utilizare a acestora. 7.2.3.2. Exemplu de utilizare cookies Exemplul pe care l prezentm este de fapt o schem cadru pentru servlet-uri care doresc s utilizeze cookies. Concret, tiut fiind c orice cerere de la un client este o pereche de string-uri (nume, valoare), servlet-ul i propune s contorizeze, prin cookies, de cte ori sunt invocate fiecare dintre nume care apar n cererile de la un anumit client (browser). Sursa acestui servlet este prezentat n programul 7.14.
import import import import javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*;

public class CookieCountParameters extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { int i, k; Cookie c; Cookie[] tc = req.getCookies(); // tc contine toate Cookies if (tc == null) tc = new Cookie[0]; // Inca nu exista cookies Enumeration e = req.getParameterNames(); // e contine toate numele // parametrilor Vector v = new Vector(); for ( ; e.hasMoreElements(); v.add((String)e.nextElement())); // numele parametrilor sunt puse in vector for ( i=0; i<tc.length; i++ ) { k = v.indexOf(tc[i].getName()); if (k >=0 ) { // un nume de parametru coincide cu un nume de Cookie tc[i].setValue(""+(1+ Integer.parseInt(tc[i].getValue()))); v.remove(k); } // if } // for // // Aici este locul pentru prelucrarea parametrilor de intrare // res.setContentType("text/html"); PrintWriter out = res.getWriter(); // Urmeaza depunerea Cookies vechi for ( i=0; i<tc.length; i++ ) { tc[i].setMaxAge(60*60*24*365);

369

res.addCookie(tc[i]); } // for // Urmeaza depunerea Cookies noi, ramase in v for (i=0; i<v.size(); i++ ) { c = new Cookie((String)v.elementAt(i), "1"); c.setMaxAge(60*60*24*365); res.addCookie(c); } // for // // Aici este locul raspunsului HTML dat de servlet // out.println("<HTML><HEAD>"); out.println("<TITLE>Lista cookies</TITLE>"); out.println("</HEAD><BODY>"); out.println("<h3>Cookies vechi</h3>"); for ( i=0; i<tc.length; i++ ) out.println("<p>"+tc[i].getName()+ " = "+tc[i].getValue()+"</p>"); out.println("<h3>Cookies noi</h3>"); for ( i=0; i<v.size(); i++ ) out.println("<p>"+((String)v.elementAt(i))+"</p>"); out.println("</BODY></HTML>"); out.close(); } // CookieCountParameters.doGet } // CookieCountParameters Programul 7.14. CookieCountParameters.java

Pentru realizarea scopului propus, fiecare dintre cookies va avea drept nume exact numele care este transmis prin cerere. Valoarea fiecrui cookie este un ntreg reprezentnd de cte ori browser-ul a invocat n cereri numele respectiv. Servlet-ul CookieCountParameters reine n tabloul tc toate cookies primite de la navigator. n cazul n care de la navigator nu vine nici un cookie, atunci tc va fi un tablou cu 0 elemente. Dup aceea, n obiectul enumerare e se rein numele cmpurilor din cerere, dup care se depun n vectorul v (pentru o prelucrare mai uoar). Se compar apoi numele cookies din tabloul tc cu numele de parametri cerere actuali din v. n caz de coinciden, valoarea cookie corespunztoare din tabloul tc este mrit cu o unitate i numele respectiv este ters din vectorul v. Pasul urmtor const n fixarea vrstei maxime (un an) att pentru cookie vechi, ct i pentru cele noi i adugarea fiecreia dintre ele n obiectul rspuns res. n final, numai pentru a se evidenia pentru client aciunea servlet-ului, se d ca rspuns un document HTML cu numerele tuturor cookies i numerele lor de apariie, aa cum se vede n fig. 7.15. 370

Figura 7.15. Numrarea parametrilor prin cookies

371

7.2.4. Sesiuni servlet


7.2.4.1. Conceptul de sesiune Obiectele HTTPSession au fost introduse pentru a permite, prin intermediul servlet-urilor, s se asigure comunicare cu stare (statefull client/server communication) ntre clieni i server-ul Web. Formal, un obiect de tip HTTPSession poate reine informaii ntre dou invocri succesive ale servlet-ului din cadrul aceleiai lansri a browser-ului. Informaiile sunt pstrate la server sub forma unor perechi (nume , obiect). Un obiect HTTPSession poate desfura, printre altele, aciunile: rspunde dac este un obiect nou sau nu (metoda isNew); se pot depune obiecte crora li se asociaz nume (metoda setAttribute); se pot extrage obiecte din el invocndu-se numele asociat (metoda getAttribute); etc. Pentru detalii se poate consulta API HTTPSession [89, 91]. Credem c aici este locul de a prezenta, telegrafic, modalitile prin care se obinuiete asigurarea comunicrii Web statefull. ntreinerea unui mecanism cookies cu fiecare navigator; simularea unei sesiuni prin intermediul construciilor ascunse la client, sub forma: <input type=hydden name=session value=...>; aceste informaii ajung la server care le poate interpreta ca i obiecte de tip session; adugarea la URL de ctre client a unor cmpuri cu rol de informaii de tip session; obiecte HTTPSession. Este evident c ultima soluie este cea veritabil, toate celelalte fiind, ntr-un fel sau altul dependente de client. Exemplul care urmeaz ilustreaz utilizarea obiectelor HTTPSession la counteri de pagini Web. Exemplul este unul similar (i inspirat de) cap. 4.7.3 (contor de pagini Web).

372

7.2.4.2. Implementarea unui counter de pagin Web Aa cum am prezentat i n 4.7.3 un counter Web este realizat, n modul cel mai simplu, prin intermediul unui fiier binar care reine ntregul reprezentnd numrul curent de accese. Prezenta implementare utilizeaz: Un fiier binar numit FileServletCounter. Pentru a fi accesat de servlet, am ales plasarea lui n $CATALINA_HOME/bin, care este directorul curent al containerului de servlet. n 7.1.4.4 am expus i alte metode de plasare a fiierelor locale. Un servlet ServletCounter sau ServletCounterSession, dup caz (diferena dintre ele o prezentm mai trziu). Servlet-ul trimite spre client un obiect Integer coninnd numrul de accese. Un client applet AppletCounter care s preia obiectul Integer i s-l afiieze pe pagin ntr-un obiect grafic Label. Fiierul binar poate fi creat, de exemplu, prin intermediul programului 7.15.
import java.io.*; public class CounterInit { public static void main(String a[]) throws Exception { RandomAccessFile f; int i; if (a.length != 2) { System.out.println("Apel: java CounterInit "+ "numeFisier valoareInitialaContor"); System.exit(1); } // if f = new RandomAccessFile(a[0], "rw"); i = Integer.parseInt(a[1]); f.writeInt(i); f.close(); } //CounterInit.main } // CounterInit Programul 7.15. CounterInit.java

7.2.4.3. Clientul counter Aa cum am spus mai sus, clientul efectiv al counter-ului este un applet. Invocarea acestuia n cadrul paginii se face prin tag-ul: 373

<APPLET code="AppletCounter.class" width="50" height="30"> </APPLET>

Textul surs al applet-ului este dat n programul 7.16.


import import import import java.net.*; java.io.*; java.applet.*; java.awt.*; AppletCounter extends Applet {

public class

public void init() { URL url; URLConnection urlCon; ObjectInputStream in; Label raspuns; Integer i; try { url = new URL("http://"+getCodeBase().getHost()+ ":8080/pdpj/servlet/ServletCounter"); urlCon = url.openConnection(); urlCon.setUseCaches(false); urlCon.setDefaultUseCaches(false); in = new ObjectInputStream(urlCon.getInputStream()); i = (Integer)in.readObject(); in.close(); raspuns = new Label(""+i.intValue()); this.add(raspuns); this.setVisible(true); } // try catch(Exception e) { e.printStackTrace(); } // catch } // AppletCounter.init } // AppletCounter Programul 7.16. AppletCounter.java

n cadrul metodei init se stabilete conexiunea URL cu servlet-ul, se citete obiectul Integer de la servlet i se afieaz grafic sub forma unui Label. n cazul apelrii lui ServletCounterSession, singura modificare este nlocuirea numelui servlet-ului. 7.2.4.4. ServletCounter Sursa acestui acestui servlet este dat n programul 7.17. 374

import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class ServletCounter extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { int i=0; RandomAccessFile f; ObjectOutputStream out; synchronized (this.getClass()) { // Blocheaza pentru incrementare f = new RandomAccessFile("FileServletCounter", "rw"); // Deschide fisierul i = f.readInt(); // Citeste numarul i++; // Incrementeaza f.seek(0); // Repozitioneaza la inceput f.writeInt(i); // Rescrie noul numar f.close(); // Inchide fisierul } // synchronized res.setContentType("application/octet-stream"); out = new ObjectOutputStream(res.getOutputStream()); out.writeObject(new Integer(i)); // Trimite numarul la client out.flush(); out.close(); } // ServletCounter.doPost public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } // ServletCounter.doGet } // ServletCounter Programul 7.17. ServletCounter.java

Trebuie mai nti s remarcm faptul c accesul la fiierul binar este sincronizat cu clasa servlet-ului, pentru a se evita multiacces simultan la fiierul contor. Dup incrementare se creeaz obiectul out de tip ObjectOutputStream prin care se trimite applet-ului obiectul Integer care conine numrul de accese. S-au definit att metoda doPost ct i doGet, deoarece, n absena trimiterii unui mesaj de la client nu se poate stabili implicit metoda de cerere. Lsm pe seama cititorului s testeze acest tandem applet - servlet destinat contorizrii accesului la o pagin Web.

375

7.2.4.5. ServletCounterSession De ce a fost nevoie de un alt servlet? Dintr-un motiv foarte simplu. Accesai o pagin ce este contorizat cu ServletCounter definit mai sus. Apoi dai <BACK> la browser i accesai din nou pagina. Efectul va fi c numrul de accese va crete. Ba chiar mai simplu dect <BACK>: cerei navigatorului <RELOAD> i numrul de accese va crete din nou! Evident c aceast numrare (cel puin dup unii) nu este una corect. Din aceast cauz vom prezenta un servlet modificat care va fi imun cu numrarea n situaiile expuse mai sus. El va considera un acces doar primul acces al browser-ului la pagin. Pentru a se numra dou accese, trebuie s se opreasc navigatorul dup care s se lanseze din nou un acces la aceeai pagin!. Pentru aceasta vom folosi un obiect HTTPSession. Sursa servlet-ului ServletCounterSession este dat n programul 7.18.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class ServletCounterSession extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { int i=0; RandomAccessFile f; ObjectOutputStream out; HttpSession sesiune; sesiune = req.getSession(true); // Creeaza o sesiune daca nu exista if (sesiune.isNew()) { synchronized (this.getClass()) { // Blocheaza pentru incrementare f = new RandomAccessFile("FileServletCounter", "rw"); // Deschide fisierul i = f.readInt(); // Citeste numarul i++; // Incrementeaza f.seek(0); // Repozitioneaza la inceput f.writeInt(i); // Rescrie noul numar f.close(); // Inchide fisierul sesiune.setAttribute("contorCurent", new Integer(i)); } // synchronized } else { // sesiune exista deja i = ((Integer)sesiune.getAttribute( "contorCurent")).intValue(); } // if res.setContentType("application/octet-stream"); out = new ObjectOutputStream(res.getOutputStream()); out.writeObject(new Integer(i)); // Trimite numarul la client

376

out.flush(); out.close(); } // ServletCounterSession.doPost public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } // ServletCounterSession.doGet } // ServletCounterSession Programul 7.18. ServletCounterSession.java

De fapt este uor de vzut c este acelai servlet ca i precedentul, cu foarte mici completri. Mai nti din parametrul cerere req s-a creat obiectul sesiune de tip HTTPSession. Se testeaz apoi dac obiectul sesiune este unul nou. Dac da, atunci se introduce n el un obiect Integer care conine valoarea curent a contorului. Acestui obiect i se asociaz numele contorCurent. Dac obiectul sesiune nu este unul nou, se extrage din el obiectul cu numele asociat contorCurent i se trimite acesta clientului.

7.3. Cteva exemple mai complexe


Aceste exemple, la fel ca i cele din seciunea precedent, sunt incluse n aplicaia pdpj.

7.3.1. Trimiterea de mesaje e-mail din servlet


Pentru compilarea i execuia programelor din aceast aplicaie este nevoie ca n zona classpath s poat fi accesat att arhiva servlet.jar, ct i o nou arhiv, j2ee.jar. Ultima arhiv se afl n distribuia "Java Enterprise Edition", j2ee1.3.1. n aceast distribuie, locul ei este n:
c:\j2sdkee1.3.1\lib\j2ee.jar

Aa cum am artat n 7.1.4.3, trebuie efectuat una dintre cele trei aciuni prin care aceast arhiv devine vizibil n zona classpath. Noi am preferat s adugm permanent aceast arhiv la variabila CLASSPATH.

377

7.3.1.1. Clientul mail.html Exemplul care urmeaz reprezint versiunea romn a celui similar descris n [2]. Intrarea se face printr-un formular prezentat n fig. 7.16.

Figura 7.16. Formular de trimitere a unui mesaj

Documentul HTML de descriere a acestui formular este dat n programul 7.19.


<HTML><HEAD><TITLE>Compunere de mesaj</title></HEAD> <BODY> <FORM ACTION="http://florin.cs.ubbcluj.ro:8080/"+ "pdpj/servlet/ServletMail" METHOD="POST"> <CENTER> <H1>Compuneti un mesaj</H1><HR><BR> <TABLE ALIGN="center" WIDTH="100%" CELLSPACING="2" CELLPADDING="2"> <TR> <TD ALIGN="right">Numele:</TD>

378

<TD><INPUT TYPE="Text" NAME="txtFirst" ALIGN="LEFT" SIZE="15"></TD> <TD ALIGN="right">Prenumele:</TD> <TD><INPUT TYPE="Text" NAME="txtLast" ALIGN="LEFT" SIZE="15"></TD> </TR> <TR> <TD ALIGN="right">Email:</TD> <TD><INPUT TYPE="Text" NAME="txtEmail" ALIGN="LEFT" SIZE="25"></TD> <TD ALIGN="right">Telefon:</TD> <TD><INPUT TYPE="Text" NAME="txtPhone" ALIGN="LEFT" SIZE="15"></TD> </TR> <TR> <TD ALIGN="right">Software:</TD> <TD> <SELECT NAME="ddlb_software" SIZE="1"> <OPTION VALUE="Word">Microsoft Word</OPTION> <OPTION VALUE="Java">Java SDK1.4</OPTION> <OPTION VALUE="SQL Server">SQL</OPTION> </SELECT> </TD> <TD ALIGN="right">Operating System:</TD> <TD> <SELECT NAME="ddlb_os" size="1"> <OPTION VALUE="W2k">Windows 2000</OPTION> <OPTION VALUE="Linux">Linux</OPTION> <OPTION VALUE="Solaris">Solaris</OPTION> </SELECT> </TD> </TR> </TABLE> </CENTER> <BR>Continutul mesajului: <BR><TEXTAREA NAME="txtProblem" COLS="50" ROWS="4"> </TEXTAREA><HR><BR> <CENTER> <INPUT TYPE="Submit" NAME="btnSubmit" VALUE="Trimite mesajul"> </CENTER> </FORM></BODY></HTML> Programul 7.19. mail.html

7.3.1.2. Clasa Mailer, expeditor de mesaje email Trimiterea mesajelor e-mail din Java se poate face n mai multe feluri. Ne vom opri doar la una dintre modaliti, aceea oferit de ctre pachetete javax.mail i javax.mail.internet, cunoscute i sub numele de pachetul JavaMail. 379

Aceste pachete nu se afl n distribuia standard j2sdk1.4 (distribuia Java Standard), dar se gsete n distribuia j2sdkee1.4 (distribuia Enterprise Edition). n absena distribuiei j2skdee, se poate descrca de la java.sun.com doar pachetul JavaMail. n esen, pentru trimiterea unui mesaj e-mail prin protocolul SMTP folosind JavaMail, trebuie parcuri urmtorii apte pai: 1. Se va crea un obiect java.util.Properties, cu informaii despre server-ul de mail. Acest obiect va fi explorat de API JavaMail. 2. Se ncarc obiectul Properties cel puin cu adresa mainii din reeaua local care este server SMTP. 3. Se creeaz un obiect Session JavaMail. 4. Din obiectul Session se creeaz un obiect Message. 5. Se seteaz n obiectul Message cmpurile From, To, Subject, eventual i cmpurile Cc, Bcc. 6. Se depune textul efectiv al mesajului n corpul acestuia. 7. Se trimite mesajul folosind metoda static Transport.send(). Pentru compilarea i pentru execuia unui astfel de program trebuie s aib specificat n classpath calea spre o arhiv jar care conine JavaMail. n cazul nostru aceast arhiv este luat din distribuia j2sdkee1.4:
c:\j2sdkee1.4\lib\j2ee.jar

Arhiva j2ee.jar conine pachetele javax.mail i javax.mail.internet Clasa Mailer, pe care o prezentm n continuare, este destinat trimiterii de mesaje e-mail. Metoda esenial este una static:
Mailer.send( . . . )

Aceast metod are ca i parametri apte string-uri (nu exist legtura cu cei 7 pai de mai sus): from indic adresa expeditorului; to indic adresa destinatarului, sau dac se dorete adresele destinatarilor separate prin virgul; cc (copy carbon) indic eventuale alte adrese la care se va trimite mesajul (poate fi vid); 380

bcc (blint copy carbon) cu acelai rol ca i cc, dar un destinatar nu vede care sunt ceilali destinatari (poate fi vid); subject conine linia cu subiectul mesajului; text conine efectiv mesajul de transmis; mailHost conine adresa mainii locale care este server SMTP. Textul surs Mailer.java este prezentat n programul 7.20.
import import import import java.io.*; java.util.*; javax.mail.*; javax.mail.internet.*;

/** Mailer este o clasa destinata trimiterii unui email */ public class Mailer { protected Session session; // Obiectul sesiune javamail protected String from; // Adresa expeditorului protected String to; // Adresele destinatarilor (To:) protected String cc; // Adresele destinatarilor (Cc:) protected String bcc; // Adresele destinatarilor din (Bcc:) protected String subject; // Linia Subject: protected String text; // Corpul mesajului protected String mailHost; // Adresa serverului local SMTP public public public public public public public public public public public public public public String String String String String String String void void void void void void void getFrom() {return from;} getTo() {return to;} getCc() {return cc;} getBcc() {return bcc;} getSubject() {return subject;} getText() {return text;} getMailHost() {return mailHost;}

setFrom(String from) {this.from = from;} setTo(String to) {this.to = to;} setCc(String cc) {this.cc = cc;} setBcc(String bcc) {this.bcc = bcc;} setSubject(String subject) {this.subject = subject;} setText(String text) {this.text = text;} setMailHost(String mailHost) {this.mailHost = mailHost;}

public boolean isComplete() { if ((from==null)||(to==null)||(subject==null)|| (text==null)||(mailHost==null)) return false; if ((from.length()==0)||(to.length()==0)|| (subject.length()==0)|| (text.length()==0)||(mailHost.length()==0)) return false; return true; } // Mailer.isComplete

381

protected InternetAddress[] adrese(String lista) throws AddressException { InternetAddress[] adr = new InternetAddress[0]; if (lista==null) return adr; if (lista.length()==0) return adr; StringTokenizer st = new StringTokenizer(lista, ","); adr = new InternetAddress[st.countTokens()]; for (int i=0; i<adr.length; i++) adr[i] = new InternetAddress(st.nextToken().trim()); return adr; } // Mailer.adrese public synchronized void doSend() throws MessagingException, AddressException { if (!isComplete()) throw new IllegalArgumentException( "nu se poate apela inainte de completarea mesajului"); Properties props = new Properties(); // Se vor transmite la MAIL API props.put("mail.smtp.host", mailHost); if (session==null) session = Session.getDefaultInstance(props, null); // Creeaza obiectul sesiune final Message mesg = new MimeMessage(session); // Creeaza un mesaj mesg.setFrom(adrese(from)[0]); // Fixeaza From: in mesaj mesg.setRecipients(Message.RecipientType.TO, adrese(to)); // Adresele din To: mesg.setRecipients(Message.RecipientType.CC, adrese(cc)); // Adresele din Cc: mesg.setRecipients(Message.RecipientType.BCC, adrese(bcc)); // Adresele din Bcc: mesg.setSubject(subject); // Fixeaza Subject in mesaj mesg.setText(text); // Fixeaza corpul mesajului // Trimite mesajul prin metoda statica Transport. // (Din ratiuni de eficienta, trimiterea se face intr-un thread separat) new Thread() { public void run() { try { Transport.send(mesg); } // try catch (MessagingException e) { throw new IllegalArgumentException( "Eroare la transport:" +e); } // catch } // run }.start(); // thread de trimitere } // Mailer.doSend

382

// Metoda apelabila din exterior pentru trimitere e-mail public static void send(String from, String to, String cc, String bcc, String subject, String text, String mailHost) throws MessagingException { Mailer m = new Mailer(); m.setFrom(from); m.setTo(to); m.setCc(cc); m.setBcc(bcc); m.setSubject(subject); m.setText(text); m.setMailHost(mailHost); m.doSend(); } // Mailer.send public static void main(String a[]) throws Exception { Mailer.send("florin@cs.ubbcluj.ro", "florin@cs.ubbcluj.ro", "florin@scs.ubbcluj.ro,fboian@yahoo.com", "", "Test Mailer multiplu", "Asta-i textul\ncu doua linii", "nessie.cs.ubbcluj.ro"); /* La executie programul se va conecta la "mailHost" (daca administratorul permite, apoi identifica protected "from", dupa care trimite mesajul */ } // Mailer.main } // Mailer Programul 7.20. Mailer.java

Cei apte parametri pot fi setai i individual, n maniera specific componentelor Java (JavaBeans), prin metode getter i setter. Dac aceste cmpuri se completeaz individual, utilizatorul poate s-i declare un obiect de tip Mailer i s apeleze metoda doSend() a acestui obiect. De fapt, metoda Mailer.send( . . . ) i creeaz propriul obiect Mailer, completeaz cmpurile cu valorile parametrilor, dup care apeleaz, la rndul ei, doSend(). n interiorul metodei doSend se poate vedea definirea celor apte pai necesari trimiterii unui e-mail. Din raiuni de eficien, trimiterera se efectueaz ntr-un thread separat. Cititorul poate renuna la acesta i s trimit direct prin Transport.send(). De remarcat, de asemenea, c metoda doSend ncepe prin a controla completarea rubricilor obligatorii ale unui e-mail, folosind n acest scop metoda isComplete(). n sfrit, metoda adrese transform adresele date prin string-uri ntrun tablou de obiecte de tip InternetAddress. Sursa Mailer se termin cu o metod main, folosit doar pentru a putea testa mai uor clasa. 383

7.3.1.3. Clasa HTML.java, auxiliar pentru servlet Clasa HTML este conceput pentru ca servlet-ul s fie ajutat s realizeze mai uor o serie de construcii HTML simple sau tabele. Lsm pe seama cititorului s deduc din surs rolul acestei clase. Pentru ca servlet-ul s o poat folosi, cel mai simplu este ca fiierul class al ei s fie depus n acelai director cu servlet-ul:
%CATALINA_HOME%\webapps\pdpj\WEB-INF\classes

Sursa este prezentat n programul 7.21.


import java.sql.*; public class HTML { public static final public static final public static final public StringBuffer int NORMAL = 0; int HEADING = 1; int LINE = 2; buffer;

public HTML(String _title) { buffer = new StringBuffer(4096); this.buffer.append("<html><head><title>"); this.buffer.append(_title); this.buffer.append("</title></head>"); this.buffer.append("<body>"); } // HTML.HTML public void add(int _style, String _text, boolean _break) { switch(_style) { case NORMAL: this.buffer.append(_text); break; case HEADING: this.buffer.append("<h1>"); this.buffer.append(_text); this.buffer.append("</h1>"); break; case LINE: this.buffer.append("<hr>"); break; default: break; } // switch if(_break) { this.buffer.append("<br>"); } // if } // HTML.add public boolean addTable(String _labels[], ResultSet _rs){ int cols = _labels.length; this.buffer.append("<table width='100%'>");

384

this.buffer.append("<tr>"); for (int i = 0; i < cols; i++) { this.buffer.append("<th align='left'>"); this.buffer.append(_labels[i]); this.buffer.append("</th>"); } // for this.buffer.append("</tr>"); try { while (_rs.next()) { this.buffer.append("<tr>"); for (int i = 1; i <= cols; i++) { this.buffer.append("<td>"); this.buffer.append(_rs.getString(i)); this.buffer.append("</td>"); } // for this.buffer.append("</tr>"); } // while } // try catch (SQLException e) { e.printStackTrace(); return false; } // catch this.buffer.append("</table>"); return true; } // HTML.addTable public String getPage() { return this.buffer.toString() + "</body></html>"; } // HTML.getPage } // HTML Programul 7.21. HTML.java

7.3.1.4. ServletMail Sursa acestui servlet este dat n programul 7.22.


import import import import javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*;

public class ServletMail extends HttpServlet { String message, msgFrom, msgTo, msgSubject; public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); getParameters(req);

385

try { Mailer.send(msgFrom, msgTo, "", "", msgSubject, message, "nessie.cs.ubbcluj.ro"); } // try catch (Exception e) { e.printStackTrace(); return; } // catch HTML h = new HTML("Transmitator de mesaje mail"); h.add(HTML.HEADING, "Mesajul a fost transmis", false); out.println(h.getPage()); out.close(); } // ServletMail.doPost private void getParameters(HttpServletRequest req) throws ServletException, IOException { StringBuffer tempStringBuffer = new StringBuffer(1024); msgSubject = "Trimitere de mesaj"; msgFrom = "florin@cs.ubbcluj.ro"; msgTo = req.getParameter("txtEmail"); tempStringBuffer.append("De la: "); tempStringBuffer.append(req.getParameter("txtFirst")); tempStringBuffer.append(" "); tempStringBuffer.append(req.getParameter("txtLast")); tempStringBuffer.append("\n"); tempStringBuffer.append("Telefon: "); tempStringBuffer.append(req.getParameter("txtPhone")); tempStringBuffer.append("\n"); tempStringBuffer.append("Email: "); tempStringBuffer.append(req.getParameter("txtEmail")); tempStringBuffer.append("\n\n"); tempStringBuffer.append("Software: "); tempStringBuffer.append(req.getParameter("ddlb_software")); tempStringBuffer.append("\n"); tempStringBuffer.append("OS: "); tempStringBuffer.append(req.getParameter("ddlb_os")); tempStringBuffer.append("\n\n"); tempStringBuffer.append("Mesajul: "); tempStringBuffer.append(req.getParameter("txtProblem")); tempStringBuffer.append("\n"); message = tempStringBuffer.toString(); } // ServletMail.getParameters } // ServletMail Programul 7.22. ServletMail.java

Efectul execuiei acestui servlet este prezentat n fig. 7.17, care prezint mesajul completat ca n fig. 7.16, iar pota este citit pe maina linux.scs.ubbcluj.ro, folosind clientul de mail pine [98].

386

Figura 7.17. Mesajul citit cu pine

7.3.2. Aplicaia Note, versiunea servlet


n 5.5 am prezentat i implementat sub RMI aplicaia Note. Este vorba de gestionarea - adugri, tergeri, modificri - de ctre un server Profesor a unor perechi (Materie,Profesor) i a unor triplete (Student,Materie,Nota). Un server Student, care apeleaz la rndul lui server-ul Profesor, permite accesul prin Web al fiecrui student s-i vad doar propriile note. n aceast seciune relum problema, dar schimbm dou elemente eseniale n implementare: 1. nlocuim pstrarea datelor (CATALOG) ca obiecte serializate cu pstrarea acestora n dou tabele dintr-o baz de date. 2. nlocuim comunicaia RMI utilizarea servlet-urilor. Aceast aplicaie va purta numele Note i att pentru maina Profesor, ct i pentru maina Student, n
%CATALINA_HOME%\webapps\

387

trebuie creat un director Note, mpreun cu toat substructura necesar, aa cum am artat n 7.1.3.5. n varianta RMI am folosit BatchCatalogDB (programul 5.16) pentru ncrcarea iniial a bazei de date prin linii date la intrarea standard. Pentru prezenta aplicaie, avem deja un astfel de program: este vorba de TestNivelJDBC (programul 3.3) prin care am exemplificat utilizarea clasei NivelJDBC (programul 3.2). 7.3.2.1. Clasa NoteAccessDB Aceast clas va fi folosit de ctre ServletProfesor pentru a accesa tabelele Perechi i Triplete din baza de date. Sursa clasei, conectabil la ACCESS, este dat n programul 7.23.
import java.util.*; import java.sql.*; public class NoteAccessDB { NivelJDBC db = null; ResultSet rs; public NoteAccessDB() throws Exception { int i; db = new NivelJDBC(); i = db.setConnection("sun.jdbc.odbc.JdbcOdbcDriver", "jdbc:odbc:Note"); } // NoteAccessDB.AccessDB public boolean isMaterieProfesor(String materie, String profesor) throws Exception { rs = db.executeQuery("SELECT Profesor FROM Perechi "+ "WHERE Materie = '"+materie+"' and Profesor = '"+profesor+"'"); return rs.next(); } // NoteAccessDB.isMaterieProfesor public String getMaterieNota(String student) throws Exception { String nume, valoare; rs = db.executeQuery("SELECT Materie, Nota FROM Triplete"+ " WHERE Student = '"+student+"'"); for ( nume="", valoare=""; rs.next(); ) { nume += rs.getString("Materie")+"\n"; valoare += rs.getString("Nota")+"\n"; } // for return nume+"\t"+valoare; } // NoteAccessDB.getMaterieNota

388

public String getStudentNota(String materie) throws Exception { String nume, valoare; rs = db.executeQuery("SELECT Student, Nota FROM Triplete"+ " WHERE Materie = '"+materie+"'"); for ( nume="", valoare=""; rs.next(); ) { nume += rs.getString("Student")+"\n"; valoare += rs.getString("Nota")+"\n"; } // for return nume+"\t"+valoare; } // NoteAccessDB.getStudentNota public void setStudentNota(String studentnota) throws Exception { int i; String materie, student, nota; StringTokenizer stt = new StringTokenizer(studentnota, "\t"); stt.nextToken(); stt.nextToken(); materie = stt.nextToken(); for ( ; stt.hasMoreTokens(); ) { student = stt.nextToken(); nota = stt.nextToken(); rs = db.executeQuery("SELECT Student, Materie, "+ "Nota FROM Triplete "+ "WHERE Student = '"+student+ "' and Materie = '"+materie+"'"); if (rs.next()) db.executeUpdate("UPDATE Triplete SET Nota='"+nota+"'"+ "WHERE Student = '"+student+ "' and Materie = '"+materie+"'"); else db.executeUpdate("INSERT INTO Triplete VALUES ('"+ student+"','"+materie+"','"+nota+"')"); } // for } // NoteAccessDB.setStudentNota public static void main(String a[]) throws Exception { new NoteAccessDB(); } // NoteAccessDB.main } // NoteAccessDB Programul 7.23. NoteAccessDB.java

Pentru acces la baza de date, clasa folosete un obiect de tip NivelJDBC, prezentat n programul 3.2. Clasa ofer trei metode: isMaterieProfesor; getStudentNota; setStudentNota. 389

Evident, toate trei acceseaz, corespunztor cerinelor, tabelele bazei de date. Metoda boolean isMaterieProfesor(String materie, String profesor) Verific dac n tabelul Perechi exist perechea (materie, profesor), adic profesor pred sau nu materie. Metoda ntoarce true sau false, dup caz. Metoda String getMaterieNota(String student) ntoarce pentru student notele lui la toate materiile. String-ul rezultat are forma potrivit pentru a putea fi preluat simplu de ctre metodele apelatoare. n el se afl mai nti numele materiilor, separate prin caracterul <NEWLINE> ('\n'); urmeaz un separator <TAB> ('\t') dup care notele la fiecare materie, separate tot prin <NEWLINE> ('\n'). Metoda setStudentNota(String studentnota) Actualizeaz sau, dup caz adaug, notele studenilor la o anumit materie. Unicul parametru de intrare este un string cu un coninut trimis de servlet. El conine o succesiune de cuvinte separate prin caracterul <TAB> ('\t'). Primele dou cuvinte sunt ignorate (dar trebuie transmise de apelator!). Al treilea cuvnt este materie pentru care se actualizeaz notele. n continuare apar un numr oarecare de perechi de cuvinte student, nota. Se verific mai nti dac perechea (student,materie) exist deja i n caz afirmativ se actualizeaz doar nota. Dac perechea nu exist, atunci se introduce un nou triplet (student,materie,nota). 7.3.2.2. Programul ServletProfesorDB Acest servlet primete de la client un obiect String. Acest string conine unul sau mai multe cuvinte separate prin caracterul <TAB> ('\t'). n funcie de numrul de cuvinte coninute metoda doPost trimite clientului, drept rspuns, un alt obiect String specific. Dac string-ul de intrare conine un singur cuvnt, atunci doPost interpreteaz acest cuvnt ca i numele unui student. Printr-un obiect de tip NoteAccessDB acceseaz baza de date i apoi i ntoarce clientului (student) notele lui la toate materiile. 390

Dac are trei sau mai multe cuvinte, atunci primele trei sunt interpretate ca i profesor, parola i materie. Mai nti se autentific profesorul pe baza parolei i apoi se verific dac pred materia respectiv. n caz de eec ntoarce clientului string-ul vid. Dac string-ul de intrare are exact trei cuvinte, atunci i ntoarce clientului (profesor) notele tuturor studenilor la materia respectiv. Dac string-ul de intrare are mai mult de trei cuvinte, atunci transmite acest ir metodei setMaterieNota a obiectului NotaAccessDB, pentru a actualiza notele la materia respectiv. Sursa acestui servlet este prezentat n programul 7.24
import import import import javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*;

public class ServletProfesorDB extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { ObjectInputStream in; ObjectOutputStream out; String student, materie="", parola, profesor; StringTokenizer st; NoteAccessDB adb = new NoteAccessDB(); in = new ObjectInputStream(req.getInputStream()); student = (String)in.readObject(); in.close(); st = new StringTokenizer(student, "\t"); int ntokens = st.countTokens(); if (ntokens == 1) { student = (String)adb.getMaterieNota(student); out = new ObjectOutputStream(res.getOutputStream()); out.writeObject(student); out.close(); return; } // Intoarce StudentNota if (ntokens >= 3) { profesor = st.nextToken(); parola = st.nextToken(); materie = st.nextToken(); if ((!AuthFTP.isFTPUser("localhost", profesor, parola))|| (!adb.isMaterieProfesor(materie, profesor))) { out = new ObjectOutputStream(res.getOutputStream()); out.writeObject(""); out.close(); return; } // Intoarce esec, parola invalida sau nu preda materia

391

} // A trecut de autentificare if (ntokens == 3) { student = adb.getStudentNota(materie); out = new ObjectOutputStream(res.getOutputStream()); out.writeObject(student); out.close(); return; } // Intoarce studentii si notele pentru o materie adb.setStudentNota(student); // Fixeaza notele studentilor la o materie out = new ObjectOutputStream(res.getOutputStream()); out.writeObject("OK"); out.close(); } // try catch (Exception e) { e.printStackTrace(); } // catch } // ServletProfesorDB.doPost public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } // ServletProfesorDB.doGet } // ServletProfesorDB Programul 7.24. ServletProfesorDB.java

Pentru ca acest servlet s funcioneze, trebuie ca pe maina Profesor, n:


%CATALINA_HOME%\webapps\Note\WEB-INF\classes\

s fie copiat fiierul class al servlet-ului, mpreun cu celelalte fiiere necesare. Deci n classes trebuie s existe fiierele: ServletProfesorDB.class; NoteAccessDB.class; NivelJDBC.class; AuthFTP.class.

7.3.2.3. Programul ServletStudentDB Acest servlet este apelat prin Web de ctre studeni pentru a-i afla notele. Clientul (student) transmite servlet-ului un obiect te tip String, compus din dou cuvinte separate printr-un caracter <TAB> ('\t'). Primul cuvnt este numele studentului iar al doilea este parola lui. Servlet-ul autentific 392

studentul, iar n caz de eec i ntoarce acestuia un obiect String de lungime zero. Dac autentificarea se face cu succes, atunci ServletStudent apeleaz, la rndul lui, ServletProfesor prin URLConnection. Dup aceast conectare, ServletStudent i transmite lui ServletProfesor un obiect String ce conine numele studentului. Prin aceeai conexiune, ServerProfesor i ntoarce un obiect String, aa cum am descris la metoda getMaterieNota a clasei NoteAccessDB. Apoi, prin conexiunea obinut de la obiectul rspuns (res), trimite clientului obiectul String primit de la ServletProfesor. Acest servlet trebuie s fie iniializat folosind fiierul web.xml a se preciza adresa mainii ServletProfesor. Deci, pe maina Student trebuie s existe fiierul:
%CATALINA_HOME%\webapps\Note\WEB-INF\web.xml

Coninutul acestui fiier este prezentat n programul 7.25.


<?xml version="1.0" encoding="ISO-8859-1"?> <!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> <servlet> <servlet-name>ServletStudentDB</servlet-name> <servlet-class>ServletStudentDB</servlet-class> <init-param> <param-name>MasinaProfesor</param-name> <param-value>localhost</param-value> </init-param> </servlet> </web-app> Programul 7.25. web.xml (aplicaia Note)

Cu aceste precizri, invitm cititorul s consulte sursa acestui servlet n programul 7.26.
import import import import import javax.servlet.*; javax.servlet.http.*; java.net.*; java.io.*; java.util.*;

public class ServletStudentDB extends HttpServlet { private String masina = null;

393

public void init(ServletConfig config) throws ServletException { super.init(config); masina = config.getInitParameter("MasinaProfesor"); if (masina == null) masina = "localhost"; } // ServletStudentDB.init public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { URL url; URLConnection urlCon; ObjectInputStream in; ObjectOutputStream out; String student, parola; in = new ObjectInputStream(req.getInputStream()); student = (String)in.readObject(); in.close(); parola = student.substring(student.indexOf("\t")+1); student = student.substring(0, student.indexOf("\t")); if (!AuthFTP.isFTPUser("localhost", student, parola)) { out = new ObjectOutputStream(res.getOutputStream()); out.writeObject(""); out.close(); return; } // Intoarce esec url = new URL("http://"+masina+ ":8080/Note/servlet/ServletProfesorDB"); urlCon = url.openConnection(); // Deschide conexiunea urlCon.setUseCaches(false); urlCon.setDefaultUseCaches(false); urlCon.setDoOutput(true); // Permite si scrierea in resursa urlCon.setDoInput(true); out = new ObjectOutputStream(urlCon.getOutputStream()); out.writeObject(student); out.close(); in = new ObjectInputStream(urlCon.getInputStream()); student = (String)in.readObject(); in.close(); out = new ObjectOutputStream(res.getOutputStream()); out.writeObject(student); out.close(); } // try catch (Exception e) { e.printStackTrace(); } // catch } // ServletStudentDB.doPost public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res);

394

} // ServletStudentDB.doGet } // ServletStudentDB Programul 7.26. ServletStudentDB.java

7.3.2.4. Clientul StudentDB Acesta este un client Web care apeleaz servlet-ul prin intermediul unui applet. Din punctul de vedere al interfeei cu utilizatorul este identic cu clientul prezentat n 5.5.6.2. Dup ce studentul i completeaz numele i parola, apas butonul <CITESTE>. n acest moment este autentificat i n caz de succes i se afieaz notele. Apelul applet-ului dintr-un document HTML se face prin:
<applet code="ClientStudentDB.class" width=300 height=400>

Applet-ul se conecteaz la ServerStudentDB prin URLConnection. textul surs al applet-ului este prezentat n programul 7.27.
import import import import import import java.applet.*; java.io.*; java.awt.*; java.awt.event.*; java.util.*; java.net.*;

public class ClientStudentDB extends Applet implements ActionListener { TextArea taNume, taValoare; TextField tfUser, tfParola; URL url; URLConnection urlCon; ObjectInputStream in; ObjectOutputStream out; public void init() { try { Label l; Button citeste; GridBagLayout gb = new GridBagLayout(); GridBagConstraints gc = new GridBagConstraints(); this.setLayout(gb); l = new Label("Materie"); gc.gridx = 0; gc.gridy = 0; gc.anchor = GridBagConstraints.CENTER; gb.setConstraints(l, gc); this.add(l); l = new Label("Nota");

395

gc.gridx = 1; gc.gridy = 0; gc.anchor = GridBagConstraints.CENTER; gb.setConstraints(l, gc); this.add(l); taNume = new TextArea(10,10); taNume.setEditable(false); gc.gridx = 0; gc.gridy = 1; gc.anchor = GridBagConstraints.EAST; gb.setConstraints(taNume, gc); this.add(taNume); taValoare = new TextArea(10, 10); taValoare.setEditable(false); gc.gridx = 1; gc.gridy = 1; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(taValoare, gc); this.add(taValoare); l = new Label("Student:"); gc.gridx = 0; gc.gridy = 2; gc.anchor = GridBagConstraints.EAST; gb.setConstraints(l, gc); this.add(l); l = new Label("Parola:"); gc.gridx = 0; gc.gridy = 3; gc.anchor = GridBagConstraints.EAST; gb.setConstraints(l, gc); this.add(l); tfUser = new TextField(10); gc.gridx = 1; gc.gridy = 2; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(tfUser, gc); this.add(tfUser); tfParola = new TextField(10); tfParola.setEchoChar('*'); gc.gridx = 1; gc.gridy = 3; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(tfParola, gc); this.add(tfParola); citeste = new Button("Citeste"); citeste.addActionListener(this); gc.gridx = 0; gc.gridy = 4; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(citeste, gc); this.add(citeste); this.setSize(this.getPreferredSize()); this.setVisible(true); url = new URL("http://"+getCodeBase().getHost()+

396

":8080/Note/servlet/ServletStudentDB"); urlCon = url.openConnection(); // Deschide conexiunea urlCon.setUseCaches(false); urlCon.setDefaultUseCaches(false); urlCon.setDoOutput(true); // Permite si scrierea in resursa } // try catch (Exception e) { e.printStackTrace(); } // catch } // ClientStudentDB.init public void actionPerformed(ActionEvent ev) { String nume, valoare; try { if ((ev.getActionCommand()).equals("Citeste")) { out = new ObjectOutputStream( urlCon.getOutputStream()); out.writeObject(tfUser.getText()+"\t"+ tfParola.getText()); out.close(); in = new ObjectInputStream(urlCon.getInputStream()); nume = (String)in.readObject(); in.close(); if (nume.length() > 0) { valoare = nume.substring(nume.indexOf("\t")+1); nume = nume.substring(0,nume.indexOf("\t")); taNume.setText(nume); taValoare.setText(valoare); tfUser.setText("OK citire"); } else tfUser.setText("ERR!citire:user,parola,materie!"); } // if tratare citeste } // try catch (Exception e) { e.printStackTrace(); } // catch } // ClientStudentDB.actionPerformed } // ClientStudentDB Programul 7.27. ClientStudentDB.java

7.3.2.5. ClientProfesorDB Comportarea n relaia cu utilizatorul a acestui client este identic cu cea a clientului descris n 5.5.6.1. Din punct de vedere al implementrii este o aplicaie Java Standalone care interfaeaz cu utilizatorul printr-un Frame. Dat fiind faptul c ClientProfesorDB este un program standalone, el poate fi scris ca un program simplu, cu dou pri: o interfa grafic i o interfa cu baza de date. Poate deci s se elimine accesul la 397

ServletProfesorDB pentru a accesa baza de date. Lsm pe seama cititorului s conceap un astfel de program. Noi vom rmne, din motive care se vor vedea imediat, la un client care cere servlet-ului s acceseze baza de date. Din punct de vedere al conexiunii, programul transmite la ServerProfesorDB un obiect String i primete napoi ca i rspuns un alt obiect de tip String. Conexiunea dintre cei doi parteneri se face prin URLConnection. ATENIE! Acest client poate cere ori de cte ori s citeasc i s scrie. Acest fapt ncalc cerinele protocolului HTTP, care permite numai o eventual scriere spre server urmat, eventual, de o citire de la el. Pentru a se permite oricte operaii i n orice ordine, la fiecare operaie de citire sau de scriere se deschide o nou conexiune! Deci, de fapt, acest client discut cu mai multe instane thread ale servlet-ului. Cu aceste precizri, prezentm n programul 7.28 sursa acestui client.
import import import import import java.io.*; java.awt.*; java.awt.event.*; java.util.*; java.net.*;

class ClientProfesorDB extends Frame implements ActionListener, WindowListener { TextArea taNume, taValoare; TextField tfUser, tfParola, tfMaterie; URL url; URLConnection urlCon; ObjectInputStream in; ObjectOutputStream out; public ClientProfesorDB(String host) throws Exception { Label l; Button citeste, scrie; GridBagLayout gb = new GridBagLayout(); GridBagConstraints gc = new GridBagConstraints(); this.setLayout(gb); l = new Label("Student"); gc.gridx = 0; gc.gridy = 0; gc.anchor = GridBagConstraints.CENTER; gb.setConstraints(l, gc); this.add(l); l = new Label("Nota"); gc.gridx = 1; gc.gridy = 0;

398

gc.anchor = GridBagConstraints.CENTER; gb.setConstraints(l, gc); this.add(l); taNume = new TextArea(10,10); gc.gridx = 0; gc.gridy = 1; gc.anchor = GridBagConstraints.EAST; gb.setConstraints(taNume, gc); this.add(taNume); taValoare = new TextArea(10, 10); gc.gridx = 1; gc.gridy = 1; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(taValoare, gc); this.add(taValoare); l = new Label("Profesor:"); gc.gridx = 0; gc.gridy = 2; gc.anchor = GridBagConstraints.EAST; gb.setConstraints(l, gc); this.add(l); l = new Label("Parola:"); gc.gridx = 0; gc.gridy = 3; gc.anchor = GridBagConstraints.EAST; gb.setConstraints(l, gc); this.add(l); l = new Label("Materie:"); gc.gridx = 0; gc.gridy = 4; gc.anchor = GridBagConstraints.EAST; gb.setConstraints(l, gc); this.add(l); tfUser = new TextField(10); gc.gridx = 1; gc.gridy = 2; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(tfUser, gc); this.add(tfUser); tfParola = new TextField(10); tfParola.setEchoChar('*'); gc.gridx = 1; gc.gridy = 3; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(tfParola, gc); this.add(tfParola); tfMaterie = new TextField(10); gc.gridx = 1; gc.gridy = 4; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(tfMaterie, gc); this.add(tfMaterie); citeste = new Button("Citeste"); citeste.addActionListener(this);

399

gc.gridx = 0; gc.gridy = 5; gc.anchor = GridBagConstraints.WEST; gb.setConstraints(citeste, gc); this.add(citeste); scrie = new Button("Scrie"); scrie.addActionListener(this); gc.gridx = 1; gc.gridy = 5; gc.anchor = GridBagConstraints.EAST; gb.setConstraints(scrie, gc); this.add(scrie); setTitle("Fixare note/materie"); this.addWindowListener(this); this.pack(); this.setSize(this.getPreferredSize()); this.show(); this.setVisible(true); } // ClientProfesorDB.ClientProfesorDB public void actionPerformed(ActionEvent ev) { StringTokenizer stp, std; String nume = "", valoare = ""; int i, n; try { if ((ev.getActionCommand()).equals("Citeste")) { url = new URL("http://localhost:8080/Note/"+ "servlet/ServletProfesorDB"); urlCon = url.openConnection(); // Deschide conexiunea urlCon.setUseCaches(false); urlCon.setDefaultUseCaches(false); urlCon.setDoOutput(true); // Permite si scrierea in resursa urlCon.setDoInput(true); out = new ObjectOutputStream(urlCon.getOutputStream()); out.writeObject(tfUser.getText()+"\t"+ tfParola.getText()+"\t"+tfMaterie.getText()); out.close(); in = new ObjectInputStream(urlCon.getInputStream()); nume = (String)in.readObject(); in.close(); if (nume.length() > 0) { valoare = nume.substring(nume.indexOf("\t")+1); nume = nume.substring(0,nume.indexOf("\t")); taNume.setText(nume); taValoare.setText(valoare); tfUser.setText("OK citire"); } else tfUser.setText("ERR!citire:user,parola,materie!"); } // if tratare citeste if ((ev.getActionCommand()).equals("Scrie")) { stp = new StringTokenizer(taNume.getText(), "\r\n\t"); std = new StringTokenizer(taValoare.getText(),

400

"\r\n\t"); n = stp.countTokens(); i = std.countTokens(); n = (n > i) ? i : n; nume = tfUser.getText()+"\t"+tfParola.getText()+"\t"+ tfMaterie.getText(); for (i=0; i<n; i++) nume += "\t"+stp.nextToken()+"\t"+std.nextToken(); url = new URL("http://localhost:8080/Note/servlet"+ "/ServletProfesorDB"); urlCon = url.openConnection(); // Deschide conexiunea urlCon.setUseCaches(false); urlCon.setDefaultUseCaches(false); urlCon.setDoOutput(true); // Permite si scrierea in resursa urlCon.setDoInput(true); out = new ObjectOutputStream(urlCon.getOutputStream()); out.writeObject(nume); out.close(); in = new ObjectInputStream(urlCon.getInputStream()); nume = (String)in.readObject(); in.close(); if (nume.length() >0) tfUser.setText("OK scriere"); else tfUser.setText("ERR!scriere:user,parola,materie!"); } // if tratare scrie } // try catch (Exception e) { e.printStackTrace(); } // catch } // ClientProfesorDB.actionPerformed public public public public public public public void void void void void void void windowClosing(WindowEvent e) {System.exit(0);} windowClosed(WindowEvent e) {} windowActivated(WindowEvent e) {} windowDeactivated(WindowEvent e) {} windowOpened(WindowEvent e) {} windowIconified(WindowEvent e) {} windowDeiconified(WindowEvent e) {}

public static void main(String a[]) throws Exception { new ClientProfesorDB((a.length >0)?a[0]:"localhost"); } // ClientProfesorDB.main } // ClientProfesorDB Programul 7.28. ClientProfesorDB.java

401

7.3.3. Conectare JDBC din Tomcat prin JNDI


Unul dintre dezavantajele conectrii directe la o baz de date prin JDBC este acela c informaiile de conectare: clasa driver; string-ul de conectare; numele user-ului bazei de date; parola acestuia

sunt fixate direct n cod, prin nite constante. O soluie de a face mai elastic aceast legare este aceea de a transmite aceste date ca i parametri de iniializare, aa cum am vzut n 7.1.4.5. Alinierea Tomcat la JNDI simplific aceast activitate i face aplicaiile mult mai scalabile. Pentru realizearea acestui mod de legare, proiectantul aplicaiei trebuie s efectueze trei mici aciuni: s defineasc n web.xml un nume JNDI, prin care programele s se refere la resursa baz de date; s asigure n codul Java conectarea la baza de date prin referina de mai sus; s defineasc n server.xml resursa baza de date i s o lege de numele ei de referin. S le lum pe rnd. 7.3.3.1. Definirea referinei la resursa baz de date Prin aceast referin servlet-urile aplicaiei se vor referi la baza de date. Se elimin astfel legarea direct a servlet-ului de un anumit tip de baz de date, sarcina detalierii parametrilor bazei urmnd s fie legai doar de aceast referin. Astfel, practic codul devine complet independent de specificul unui anumit tip de baz de date. Descrierea unei astfel de referine se face prin intermediul unei substructuri XML nglobat n tag-ul <resource-ref>. Structura acestuia este:
<resource-ref> <res-ref-name> - - - </res-ref-name> <res-type> - - - </res-type> <res-auth> - - - </res-auth> <res-sharing-scope> - - - </res-sharing-scope> </resource-ref>

402

Conform specificaiilor Tomcat, tag-urile resource-ref trebuie s fie specificate n web.xml numai dup tag-urile care se refer la parametri ai servlet-urilor. res-ref-name este numele referinei la resurs. Acesta este un nume JNDI relativ la contextul java:comp/env i trebuie s fie unic n cadrul aplicaiei. De obicei, pentru baze de date acest nume are forma:
jdbc/numeConexiune

n codul Java, acest nume este de obicei ataat contextului lui, aa c de multe ori referina este ntlnit sub forma:
java:comp/env/jdbc/numeConexiune

res-type indic tipul clasei Java utilizat. n cazul JDBC prin JNDI, aceast clas este, dup cum am mai artat n 3.2.3, clasa DataSource. res-auth specific cine autorizeaz accesul la resurs. Valorile posibile sunt Container sau Application. res-sharing-scope indic gradul de partajabilitate al resursei. Valoarea poate fi Shareable - implicit -sau Unshareable. S exemplificm. Dac dorim ca pentru aplicaia Note descris mai sus accesul s se fac prin JNDI, atunci fiierul web.xml al ei va fi cel din programul 7.29.
<?xml version="1.0" encoding="ISO-8859-1"?> <!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> <servlet> <servlet-name>ServletStudentDB</servlet-name> <servlet-class>ServletStudentDB</servlet-class> <init-param> <param-name>MasinaProfesor</param-name> <param-value>localhost</param-value> </init-param> </servlet> <resource-ref> <res-ref-name>jdbc/NoteDB</res-ref-name> <res-type>javax.sql.DataSource</res-type>

403

<res-auth>Container</res-auth> </resource-ref> </web-app> Programul 7.29. web.xml (Note JNDI)

Referina am numit-o jdbc/NoteDB. Rugm cititorul s compare acest fiier cu cel din programul 7.25. 7.3.3.2. Utilizarea referinei n codul Java Aa cum am artat n 3.2.3, aceast referin este utilizat n locul conexiunii directe la baza de date. Pentru fabricarea n acest mod a unei conexiuni JNDI la o baz de date, secvena minimal este:
javax.naming.Context cx = new InitialContext(); javax.sql.DataSource ds = (DataSource)cx.lookup("numeJNDIBazaDate"); javax.sql.Connection co = ds.getConnection();

n cazul aplicaiei Note, conexiunea se obine prrin:


javax.naming.Context cx = new InitialContext(); javax.sql.DataSource ds = (DataSource)cx.lookup("java:comp/env/jdbc/numeConexiune"); javax.sql.Connection co = ds.getConnection();

Cititorul poate refolosi extrem de simplu codurile claselor aplicaiei Note pentru a alinia aplicaia la JNDI. Singura modificare n cod este n clasa NoteAccessDB (programul 7.23). n acest program, n locul inrtruciunii:
i = db.setConnection("sun.jdbc.odbc.JdbcOdbcDriver", "jdbc:odbc:Note");

se va folosi instruciunea:
i = db.setConnection("java:comp/env/jdbc/NoteDB");

Se va apela astfel cea de-a doua metod de conectare din clasa NivelJDBC (programul 3.2). Evident, fiierul web.xml trebuie configurat aa cum am artat n seciunea precedent. n cazul mutrii aplicaiei, sau a schimbrii motorului de baz de date, nici fiierul web.xml, nici codul Java al aplicaiei nu se vor modifica! Ceea ce se va modifica prezentm n seciunea urmtoare.

404

7.3.3.3. Definirea resursei baza de date Definirea acestei resurse se face n fiierul server.wml de configurare a Tomcat, despre care am mai vorbit n 7.1.3.2. Aceast definire se poate face fie n contextul implicit Tomcat (<DefaultContext>), fie n contextul aplicaiei n care se folosete resursa. Pentru fixarea ideilor, n programul 7.30 prezentm tag-ul <CONTEXT al aplicaiei Note, care conine n el i definirea resursei baza de date.
<Context path="/Note" docBase="Note" debug="0" privileged="true"> <ResourceParams name="jdbc/NoteDB"> <parameter> <name>user</name> <value/> </parameter> <parameter> <name>password</name> <value/> </parameter> <parameter> <name>driverClassName</name> <value>sun.jdbc.odbc.JdbcOdbcDriver</value> </parameter> <parameter> <name>driverName</name> <value>jdbc:odbc:Note</value> </parameter> </ResourceParams> </Context> Programul 7.30. server.xml (parial, aplicaia Note, resursa baz de date)

Partea referitoare la resursa baz de date este cuprins n tag-ul <ResourceParams>. Pentru aplicaia Note am folosit ca i n 7.3.2.1, o baz de date Access aflat pe maina local a containerului. Resursa a fost definit prin cei patru parametri caracteristici resursei: user Specific numele utilizatorului care acceseaz motorul de baz de date. n cazul nostru, am indicat ca i valoare a acestui nume irul vid, deoarece Access nu pretinde neaprat o autentificare pe baz de nume de user, aa cum o face ORACLE, MSSQLServer, MySQL.

405

password Este parola de acces la baz, de asemenea aici are ca valoare irul vid. Atenie! Pentru situaiile n care se impune o parol, fiierul server.xml trebuie bine securizat, deoarece apare parola n clar! driverClassName Indic numele clasei driver pentru acces la baza de date. driverName, sau url (sunt echivalente) Indic string-ul de conectare la baza de date. Absena adresei main face ca implicit ea s coincid cu adresa local (unde se afl Tomcat). Lsm pe seama cititorului s testeze aplicaia Note prin JNDI i pe celelalte tipuri de baze de date prezentate n 3.2.2.2. Atenie ns: Dac pentru driver este necesar o arhiv jar (este vorba de driver ce nu este n distribuia Java), atunci acesta trebuie s fie prezent n:
$CATALINA_HOME/common/lib/

spre a putea fi accesat de ctre containerul Tomcat.

406

S-ar putea să vă placă și