Primul Servlet

 

2.8. Un exemplu de initializare folosind servlet si date de modificare a paginii.

 

 

Listingul 2.11. arata un servlet care foloseste init pentru a face doua lucruri. Primul, construieste o matrice (retea) de 10 numere intregi. Deoarece aceste numere se bazeaza pe calcule complexe, nu vreau sa repet calculul pentru fiecare cerere. Deci am privit valorile care initiaza  calculatia in loc de a le genera de fiecare data. Rezultatele acestei tehnici sunt aratate in Figura 2-8.

Totusi, deoarece toti utilizatorii obtin acelasi rezultat, init stocheaza, de asemenea, o data de modificare a paginii, care este folosita de metoda “getLastModified”. Aceasta metoda ar putea returna un timp de modificare exprimat in milisecunde, din 1970, deoarece este standard.

 

 

Figura 2–8 Rezultatul servlet “LotteryNumbers” (Numere Loto)

 

cu date java. Ora este convertita automat corespunzator GMT, pentru headeru-ul “Last-Modified” (Modificat ultima oara).

Mai important, daca serverul primeste o cerere conditionala GET (specificand ca clientul doreste numai pagini marcate If-Modified dintr-o anumita data), sistemul compara data modificata cu cea returnata de getLastModified, doar intorcand pagina, daca a fost schimbata dupa data specificata. Browserele rezolva frecvent aceste cereri conditionale pentru paginile stocate in barele lor, deci sustinerea  cererilor conditionale ajuta atat pe utilizatorii dvs. cat si reducerea incarcarii serverului. Deoarece headerele “Last-Modified” si “If-Modified-Since” folosesc numai secunde intregi, metoda “get-LastModified” ve rotunji ora in minus pane la cea mai apropiata secunda.

Figurile 2-9 si 2-10 arata rezultatul cererilor pentru acelasi servlet, cu doua date “If-Modified-Since” usor diferite. Pentru a stabili headerele cerere si a vedea headerele raspuns, am folosit “WebClient”, o aplicatie Java, aratata in sectiunea 2.10. (WebClient: Talking to Web Servers Interactively), care va permite stabilirea (rezolvarea) interactiva a cererilor HTTP, trimiterea lor si vizualizarea rezultatelor.

 

package coreservlets;

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

/** Exemplu utilizand initializarea servletului si

*   metoda getLastModified 
*/

public class LotteryNumbers extends HttpServlet {
 private long modTime;
 private int[] numbers = new int[10];

 /** Metoda init este apelata doar atunci cand servel-ul 

* este incarcat pentru prima oara, inainte de prima cerere

* este procesat

*/

 

public void init() throws ServletException {
   // Round to nearest second (ie 1000 milliseconds)
   modTime = System.currentTimeMillis()/1000*1000;
   for(int i=0; i<numbers.length; i++) {

 numbers[i] = randomNum();
 }
}

 public void doGet(HttpServletRequest request,
   HttpServletResponse response)

 throws ServletException, IOException {
   response.setContentType("text/html");
   PrintWriter out = response.getWriter();
   String title = "Your Lottery Numbers";
   out.println(ServletUtilities.headWithTitle(title) +

   "<BODY BGCOLOR=\"#FDF5E6\">\n" +
   "<H1 ALIGN=CENTER>" + title + "</H1>\n" +
   "<B>Based upon extensive research of " +
   "astro-illogical trends, psychic farces, " +
   "and detailed statistical claptrap, " +
   "we have chosen the " + numbers.length +
   " best lottery numbers for you.</B>" +
   "<OL>");

   for(int i=0; i<numbers.length; i++) {

 out.println("  <LI>" + numbers[i]);
 }
   out.println("</OL>" +

   "</BODY></HTML>");
}

