Tehnologii Web - Servlet

Introducere

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.

FIGURA

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.

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.

FIGURA

Ciclul de viata al unui servlet

Ciclul de viata al servletului:

  • Creare şi iniţializare servlet. In momentul în care un servlet este încărcat în memorie va fi apelată metoda init(ServletConfig cfg);
  • Preluare si deservire cereri sosite de la clienti;
  • Distrugere servlet si eliberare memore. In momentul în care un servlet este distrus va fi apelată metoda destroy();

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ă:

  • Reduce memoria ocupată de servlet-uri;
  • Micşorează timpul de răspuns şi resursele de procesor cerute pentru deservirea unui client de către un servlet, deoarece nu trebuie construită câte o instanţă a servletului pentru fiecare cerere a unui client;
  • La iniţializare, un servlet poate iniţializa toate resursele de care are nevoie, acestea persistând pe parcursul apelurilor de la diverşi clienţi. Un exemplu este iniţializarea unei conexiuni la baza de date ce mai apoi poate fi reutilizată pentru a deservi toţi clienţii;

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ţ.

FIGURA

Exemplu aplicatie cu servlet-uri

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ă.

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
 
 
/**
 *
 */
 
public class ServletCheck extends HttpServlet {
  private static final String CONTENT_TYPE = "text/html";
  private Connection con;
  private ArrayList cos;
  private Statement st;
 
  /**
   * Meotda este apelata automat cand este invocat prima data acest
   * servlet. In cadrul aceste metode se initializeaza conexiunea la baza de date.
   * 
   */
   public void init() throws ServletException {
    try{
      Class.forName("com.mysql.jdbc.Driver");
      con = DriverManager.getConnection("jdbc:mysql://"+DBConfig.HOST+"/"+DBConfig.DATABASE+"?user="+DBConfig.USER+"&password="+DBConfig.PWD);
      st = con.createStatement();
    }catch(Exception e){
        e.printStackTrace();
    }
  }
 
  /**
   * Proceseaza cereripe primite de la clienti. Acest servlet este 
   * invocat de client in momentul in care doreste sa cumpere un 
   * nou produs.
   */
  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();}
  }
}
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>");
  }
}

Clasa utilitara pentru parametrii bazei de date:

public class DBConfig {
 
    public static final String HOST =  "localhost";
 
    public static final String USER="stud";
 
    public static final String PWD="some_pass";
 
    public static final String DATABASE="magazin";
 
}

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.

Pagina html ce implementeaza form-ul pentru introducere de comenzi, si servletul pentru finalizare comenzii sunt prezentate in listingul urmator.

<html>
 
<head>
<title>Magazin Online</title>
</head>
<body>
<p><font face="Courier">Magazin Online *</font></p>
 
<form method="POST" action="check">
  <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="buy">
  <p><input type="submit" value="Trimite Comanda" name="B1"></p>
</form>
</body>
</html>

Fisierul web.xml pentru configurarea servleturilor:

<!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>buy</servlet-name>
    <servlet-class>ServletBuy</servlet-class>
  </servlet>
  
  <servlet>
    <servlet-name>check</servlet-name>
    <servlet-class>ServletCheck</servlet-class>
  </servlet>
 
 <servlet-mapping>
    <servlet-name>buy</servlet-name>
    <url-pattern>/buy</url-pattern>
  </servlet-mapping>
  
  <servlet-mapping>
    <servlet-name>check</servlet-name>
    <url-pattern>/check</url-pattern>
  </servlet-mapping>
</web-app>