MokaByte Numero  45  - Ottobre 2000
 
Cartolina
Una servlet per l’invio
di cartoline virtuali
di 
Stefano Bussolon
Cartolina.java, una servlet che permette di 
realizzare le ormai note cartoline virtuali web

Ognuno di noi ha inviato, o ricevuto, delle cartoline virtuali. La tecnologia servlet permette di realizzare questo servizio sul nostro sito. 
In questo articolo verrà descritta . La servlet raccoglie i dati dell’utente, li controlla, crea un file html che costituisce la cartolina vera e propria, invia una mail di notifica al destinatario ed infine restituisce al mittente una pagina di conferma. L’articolo descrive una versione base, funzionante, della servlet. In seguito verranno analizzati alcuni miglioramenti apportati alla classe, focalizzando l'attenzione  sulla classe che si occupa di spedire il messaggio di posta elettronica al destinatario.

Introduzione
Le cartoline elettroniche costituiscono un fenomeno di costume piuttosto interessante all’interno del macrocosmo degli utenti internet: oramai tutti i maggiori portali offrono questo servizio, ed alcuni siti hanno basato la propria fortuna proprio sulle e-cards.
Uno dei fattori di successo della rete è costituita dalla possibilità di instaurare un rapporto personale con l’interlocutore; non a caso lo strumento di gran lunga più utilizzato risulta essere la posta elettronica. Sotto questo profilo i siti web “statici” assomigliano molto ai mezzi di comunicazioni tradizionali, con un produttore di informazione e molti fruitori passivi. Le cartoline virtuali sono uno degli strumenti che permettono ad un sito web di essere fruito in maniera personalizzabile - e personale - dai navigatori.
Le cartoline virtuali costituiscono inoltre un buon mezzo pubblicitario: chi fruisce del servizio “invita” un suo amico ad una vostra pagina web. Naturalmente il destinatario può limitarsi a leggere la cartolina, ma può anche decidere di spedirne una lui stesso, e magari dare uno sguardo ai contenuti del vostro sito.
Realizzare il servizio
Quando andate in vacanza, vi capita di spedire una cartolina ai vostri amici. Scegliete l’immagine che più vi piace, scrivete l’indirizzo del destinatario, un breve messaggio, la vostra firma; attaccate il francobollo e spedite. Su internet i francobolli non servono, ma per il resto la procedura, agli occhi dell’utente, è la stessa: il mittente sceglie un’immagine e compila una form dove immette il proprio nome (o nick), il proprio indirizzo e-mail, il nome e l’indirizzo dell’amico, ed un breve messaggio.
Cosa succede dietro le quinte? Lo script (nel nostro caso la servlet) legge le informazioni inviate (nomi, indirizzi, messaggio, immagine scelta), controlla che tutti i campi siano immessi. Se qualche informazione è mancante, ripropone all’utente la form originale, evidenziando che tutti i campi vanno compilati.
Se l’utente compila in maniera corretta la form, la sevlet crea una pagina html contenente l’immagine scelta, il nome del mittente, quello del destinatario, ed il messaggio desiderato. Invia, in maniera automatica, un messaggio di posta elettronica al destinatario, nel quale viene notificato che il mittente ha inviato una cartolina, ed il link alla pagina generata.
Infine la servlet invia al browser dell’utente un messaggio di conferma, con il link alla pagina generata.
La form iniziale
Come abbiamo visto la servlet ha bisogno di alcune informazioni da parte dell’utente, che vengono raccolte attraverso una pagina html contenente una form.

<html><head><title>Cartoline del mio sito</title></head><body>
<!-- la tabella contenente i radio-button e le immagini disponibili -->
<table border ="0" align="center" width="750">
<tr><td width="25%" align="center">
<input name="immagine" type="radio" value="http://www.miosito.it/cartoline/panorama.jpg">
<img src=" http://www.miosito.it/cartoline/panorama.jpg" border="0">
</td>
<td width="25%" align="center">
<input name="immagine" type="radio" value="http://www.miosito.it/cartoline/fiori.jpg">
<img src=" http://www.miosito.it/cartoline/fiori.jpg " border="0">
</td>
<!--  .... inserire in questo modo tutte le immagini desiderate -->
</tr>
</table>

<!--  i campi per raccogliere nome e indirizzo del mittente,
nome ed indirizzo del destinatario ed il testo del messaggio -->