 /**

   * Metoda de service standard compara aceste date
   * Cu orice date specificate in headerul cerere

   * “If-Modified-Since”.
   * Daca data getLastModified este mai tarziu,

* sau daca nu exista nici un header “If-Modified-Since”,                                                                                  * metoda “doGet” este apelata normal.

* Dar daca data getLastModified este aceeasi sau mai                     * devreme, metoda service trimite inapoi un raspuns 304        * (Nemodificat), si face <B>not</B> apeland doGet.
 */

 

 

public long getLastModified(HttpServletRequest request) {
   return(modTime);
}

 // un numar intreg de la 0 la 99.

 private int randomNum() {
   return((int)(Math.random() * 100));
}
}

 

 

 

 

 

 Figura 2–9 Accesand servletul LotteryNumbers, cu o cerere neconditionala GET

                    sau cu o cerere conditionala specificand o data inainte de initializarea

        servlet, rezulta o pagina Web normala.

Figura 2-10 Accesarea servletului LotteryNumbers cu o cerere                    conditionala GET, specificand o data la momentul sau dupa initializarea servlet, conduce la un raspuns 304 (nemodificat).

 

 

 

 

 

 

 

 

 

 

 

 

2.9        Corectarea Servlet-urilor

 

In mod normal, cand scrieti servlet-uri, nu faceti niciodata greseli. Totusi, unii dintre colegii dvs. ar putea face o eroare ocazionala si le puteti transmite sfatul urmator. Corectarea servlet-urilor poate fi speciala  deoarece nu le executati direct. In schimb, initializati executia lor prin mijloacele unei solicitari HTTP, iar acestea sunt executate de serverul Web. Aceasta executie la distanta face dificila inserarea punctelor de intrerupere sau cititrea mesajelor de corectie si a stivei. Astfel, corectarea servlet difera cumva de cele folosite in dezvoltarea generala. Aici sunt sapte strategii mai usoare care va pot face viata mai usoara.

 

 

1.      Priviti sursa HTML.  Daca rezultatul pe care-l vedeti in browser arata ciudat, alegeti “View Source” din meniul de browsere. Uneori, o mica eroare HTML, cum ar fi <TABLE> in loc de </TABLE>, poate impiedica mare parte din pagina sa fie vizualizata. Chiar mai bine, folositi un validator official HTML pe iesirea servlet-ului. Vezi sectiunea 2.5. (Simple HTML – Building Utilities) pentru o discutie asupra acestei abordari.

 

2.      Returnati clientului paginile cu erori. Uneori, anumite clase de erori pot fi anticipate de servlet. In aceste cazuri, servlet-ul poate construi informatii descriptive asupra problemei si le poate returna clientului intr-o pagina normala sau prin intermediul metodei “sendError” a HTTPServlet Response. Vezi capitolul 6 (Generarea raspunsului serverului: Coduri de stare HTTP) pentru detalii asupra “sendError”. De exemplu, ati putea face o planificare pentru cazurile in care clientul uita unele date solicitate prin formular si puteti trimite o pagina cu erori, detaliind ceea ce lipseste. Totusi, paginile cu erori (privind erorile) nu sunt posibile intotdeauna. Uneori ceva neasteptat merge prost la servletul dvs., care pur si simplu clacheaza. Abordarile ramase va ajuta in aceste situatii.

 

3.      Porniti serverul din linia de comanda. Cele mai multe servere Web executa dintr-un background care este adesea pornit automat atunci cand sistemul este pornit. Daca aveti probleme cu serverul dvs., ar trebui sa aveti in vedere inchiderea serverului si restartarea lui din linia de comanda. Dupa aceea, apelurile “System.out.println or Sys-tem.err.println” pot fi usor citite din fereastra din care a fost pornit serverul. Cand ceva merge prost cu servletul dvs., prima dvs. sarcina este sa descoperiti cu exactitate cat de departe a mers serverul inainte sa pice si sa adunati niste informatii despre structurile datelor cheie pe durata premergatoare caderii. Simple declaratii “printIn” sunt, in mod surptinzator, eficiente pentru acest scop. Daca va operati servletul pe un server pe care nu-l puteti usor opri si restarta, atunci faceti-va corectia cu JSWDK, Tomcat, sau Java Web Server pe aparatul dvs. personal, si salvati-va ce aveti in desfasurare pe serverul real sau mai tarziu.

 

