3. Compilarea şi instalarea unui servlet
Obiectivul
lucrării este de a prezenta tehnologiile Java pentru implementarea de
aplicaţii web.
Pe măsură ce Web s-a dezvolat şi a început să fi utilizat pentru
oferirea de servicii, a apărut necesitatea de a construi pagini cu
conţinut dinamic. Applet-urile reprezintă
un exemplu de tehnologie, ce utilizează platforma clientului pentru a
oferi un conţinut dinamic.
La nivelul serverelor de web,
printre primele tehnologii folosite pentru creare de conţinut dinamic a
fost Common Gateway Interface (CGI). Ulterior au apărut şi s-au
dezvoltat tehnologii similare (PHP, ASP) care oferă metode pentru
construirea de pagini ce permit interacţiunea cu utilizatorul. Tehnologia
propusă de firma SUN pentru construirea de pagini cu conţinut dinamic
este tehnologia Servlet. Această tehnologie este
bazată pe limbajul Java, componentele de timp servlet
fiind implementate în acest limbaj. Bazat pe tehnologia Servlet
s-au dezvoltat ulterior tehnologii ca Java Server Pages
(JSP) şi Java Server Faces (JSF) -ambele destinate construirii de pagini cu conţinut
dinamic.
Un servlet
reprezintă o componentă web ,
gestionată de un container ,
care generează conţinut dinamic. Servlet-urile
sunt clase java, ce oferă independenţă de platformă şi
sunt incărcate şi executate dinamic de
către server. Servlet-urile comunică cu
clienţii pe baza paradigmei cerere – raspuns.
Acest model cerere – raspuns se bazează de
obicei pe protocolul Hypertext Transport Protocol
(HTTP).
Containerul de servleturi este o
componentă ce oferă servicii de reţea prin intermediul
cărora servleturile primesc şi transmit
cereri şi răspunsuri de la şi către clienţi. Containerul
de servleturi înmagazinează servleturile
şi este responsabil pentru gestionarea acestora. Un container poate exista
în cadrul unui server web sau poate fi adăugat
ulterior utilizând mecanismul de extensie al serverului.
Exemplu: Un program client (web browser) accesează un server web
şi transmite o cerere HTTP (poate fi de exemplu un form
completat cu datele unei persoane). Acceastă
cerere este preluată de către serverul de web
şi în cazul în care este destinată unui servlet,
este transmisă mai departe către containerul de servleturi.
Containerul determină cărui servlet îi este
adresată cererea şi va invoca respecivul servlet, transmiţînd-ui ca
parametri două obiecte cerere (request) şi raspuns (response).
Servletul va utiliza obiectul request
pentru a determina cererea făcută de clientul web.
După realizarea operaţiilor necesare (de exemplu scrierea sau citirea
unor date dintr-o bază de date ), servletul va tansmite către client un raspuns
prin intermediul obiectului response.
In continuare este prezentată structura de bază a unui servlet.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class UnServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
PrintWriter out;
String title =
"Simple Servlet ";
//seteaza tipulcontinutului
response.setContentType("text/html");
// trmite rasuns catre
client
out = response.getWriter();
out.println("<HTML><HEAD><TITLE>");
out.println(title);
out.println("</TITLE></HEAD><BODY>");
out.println("<H1>"
+ title + "</H1>");
out.println("<P>Raspuns de la UnServlet.");
out.println("</BODY></HTML>");
out.close();
}
}
Pentru a construi un servlet care
lucrează prin protocolul HTTP trebuie extinsă clasa HttpServlet. In continuare este suprascrisă metoda doGet(…) pentru a prelucara
cereri GET. De asemenea poate fi rescrisă
şi metoda doPost pentru a prelucra cereri POST.
Metodele doGet() şi doPost()
primesc ca argumente doua obiecte: HttpServletRequest
şi HttpServletRespons. Obiectul HttpServletRequest este utilizat pentru a determina cererea
pe care a făcut-o clientul (datele din FORM , header
HTTP etc.). Pentru aceasta clasa pune la dispoziţie metode cum ar fi: getHeaders(), getMethod(), getPathInfo(), getParameter(), getInputStream(), getReader().
Obiectul HttpServletResposn este utizat pentru a transmite raspunsul
către client. Acesta conţine metodele necesare pentru a stabili headerul, tipul de raspuns
şi pentru a obţine fluxul de ieşire prin intermediul căruia
răspunsul este transmis către client.Clasa
pune la dispoziţie metode cum ar fi: setHeader(), setStatus(), getWriter,
getOutputStream() etc.
Observaţie: Metodele doGet() şi doPost() sunt apelate de către metoda service(), şi există posibilitatea să se rescrie metoda service(), prin
intermediul acesteia fiind prelucrate atât cererile POST cât şi cererile
GET.
DoGet | DoPost |
In doGet Method the parameters are appended to the URL and sent along with header information | In doPost, parameters are sent in separate line in the body |
Maximum size of data that can be sent using doget is 240 bytes | There is no maximum size for data |
Parameters are not encrypted | Parameters are encrypted |
DoGet method generally is used to query or to get some information from the server | Dopost is generally used to update or post some information to the server |
DoGet is faster if we set the response content length since the same connection is used. Thus increasing the performance | DoPost is slower compared to doGet since doPost does not write the content length |
DoGet should be idempotent. i.e. doget should be able to be repeated safely many times | This method does not need to be idempotent. Operations requested through POST can have side effects for which the user can be held accountable, for example, updating stored data or buying items online. |
DoGet should be safe without any side effects for which user is held responsible | This method does not need to be either safe |
Ciclul de viata a unui servlet cuprinde următoarele etape:
In momentu în care un servlet
este invocat pentru prima dată containerul de servleturi
va încărca
o instanţă a acestui servlet în memorie. Această
instanţă va prelua şi deserv toate
cererile ce sosesc de la clienţi pentru acest servlet.
Existenţa unei singure instanţe a unui servlet
în memorie,
ce deserveste toţi clienţii prezintă
avantaje prin faptul că:
Pentru a demonstra persistenţa unui servlet
se construieşte următorul servlet:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class SimpleCounter extends
HttpServlet {
int
count = 0;
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/plain");
PrintWriter out =
res.getWriter();
count++;
out.println("Since loading, this servlet
has been accessed " +
count + "
times.");
}
}
Servletul va fi încărcat în memorie la prima invocare a acestuia de
către un client, după care această instanţă
creată va deservi toţi clienţii. Din punctul de vedere al
programatorului unui servlet, fiecare client poate fi
văzut ca un nou thread ce apelează metodele
service(), doGet() sau doPost().
Marea majoritatea a containerelor de servleturi
permit comunicarea între servlet-urile încărcate
în memorie, astfel încât o cerere transmisă către un servlet poate fi redirectată
către un alt servlet, pagina de răspuns
fiind transmisă către client de ultimul servlet
din lanţ.
Modul de instalarea a unui servlet
variază în funcţie de containerul de servleturi
folosit. În cadrul acestui laborator se va folosi containerul de servleturi Apache Tomcat 5.5. Deoarece în cadrul laboratorului se
lucrează cu mediul de dezvoltare Eclipse, în cadrul acestuia a fost
instalat plugin-ul Sysdeo Tomcat Launcher (http://www.sysdeo.com/sysdeo/eclipse/tomcatplugin).
Acest plugin ofere posibilitatea de startare\restartare a serverului Tomcat
direct din mediul Eclipse, de asemenea oferă wizard
pentru construirea de proiect pentru aplicaţie cu Servleturi
şi permite testarea servlet-urilor direct din
mediul Eclipse.
Paşii necesati pentru
a construi un proiect de tip Tomcat
import
java.io.*;
import
javax.servlet.*;
import
javax.servlet.http.*;
public
class UnServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
PrintWriter
out;
String
title = "Simple Servlet
";
//seteaza tipulcontinutului
response.setContentType("text/html");
//
trmite rasuns catre client
out = response.getWriter();
out.println("<HTML><HEAD><TITLE>");
out.println(title);
out.println("</TITLE></HEAD><BODY>");
out.println("<H1>" + title + "</H1>");
out.println("<P>Raspuns de la UnServlet.");
out.println("</BODY></HTML>");
out.close();
}
}
<!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>hello</servlet-name>
<servlet-class>UnServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
In cadrul acestei secţiuni este prezentată o aplicaţie
simplificată, bazată pe servleturi pentru
realizarea unui magazin online. Sunt construite
două servleturi, acestea fiind descrise în listingul următor. Pe lângă servleturi,
aplicaţia mai conţine o bază de date şi o pagină html.
Baza de date conţine un tabel ce are campurile
produs, cantitate şi pret. In cadrul acestei
baze de date sunt memorate produsele ce sunt disponibile în cadrul magazinului online.
Pagina html conţine două formuri. Primul dintre ele conţine două câpuri text, şi un buton submit
, şi este utilizată pentru a transmite comenzi produs – cantitate
către servletul ServletChek.
Cel de al doile form
conţine un singur buton submit, ce este utilizat
pentru a transmite către ServletBuy comanda
finală.
Servlet 1
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
/**
*
*/
public class ServletChek extends HttpServlet {
private static final String CONTENT_TYPE =
"text/html";
private String dburl="jdbc:odbc:dbShop";
private Connection con;
private ArrayList cos;
private Statement st;
//metoda este apelata automat la prima apelare a servletului
public
void init() throws ServletException {
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
con = DriverManager.getConnection(dburl);
st = con.createStatement();
}catch(Exception e){e.printStackTrace();}
}
//Procesare cereri primite de la form-uri
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession(true);//.1
ArrayList cos = (ArrayList)session.getValue(session.getId());
if(cos==null) {
cos = new ArrayList();
session.setAttribute(session.getId(),cos); //.2
}
String r = null;
try{
String prod = request.getParameter("produs");
int cant =Integer.parseInt(request.getParameter("cantitate"));
if(comandaProdus(prod,cant)) {
r
= "Comanda primita";
cos.add(prod+" "+cant);
}
else r = "Eroare receptionare
comanda";
}catch(Exception e){r =
"Eroare receptionare comanda";}
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>ServletChek</title></head>");
out.println("<body>");
out.println("<p>"+r+"</p>");
out.println("</body></html>");
}
boolean comandaProdus(String
prod, int cant){
ResultSet rs=null;
try{
rs = st.executeQuery("SELECT
CANTITATE FROM PRODUSE WHERE PRODUS = '"+prod+"'");
//verific daca produsul exista
if(rs.next()==false) return false;
int exista = rs.getInt("cantitate");
//cantiatea dorita este insuficienta
if((exista-cant)<0) return
false;
//daca cantitatea este suficienta atunci se modifica cantitatea din baza
de date
int cantNoua=exista-cant;
st.executeUpdate("UPDATE PRODUSE SET CANTITATE="+cantNoua+" WHERE PRODUS= '"+prod+"'");
}catch(Exception e){e.printStackTrace();return
false;}
return true;
}
//Stergere resurse
public void destroy()
{
try{
con.close();
con=null;
}catch(Exception e){e.printStackTrace();}
}
}
Servlet 2
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class ServletBuy extends HttpServlet implements SingleThreadModel {
private static final String CONTENT_TYPE =
"text/html";
//Process the HTTP Get request
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession(true);
ArrayList cos = (ArrayList)session.getAttribute(session.getId());
//.3
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>ServletBuy</title></head>");
out.println("<body>");
if((cos==null)||(cos.size()==0)){
out.println("<p>Cosul
este gol.</p>");
}
else
{
for(int i=0;i<cos.size();i++){
out.println("<p>Produs:
"+(String)cos.get(i)+"</p>");
}
session.invalidate(); //.4
}
out.println("</body></html>");
}
}
Primul servlet are rolul de a prelua comenzile
de la clienţi. Un client poate fi : un applet,
un alt program java (care comunică cu servletul
utilizând protocolul HTTP sau un form din cadrul unei
pagin html.
In momentul în care servletul ServletChek primeşte o cerere POST sau GET, este
executată metoda service() care va prelucra
respectiva cerere. O variantă alternativă este de a implementa în
cadrul servletului metodele doGet()
şi doPost() pentru a prelucra separat cererile
GET şi POST.
In cadrul servletului ServletChek
se utilizează un mecanism pentru memorarea unui set cereri pe care un
client le-a făcut (session tracking).
Acest mecanism poartă numele de memorarea sesiunii. Acest mecanism permite
servletului să memoreze toate comenzile pe care
clientul le-a făcut pe parcursul unei seiuni de
cumpărături.
Pentru urmărirea comenzilor unui client prima operaţie este de
obţinerea a obiectului HttpSession ( 1.) .
Dacă clientul a accesat pentru prima dată servletul
atunci getSession(true)
creează un nou obiect sesiune şi este returnat
(daca parametrul este false atunci metoda returnează
null).
Obiectul sesiune oferă posibilitatea de a adăuga in cadrul
acestuia perechi nume – valoare, unde nume este un String
iar valoare este un obiect java (2.).
Memorarea sesiunii este un mecanism prin care servleturile
din cadrul aceluiaşi container pot comunica între ele pentru un anumit
client . In linia (3) se observă modul în care pe baza unui obiect
sesiune, un servlet poate obţine o valoarea
ataşată unui nume ( adăugarea s-a făcut în (2) ).
O sesiune poate fi invalidată automat sau manual. Invalidarea
automată se realizează de către serverul de web
când pentru o anumită perioadă de timp un client nu a mai făcut
nici o cerere. Invalidarea manuală se realizează de către servlet când, de exemplu clientul a terminat sesiunea de
cumpărături. Invalidarea unei sesiuni înseamnă eliminarea
obiectului HttpSession şi a perechilor nume –
valoare asociate cu acesta. Acest lucru se realizeaza
in cadrul programului pe linia (4).
Observaţie: Mecanismul de memorare a sesiunii utilizează cookies pentru asocierea unui utilizator cu un obiect
sesiune. Dacă clientul nu suportă cookies
sau are dezactivat acest mecanism atunci exemplul anterior nu va mai
funcţiona. In acest caz trebuie utilizat mecanismul de rescriere
a URL-urilor.
Cel de al doilea servlet (ServletBuy)
realizează operaţiile finale în momentul în care un client decide
terminarea sesiunii de cumpărături. In cazul de faţă servletul verifică dacă clientul are produse în
coş sau nu are, după care realizează invalidarea sesiunii.
Pentru verificarea aplicaţiei prezentate anterior adiţional pe
lângă cele două servleturi trebuie
construită baza de date ce va fi localizată pe acelaşi
calculator cu servleturile şi vor trebui
realizate setările necesare pentru ca servleturile
sa se poată conecta la baza de date (vezi lucrarea 6).
Listingul următor prezintă pagina html necesară pe partea de client.
<html>
<head>
<title>Magazin
Online</title>
</head>
<body>
<p><font face="Courier">Magazin Online</font></p>
<form method="POST" action="servlet/ServletChek">
<p><b>
Produs: <input type="text"
name="produs" size="20">
Cantitate:<input type="text"
name="cantitate" size="20">
</b></p>
<p><input type="submit"
value="Adauga Produs"
name="B1"></p>
</form>
<form method="POST" action="servlet/ServletBuy">
<p><input type="submit"
value="Trimite Comanda"
name="B1"></p>
</form>
</body>
</html>
Tehnologia JSP are
la bază tehnologia servlet-urilor şi
replică într-o mare măsură tehnologia Active Server Pages (ASP) de la Microsoft.
În esenţă este vorba de posibilitatea de inserare de cod java
în cadrul paginilor HTML. Codul java este inclus între tagurile
speciale <% şi %> .
O pagină HTML cu secvenţe de cod JSP arată astfel:
<HTML>
<HEAD><TITLE>Hello</TITLE></HEAD>
<BODY>
<H1>
<%
if (request.getParameter("name")
== null) {
out.println("Hello World");
}
else {
out.println("Hello, " + request.getParameter("name"));
}
%>
</H1>
</BODY></HTML>
În momentul în care paginile JSP sunt încărcate, serverul (care
găzduieşte paginile JSP) va genera automat, compila, încărca
şi rula servleturi speciale pentru a genera
conţinutul dinamic al paginii.
Servletul generat în fundal ca urmare a realizării
cererii de încărcare a paginii prezentate în listingul
anterior arată astfel:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class _hello1_xjsp extends HttpServlet
{
public void service(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out =
response.getWriter();
BufferedReader in
= request.getReader();
out.println("<HTML>");
out.println("<HEAD><TITLE>Hello</TITLE></HEAD>");
out.println("<BODY>");
out.println("<H1>");
if (request.getParameter("name") == null) {
out.println("Hello World");
}
else {
out.println("Hello, " + request.getParameter("name"));
}
out.println("</H1>");
out.println("</BODY></HTML>");
}
}
Prima încărcare a paginii JSP va dura mai mult deoarece serverul va
trebui să genereze, compileze şi să încarce servletul
corespunzător.
Codul JSP poate fi inserat în cadrul paginilor în trei moduri.
<%= expression %>
în acest caz expresia este evaluată şi
rezultatul este trimis către ieşire (afişare).
<% code %>
acest cod este inserat în
cadrul metodei service() corespunzătoare servletului ce va fi generat pe baza paginii JSP.
<%! code %>
astfel de declaraţii
sunt folosite pentru a insera cod în cadrul servletului
dar în afara oricărei metode.
După cum s-a precizat expresiile sunt evaluate, convertite în
şir de caractere şi trimise către ieşire. De exemplu
următoarea secvenţă va trimite către ecran timpul curent al
sistemului.
Current
time: <%= new java.util.Date() %>
Pentru lucrul cu expresii sunt câteva expresii care sunt predefinite şi pot fi folosite în cadrul acestora.
Cele mai importante sunt următoarele:
request
, reprezintă obiectul de tip HttpServletRequest
; response
, reprezintă obiectul de tip HttpServletResponse
; session
, reprezintă obiectul de tip HttpSession
asociat cu obiectul
request;out
, reprezintă un obiect de tip PrintWriter
ce poate fi folosit pentru trimiterea
datelor către ieşire.Un alt exemplu:
Your
hostname: <%= request.getRemoteHost() %>
Paginile JSP permit inserarea de cod complex java prin intermediul tagurilor <% %>. Câteva exemple:
<%
String
queryData = request.getQueryString();
out.println("Attached
GET data: " + queryData);
%>
<%
if (Math.random() < 0.5) { %>
Have
a <B>nice</B> day!
<% } else { %>
Have
a <B>lousy</B> day!
<%
} %>
if (Math.random() < 0.5) {
out.println("Have a <B>nice</B> day!");
}
else {
out.println("Have a <B>lousy</B> day!");
}
Folosind tagurile <% %> se poate scrie
cod mixt HTML şi java.
Declaraţiile permit definirea de metode sau atribute ce vor fi
inserate în codul servletului ce va fi generat pe
baza paginii JSP ( în afara metodei service()).
Inserarea declaraţiilor se face cu tagurile
<%! %>. Declaraţiile nu pot fi utilizate pentru a genera direct text
către ieşire standard. Exemplu:
<%!
private int accessCount = 0; %>
Accesses
to page since server reboot:
<%=
++accessCount %>
Există două tipuri importante de directive ce pot fi utilizate
în cadrul unei aplicaţii. Directiva page
permite definirea de importuri de pachete şi setarea a diferiţi
parametri ai clasei servlet ce va fi generate pe baza
paginii JSP. Directiva include permite
includerea de fişiere în momentul în care este generat servletul
asociat paginii JSP.
Ca şi exemplu directiva include
poate fi folosite pentru a include o bară de navigaţie în cadrul
paginilor unui site:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<TITLE>Servlet Tutorial: JavaServer
Pages (JSP) 1.0</TITLE>
<META
NAME="author" CONTENT="webmaster@somesite.com">
<META
NAME="keywords" CONTENT="...">
<META
NAME="description" CONTENT="...">
<LINK
REL=STYLESHEET
HREF="Site-Styles.css"
TYPE="text/css">
</HEAD>
<BODY>
<%@
include file="/navbar.html" %>
<!-- Part specific to this page ... -->
</BODY>
</HTML>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0
Transitional//EN">
<HTML>
<HEAD>
<TITLE>Using
JavaServer Pages</TITLE>
<META
NAME="author" CONTENT="content xxx yyy
zzz">
<META
NAME="keywords"
CONTENT="JSP,JavaServer Pages,servlets">
<META
NAME="description"
CONTENT="A quick example of the four
main JSP tags.">
<LINK
REL=STYLESHEET
HREF="My-Style-Sheet.css"
TYPE="text/css">
</HEAD>
<BODY
BGCOLOR="#FDF5E6" TEXT="#000000" LINK="#0000EE"
VLINK="#551A8B"
ALINK="#FF0000">
<CENTER>
<TABLE
BORDER=5 BGCOLOR="#EF8429">
<TR><TH CLASS="TITLE">
Using JavaServer
Pages</TABLE>
</CENTER>
<P>
Some
dynamic content created using various JSP mechanisms:
<UL>
<LI><B>Expression.</B><BR>
Your hostname: <%= request.getRemoteHost()
%>.
<LI><B>Scriptlet.</B><BR>
<% out.println("Attached
GET data: " +
request.getQueryString());
%>
<LI><B>Declaration (plus
expression).</B><BR>
<%! private int accessCount = 0; %>
Accesses to page since server reboot:
<%= ++accessCount %>
<LI><B>Directive (plus
expression).</B><BR>
<%@ page import = "java.util.*" %>
Current date: <%= new Date() %>
</UL>
</BODY>
</HTML>