<table border ="0" align="center" width="750">
<tr><td>Inserisci il tuo nome</td>
<td><input name="nomemitt" type="text" value=""></td></tr>
<tr><td>La tua e-mail</td>
<td><input name="mailmitt" type="text" value=""></td></tr>
<tr><td>Il nome del destinatario</td>
<td><input name="nomedest" type="text" value=""></td></tr>
<tr><td>L'e-mail del destinatario</td>
<td><input name="maildest" type="text" value=""></td></tr>
<tr><td>Il testo della cartolina</td>
<td><textarea name="textfield" rows="5" cols="50" ></textarea></td></tr>
</table><input type="submit" value="Invia la cartolina"></form></body>
</html>

A prescindere dall’impaginazione, la form deve inviare alcune variabili, e più specificamente immagine, contenente il link alla picture selezionata, nomemitt, mailmitt, nomedest, maildest e textfield.
 
 
 

Inizializzare la classe Cartolina.java
Cartolina.java dispone di una variabile contatore, utilizzata per fini statistici e per identificare, assieme alla data di creazione, ogni cartolina inviata. Per questo motivo questa variabile dev’essere in qualche modo persistente, per evitare che la servlet assegni lo stesso numero progressivo a due differenti cartoline. Se la servlet non viene utilizzata per qualche ora, la servlet engine decide generalmente di distruggere l’istanza in corso, e crearne una nuova alla successiva richiesta. Questa procedura azzera il valore della variabile contatore, che dev’essere pertanto salvata su disco.
Uno dei metodi più importanti della classe HttpServlet, che Cartolina.java estende, è il metodo init(ServletConfig conf), utilizzato per inizializzare la servlet. Nella versione più semplice di Cartolina.java, il metodo init viene utilizzato esclusivamente per leggere il file contatore e reinizializzare la variabile corrispondente:

public void init(ServletConfig conf) 
                 throws ServletException {
   super.init(conf);
   contatore = 0;
   try {
     BufferedReader dis =
        new BufferedReader (new FileReader (countfilename));
     String riga = dis.readLine ();
     if (riga == null) return;
     StringTokenizer tok = 
        new StringTokenizer (riga, "\t");
     int contatoc = tok.countTokens();
     if (contatoc < 2) { return; }
     String countstring = tok.nextToken ();
     contatore = Integer.parseInt (countstring);
   }
   catch (IOException e) {
      System.out.println ("ioexception in init");
   }
   catch (NumberFormatException e) {
     System.out.println ("NumberFormatException in init");
   }
 }

La variabile countfilename deve rappresentare l’indirizzo del file contatore.txt, che contiene, su una sola riga, il numero progressivo e la data dell’ultima cartolina spedita, separate da un carattere di tabulazione (‘\t’).
 
 

Gestire il metodo doPost
il metodo

 public synchronized void doPost(HttpServletRequest request,
                          HttpServletResponse response)

costituisce il cuore della servlet. In primo luogo è utile far notare che il metodo è stato dicharato synchronized; ad ogni richiesta da parte dei client, il server web attiva un nuovo processo; ogni processo utilizza le risorse dell’unica istanza della classe servlet creata. Poiché ad ogni richiesta la classe Cartolina.java fa uso di risorse condivise (principalmente nella lettura e scrittura del disco) risulta necessario evitare che due processi si sovrappongono. Teoricamente sarebbe sufficiente isolare e sincronizzare soltanto i punti critici, ma poiché tali punti sono distribuiti praticamente in tutto il metodo, è risultato più semplice sincronizzare l’intero metodo. L’effetto collaterale risulta minimo: nelle poche occasioni in cui due utenti invocano contemporaneamente la servlet, il secondo sarà costretto ad attendere alcuni decimi, finché il server non ha finito di rispondere al primo. Visti i tempi di risposta della rete, tutto ciò non costituisce un problema.
Il metodo legge le informazioni che si aspetta di ottenere dal client, attraverso la funzione request.getParameter(nomeparametro), e delega ad una funzione separata il controllo delle informazioni:

  boolean prosegui = controlla (request, response);
  if (prosegui == false) return;

la funzione controlla (request, response) si assicura che tutti i parametri siano presenti; 
in caso affermativo ritorna la variabile booleana true, altrimenti si occupa di riproporre all’utente la form, pregandolo di compilare tutti i campi richiesti.