4.      Folositi fisierul log. Clasa HTTPServlet are o metoda numita log, care va permite sa scrieti informatii intr-un fisier logging pe server. Citirea mesajelor de corectie dintr-un fisier log este ceva mai putin convenabila decat urmarirea lor dintr-o fereastra, conform abordarii anterioare, dar folosirea fisierului log nu necesita oprirea si repornirea serverului. Sunt doua variatii ale acestei metode: una care ia un “String”, iar cealalta care ia un “String” si un “Throwable” (un predecessor al Exception). Locatia exacta a fisierului log este server-specific, dar, in general, este clar documentata sau poate fi gasita in subdirectoarele directorului de instalare a serverului.

 

5.      Priviti datele cererii separat. Datele citite Servlet din solicitarea HTTP construiesc un raspuns si il trimit inapoi clientului. Daca ceva nu merge bine in proces, trebuie sa descoperiti daca asta se datoreaza faptului ca clientul trimite date gresite sau deoarece servlet le proceseaza incorect. Clasa EchoServer, aratata in sectiunea 16.12 ( A Debbuging Web Server), va permite sa trimiteti formulare HTML si sa dati un rezultat care va arata cu exactitate modul in care datele au sosit pe server.

 

6.      Priviti datele raspunsului separat. Odata ce ati privit separate datele cererii (solicitarii), veti dori sa faceti acelasi lucru si pentru datele de raspuns. Clasa WebClient, prezentata ulterior in sectiunea 2.10 (WebClient: Talking to Web Servers Inter­actively), va permite sa va conectati la server in mod interactiv, sa trimiteti clientului datele solicitarii HTTP si sa vedeti tot ce se intoarce , headerele HTTP si totul.

 

7.      Oprirea si repornirea serverului. Cele mai multe servere Web full-down care sustin servlet-uri, au o locatie proiectata pentru servelt-uri, care este in dezvoltare. Servlet-urile din aceasta locatie (ex. Directorul de servleuri pentru serverul Java web) se presupune ca sunt reincarcate automat cand fisierul clasei lor associate se modifica. Totusi, unele servere pot deveni confuze, mai ales cand singura dvs. modificare este la o clasa inferioara nu la una superioara de servlet. Astfel, daca pare ca modificarile pe care le-ati facut la servlet nu se reflecta in comportamentul acestuia, incercati sa restartati serverul. Cu JSWDK si Tomcat trebuie sa faceti asta de fiecare data cand faceti o modificare, deoarece aceste mini-servere nu au nici un support pentru reincarcare automata a servletului.

 

 

2.10  Web Client: Vorbirea interactiva in Serverele Web

 

Aceasta sectiune prezinta codul sursa pentru programul WebClient, discutat in sectiunea 2.9 (Debugging Servlets) si utilizat in sectiunea 2.8. (Exemplu de Initializare folosind servlet si date de modificare a paginii) si, extins, in capitolul 16 (Folosirea formularelor HTML). Ca de obicei, codul sursa poate fi descarcat din arhiva on-line la http://www.coreserv-lets.com/, neexistand restrictii la utilizarea sa.

 

WebClient

Aceasta clasa este programul cel mai avansat pe care il veti folosi. Porniti-l din linia de comanda, apoi personalizati linia cererii HTTP si headerele cerute, apoi apasati “Submit request” (trimite cerere).

