MokaByte Numero  39  - Marzo 2000
Analizzatore di movimenti di
un utente internet
III parte
di 
Rossella Mirizzi
Come monitorare ed ottimizzare il cammino 
ipertestuale in  una struttura web

Eccoci arrivati alla terza puntata della costruzione dell’analizzatore di movimenti di un utente di Internet. Nelle scorse puntate abbiamo dato una rapida occhiata generale alla sua struttura ed abbiamo approfondito la costruzione del primo modulo, quello del “Proxy”. Oggi ci occuperemo del secondo dei tre moduli nominati nel primo articolo, quello dell’“Admin Server”

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:

  1. password dell’amministratore del sistema;
  2. presenza di un  secondo proxy, di cui si devono indicare indirizzo e numero di porta d’ascolto;
  3. lista dei siti Internet da monitorare;
  4. 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:

  1. l’applet Admin;
  2. l’applet TimeCounter;
  3. 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.
 

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