public boolean controlla (HttpServletRequest request, HttpServletResponse response) 
   throws IOException, ServletException {
  boolean complete = true;
  if ((nomemitt == null) || (nomemitt.equalsIgnoreCase (""))) complete = false;
  if ((mailmitt == null) || (mailmitt.equalsIgnoreCase (""))) complete = false;
  if ((nomedest == null) || (nomedest.equalsIgnoreCase (""))) complete = false;
  if ((maildest == null) || (maildest.equalsIgnoreCase (""))) complete = false;
  if (complete == true) return true;
// i dati non sono completi: genera una pagina di risposta
// riproponendo la form all’utente
  response.setContentType("text/html");
  PrintWriter out = response.getWriter();
  out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">");
  out.println("<HTML><HEAD><TITLE>Cartolina elettronica</TITLE>");
  out.println("</head><BODY  BGCOLOR=\"ffffff\">");
  out.println("<TABLE BORDER=\"0\" CELLPADDING=\"2\" VALIGN=\"top\" width=\"750\" align=\"center\">");
  out.println("<tr><td VALIGN=\"middle\">");
  out.println("</td>");
  out.println("<td VALIGN=\"middle\"><h2 STYLE=\"font-size:15pt; font-family:verdana\">");
  out.println("Attenzione: parametri mancanti! tutti i campi devono essere compilati</h2></tr></table>");
  out.println("<form action=\"" + servletpath + "\" method=\"post\">");
  out.println("<input name=\"immagine\" type=\"hidden\" value=\"" + immagine + "\">");
  out.println("<table><tr><td>L'immagine della cartolina<br>the image of your e-card</td>");
  out.println("<td><img src=\"" +  immagine + " alt=\"l'immagine\" border=\"0\"></td></tr>");
  if (nomemitt == null) nomemitt = "";
  if (mailmitt == null) mailmitt = "";
  if (nomedest == null) nomedest = "";
  if (maildest == null) maildest = "";
  out.println("<tr><td>Inserisci il tuo nome<br>insert your name</td>");
  out.println("<td><input name=\"nomemitt\" type=\"text\" value=\""+ nomemitt + "\"></td></tr>");
  out.println("<tr><td>La tua e-mail<br>your e-mail</td>");
  out.println("<td><input name=\"mailmitt\" type=\"text\" value=\""+ mailmitt + "\"></td></tr>");
  out.println("<tr><td>Il nome del destinatario<br>the name of the receiver</td>");
  out.println("<td><input name=\"nomedest\" type=\"text\" value=\""+ nomedest + "\"></td></tr>");
  out.println("<tr><td>L'e-mail del destinatario<br>the receiver e-mail</td>");
  out.println("<td><input name=\"maildest\" type=\"text\" value=\""+ maildest + "\"></td></tr>");
  out.println("<tr><td>Il testo della cartolina<br>the text of your e-card</td>");
  out.println("<td><textarea name=\"mailtext\" rows=\"5\" cols=\"50\" ></textarea></td></tr>");
  out.println("</table><input type=\"submit\" value=\"Invia / Submit\"></form></body></html>");
  return false;
 }

Rintengo non sia necessario entrare nei dettagli: la funzione controlla che le variabili richieste ritornino una stringa non vuota. Se almeno uno dei campi non è completo, la variabile complete viene settata a false; se tutte le variabili sono presenti la funzione ritorna true, ed il controllo ritorna alla funzione doPost. In caso di dati mancanti viene generata la risposta all’utente; le variabili null vengono impostate come stringhe vuote, ed i valori delle variabili vengono assegnati al parametro value del campo corrispondente. In questo modo l’utente non dovrà riscrivere i campi già compilati, ma soltanto quelli mancanti.
 
 
 

