Java e la posta elettronica
Il framework di Java 2 Standard Edition consente di sviluppare con semplicità applicazioni in grado di inviare e ricevere posta nei protocolli più comuni.Una delle migliori ragioni per scegliere di adottare la piattaforma Java è la grande versatilità e la buona progettazione del framework applicativo costituito dall’insieme delle classi di supporto. In altri termini, in Java la maggior parte dei compiti applicativi possono essere portati a termine creando e componendo classi della libreria di corredo e, spesso, le classi di libreria rendono un compito semplice e logico quanto è ragionevolmente possibile.
La seconda ragione per preferire Java, ma solo la seconda a personale giudizio di chi scrive, è la possibilità di utilizzare quello che si ha come macchina di sviluppo (anche Windows 98) e consegnare l’applicazione su quello che il cliente ha messo a disposizione.
Questi due vantaggi bastano per sgombrare il campo dalla maggioranza delle obiezioni sull’opportunità di usare Java come piattaforma per lo sviluppo server, mentre per lo sviluppo di applicazioni client bisogna ancora digerire un rospo troppo grosso: un toolkit grafico progettato con criteri molto discutibili, un problema che, sempre a personale giudizio di chi scrive, ha due soluzioni: la prima è che l’interfaccia utente non è critica per l’applicazione, la seconda è che si sceglie il toolkit di Eclipse, lo Swt.
La libreria di interfaccia con i server di posta elettronica è javax.mail, che non fa parte della distribuzione standard di Java, cioè non è disponibile se si è installato Java 2 Standard Edition.
Il modo più semplice di avere tutto il necessario per operare con la posta è di usare Java 2 Enterprise Edition, un download separato di una trentina di megabyte da installare in aggiunta alla versione Standard di Java. Java 2 Enterprise Edition porta con sé un database, un server Web che incorpora una versione aggiornata di Tomcat, un server di accodamento messaggi e un ambiente di esecuzione per Enterprise Java Beans. La versione 1.4, che è ancora in beta mentre scriviamo, contiene anche gli strumenti per creare e consumare Web Service.
Se tutto questo pare troppo, o se non si vuole turbare più del dovuto l’equilibrio di una configurazione che ha già alcuni componenti installati, rimangono aperte altre due opzioni. URL http://java.sun.com/products/javamail. La home page di JavaMail contiene anche un link al Java activation framework, che è un altro componente indispensabile della soluzione.
L’installazione di JavaMail porta con sé anche un bagaglio molto utile di applicazioni di prova, che vale la pena studiare in dettaglio. C’è un client progettato come applicazione, una semplice servlet e un’applicazione Web un po’ più rifinita.
In alternativa, si possono usare le librerie activation.jar e mail.jar che porta con sé un’installazione di Tomcat, nella directory common\lib al di sotto della posizione di installazione di Tomcat. Il ciclo di vita di un messaggio
Il problema principale che si incontra nel realizzare un’applicazione che si occupa di posta elettronica è la relativa complicazione dell’insieme di tecnologie indispensabili per fare funzionare il meccanismo.
Nonostante la semplicità con cui la usiamo, la posta elettronica è resa possibile da un insieme di servizi di una certa complessità.
Per capire come funziona la posta cominciamo considerando il ciclo di vita di un messaggio di posta elettronica, in questo modo possiamo mettere a fuoco quali sono gli attori e quali sono i passaggi fondamentali.
La prima complicazione la incontriamo prima ancora di considerare l’invio di posta, ed è la confezione del messaggio stesso.
Creare un archivio MIME
Un messaggio di posta elettronica nella sua forma più semplice è un testo. Nel lontano 1993 fu creata una specifica, chiamata Multipurpose Internet Mail Extension (Mime), con il proposito di permettere di creare messaggi di posta elettronica più sofisticati. La specifica originale fu pubblicata come RFC 1521 ed è stata modificata in seguito per estendere le possibilità di codifica e per consentire la crittografia e la firma digitale dei messaggi.
Già nella versione originale del 1993, Mime dava una strutturazione particolarmente flessibile ai messaggi di posta. È grazie alla possibilità di creare messaggi composti di più parti, mescolando diverse tecniche di codifica per la trasmissione, che possiamo creare (purtroppo dirà qualcuno) messaggi in formato Html accompagnati da immagini, file Zip ed eseguibili (anche virus!).
Se fossimo rimasti al tempo in cui un messaggio poteva essere solo testo, non avremmo uno strumento così utile nelle mani di spammer e produttori di virus, che però è indispensabile per tanti altri tipi di comunicazione, ad esempio per mandare in redazione l’articolo in formato Word, accompagnato da immagini Png e didascalie.
Sembra difficile, ma in fin dei conti è così abituale aprire un messaggio e trovare al suo interno documenti e messaggi nel lavoro di tutti i giorni.
La tecnica usata per confezionare archivi su più livelli, componendo file di testo scritti nei più diversi alfabeti e file binari di ogni genere, è al di là di quanto possiamo affrontare nello spazio a disposizione. Basterà sapere che il risultato finale è testo, che fino a qualche anno fa era codificato a sette bit in alfabeto Ascii e ora può essere scritto anche in un alfabeto europeo a otto bit, come Iso-8859-1. Anche i file binari sono codificati in testo normale e appaiono come una lista di caratteri senza senso nel testo originale del messaggio, prima della decodifica.
Consegnare un messaggio a un server
Dopo che il programma che usiamo per l’invio della posta ha confezionato il messaggio impacchettando tutto quello che gli è stato richiesto di spedire, il messaggio va consegnato a un server che provveda all’inoltro.
Se nella scelta di un programma per la lettura e l’invio della posta abbiamo la massima libertà, per inviare un messaggio abbiamo quasi sempre una scelta obbligata. Il nome del server che possiamo utilizzare, spesso, è uno solo: quello messo a disposizione dal provider di accesso utilizzato per la connessione Internet.
Il protocollo per la spedizione della posta è esclusivamente Simple Mail Transfer Protocol (Smtp). Fanno eccezione solamente gli utenti di server di groupware, che usano strumenti come Exchange o Notes per inviare la posta con meccanismi proprietari, ma il server di groupware parlerà certamente Smtp quando invierà il messaggio.
|
Un messaggio contiene dei Flag, degli oggetti Content e fa riferimento a uno o più Flag. | |
Un server Smtp ha l’intelligenza necessaria per prendere in carico un messaggio e recapitarlo a destinazione o gestire i problemi di instradamento che si presentassero.
Il recapito di un messaggio può essere abbastanza complicato e richiedere diversi inoltri, da un server dipartimentale a uno centrale, fino ad arrivare a quello che ospita effettivamente la casella postale del destinatario. In altri casi, che oggi sono la maggioranza, il recapito è diretto, dal server Smtp del nostro provider a quello finale.
Il ritiro della posta
Una volta che il messaggio è arrivato a destinazione, il messaggio viene recuperato dall’utente finale, che userà il client di posta che più gli aggrada e il protocollo Post Office Protocol versione 3 (Pop3). Il protocollo Pop3 è semplicissimo e consente pochissime cose, in sintesi, solo di avere la lista dei messaggi in attesa, scaricare gli header o leggerli per intero.
Come al solito, fanno eccezione gli utenti di sistemi di groupware, che useranno uno strumento proprietario e interfacce applicative proprietarie. Un’altra eccezione importante sono gli utenti che hanno la possibilità di leggere la posta con il protocollo Imap e quindi lasciano i loro messaggi sul server e creano le loro cartelle per l’archiviazione sul server di posta, come gli utenti dei sistemi di groupware, server che spesso hanno un’interfaccia Imap.
|
In questo specchietto è riassunto il modo in cui si può navigare attraverso le diverse sezioni di un messaggio di posta in formato Mime Multipart. | |
Dopo che è stato trasferito sul disco del destinatario, un messaggio viene archiviato nel modo preferito da chi ha scritto il client di posta, quindi in modo diverso da Outlook e Outlook Express, per fare un esempio.
Sta al client di posta capire come esplorare il messaggio nelle sue componenti e come rispondere a tono alla richiesta di aprire un file in formato Acrobat o uno spreadsheet in formato Excel secondo la configurazione del sistema di chi legge la posta.
Naturalmente, se abbiamo inviato un file in formato Jpeg a un utente che ci legge su un AS/400, i protocolli assicureranno l’invio del messaggio, ma vedere l’immagine su un terminale sarà comunque impossibile.
Spedire posta con Java
Cominciamo dalla parte più semplice del percorso di un messaggio di posta, l’invio.
Per inviare la posta occorrono due dati di configurazione, il nome del server e il nome dell’utente. Noi usiamo semplicemente stringhe inizializzate. In un programma di produzione, questi dati andrebbero letti da qualche archivio di configurazione, però non ci costa nulla usare la funzione per l’internazionalizzazione di Eclipse o di un altro ambiente di sviluppo per estrarre tutte le costanti stringa da un programma e sostituirle con dati di configurazione letti da un file di proprietà. Questo probabilmente è il modo giusto di memorizzare il nome del server, mentre nel caso del nome dell’utente bisogna aver cura di usare un file specifico per ogni utente del sistema.
// dati stabili di configurazione
tring smtpServer = “mail”;
tring from =
“Utente di prova
<utente@sistema.diprova.it>”;
Per spedire un messaggio occorre avere un indirizzo a cui mandare il tutto, il testo del messaggio e, eventualmente, un’intestazione con l’oggetto del messaggio. Questi dati sono conservati in variabili, che nel programma di prova aggiorniamo con parametri passati dalla riga di comando, nel caso di to e subject, mentre il testo del messaggio lo leggeremo dallo standard input del programma.
// dati individuali di ogni messaggio
String to = args[0];
String subject = args[1];
// il testo del messaggio
String body;
il resto del main del programma serve solo per scrivere un prompt sulla finestra di comando da cui abbiamo lanciato il programma
body = “”;
System.out.println(“Scrivere il testo del
messaggio.”);
System.out.println(“Terminare con un punto
su riga vuota”);
e per leggere del testo dallo standard input fino a che l’utente non scrive un punto su una riga vuota. Usando un meccanismo abbastanza standard prendiamo System.in e costruiamo un BufferedReader di nome in, che ci permette di usare un metodo readLine per leggere riga a riga.
try {
BufferedReader in =
new BufferedReader(new
InputStreamReader(System.in));
String str = in.readLine();
while (!str.equals(“.”)) {
body += str + “\n”;
str = in.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
Ora, non resta che inviare il messaggio. Lasciamo questa responsabilità a un metodo send con cinque parametri, il nome del server, l’indirizzo del mittente, l’indirizzo del destinatario, l’oggetto e il testo del messaggio.
send(smtpServer, to, from, subject, body);
Esaminando il metodo send, osserviamo che per prima cosa otteniamo un riferimento a un oggetto di tipo Session. Ci sono più modi di farlo, ma il più semplice è recuperare quello di default. L’oggetto Session richiede un riferimento a un file di proprietà che contenga un valore per il nome dell’host da usare (“mail.smtp.host”).
Possiamo usare un file di sistema o, come in questo caso, costruire al volo un insieme di proprietà. Un’applicazione di produzione, ovviamente, avrebbe un comportamento diverso.
Properties props = System.getProperties();
// Colleghiamoci alla sessione di default
props.put(“mail.smtp.host”, smtpServer);
Session session =
Session.getDefaultInstance(props, null);
Una volta che abbiamo acquistato una Session possiamo usarla per creare un messaggio
// Creiamo un messaggio
Message msg = new MimeMessage(session);
Il messaggio è un contenitore per tutti i dati da inviare a destinazione: testo, attachment e tutto il resto. Ogni proprietà del messaggio può essere impostata con un metodo come serFrom, serTo e così via.
// Impostiamo From, To e Subject
msg.setFrom(new InternetAddress(from));
msg.setRecipients(
Message.RecipientType.TO,
InternetAddress.parse(to, false));
msg.setSubject(subject);
msg.setText(body);
Non illustriamo il modo in cui si trattano gli attachment per semplicità, chi volesse approfondire ha molto materiale a disposizione nella distribuzione di JavaMail, in particolare il programma sendfile.java nella directory demo.
Prima di inviare un messaggio ci permettiamo anche il lusso di “firmarlo” con un header, un’intestazione specifica. Il protocollo Smtp permette di inserire header personalizzati nei messaggi e per convenzione li si distingue facendoli iniziare con X e un trattino. X-Mailer è un header abbastanza utilizzato, per esempio Outlook Express lo include.
// Un header custom
msg.setHeader(“X-Mailer”, “InviaPosta”);
msg.setSentDate(new Date());
L’ultimo passaggio è l’invio vero e proprio del messaggio. Usiamo il metodo statico send della classe Transport. Dopo questo punto o abbiamo un errore o dobbiamo vedere i bit che si mettono in cammino verso la rete.
// Inviamo il messaggio!
Transport.send(msg);
System.out.println(“Messaggio spedito.”); Leggere posta
Leggere la posta è più complicato. La ragione della complicazione sta nel fatto che l’invio di un messaggio è un’operazione semplice e senza possibilità di variazione, mentre la lettura della posta è un’operazione più articolata: se si usa un protocollo evoluto come Imap si può avere a che fare con diverse caselle di posta, normalmente si riceve più di un messaggio alla volta e, infine, i messaggi stessi sono ancora oggetti da navigare, data la loro struttura complessa.
L’avvio del nostro programma è piuttosto semplice: raccogliamo i dati indispensabili dalla riga di comando e chiamiamo una funzione che ha la responsabilità di farne buon uso. I dati necessari sono il nome del server, la login e la password. Si tratta di dati che variano per ogni account di posta, memorizzarli in qualche modo comporterebbe la possibilità di usare il programma con un solo account, quindi li lasciamo sulla riga di comando fino al momento in cui avremo scelto come archiviare in modo più consono le diverse sorgenti di messaggi a cui ci possiamo collegare.
Public static void main(String args[]) {
try {
String popServer = args[0];
String popUser = args[1];
String popPassword = args[2];
receive(popServer, popUser, popPassword);
} catch (Exception ex) {
System.out.println(“Uso: java LeggiPosta
server user password”);
}
System.exit(0);
}
La prima operazione che facciamo nel metodo receive è ottenere un riferimento ad un oggetto Session, proprio come abbiamo fatto nel caso dell’invio.
// Contattiamo un oggetto Session
Properties props = System.getProperties();
Session session = Session.getDefaultInstance(props, null);
Usiamo l’oggetto Session che abbiamo ottenuto, quello di default, per avere accesso a uno Store, un archivio di messaggi. Nella richiesta getStore scegliamo lo Store più comune, una casella di posta Pop3.
// Colleghiamoci a un message store POP3
store = session.getStore(“pop3”);
store.connect(popServer, popUser,
popPassword);
Dopo avere collegato il nostro oggetto Store con la casella di posta che corrisponde a server, user e password che abbiamo specificato sulla riga di comando, possiamo aprire la casella di posta che contiene la posta in arrivo. Usiamo il metodo getDefaultFolder per avere accesso a un oggetto Folder.
Nel caso del protocollo Pop3 c’è una sola cartella, quella che contiene la posta in arrivo e quindi l’uso di getDefaultFolder potrebbe essere evitato, ma chi ha progettato il framework JavaMail ha tenuto conto di altri casi più generali, come un server Imap e non ha ritenuto di fare casi particolari.
// Apriamo la cartella predefinita
folder = store.getDefaultFolder();
if (folder == null)
throw new Exception(“Nessuna cartella di
default”);
Un altro passo, che probabilmente è necessario per uniformità con altri protocolli è l’uso del metodo getFolder per avere accesso alla casella di posta in arrivo. Come abbiamo già rilevato, in una connessione Pop3 siamo connessi fin dall’inizio all’unica casella disponibile.
// Apriamo la cartella della posta in arrivo
folder = folder.getFolder(“INBOX”);
if (folder == null)
throw new Exception(“Errore in apertura
INBOX”);
Il protocollo Pop3 permette sostanzialmente di scaricare i messaggi in coda. Il comportamento predefinito dei client di posta è cancellare dal server i messaggi già letti. Noi per le nostre prove impostiamo un’opzione di apertura della posta in arrivo read only, in modo da non dovere spedire messaggi di prova nuovi a ogni connessione.
// Read only per non cancellare i messaggi a
// ogni test
folder.open(Folder.READ_ONLY);
Di passaggio, notiamo che possiamo configurare un client di posta in modo da lasciare i messaggi sul server, ma ad ogni nuova connessione vediamo solo i messaggi nuovi. Questo dipende dal fatto che il client sa trovare nei messaggi un identificatore univoco messo dal server e sa chiedere al server la lista di identificatori dei messaggi in attesa (il comando Uidl del protocollo). Grazie a questa funzione non scarichiamo ad ogni connessione i messaggi già letti, a meno che non cambiamo client o computer. La nozione di cosa è stato già letto - in un server Pop3 - è lato client.
Ora siamo al punto in cui possiamo scaricare i messaggi dal server.
Il metodo getMessages di un oggetto folder restituisce un array di messaggi. Noi percorriamo per intero l’array in un ciclo for chiamando ogni volta un metodo printMessage a cui demandiamo la responsabilità di navigare per l’oggetto messaggio e stampare informazioni su ogni messaggio.
Message[] msgs = folder.getMessages();
for (int i = 0; i < msgs.length; i++) {
printMessage(msgs[i]);
}
Navigare per i messaggi
Un messaggio di posta ha un mittente, un destinatario, un oggetto e un corpo. Non è una sorpresa vedere la stessa struttura nei metodi della classe Message. Avremo quindi a disposizione metodi come getFrom, getTo, getSubject ed ecco come li usiamo.
// Leggiamo qualche header
String from =
((InternetAddress)
message.getFrom()[0]).getPersonal();
if (from == null)
from = ((InternetAddress)
message.getFrom()[0]).getAddress();
System.out.println(“FROM: ” + from);
String subject = message.getSubject();
System.out.println(“SUBJECT: ” + subject);
Allo stesso modo possiamo leggere il contenuto del messaggio
// Leggiamo il contenuto del messaggio
Part messagePart = message;
Object content = messagePart.getContent();
Notiamo che getContent restituisce un oggetto di tipo Part e non una stringa. L’oggetto Part contiene informazioni che riguardano il tipo del file, ossia la specifica tipologia Mime con cui è stato contrassegnato e altre informazioni che permettono di trattare correttamente una porzione di messaggio.
Adesso cominciamo la navigazione all’interno del messaggio. Se l’oggetto restituito da getContent è un’istanza di Multipart, si tratta di un messaggio composto di più porzioni, come è diventato più regola che eccezione. Spesso i messaggi contengono una sezione con la firma o un disclaimer aziendale e, se si usa Outlook Express senza disabilitare l’invio in formato Html, i messaggi, anche composti di semplice testo, sono in due sezioni: una in testo normale e una in Html con lo stesso identico contenuto.
Se abbiamo a che fare con un messaggio in più parti, possiamo leggere il numero di sezioni con il metodo getCount, selezionare una sezione con il metodo getBodyPart e conoscerne il tipo con getContentType.
if (content instanceof Multipart) {
System.out.println(“[ Multipart Message ]”);
int count = ((Multipart) content).getCount();
for (int j = 0; j < count; j++) {
messagePart = ((Multipart)
content).getBodyPart(j);
System.out.println(j + “: CONTENT: ” +
messagePart.getContentType());
}
}
Finalmente, ci focalizziamo sulla prima sezione, quella che di solito contiene il testo del messaggio e se si tratta di testo comune o Html lo stampiamo sulla console. Non possiamo fare molto di più nel nostro programma, che viene lanciato dalla riga di comando. Altri client più evoluti possono mostrare le immagini, suonare i file musicali e così via.
Tutte cose che grazie a spammer e costruttori di virus hanno perso molto del loro fascino.
Per stampare il messaggio otteniamo una InputStream dall’oggetto messagePart, ci costruiamo intorno un BufferedReader e lo leggiamo riga per riga.
if (contentType.startsWith(“text/plain”)
|| contentType.startsWith(“text/html”)) {
InputStream is =
messagePart.getInputStream();
BufferedReader reader =
new BufferedReader(new
InputStreamReader(is));
String thisLine = reader.readLine();
while (thisLine != null) {
System.out.println(thisLine);
thisLine = reader.readLine();
}
}
Conclusioni
Quello che abbiamo presentato in questo spazio è il minimo necessario per iniziare a lavorare con la posta elettronica: non siamo andati molto più in là di inviare un messaggio senza attachment e stampare la lista dei messaggi in arrivo e degli eventuali allegati.
Nonostante questa semplificazione, lo spazio richiesto non è stato piccolo perché, lo avevamo detto all’inizio, la posta è fatta di moltissime parti mobili in interazione complessa fra loro.
Chi desidera approfondire l’argomento deve prevedere una congrua fase di studio e può trovare abbondante materiale nelle applicazioni di esempio che si possono scaricare insieme a JavaMail.









Ancora nessun commento.