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 Interactively), 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();
}