import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class WebClient extends CloseableFrame
   implements Runnable, Interruptible, ActionListener {
public static void main(String[] args) {
   new WebClient("Web Client");
}

 private LabeledTextField hostField, portField,

 requestLineField;
 private TextArea requestHeadersArea, resultArea;
 private String host, requestLine;
 private int port;
 private String[] requestHeaders = new String[30];
 private Button submitButton, interruptButton;
 private boolean isInterrupted = false;

public WebClient(String title) {
   super(title);
   setBackground(Color.lightGray);
   setLayout(new BorderLayout(5, 30));
   int fontSize = 14;
   Font labelFont =

 new Font("Serif", Font.BOLD, fontSize);
   Font headingFont =
 new Font("SansSerif", Font.BOLD, fontSize+4);
   Font textFont =

 new Font("Monospaced", Font.BOLD, fontSize-2);
   Panel inputPanel = new Panel();
   inputPanel.setLayout(new BorderLayout());
   Panel labelPanel = new Panel();
   labelPanel.setLayout(new GridLayout(4,1));
   hostField = new LabeledTextField("Host:", labelFont,

30, textFont);
   portField = new LabeledTextField("Port:", labelFont,

"80", 5, textFont);
   // Folositi HTTP 1.0 pentru compatibilitate cu cele mai multe servere.
   // Daca faci schimbarea la 1.1, tu *trebuie* asigurat un
   // Host: request header.
  
requestLineField =

 new LabeledTextField("Request Line:", labelFont,

  "GET / HTTP/1.0", 50, textFont);
   labelPanel.add(hostField);
   labelPanel.add(portField);
   labelPanel.add(requestLineField);
   Label requestHeadersLabel =

 new Label("Request Headers:");
   requestHeadersLabel.setFont(labelFont);
   labelPanel.add(requestHeadersLabel);
   inputPanel.add(labelPanel, BorderLayout.NORTH);
   requestHeadersArea = new TextArea(5, 80);
   requestHeadersArea.setFont(textFont);
   inputPanel.add(requestHeadersArea, BorderLayout.CENTER);
   Panel buttonPanel = new Panel();
   submitButton = new Button("Submit Request");
   submitButton.addActionListener(this);
   submitButton.setFont(labelFont);
   buttonPanel.add(submitButton);

   inputPanel.add(buttonPanel, BorderLayout.SOUTH);
   add(inputPanel, BorderLayout.NORTH);
   Panel resultPanel = new Panel();
   resultPanel.setLayout(new BorderLayout());
   Label resultLabel =

 new Label("Results", Label.CENTER);
   resultLabel.setFont(headingFont);
   resultPanel.add(resultLabel, BorderLayout.NORTH);
   resultArea = new TextArea();
   resultArea.setFont(textFont);
   resultPanel.add(resultArea, BorderLayout.CENTER);
   Panel interruptPanel = new Panel();
   interruptButton = new Button("Interrupt Download");
   interruptButton.addActionListener(this);
   interruptButton.setFont(labelFont);
   interruptPanel.add(interruptButton);
   resultPanel.add(interruptPanel, BorderLayout.SOUTH);
   add(resultPanel, BorderLayout.CENTER);
   setSize(600, 700);
   setVisible(true);

}

 public void actionPerformed(ActionEvent event) {

   if (event.getSource() == submitButton) {
 Thread downloader = new Thread(this);
 downloader.start();

   } else if (event.getSource() == interruptButton) {
 isInterrupted = true;
 }
}

 public void run() {
   isInterrupted = false;
   if (hasLegalArgs())

 new HttpClient(host, port, requestLine,
 requestHeaders, resultArea, this);
}

public boolean isInterrupted() {
   return(isInterrupted);
}

 private boolean hasLegalArgs() {
   host = hostField.getTextField().getText();
   if (host.length() == 0) {

 report("Missing hostname");
 return(false);
 }

   String portString =
 portField.getTextField().getText();

   if (portString.length() == 0) {
 report("Missing port number");
 return(false);

 }
   try {
 port = Integer.parseInt(portString);

   } catch(NumberFormatException nfe) {
 report("Illegal port number: " + portString);
 return(false);

 }
   requestLine =
 requestLineField.getTextField().getText();

   if (requestLine.length() == 0) {
 report("Missing request line");
 return(false);

 }
   getRequestHeaders();
   return(true);

}

 private void report(String s) {
   resultArea.setText(s);
}

 private void getRequestHeaders() {
   for(int i=0; i<requestHeaders.length; i++)

 requestHeaders[i] = null;
   int headerNum = 0;
   String header =

 requestHeadersArea.getText();
   StringTokenizer tok =
 new StringTokenizer(header, "\r\n");
   while (tok.hasMoreTokens())
 requestHeaders[headerNum++] = tok.nextToken();
}
}

