Introduzione
Questo
modulo, come già abbiamo introdotto nelle scorse puntate, permette
all’amministratore dell’analizzatore di configurarlo e di controllarne
il comportamento ed è formato da ben tre classi (AdminServer, AdminThread,
AdminCommunicator) e dall’applet Admin, che presenta all’amministratore
una finestra di dialogo con cui definire i valori di configurazione. Queste
classi comunicano tra loro e permettono l’accesso ai dati di configurazione
che sono mantenuti e gestiti nella classe Config, descritta nella scorsa
puntata. Lo schema è quello illustrato dalla fig. 1.
Dallo
schema si può subito capire come avviene lo scambio di messaggi
tra i vari moduli: quando l’utente chiede di configurare o controllare
il sistema, il proxy gli invia l’applet Admin, che tramite la classe AdminCommunicator
si collega all’AdminServer, con cui si può accedere ai dati contenuti
nella classe Config. Vediamo ora in dettaglio le singole classi che contribuiscono
a creare l’intero modulo.
Applet Admin
L’applet
Admin si presenta inizialmente chiedendo la password dell’amministratore
del sistema. Al suo interno, inoltre, mantiene, oltre ai componenti grafici
che gestisce (campi di inserimento dei dati), anche una copia locale della
classe Config, in cui verranno memorizzate le modifiche che l’amministratore
farà alla configurazione del proxy stesso.
Quando
l’utente immette la password, l’applet, tramite l’AdminCommunicator, si
collega al proxy e ne controlla l’esattezza (i parametri “adminHost” e
“adminPort” sono nascosti nel file HTML in cui viene caricato l’applet
Admin). Inoltre, inizializza la Config locale con i valori attuali dei
dati di configurazione contenuti nella Config globale utilizzata dal proxy.
In questo modo si avrà in locale una copia della Config aggiornata.
Questa copia sarà utilizzata come appoggio per le modifiche dei
parametri effettuate dall’utente, prima che decida di inviarle al proxy
stesso e renderle in tal modo operative.
void
passwordField_EnterHit(java.awt.event.ActionEvent event) {
//Collegamento
al
Proxy con l’AdminCommunicator
adminComm=new AdminCommunicator(getParameter("adminHost"),
Integer.parseInt(getParameter("adminPort")));
config=new Config();
config.setIsAppletContext(true);
configString=adminComm.getConfigParams();
if (!configString.equals("ERROR")) {
config.parse(configString);
String password=passwordField.getText();
//Controllo della password
if (config.getPassword().compareTo(password)==0) {
//amministratore del proxy riconosciuto
//inviare il dialog per prelevare le modifiche al proxy
setControls();
}
else {
//amministratore non riconosciuto
//inviare messagio di errore
errorLabel1.setVisible(true);
errorLabel2.setVisible(true);
}
}
else
reportError("Errore nel contattare l'AdminServer");
}//Fine passwordField_EnterHit
Una
volta controllata la validità della password ed inizializzata la
Config locale, l’applet visualizza all’utente una serie di campi per l’immissione
dei dati che gli consentono di modificare i seguenti parametri:
-
password
dell’amministratore del sistema;
-
presenza
di un secondo proxy, di cui si devono indicare indirizzo e numero
di porta d’ascolto;
-
lista
dei siti Internet da monitorare;
-
orario
di salvataggio dei dati raccolti sui movimenti dell’utente, ossia l’ora
in cui si deve attivare il demone per l’invio di tali dati alle rispettive
banche dati.
Questi
componenti grafici vengono resi visibili/invisibili all’occorrenza dall’applet
stesso.
Una
volta che l’utente ha inserito i dati che vuole modificare e preme il pulsante
OK, tramite il quale chiede di rendere operative le modifiche, l’applet
esegue una verifica della correttezza formale dei dati inseriti, attraverso
il metodo checkValidData, e quindi tramite la classe AdminCommunicator
invia al proxy i nuovi parametri di configurazione, dopo aver aggiornato
la configurazione nella Config locale.
void
oKButton_MouseClicked(java.awt.event.MouseEvent event)
{
if
(checkValidData()) {
//Aggiornamento Config locale
//proxy father
if (proxyCheckbox.getState()) {
config.setIsFatherProxy(true);
config.setFatherProxyHost(addrProxyField.getText());
String fatherProxyPort=portProxyField.getText();
if (fatherProxyPort.compareTo("")==0)
config.setFatherProxyPort(8080);
else
config.setFatherProxyPort(Integer.parseInt(fatherProxyPort));
}
else {
config.setIsFatherProxy(false);
config.setFatherProxyHost("");
config.setFatherProxyPort(8080);
}
//password
if (changePassCheckbox.getState()) {
config.setPassword(newPassField.getText());
}
//start-time
config.setStartTime(Integer.parseInt
(startHourField.getText()),
Integer.parseInt(startMinField.getText()));
//nuovi siti
for (int i=0;i<newHost.length;i++)
config.insertIntoHostList(newHost[i]);
//si invia la nuova config al proxy
configString=config.toString();
if (adminComm.sendConfigParams
(configString).equals("OK"))
reportError("Invio dei dati effettuato");
else
reportError("Errore nell'invio dei dati al proxy");
//Chiusura Applet
stop();
}
}//Fine oKButton_MouseClicked
Nel
caso, invece, in cui l’utente prema il tasto CANCEL l’applet prima di chiudersi
non invia alcun dato al proxy, che quindi mantiene la precedente configurazione.
Infine, il tasto RESET permette all’utente di cancellare tutte le modifiche
apportate finora e continuare a riconfigurare il proxy.
Classe AdminCommunicator
Questa
classe ha il compito di mettere in contatto l’applet di amministrazione
con il proxy. La comunicazione che si instaura tra l’AdminCommunicator
e l’AdminServer segue il seguente protocollo. Come prima cosa, l’AdminCommunicator
si fa riconoscere come tale attraverso la parola chiave “ADMIN”. Quindi
attende la risposta dal server in cui gli vengono passati i dati dell’attuale
configurazione del proxy e che passa a sua volta all’applet Admin, per
consentirgli di inizializzare la copia locale della Config.
public
String getConfigParams()
{
try {
//si crea il socket verso il proxy
adminSocket=new Socket(adminHost,adminPort);
out=new DataOutputStream(adminSocket.getOutputStream());
in=new BufferedReader(new InputStreamReader(adminSocket.getInputStream()));
out.writeBytes("ADMIN\n");
out.flush();
String configParams=in.readLine();
return (configParams);
}
catch(IOException e) {
try {
out.close();
in.close();
adminSocket.close();
}
catch (Exception e2)
{}
System.out.println("Errore nel contattare l'AdminServer "+e.toString());
return "ERRORE";
}
}//Fine getConfigParams
Quando
l’utente decide di inviare le modifiche apportate alla Config al proxy,
l’AdminCommunicator invia la parola chiave “OK”, che avvisa il server dell’invio
dei dati, quindi invia tali modifiche sotto forma di stringa e chiude la
connessione con il server. Se invece l’utente chiude l’applet senza rendere
operative le modifiche, l’AdminCommunicator invia al server il solo messaggio
“CANCEL”, con il quale lo avvisa che non saranno inviati i dati aggiornati.
public
String sendConfigParams(String configString)
{
if (!configString.equals("NULL")) {
try {
out.writeBytes("OK\n");
out.writeBytes(configString);
out.flush();
out.close();
in.close();
adminSocket.close();
return "OK";
}
catch(IOException ioe) {
return "ERRORE";
}
}
else {
try {
out.writeBytes("CANCEL\n");
out.flush();
out.close();
in.close();
adminSocket.close();
return "OK";
}
catch(IOException ioe) {
return "ERRORE";
}
}
}//Fine sendConfigParams
Classi AdminServer
e AdminThread
Queste
due classi permettono alle varie applicazioni ed applet, di comunicare
con il proxy per fornire aggiornamenti sui valori da mantenere nella classe
Config. Lo schema algoritmico seguito è descritto nelle fig. 2 e
fig. 3.
Fig.
2 AdminServer
L’AdminServer
viene creato ed avviato dal ProxyServer. Dopo aver inizializzato i propri
parametri interni, il server si mette in ascolto su una porta ben precisa,
nota alle applicazioni che vogliono comunicare con esso. Quando arriva
una richiesta su questa porta, il server la passa immediatamente ad un
nuovo thread della classe AdminThread, che si occupa di gestirla. Il server
può quindi tornare all’ascolto di nuove richieste di accesso al
proxy.
public
void run()
{
//loop fino a che il proxy-server è attivo
try {
while(config.getServerAlive()) {
//si attende un collegamento da un client
appletSocket = adminSocket.accept();
//si passa la richiesta ad un nuovo admin-thread
AdminThread adminThd=new AdminThread(config,appletSocket);
adminThd.start();
}//while
}
catch (Exception e)
{}
finally {
//si attende che tutti gli admin-thread terminino
while(config.getAdminThreads()>0)
{}
try {
adminSocket.close();
}
catch (Exception e)
{}
}
}//Fine run
L’AdminThread,
prima di gestire la richiesta, deve capire chi l’ha mandata. I tre possibili
applet che possono comunicare con il proxy sono:
-
l’applet
Admin;
-
l’applet
TimeCounter;
-
l’applet
UserInfo.
Fig.3
AdminThread
Nel
primo caso, la richiesta è caratterizzata dalla parola chiave “ADMIN”
e quindi deriva dall’applet di amministrazione del proxy, a cui l’AdminThread
deve rispondere inviando i dati dell’attuale configurazione del proxy,
in forma di stringa. Prima di farlo, però, controlla che la Config
non sia già occupata da un altro applet di amministrazione, e quindi
si mette in attesa che questa risulti libera, per evitare sovrapposizioni
di modifiche diverse. Quindi, dopo aver inviato i dati di configurazione,
attende una risposta dall’applet che gli indichi se saranno inviati i dati
della nuova configurazione del proxy (“OK”), oppure no (“CANCEL”). Se i
nuovi dati devono essere resi operativi, l’AdminThread si occuperà
di aggiornare la Config globale utilizzata dal proxy.
//se
la richiesta è da parte dell'applet "admin"
if (request.equals("ADMIN")) {
while(config.getIsBusy())
{}
config.setIsBusy(true);
//si invia la config
out.writeBytes(config.toString());
out.flush();
if (in.readLine().equals("OK")) {
//si riceve la nuova configurazione dall'applet
//e si passa alla config
config.parse(in.readLine());
}
config.setIsBusy(false);
}
Nel
secondo caso, l’applet TimeCounter vuole comunicare i dati calcolati sul
documento appena letto dall’utente al proxy: per questo invia la parola
chiave “TIMECOUNTER”, a cui il thread risponde con la parola chiave “ACCEPT”.
Quindi l’applet può inviare i dati necessari per l’operazione di
aggiornamento delle informazioni raccolte sui movimenti dell’utente, ossia
il sito monitorato, il documento appena letto ed il tempo di lettura. Anche
in questo caso, l’AdminThread si occupa di aggiornare i dati corrispondenti
nella Config.
else
if (request.equals("TIMECOUNTER"))
//la richiesta è da parte dell'applet TimeCounter
//che conta il tempo di lettura di un documento
{
//si invia un messaggio di accettazione
out.writeBytes("ACCEPT\n");
out.flush();
//si ricevono i dati
int host=Integer.parseInt(in.readLine());
int doc=Integer.parseInt(in.readLine());
long time=Long.parseLong(in.readLine());
config.updateDocList(host,doc,time);
config.closeThread();
}
Infine,
nella terza situazione, è l’applet UserInfo che vuole comunicare
con il proxy per passargli i dati personali (login e password) dell’utente
appena riconosciuto. La parola chiave di riconoscimento è “USERINFO”,
alla quale il thread risponde con “ACCEPT” ed attende i dati dell’utente
per passarli alla Config a cui fa riferimento il proxy.
else
if(request.equals("USERINFO"))
//la
richiesta è da parte dell'applet
//che
riconosce l'utente
{
//si invia un messaggio di accettazione
out.writeBytes("ACCEPT\n");
out.flush();
//si ricevono i dati
request=in.readLine();
int host=config.isInHostList(request);
if (host>-1) {
request=in.readLine();
config.saveUserInfo(request,host);
out.writeBytes("INSERTOK\n");
out.flush();
}
}
In
tutti e tre i casi visti, una volta aggiornati i valori della Config, il
thread si chiude liberando le risorse che aveva acquisito. Quindi ogni
attivazione dell’AdminThread corrisponde alla gestione di un’unica richiesta
da parte di uno dei tre applet.
Anche
questa volta siamo giunti alla fine della puntata. Per terminare la presentazione
dell’analizzatore di movimenti di un utente di Internet, ora ci resta solo
da descrivere l’ultimo dei tre moduli che lo compongono, il modulo “Demone”.
Ma questo lo vedremo alla prossima ed ultima puntata.
|