- Una semplice chat in AJAX -
 
COSA SERVE PER QUESTO TUTORIAL
 DownloadChiedi sul FORUM | Glossario cognizioni generiche sul funzionamento del web
Una chat in AJAX che sfrutta PHP o ASP .Net

STRUTTURA DELLA CHAT
Pagine coinvolte e funzionamento della chat AJAX

In questo tutorial vedremo come creare una chat asincrona in AJAX, facendo uso di linguaggi lato server (PHP o ASP .Net) esclusivamente per scrivere i dati in un file. Faremo dunque delle periodiche richieste al server per sapere la dimensione del file contenente il log della chat e se è aumentata dall'ultimo controllo richiederemo al server solamente la parte che ci manca.
Vediamo i file che entrano in gioco nella chat:

  • ajax.js: è un semplice file contenente la funzione GetXMLHttpRequest che restituisce un oggetto XMLHttpRequest tenendo conto delle differenze tra i browser (per ulteriori dettagli su questa funzione riferirsi all'articolo introduttivo ad AJAX); GetXMLHttpRequest prende inoltre come parametro un riferimento alla funzione che deve gestire l'evento onreadystatechange;
  • main.htm: documento principale, contiene il codice per inviare messaggi al server, le relative caselle di testo e un IFRAME contenente chat_content.htm;
  • chat_content.htm: pagina che controlla se sono arrivati nuovi messaggi ogni secondo, scaricando solo la parte nuova del log (log_chat.cha);
  • log_chat.cha: file contente il log della chat in formato HTML ma senza tag HTML, BODY o HEAD, solo una serie di DIV contenenti i messaggi; l'estensione del file è CHA perché non venga conteggiata nelle statistiche; se avesse estensione HTM genererebbe moltissime visite che in realtà sono micro-richieste di servizio; esistono ovviamente altri metodi per evitare questo inconveniente;
  • scrivi.aspx e scrivi.php: due semplici pagine rispettivamente in VB .Net e PHP che non fa null'altro che scrivere le informazioni che gli vengono inviate in fondo al log (log_chat.cha);
INVIARE UN MESSAGGIO
La pagina principale attraverso la quale si inviano messaggi

La pagina main.htm contiene, come detto, un IFRAME che punta a chat_content.htm e varie INPUT, una per specificare il nick, una per il messaggio, una per il colore e in più una SELECT per impostare il linguaggio in uso. Infine vi è un pulsante per inviare un messaggio che richiama la funzione Send() al click da parte dell'utente.

<input type="button" value="Invia" onclick="Send()">

Vediamo questa funzione il cui obiettivo è inviare l'autore, il messaggio e il colore al server cosicché possa memorizzarlo.


function Send() {
    // Mettiamo i value dei vari campi in variabili locali
    var message = document.getElementById("messaggio").value;
    var author = document.getElementById("autore").value;
    var colore = document.getElementById("colore").value;
    var pagina = document.getElementById("lang").value;
    
    // Creiamo la richiesta AJAX passando un riferimento alla funzione che 
    // deve gestire il risultato della richiesta
    xmlHttp = GetXMLHttpRequest(xmlHttp_readystatechange);
    // Prepariamo la richiesta verso la pagina ASP .Net o PHP con metodo POST
    // e in modo che sia asincrona (l'ultimo parametro)
    xmlHttp.open("POST", pagina, true);
    // Impostiamo il tipo di dati che stiamo per inviare sul formato delle query
    // che seguono il ? negli URL (es. http://sito/pagina.asp?par1=val1&par2=val2)
    xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    // Inviamo i dati
    xmlHttp.send("messaggio=" + message + "&autore=" + author + "&colore=" + colore);
}

Come si può facilmente intuire nelle prime righe memorizziamo in variabili i vari valori dei campi, poi, con una chiamata a GetXMLHttpRequest, creiamo un nuovo oggetto XMLHttpRequest (il cuore di AJAX), impostando anche la funzione che deve gestire l'avanzamento della richiesta che stiamo per fare su xmlHttp_readystatechange (funzione che non fa null'altro che svuotare la casella del messaggio quando la richiesta è stata completata).
In seguito prepariamo la nostra richiesta asincrona POST (più adatta per dati potenzialmente lunghi) chiamando xmlHttp.open verso scrivi.aspx o scrivi.php a seconda se della scelta della SELECT lang; nella preparazione della richiesta bisogna specificare (tramite l'impostazione dell'header Content-Type) che tipo di dati stiamo inviando (potrebbe trattarsi di un'immagine, di un file audio o altro), in questo caso application/x-www-form-urlencoded, ovvero una serie di coppie nomi-valori. Infine inviamo i dati: tre coppie nome-valore, messaggio, autore e colore.
La richiesta sarà dunque inoltrata alla pagina PHP o ASP .Net che scriverà alla fine del file log_chat.cha qualcosa di simile a quanto segue:

<div><strong>Piero</strong> - <span style="color: red">Messaggio</span></div>
LA FINESTRA DELLA CHAT
La pagina che si auto-aggiorna con gli ultimi contenuti

Vediamo come ora invece come è stata implementata la pagina che controlla ogni secondo se sono arrivati nuovi messaggi, ed eventualmente scarica esclusivamente quelli. Lo script di questa pagina si articola in due fasi: la prima fase in cui viene effettuata una richiesta HEAD al server per ottenere la dimensione del file di log (log_chat.cha) e confrontarla con l'ultima nota e quindi se necessario, passare ad una seconda fase nella quale si scarica la parte mancante del log tramite una richiesta GET.
Al caricamento della pagina (onload) chat_content.htm richiama per la prima volta la funzione GetNewMessages(), che inizializza la richiesta HEAD per verificare la dimensione del file:


// Dimensione del log l'ultima volta che è stato verificato
var dimensione=0;
// Valore che indica la fase in cui ci troviamo
// 1 = verifica della dimensione
// 2 = download dei dati mancanti
var fase;
// Percorso del file di log
var path = "log_chat.cha";
// Intervallo tra una verifica e l'altra in millisecondi
var checkEvery = 1000;

// Funzione che avvia la verifica delle presenza di nuovi dati
function GetNewMessages() {
    fase = 1;
    // Creiamo la richiesta AJAX
    xmlHttp = GetXMLHttpRequest(xmlHttp_readystatechange);
    // Richiesta HEAD: non prende i contenuti ma solo le intestazioni
    // Il parametro nc serve per evitare che sia restituito un risultato dalla cache
    xmlHttp.open("HEAD", path + '?nc=' + Date.parse(new Date()), true);
    // Effettuiamo la richiesta
    xmlHttp.send(null);
}

Nota: al percorso verso cui si effettua la richiesta, come si può vedere, viene aggiunto un parametro nc contenente la data corrente. Questo è indispensabile per i browser come FireFox che mantengono una cache delle richieste.
Vediamo ora cosa accade quando viene completata la richiesta HEAD:


function xmlHttp_readystatechange() {
    // Al completamento di una richiesta
    if(xmlHttp.readyState == 4) {
        switch (fase) {
            case 1:
                // Se siamo nella prima fase prendiamo la dimensione del log in remoto
                // e la confrontiamo con quella a noi nota
                dimensioneCorrente = xmlHttp.getResponseHeader("Content-Length");
                if (dimensioneCorrente > dimensione) {
                    // Se le dimensioni sono aumentate effetuiamo la richiesta dei
                    // dati mancanti
                    fase = 2;
                    // Creiamo la richiesta AJAX
                    xmlHttp = GetXMLHttpRequest(xmlHttp_readystatechange);
                    // Richiesta asincrona verso il log
                    xmlHttp.open("GET", path, true);
                    // Limitiamo i risultati solamente ai byte successivi all'ultimo
                    // che abbiamo già ricevuto
                    xmlHttp.setRequestHeader("Range", "bytes=" + (dimensione) + "-");
                    // Effettuiamo la richiesta
                    xmlHttp.send(null);
                    // Aggiorniamo la dimensione del log
                    dimensione = dimensioneCorrente;
                } else {
                    // Impostiamo un timer per ripetere la verifica della dimensione
                    // del log
                    window.setTimeout(GetNewMessages, checkEvery)
                }
                break;
            case 2:
                // Aggiungiamo in fondo al body i nuovi dati
                document.body.innerHTML = document.body.innerHTML + xmlHttp.responseText;
                // Scrolliamo fino alla fine del documento
                window.scrollTo(0, 9999999999)
                // Impostiamo un timer per ripetere la verifica della dimensione
                // del log
                window.setTimeout(GetNewMessages, checkEvery)
        }
    }
}

Nota: si ricordi che questa funzione xmlHttp_readystatechange non è la stessa della pagina main.htm; si tratta di due funzioni totalmente distinte.
Occupiamoci ora di quello che accade quando è stata completata una richiesta (xmlHttp.readyState == 4) e ci troviamo nella fase 1 (primo caso dello switch): tramite la funzione getResponseHeader recuperiamo la dimensione del log (header Content-Length) e la confrontiamo con l'ultima a noi nota (dimensione), se la dimensione è aumentata possiamo passare alla seconda fase, altrimenti viene impostato un timer che per verificare nuovamente entro un secondo (o comunque checkEvery-millisecondi).
La fase 2 consiste nell'effettuare una richiesta GET (sempre asincrona e sempre verso il log della chat, log_chat.cha) passandogli però un header Range: tramite Range si può specificare al server quale parte della pagina che si sta richiedendo si desidera (ad esempio gli ultimi 10 byte, dal byte 20 al 30 e così via), nel nostro caso dal byte corrispondente all'ultima dimensione (dimensione) che avevamo memorizzato in poi, in sostanza la parte di log che ci manca. Inoltrata la richiesta aggiorniamo la variabile che contiene la dimensione del file (dimensione) sul nuovo valore (dimensioneCorrente).
Completata anche questa richiesta (secondo caso dello switch, dato che ci troviamo nella fase 2) aggiungiamo al BODY della pagina quanto scaricato (xmlHttp.responseText) tramite innerHTML, quindi scrolliamo la pagina fino in fondo e infine impostiamo nuovamente un timer per controllare in seguito se sono arrivati nuovi messaggi.

 

<< INDIETRO by VeNoM00