In questa puntata viene descritto il modulo Admin Server dell'analizzatore di movimenti internet. Include le classi AdminServer, AdminThread, AdminCommunicator e l'applet Admin per la configurazione e il controllo del sistema.
Introduzione
Questo modulo permette all'amministratore dell'analizzatore di configurarlo e di controllarne il comportamento. È formato da 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 mantenuti e gestiti nella classe Config, descritta nella puntata precedente.
Figura 1: Schema del modulo Admin Server
Dallo schema si capisce immediatamente come avvenga 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.
Applet Admin
L'applet Admin si presenta inizialmente chiedendo la password dell'amministratore del sistema. Al suo interno mantiene, oltre ai componenti grafici che gestisce, anche una copia locale della classe Config, nella quale vengono memorizzate le modifiche che l'amministratore apporta alla configurazione del proxy.
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 l'applet inizializza la Config locale con i valori attuali dei dati di configurazione contenuti nella Config globale utilizzata dal proxy. In questo modo si ha in locale una copia aggiornata della configurazione, da usare come appoggio prima di decidere di inviare le modifiche al proxy e renderle 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 messaggio di errore
errorLabel1.setVisible(true);
errorLabel2.setVisible(true);
}
} else {
reportError("Errore nel contattare l'AdminServer");
}
}
Una volta controllata la validità della password ed inizializzata la Config locale, l'applet visualizza una serie di campi per l'immissione dei dati che consentono di modificare:
1. la password dell'amministratore del sistema; 2. la presenza di un secondo proxy, con relativo indirizzo e numero di porta d'ascolto; 3. la lista dei siti Internet da monitorare; 4. l'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.
Una volta che l'utente ha inserito i dati da modificare e preme il pulsante OK, l'applet esegue una verifica della correttezza formale dei dati inseriti attraverso il metodo checkValidData, quindi tramite 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();
}
}
Nel caso in cui l'utente prema CANCEL, l'applet si chiude senza inviare alcun dato al proxy, che mantiene quindi la precedente configurazione. Il tasto RESET permette invece di cancellare tutte le modifiche apportate fino a quel momento 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 AdminCommunicator e AdminServer segue un protocollo molto semplice. Come prima cosa, AdminCommunicator si fa riconoscere attraverso la parola chiave ADMIN. Quindi attende la risposta dal server, in cui gli vengono passati i dati dell'attuale configurazione del proxy, che a sua volta verranno passati all'applet Admin.
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";
}
}
Quando l'utente decide di inviare le modifiche apportate alla Config, l'AdminCommunicator invia la parola chiave OK, che avvisa il server dell'invio dei dati, quindi spedisce tali modifiche sotto forma di stringa e chiude la connessione. Se invece l'utente chiude l'applet senza rendere operative le modifiche, l'AdminCommunicator invia al server il solo messaggio CANCEL.
Queste due classi permettono alle varie applicazioni ed applet di comunicare con il proxy per fornire aggiornamenti sui valori da mantenere nella classe Config. 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 e delega poi la gestione delle singole richieste a thread dedicati.
Figura 2 - AdminServer
L'idea generale è analoga a quella già vista per il proxy: il server principale resta in ascolto, accetta una connessione e crea un thread dedicato per la singola interazione amministrativa. In questo modo l'interfaccia di amministrazione e gli altri moduli possono scambiare parametri di configurazione senza bloccare l'intero sistema.
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();
}
} catch (Exception e) {}
finally {
// si attende che tutti gli admin-thread terminino
while(config.getAdminThreads() > 0) {}
try {
adminSocket.close();
} catch (Exception e) {}
}
}
L'AdminThread, prima di gestire la richiesta, deve capire chi l'ha mandata. I tre possibili applet che possono comunicare con il proxy sono:
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.
MokaByte®
è un marchio registrato da MokaByte s.r.l.
Java®, Jini® e tutti i nomi derivati sono marchi
registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione
anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it