´Cwiczenie 8. Rozległe sieci komputerowe
Transkrypt
´Cwiczenie 8. Rozległe sieci komputerowe
Ćwiczenie 8. Rozległe sieci komputerowe
Biblioteka JavaMail API
Standardowym rozszerzeniem j˛ezyka Java 1.6 i kolejnych wersji jest biblioteka
klas służaca
˛ do pisania zaawansowanych klientów e-mail. Jest ona obowiazkowym
˛
składnikiem Java 2 Platform, Enterprise Edition (J2EE). Można je zaimplementować w modelu 100% Pure Java za pomoca˛ gniazd oraz strumieni. Jednakże
dzi˛eki JavaMail API nie trzeba si˛e koncentrować na niskopoziomowych szczegółach, a można si˛e skupić na programach, które moga˛ obsługiwać inne systemy
pocztowe (POP3, Lotus Notes itp.).
JavaMail API to wysokopoziomowa reprezentacja podstawowych składników
dowolnego systemu pocztowego. Poszczególne składniki sa˛ reprezentowane przez
abstrakcyjne klasy z pakiety javax.mail. Klasy te sa˛ abstrakcyjne, gdyż nie
czynia˛ żadnych założeń co do sposobu przechowywania poczty lub przesyłania
jej pomi˛edzy komputerami. Poszczególne podklasy dostosowuja˛ te abstrakcyjne
klasy do danych protokołów i formatów pocztowych. Korzystanie z JavaMailAPI
to z grubsza ”fabryczny” sposób projektowania aplikacji. Na przykład protokoły
i formaty danych sa˛ wybierane za pomoca˛ jednej linii kodu, w której podaje si˛e
nazw˛e protokołu. Zmiana nazwy protokołu spowoduje przystosowanie programu
do obsługi innego protokołu.
Ponieważ poczta może przybywać w dowolnym momencie, JavaMail API obsługuje poczt˛e przychodzac
˛ a˛ za pomoca˛ zdarzeniowego mechanizmu wywołań zwrotnych. Dokładnie tak samo działaja˛ AWT i JavaBeans. Pakiet JavaMail API używaja˛ JavaBeans. Pakiet javax.mail.event definiuje kilka różnych zdarzeń
pocztowych oraz zwiazane
˛
z nimi interfejsy odbiorcze i klasy adaptacyjne.
Biblioteka JavaMail API to rozszerzenie Javy, które nie wchodzi w skład biblioteki klas JDK ani JRE. Jest ona dost˛epna bezpłatnie pod adresem
http://java.sun.com/products/javamail/. Biblioteka ta zawiera aktualna˛ wersj˛e JavaMail API, w tym najważniejszy plik mail.jar, w którym znajduja˛ si˛e pliki
.class implementujace
˛ JavaMail API. Aby skompilować i uruchomić przykłady
do tych ćwiczeń trzeba umieścić ten plik na ścież-ce klas, dodajac
˛ jego katalog do
zmiennej środowiskowej CLASSPATH, albo umieszczajac
˛
mail.jar w katalogu jre/lib/ext.
1
JavaBeans Activation Framework jest również standardowym rozszerzeniem
Javy, a nie cz˛eścia˛ podstawowego API. Można je pobrać pod adresem
http://java.sun.com/beans/glasgow/jaf.html. Znajduje si˛e w nim plik
activation.jar, który również należy umieścić na ścieżce klas.
1. Wysyłanie poczty
Wysyłanie poczty odbywa si˛e poprzez wykonanie nast˛epujacych
˛
operacji:
1) Ustawia właściwości mail.host tak, aby wskazywała lokalny serwer
pocztowy.
2) Uruchamia sesj˛e pocztowa˛ za pomoca˛ metody Session.getInstance().
3) Tworzy nowy obiekt Message, zwykle konkretyzujac
˛ jedna˛ z podklas.
4) Ustawia adres nadawcy wiadomości (From:).
5) Ustawia adres odbiorcy wiadomości (To:).
6) Ustawia temat wiadomości (Subject:).
7) Ustawia treść wiadomości.
8) Wysyła wiadomość za pomoca˛ metody Transport.send().
Porzadek
˛
tych operacji nie musi być ściśle zachowany. Każda z operacji jest
wykonywana oddzielnie.
Najpierw sa˛ ustawiane właściwości sesji pocztowej. W tym celu wystarczy
ustawić właściwość mail.host. Jest to obiekt klasy java.util.Properties,
a nie zmienna˛ środowiskowa.
˛ Np. poniższy kod
Properties props = new Properties();
props.put(”mail.host”, ”mail.cloud8.net”);
ustawia adres mail.host na adres mail.cloud8.net. Oczywiście należy
ustawiać t˛e właściwość na adres swojego serwera pocztowego. Właściwość t˛e
wykorzystuje si˛e też do pobrania obiektu Session za pomoca˛ metody
2
Session.getInstance():
Session mailConnection = Session.getInstance(props, null);
Obiekt Session reprezentuje połaczenie
˛
mi˛edzy programem a serwerem pocztowym. Drugi argument metody getInstance(), tutaj null, to obiekt
javax.mail.Authenticator, który poprosi użytkownika o hasło, jeśli serwer tego zażadał.
˛
Z reguły nazw˛e użytkownika i hasło trzeba podać tylko przy
odbieraniu poczty, a nie przy wysyłaniu.
Obiekt Session jest używany do skonstruowania obiektu Message. W przypadku sieci Internet jest używana konkretna klasa MimeMessage. Nast˛epnie
sa˛ ustawiane pola i treść obiektu Message. Adresy From: i To: sa˛ obiektami
javax.mail.internet.InternetAddress. Można podać adres e-mail
albo sam adres i prawdziwe nazwisko, np.
Address bill = new InternetAddress(”billmicrosoft.com”,
”Bill Gates”);
Metoda SetFrom() pozwala określić nadawc˛e wiadomości przez ustawienie nagłówka From:. Nie ma żadnego zabezpieczenia przed fałszowaniem adresu,
można podszyć si˛e pod Billa Gatesa, używajac
˛ podanego wyżej (fikcyjnego) adresu:
msg.setFrom(bill);
Metoda SetRecipient() jest bardziej skomplikowana. Nie tylko podaje
adres, na który zostanie wysłana wiadomość, ale także sposób interpretacji tego
adresu, tzn. czy znajduje si˛e on w polu To:, Cc: czy Bcc:. Określa si˛e to za pomoca˛ trzech stałych klas Message.RecipientType:
Message.RecipientType.TO
Message.RecipientType.CC
Message.RecipientType.BCC
Na przykład:
msg.setSubject(”Czekam na Ciebie.”);
3
Temat ustawia si˛e za pomoca˛ prostego ciagu
˛ tekstowego, np.:
msg.setSubject(”Spotkanie.”);
Treść również ustawia si˛e za pomoca˛ jednego ciagu
˛ tekstowego. Używa si˛e
typu text/plain, np.:
msg.setContent(”Mam spraw˛
e do Ciebie.”, ”text/plain”);
Wreszcie statyczna metoda Transport.send() łaczy
˛
si˛e z serwerem pocztowym określonym we właściwości mail.host() i wysyła wiadomość w świat:
Transport.send(msg);
Poniższy przykład ilustruje wysłanie wiadomości.
Przykład 1
import javax.mail.*;
import javax.mail.internet.*;
import java.util.*;
public class Assimilator {
public static void main(String[] args) {
try {
Properties props = new Properties props = Properties();
props.put(”mail.host”, ”mail.cloud8.net”);
Session mailConnection = Session.getInstance(props, null);
Message msg = new MimeMessage(mailConnection);
Address bill = new InternetAddress(”billmicrosoft.com”,
”Bill Gates”);
Address jan = new InternetAddress(”janekgmail.com”);
4
msg.setContent(”Mam spraw˛
e do Ciebie”, ”text/plain”);
msg.setFrom(bill);
msg.setRecipient(Message.RecipientType.TO, jan);
msg.setSubject(”Spotkanie.”);
Transport.send(msg);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Przykład 2. Graficzny klient SMTP
Wysyłanie wiadomości przy użyciu prostego interfejsu graficznego przedstawiono poniżej. Kod pocztowy jest zawarty w metodzie actionPerformed() i
przypomina metod˛e main z przykładu poprzedniego. Różnica polega na tym, że
w poniższym przykładzie host, temat, adresy From: i To: oraz tekst wiadomości
sa˛ odczytywane ze składników interfejsu graficznego w czasie wykonania, a nie
sa˛ zakodowane ”na sztywno".
Poniżej zamieszczono kod prostego apletu wysyłajacego
˛
poczt˛e.
import javax.mail.*;
import javax.mail.internet.*;
import java.util.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class SMPTClient extends JFrame {
private JButton sendButton = new JButton(”Wyślij wiadomość”);
private JLabel fromLabel = new JLabel(”Od: ”);
private JLabel toLabel = new JLabel(”Do: ”);
private JLabel hostLabel = new JLabel(”Serwer SMTP: ”);
5
private JLabel subjectLabel = new JLabel(”Temat: ”);
private JLabel fromField = new JTextField(40);
private JTextField toField = new JTextField(40);
private JTextField hostField = newJTextField(40);
private JTextField subjectField = new JTextField(40);
private JTextArea message = new JTextArea(40, 72);
private JScrollPane jsp = new JScrollPane(msg);
public SMTPClient() {
super(”SMTP Client”);
Container contentPane = this.getContentPane();
contentPane.setLayout(new BorderLayout());
JPanel labels = new JPanel();
labels.setLaout(new GridLayout(4, 1));
labels.add(hostLabel);
JPanel fields = new JPanel();
fields.setLayout(4, 1));
String host = System.getProperty(”mail.host”, ” ”);
hostField.setText(host);
fields.add(hostField);
labels.add(toLabel);
fields.add(toField);
String from = System.getProperty(”mail.from”,” ”);
fromField.setText(from);
labels.add(fromLabel);
fields.add(fromField);
labels.add(subjectLabel);
fields.add(subjectField);
Box north = Box.createHorizontalBox();
north.add(labels);
north.add(fields);
6
contentPane.add(north, BorderLayout.NORTH);
message.setFont(new Font(”Monospaced”, Font.PLAIN, 12));
contentPane.add(jsp, BorderLayout.CENTER);
JPanel south = new JPanel();
south.setLayout(new FlowLayout(FlowLayout.CENTER));
south.add(sendButton);
sendButton.addActionListener(new SendAction());
contentPane.add(south, BorderLayout.SOUTH);
this.pack();
}
class SendAction implements ActionListener {
public void actionPerformed(ActionEvent evt) {
try {
Properties props = new Properties();
props.put(”mail.host”, hostField.getText());
Session mailConnection = Session.getInstance(props, null);
final Message msg = new MimeMessage(mailConnection);
Address to = new InternetAddress(toField.getText());
Addres from = new InternetAddress(fromField.getText());
msg.setContent(message.getText(), ”text/plain”);
msg.setFrom(from);
msg.setRecipient(Message.RecipientType.TO, to);
msg.setSubject(subjectField.getText());
/ / Tworzymy watek
˛ w celu wysłania poczty.
Runnable r = new Runnable() {
public void run() {
try {
Transport.send(msg);
7
}
catch (Exception e) {
e.printStackTrace();
}
}
};
Thread t = new Thread(r);
t.start();
message.setText(” ”);
}
catch (Exception e) {
/ / Tu powinien być komunikat o bł˛edzie
e.printStackTrack();
}
}
}
public static void main(String[] args) {
SMTPClient client = new SMTPClient();
client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
client.show();
}
}
2. Odbieranie poczty
Odbieranie poczty jest bardziej złożone niż jej wysłanie. Do uzyskania dost˛epu
do serwerów SMTP wystarczy jedno proste polecenie Helo, co jest przyczyna˛
rozsyłania ”spamu”, natomiast do odbierania poczty zwykle trzeba podawać nazw˛e
użytkownika i hasło. SMTP stosuje 14 różnych poleceń, z których klientowi email wystarczy tylko pi˛eć. Natomiast POP3 używa 12 poleceń, z których wszystkie musza˛ być obsługiwane przez klienta, a w IMAP4 sa˛ aż 24 polecenia.
JavaMail API zaprojektowano z założeniem, że wiadomości b˛eda˛ pobierane z
serwera IMAP lub NNTP. Jednakże wi˛ekszość użytkowników aktualnie używa
protokołu POP, a nie protokołu IMAP4. Z perspektywy JavaMail API, IMAP
można traktować za POP uzupełniony o kilka poleceń do manipulowania fold8
erami. W prostych zastosowaniach JavaMail API, które operuja˛ na folderze INBOX, klienty POP oraz IMAP sa˛ bardzo podobne.
Odczytywanie zdalnej skrzynki pocztowej składa si˛e z około 12 etapów (dokładna
liczba może si˛e różnić). Sa˛ to:
1. Ustawić właściwości, które b˛eda˛ używane podczas połaczenia.
˛
2. Skonstruować obiekt Authenticator, który b˛edzie używany podczas
połaczenia.
˛
3. Pobrać obiekt Session za pomoca˛ metody Session.getDefaultInstance().
4. Użyć metody getStore() sesji, aby uzyskać magazyn Store.
5. Połaczyć
˛
si˛e z magazynem.
6. Pobrać z magazynu folder INBOX za pomoca˛ metody getFolder().
7. Otworzyć folder INBOX.
8. Otworzyć folder wewnatrz
˛ folderu INBOX. Powtarzać dopóki nie znajdziesz
szukanego folderu.
9. Pobrać wiadomość z folderu jako tablic˛e obiektów Message.
10. Przetwarzać elementy tablicy za pomoca˛ metod klasy Message.
11. Zamknać
˛ folder.
12. Zamknać
˛ magazyn.
Każdy z etapów polega na określeniu właściwości sesji pocztowej. Jeśli podczas sesji pocztowej b˛edzie tylko pobierana poczta, to wystarczy pusty obiekt
Properties. Na przykład:
Properties props = new Properties();
Nast˛epnie musi być utworzona instancja klasy javax.mail.Authenticator, która zapyta użytkownika o hasło. Póki co hasło zostanie zakodowane w
programie i przekazana zostanie wartość null zamiast obiektu Authenticator.
9
Później zostanie to zmienione.
Authenticator a = null;
Nast˛epnie używajac
˛ obiektów Properties oraz Authenticator uzyskujemy instancj˛e klasy Session:
Session session = Session.getDefaultInstance(props, a);
Nast˛epnie prosimy sesj˛e o magazyn konkretnego dostawcy. Tutaj chcemy uzyskać
magazyn POP3:
Store store = session.getStore(”POP3”);
Teraz musi nastapić
˛ połaczenie
˛
z magazynem za pomoca˛ metody connect().
Musi być określony host, z którym należy si˛e połaczyć,
˛
oraz nazwa użytkownika
i hasło:
store.connect(”gmail.com”, ”mojenazwisko”, ”moj_password”);
Można podać wartość null zamiast hasła, aby poinformować, że o hasło należy
zwrócić si˛e do określonego wcześniej obiektu Authenticator.
Po połaczeniu
˛
si˛e z magazynem można otworzyć jeden z jego folderów. Ten etap
dotyczy innych protokołów niż POP3 (np. IMAP), gdyż POP3 ”wrzuca” do jednego worka wszystkie wiadomości. W JavaMail API dostawcy POP3 posługuja˛
si˛e folderem o nazwie INBOX:
Folder inbox = store.getFolder("INBOX");
Uzyskany folder jest poczatkowo
˛
zamkni˛ety. Niektóre operacje, jak usuni˛ecia
lub zmiana nazwy, moga˛ być przeprowadzone nawet na zamkni˛etym folderze.
Można też otworzyć folder tylko do odczytu, przekazujac
˛ do metody open()
stała˛ mnemoniczna˛ Folder.READ_ONLY, a w trybie do odczytu i zapisu
Folder.READ_WRITE:
10
inbox.open(Folder.READ_WRITE);
Teraz można pobrać wiadomości. Służy temu metoda getMessages(), która
zwraca tablic˛e z wszystkimi wiadomościami zawartymi w folderze:
Message[] messages = inbox.getMessages();
Klasa Message zawiera wiele metod operujacych
˛
na pojedynczych wiadomościach: do pobierania różnych pól nagłówka, do pobierania treści wiadomości, do
odpowiadania na wiadomość itp. Najprostsza˛ możliwa˛ operacja˛ jest wyświetlenie
każdej wiadomości na System.out, używajac
˛ metody writeTo() oraz klasy
Message:
for (int i = 0; i < messages.length; i++) {
System.out.println(----- Wiadomosc "+ (i+1)
+ --------");
messages[i].writeTo(System.out);
}
Na zakończenie musi być zamkni˛ety folder, a nast˛epnie magazyn wiadomości za
pomoca˛ metod close():
inbox.close(false);
store.close;
Argument false oznacza, że serwer nie powinien rzeczywiście kasować wiadomości usuni?etych z folderu. Chcemy po prostu przerwać połaczenie
˛
z folderem.
Poniższy przykład ilustruje działanie klienta skrzynki pocztowej POP3.
Przykład 3. Klient skrzynki pocztowej POP3
import javax.mail.*;
import javax.mail.internet.*;
import java.util.*;
import java.io.*;
public class POP3Client {
11
public static void main(String[] args) {
Properties props = new Properties();
String host = ”nasz.serwer.edu”;
String username=”nazwisko”;
String password = ”mojehaslo”;
String provider = ”pop3”;
try {
/ / Łaczymy
˛
si˛e z serwerem POP3
Session session = Session.getDefaultInstance(props, null);
Store store = session.getStore(provider);
store.connect(host, username, password);
/ / Otwieramy folder
Folder inbox = store.getFolder(”INBOX”);
if (inbox == null) {
System.out.println(”Brak folderu INBOX”);
System.exit(1);
}
inbox.open(Folder.READ_ONLY);
/ / Pobieramy wiadomości z serwera
Message[] message = inbox.getMessages();
for (int i = 0; i < messages.length; i++) {
System.out.println(”———Wiadomosc ” + (i+1)
+ ”——–”);
messages[i].writeTo(System.out);
}
/ / Zamykamy połaczenie,
˛
ale nie kasujemy wiadomości z serwera
inbox.close(false);
store.close();
}
catch (Exception e) {
12
e.printStackTrace();
}
}
}
3. Uwierzytelnianie za pomoca˛ haseł
Uwierzytelnianie poczty za pomoca˛ hase?l zwi˛eksza bezpieczeństwo danych.
Hasło nie powinno ukazywać si˛e na ekranie. Byłoby najlepiej, gdyby ono nie było
przesyłane jawnym tekstem, choć wiele klientów i serwerów POP3 tak post˛epuje.
Kiedy otwiera si˛e połaczenie
˛
z magazynem, JavaMail API może obiektu
javax.mail.Authenticator w celu uzyskania nazwy użytkownika i hasła.
Authenticator to klasa abstrakcyjna:
public abstract class Authenticator extends Object
Kiedy dostawca usług musi uzyskać nazw˛e użytkownika lub hasło, to wywołuje
zwrotnie metod˛e getPasswordAthentication w zdefiniowanej przez użytkownika podklasie Authenticator. Metoda zwraca obiekt PasswordAuthentication, zawierajacy
˛ te informacje:
protected PasswordAuthentication getPasswordAuthentication()
Aby dodać do programu obsług˛e uwierzytelniania w czasie wykonywania, należy
napisać podklas˛e Authenticator i nadpisać getPasswordAuthentication() metod˛e, która potrafi bezpiecznie zapytać użytkownika o hasło. Do
tego nadaje si˛e składnik JPasswordField pakietu Swing. Poniższy przykład
oparty na pakiecie Swing używa podklasy Authenticator, która wświetla
okno dialogowe z pytaniem o nazw˛e użytkownika i hasło.
Przykład 3. Klasa uwierzytelniajaca
˛ z interfejsem graficznym
import javax.mail.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
13
public class MailAuthenticatior extends Authenticator {
private JDialog paswordDialog = new JDialog(new JFrame(), true);
private JLabel mainLabel = new JLabel(
”Wpisz nazw˛e użytkownika i hasło: ”);
private JLabel userLabel = new JLabel(”Nazwa: ”);
private JLabel passwordLabel = new JLabel(”Hasło: ”);
private JTextField usernameField = new JTextField(20);
private JPasswordField passwordField = new JPasswordField(20);
private JButton okButton = new JButton(”OK”);
public MailAuthenticator() {
this(” ”);
}
public MailAuthenticator(String username) {
Container pane = passwordDialog.getContentPane();
pane.setLayout(new GridLayout(4, 1));
pane.add(mainLabel);
JPanel p2 = new JPanel();
p2.add(userLabel);
p2.add(usernameField);
usernameField.setText(username);
pane.add(p2);
JPanel p3 = new JPanel();
p3.add(passwordLabel);
p3.add(passwordField);
pane.add(p3);
JPanel p4 = new JPanel();
p4.add(okButton);
pane.add(p4);
passwordDialog.pack();
ActionListener al = new HideDialog();
okButton.addActionListener(al);
usernameField.addActionListener(al);
passwordField.addActionListener(al);
14
}
class HideDialog implements ActionListener {
public void actionPerformed(ActionEvent e) {
passwordDialog.hide();
}
}
public PasswordAuthentication getPasswordAuthentication() {
passwordDialog.show();
String password = new String(passwordField.getPassword());
String username = usernameField.getText();
passwordField.setText(” ”);
return new PasswordAuthentication(username, password);
}
}
15