HttpClient

Clasa HttpClient face comunicarea reala in retea. Aceasta trimite serverului Web linia solicitarii desemnate si headerele cererii, apoi citeste liniile care se intorc , plasandu-le intr-un “text area” (zona de text) pana ce fie serverul inchide conexiunea, fie HttpClient este interrupt prin intermediul reperului “isInterrupted” (este intrerupt).

import java.awt.*;
import java.net.*;
import java.io.*;

/**

* Implicit clientul retelei foloseste WebClient.
 */

public class HttpClient extends NetworkClient {
 private String requestLine;
 private String[] requestHeaders;
 private TextArea outputArea;
 private Interruptible app;

public HttpClient(String host, int port,
   String requestLine, String[] requestHeaders,
   TextArea outputArea, Interruptible app) {

   super(host, port);
   this.requestLine = requestLine;
   this.requestHeaders = requestHeaders;
   this.outputArea = outputArea;
   this.app = app;
   if (checkHost(host))

 connect();
}

protected void handleConnection(Socket uriSocket)
 throws IOException {

   try {
 PrintWriter out = SocketUtil.getWriter(uriSocket);
 BufferedReader in = SocketUtil.getReader(uriSocket);
 outputArea.setText("");
 out.println(requestLine);
 for(int i=0; i<requestHeaders.length; i++) {

   if (requestHeaders[i] == null)
 break;
   else

 out.println(requestHeaders[i]);
 }
 out.println();
 String line;
 while ((line = in.readLine()) != null &&!app.isInterrupted())
   outputArea.append(line + "\n");
 if (app.isInterrupted())
   outputArea.append("---- Download Interrupted ----");
   } catch(Exception e) {
 outputArea.setText("Error: " + e);
 }
}

 private boolean checkHost(String host) {

   try {
 InetAddress.getByName(host);
 return(true);

   } catch(UnknownHostException uhe) {
 outputArea.setText("Bogus host: " + host);
 return(false);

 }
}
}

 

NetworkClient

 

Clasa NetworkClient este un punct de pornire generic pentru clientii retelei si este extins la HttpClient.

 

import java.net.*; 
import java.io.*;

/** Un punct de pornire pentru clientii de retea.

  *  Trebuie sa ai prioritate peste “handleConnection”

  *  dar in multe cazuri conectarea ramane neschimbata.

 *  Se foloseste SocketUtil pentru a simplifica

 * crearea PrintWriter si BufferedReader
 *

 */

 

public class NetworkClient {
 protected String host;
 protected int port;

 

/** Conexiunea nu va fi efectiv stabilita pana cand nu veti apela connect.
*/



 

 public NetworkClient(String host, int port) {
   this.host = host;
   this.port = port;

}

 

 /** Stabiliti conexiunea,apoi trece  socket-ul

 *  la handleConnection (conexiune manuala).
 *
 */

 

 public void connect() {

   try {
 Socket client = new Socket(host, port);
 handleConnection(client);

   } catch(UnknownHostException uhe) {
 System.out.println("Unknown host: " + host);
 uhe.printStackTrace();

   } catch(IOException ioe) {
 System.out.println("IOException: " + ioe);
 ioe.printStackTrace();

 }
}

 /** Aceasta este metoda pe care o veti folosi prioritar cand

       faceti un client al retelei pentru sarcina dvs.

       Versiunea default trimite o singura linie

       ("Generic Network Client") catre server

      citeste o linie de raspuns, o tipareste, apoi iese din program

   */

 

protected void handleConnection(Socket client)
 throws IOException {
   PrintWriter out =
 SocketUtil.getWriter(client);
   BufferedReader in =

 SocketUtil.getReader(client);
   out.println("Generic Network Client");
   System.out.println

 ("Generic Network Client:\n" +
  "Made connection to " + host +
  " and got ’" + in.readLine() + "’ in response");

   client.close();
}