Generare la cartolina

  // incremento il contatore
  contatore++;
  // creo la stringa giorno, che contiene la data
  // nella forma aaaammgg, ad esempio 20000927
  Calendar calendar = new GregorianCalendar();
  String giorno = "" + calendar.get(Calendar.YEAR);
  if (calendar.get(Calendar.MONTH) < 9)
   giorno +="0";
  giorno += "" + (calendar.get(Calendar.MONTH)+ 1);
  if (calendar.get(Calendar.DATE) < 10)
   giorno +="0";
  giorno += "" + calendar.get(Calendar.DATE);
  File countfile = new File (countfilename);
  DataOutputStream countos = new DataOutputStream (new FileOutputStream (countfile)); 
  countos.writeBytes ("" + contatore + "\t" + giorno); // set the last number and the last date
  countos.close ();

  // aggiungo il contatore alla stringa giorno
  // il risultato sarà del tipo 20000927_247
  giorno += "_" + contatore;
  // creo un file all’interno di una directory del mio web server
  // ad esempio /home/httpd/html/cartoline/20000927_247.html
  // ed attraverso un outputstream genero il codice html della cartolina
  File dest= new File (serverdirpath + giorno + ".html");
  DataOutputStream cartolina = new DataOutputStream (new FileOutputStream (dest)); 

  cartolina.writeBytes ("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">");
  cartolina.writeBytes ("<HTML><HEAD><TITLE>" + cardFromTitle + " " + nomemitt + "</TITLE>");
  cartolina.writeBytes ("<style>\nTD {font-size:11pt; font-family:verdana}\n</style>");
  cartolina.writeBytes ("</HEAD>\n<BODY bgColor=\"#ffffff\">\n<H3>");
  cartolina.writeBytes ("<CENTER><FONT face=\"verdana\">" + CustomCardTitle + "</FONT></CENTER></H3>\n<CENTER>");
  cartolina.writeBytes ("<TABLE bgColor=\"#ffffff\" border=\"0\" cellPadding=\"5\" cellSpacing=\"0\" width=\"750\">");
  cartolina.writeBytes ("<TR><TD width=\"320\" align=\"right\" rowspan=\"2\">");
  // se la variabile immagine contiene un riferimento assoluto si usa quello
  // altrimenti si aggiunge la dir predefinita
  if (!immagine.startsWith ("http")) immagine = imageURL + immagine;
  cartolina.writeBytes ("<img src=\"" + immagine + "\" border=\"0\">\n</TD>");
  cartolina.writeBytes ("<TD width=\"300\">da: " + nomemitt + "<BR>a: " + nomedest + "</TD>");

  // francobollo contiene l’url di una piccola immagine “francobollo”
  cartolina.writeBytes ("<TD width=\"120\" align=\"right\">" + francobollo +"</TD>");
  cartolina.writeBytes ("</TR>\n<TR><TD vAlign=\"center\" colspan=\"2\">" + request.getParameter("mailtext"));
  cartolina.writeBytes ("</TD></TR></TABLE>");
  referer = request.getHeader("referer");
  if (referer != null) {
   cartolina.writeBytes ("<p><A href=\"" + referer + "\">" + cardsend + "</a></FONT></p>");
  }
  cartolina.writeBytes ("</CENTER></BODY></HTML>");

Le parti più salienti del codice sono state commentate. Il nome della cartolina è nel formato data_contatore. Per ricostruire le cifre della data viene utilizzata la classe Calendar. Uno dei passaggi più interessanti riguarda la variabile referer. referer è una delle variabili che, attraverso http, il browser invia al server: la variabile contiene l’url della pagina da cui l’utente è giunto alla risorsa richiesta (nel nostro caso la servlet). Utilizzando la variabile referer noi permettiamo a chi vede la cartolina di ritornare alla form iniziale (per spedire a sua volta una cartolina, ad esempio); questo ci permette di utilizzare la servlet a partire da pagine diverse, potenzialmente da siti diversi; in questo modo la nostra servlet può essere utilizzata anche da altri siti, a patto che creino una form contenente le variabili richieste. Se al contrario vogliamo escludere questa possibilità, sarà utile utilizzare proprio la variabile referer per bloccarne l’uso, ad esempio introducendo le seguenti righe:

 if (!referer.startsWith ("http://nomemioserver"))
  // invia una pagina di feedback di errore
  out.println("<html>\n<head><title>Accesso negato</title>\n<body>");
  out.println("h1>Questo servizio è disponibile solo all’interno del sito nomemioserver </h1></body></html>");
 
 
 

Inviare la mail di notifica
L’invio di un messaggio di notifica viene delegato ad una classe esterna, SendMail.java. Ci occuperemo del funzionamento di questa classe (che potete scaricare da http://www.hyperlab.net/java/source/SendMail.java) in un successivo articolo. Per ora ci limitiamo ad osservare che il metodo sendMail “dialoga” con il server SMTP e gli invia i parametri necessari ad inviare una e-mail.
I parametri che la classe utilizza sono il corpo della mail, in cui va specificato il nome del mittente e l’url della cartolina; il mittente della mail, che corrisponde all’indirizzo del mittente, l’indirizzo del vostro server mail, che si occuperà di inviare fisicamente il messaggio, ed il soggetto della mail.

  String corpo = nomemitt + " ti ha spedito una cartolina elettronica\n";
  corpo += "La puoi leggere all'indirizzo "+ URLpath + giorno +".html\n";
  corpo += "se vuoi spedire una cartolina elettronica, vai su" + referer;
  corpo += "\n--------------------------------------------\n";
// un link al vostro sito, nel corpo della mail
  corpo += "http://nomemioserver";
  String subject = "Una cartolina per te"; 
  SendMail send = new SendMail ();
  Vector tracer = new Vector ();
  send.sendMail(mailserver, mailserver, mailmitt, maildest, subject, corpo, tracer);
 
 
 

Inviare il feedback al mittente
Una volta generata la cartolina ed inviato il messaggio di posta elettronica al destinatario, è necessario notificare al mittente il buon esito dell’operazione. Questo messaggio costituisce la risposta all’utente che ha utilizzato la servlet, e costituisce dunque la parte più “tradizionale” del metodo.

  response.setContentType("text/html");
  PrintWriter out = response.getWriter();
  out.println("<html>\n<body>\n<head>");
  out.println("<title>Cartolina inviata</title>");
  out.println("<body bgcolor=\"white\">");
  out.println("la cartolina è stata creata ed un messaggio di notifica è stato spedito a " + nomedest);
  out.println("all'indirizzo <a href=\"mailto:" + maildest +"\">"+ maildest + "</a><br>\n");
  out.println("la cartolina può essere visualizzata all'indirizzo <a href=\"" + URLpath + giorno +".html\">");
  out.println("" + URLpath + giorno +".html</a>\n</body></html>\n");
 

Il codice utilizza la variabile response per stabilire il contenuto della pagina di risposta ("text/html") e per ottenere uno stream di output: PrintWriter out. Attraverso la variabile out la servlet scrive il codice html che verrà visualizzato sul browser dell’utente, specificando che la mail è stata spedita al destinatario e comunicando l’indirizzo della cartolina. Questa parte può essere personalizzata inserendo ad esempio un link alla home page, ad alcune sezioni del sito, oppure un banner pubblicitario.
Aggiornare il file di log
L’ultima operazione consiste nell’aggiungere una riga al file di log, contenente i dati ottenuti dalla form:
  RandomAccessFile logos = new RandomAccessFile(logname, "rw");
    logos.seek(logos.length());
  logos.writeBytes ("" + contatore + "\t" + giorno + "\t" + immagine);
  logos.writeBytes ("\t" + mailmitt + "\t" + nomemitt + "\t" + maildest + "\t" + nomedest + "\n");
  logos.close ();

Per aggiungere una riga ad un file viene usata la classe RandomAccessFile, che permette di scrivere su di un file a partire da un punto arbitrario. Per accodare una riga al file (append) è sufficiente spostarsi, con la funzione seek (int pos), al termine del file (identificato dalla funzione length()).
Sotto il profilo tecnico generare il file di log è piuttosto banale. Sotto il profilo legale (e di netiquette) ricordate che, salvo espressa autorizzazione, non potete utilizzare gli indirizzi raccolti per scopi non previsti e non richiesti dagli utenti.
 
 
 

Conclusioni
La servlet che ho descritto in questo articolo è piuttosto grezza: nel prossimo numero introdurremo alcuni miglioramenti, quali la possibilità di personalizzare alcuni parametri o di utilizzare la classe Locale per realizzare una cartolina multilingue. La classe che ho descritto funziona comunque piuttosto bene, e dimostra che utilizzando java sul lato server è possibile aggiungere ad un sito alcuni servizi interessanti. Potete scaricare il codice di esempio e provare il servizio dalla pagina http://www.hyperlab.net/java/ecards.html
 
 
 

Bibliografia
servlet tutorial
http://java.sun.com/docs/books/tutorial/servlets/index.html 
la versione on line di Cartolina.java
http://www.hyperlab.net/java/ecards.html
 


Stefano Bussolon, laureato in psicologia sperimentale. Partecipa, nella veste di "cultore della materia", all'attività didattica dei corsi di Intelligenza Artificiale e di Ergonomia della Facoltà di Psicologia di Padova. Per il corso di Ergonomia ha condotto un seminario sull’usabilità del www. Fa parte del laboratorio di realtà virtuale del dipartimento di Psicologia Generale di Padova. E-mail: bussolon@psy.unipd.it
 

Chi volesse mettersi in contatto con la redazione può farlo scrivendo a mokainfo@mokabyte.it