 /** The hostname of the server we’re contacting. */

 public String getHost() {
   return(host);
}

 /**Conexiunea portului va fi activata. */

public int getPort() {
   return(port);
}
}

 

SocketUtil

SoketUtil este o clasa simpla de utilitati care simplifica crearea unor fluxuri folosite in programarea retelei. Este utilizata de NetworkClient si HttpClient.

 

 

 

 

import java.net.*;
import java.io.*;

public class SocketUtil {
 /** Make a BufferedReader to get incoming data. */

 public static BufferedReader getReader(Socket s)
 throws IOException {
   return(new BufferedReader(
new InputStreamReader(s.getInputStream())));
}

 /** Face ca PrintWriter sa trimita date de iesire

 

*  PrintWriter va face automat “fush stream”
 *  la apelul println-ului
 */

 

 public static PrintWriter getWriter(Socket s)

 throws IOException {
   // al 2-lea argument semnifica autoflush
   return(new PrintWriter(s.getOutputStream(), true));

}
}

 

CloseableFrame

CloseableFrame este o extensie a clasei Frame standard. Aceasta este fereastra superioara pe care este construit WebClient.

 

import java.awt.*;
import java.awt.event.*;

/** Un cadru  pe care il puteti efectiv anula.

Folosit ca punct de pornire pentru cele mai

 multe aplicatii grafice java 1.1.

*/

 

public class CloseableFrame extends Frame {
 public CloseableFrame(String title) {
   super(title);
   enableEvents(AWTEvent.WINDOW_EVENT_MASK);
}

 /** Intrucat facem ceva permanent, avem nevoie

         * sa apelam super.processWindowEvent <B>first</B>.
 */

public void processWindowEvent(WindowEvent event) {
   super.processWindowEvent(event); // Handle listeners
   if (event.getID() == WindowEvent.WINDOW_CLOSING)

 System.exit(0);
}
}

LabeledTextField

 

Clasa Labeled TextField  este o combinatie simpla intre un TextField (camp text) si un Label (eticheta) si este utilizat de WebClient.

 

import java.awt.*;

/** un TextField cu asociere Label.
 */

public class LabeledTextField extends Panel {
 private Label label;
 private TextField textField;

public LabeledTextField(String labelString,
 Font labelFont,
 int textFieldSize,
 Font textFont) {

   setLayout(new FlowLayout(FlowLayout.LEFT));
   label = new Label(labelString, Label.RIGHT);
   if (labelFont != null)

 label.setFont(labelFont);
   add(label);
   textField = new TextField(textFieldSize);
   if (textFont != null)

 textField.setFont(textFont);
   add(textField);
}

public LabeledTextField(String labelString,
 String textFieldString) {
   this(labelString, null, textFieldString,
textFieldString.length(), null);
}

public LabeledTextField(String labelString,
 int textFieldSize) {
   this(labelString, null, textFieldSize, null);
}

public LabeledTextField(String labelString,
 Font labelFont,
 String textFieldString,
 int textFieldSize,
 Font textFont) {

   this(labelString, labelFont,
textFieldSize, textFont);
   textField.setText(textFieldString);
}

 public Label getLabel() {
   return(label);
}

 public TextField getTextField() {
   return(textField);
}
}

Interruptible

Interruptible este o interfata simpla folosita pentru a identifica clasele care au o metoda “isInterrupted”. Este folosit de HttpClient pentru a selecta (? poll) WebClient, pentru a vedea daca utilizatorul l-a interrupt.

 

/**
* O interfata pentru clase care poate fi selectata pentru a vedea
* daca sunt intrerupte. Folosite de HttpClient

* si WebClient sa permita utilizatorului sa intrerupa un download de retea
*/

 

public interface Interruptible {
public boolean isInterrupted();
}