Vai al contenuto

Progetto completo di una chat con Server-Sent Event e HTML5 + Jquery

Continuiamo la panoramica sulle API Server-Sent Event in HTML5. In questo caso scopriremo e analizzaremo passo dopo passo come creare una chat funzionante. La ragione di questo esempio è principalmente applicare i concetti visti negli articoli precedenti, per meglio comprendere la potente tecnologia che HTML5 ci offre.

Avremo bisogno al solito di un web server, va bene anche XAMPP o qualsiasi altro web server Apache+PHP+MySQL.

Il database

La prima cosa da fare è creare il database con la tabella che conterrà i messaggi che gli utenti invieranno alla chat e che saranno consegnati a tutti i client in ascolto. Il database ha una sola tabella, mediante la quale, oltre a fare da intermediario potrebbe anche rappresentare un’ottimo strumento per filtrare parole non gradite. Lo script SQL è il seguente, potete eseguirlo facilmente su PhpMyAdmin.

CREATE DATABASE ChatTest CHARACTER SET latin1 COLLATE latin1_general_ci;

USE ChatTest;

CREATE TABLE IF NOT EXISTS `tbl_msg` (
  `IdMsg` bigint(20) NOT NULL AUTO_INCREMENT,
  `DateTimeMsg` bigint(20) NOT NULL,
  `Usr` varchar(15) COLLATE latin1_general_ci DEFAULT NULL,
  `Msg` varchar(255) COLLATE latin1_general_ci DEFAULT NULL,
  PRIMARY KEY (`IdMsg`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci AUTO_INCREMENT=1;

La chat

 Chiamiamo questo file chat.html, ed è il file che conterrà il codice HTML+CSS e gli script Javascript del client. L’interfaccia è molto semplice ed è composta principalmente da un contenitore DIV in cui vengono aggiunti man mano i sotto-contenitori DIV dei messaggi. Per completare, ho posizionato sotto la chat due caselle di testo per inserire il nome, il messaggio e il pulsante per inviare tutto al server e di conseguenza agli altri client connessi.

<html>
<head>
	<title>Chat</title>
	<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
	<script>
		$(document).ready(function() {
			$("#ChatString").keypress(function(e) {
				if(e.which == 13) {
	     			submitMsgChat();
	  			}
			});
		});

		var queueMsg = [];
		var chatTimer = null;

		function init() {
			LoadPage();
			startChatTimer();
		}

		function submitMsgChat() {
			if($("#NameUser").val() == "") {
 				alert("Nome utente obbligatorio");
 				return false;
 			}

			var val = $("#ChatString").val();
			if(val != "") {
				queueMsg.push(val);
				$("#ChatString").val("");
			}
			$("#ChatString").val("");
		}

		function startChatTimer() {
			stopChatTimer();
			chatTimer = setInterval(chatTimerHandler, 500);
		}

		function stopChatTimer() {
			if (chatTimer != null) {
				clearInterval(chatTimer);
				chatTimer = null;
			}
		}

		function chatTimerHandler() {
			stopChatTimer();

			if (queueMsg.length > 0 ) { 
				while (queueMsg.length > 0) {
					var value = queueMsg.pop();

					$.ajax({      
					    url: "chatsubmit.php",
					    data: { NameUser: $("#NameUser").val(), ChatString: value },
					    type: "GET",
					    complete: function(data) {}
					});
				} 
			}   

			startChatTimer();
		}

		function LoadPage() {
			var source = new EventSource("chatlist.php");
			source.addEventListener("message", InsertMsg, false);  
		}

		function InsertMsg(e) {
			$("#chat_wrapper").append("<div>" + e.data + "</div>");
		}
	</script>

	<style>
	body {
		font-family: tahoma, verdana arial, serif;
		font-size: 13px;
	}
	#chat_wrapper {
		width: 400px;
		height: 150px;
		padding: 10px;
		margin-bottom: 10px;
		overflow: auto;
		border: 1px solid silver;
	}
	#chat_wrapper div {
		padding: 5px;
	}
	#chat_wrapper div:nth-child(odd) {
		background: #f5f5f5;
	}
	#chat_wrapper div:nth-child(even) {
		background: white;
	}
	</style>
</head>

<body onload="init()">

	<div id="chat_wrapper"></div>	
	<div id="chat_status"></div>	
	<table>
		<tr>
			<td>Utente*</td>
			<td>Messaggio</td>
			<td>&nbsp;</td>
		</tr>
		<tr>
			<td><input id="NameUser" name="NameUser" type="text" value="" maxlength="15" size="10" /></td>
			<td><input id="ChatString" name="ChatString" type="text" value="" maxlength="48" size="30" /></td>
			<td><button onclick="submitMsgChat()">Invia Messaggio</button></td>
		</tr>
	</table>

</body>
</html>

Stream chat

Chiamiamo questo file chatlist.php. Lo stream dei messaggi sarà formato da un file PHP, in particolare in ciclo infinito che effettua un’interrogazione al database MySQL per poi “addormentarsi” per un secondo. Le prime due righe di intestazione sono molto importanti per il meccanismo SSE di HTML5, in quanto per il corretto funzionamento della chat, è necessario impostare il Content-Type come “text/event-stream” e definire il Cache-Control uguale a “no-cache”. Non dimenticatelo.

header("Content-Type: text/event-stream\n\n");
header("Cache-Control: no-cache");

require_once('dbFunctions.inc');

$MM_LastTime = 0;

while(true) {
	$MM_LastTime = ListChat($MM_LastTime);
	sleep(1);
}

Le operazioni sul database

Tutte le operazioni sul database preferisco scriverle in un file diverso dagli altri, per mettere un po’ di ordine. Lo chiamiamo dbfunctions.inc e contiene tra l’altro la gestione della connessione al database, oltre alla stampa a video del messaggio necessario al Server-Sent Event.

function ConnectDatabase() {
	$objConn = null;

	$user = "root";
	$password = "";   // Place your password here
	$database = "chattest";

	$objConn = mysql_connect ('localhost', $user, $password);
	if($objConn) {
		if(!mysql_select_db($database)) {
			mysql_close($objConn);
			$objConn = null;
		}
	}

	return $objConn;
}

function CloseDatabase($inConnection) {
	if($inConnection != null) {
		mysql_close($inConnection);
		$inConnection = null;
	}
}

function InsertChat ($inNameUser, $inChatString) {
	$MM_Result = false;
	$objConn = ConnectDatabase();
	if($objConn != null ) {
		$l_ServerTime = time();

		$MM_querySql = "INSERT INTO tbl_msg VALUES (null, $l_ServerTime, '$inNameUser', '$inChatString')";

		mysql_query($MM_querySql, $objConn);

		CloseDatabase($objConn);

		$MM_Result = true;
	}

	return $MM_Result; 
}

function sendMsg($inId, $inMsg) {
	echo "id: $inId" . PHP_EOL;
	echo "data: $inMsg" . PHP_EOL;
	echo PHP_EOL;
	ob_flush();
	flush();
}

function ListChat($inLastTime) {
	$MM_LastTime = $inLastTime;

	$objConn = ConnectDatabase();

	if($objConn != null) {
		if($MM_LastTime == 0) {
			$MM_LastTime = time();
		}
		$MM_Time = time();

		$MM_querySql = "SELECT * FROM tbl_msg WHERE DateTimeMsg > " . $MM_LastTime . " AND DateTimeMsg <= " . $MM_Time; 

		$MM_Result = mysql_query($MM_querySql, $objConn);
		if($MM_Result) {
			$MM_NumRows = mysql_numrows($MM_Result);
			if($MM_NumRows > 0) {      
				for ($i = 0; $i < $MM_NumRows; ++$i) {
					$MM_LastTime = mysql_result($MM_Result, $i, "DateTimeMsg");

					$MM_date = date('d-m-Y H:i:s', mysql_result($MM_Result, $i, "DateTimeMsg"));
					$MM_user = mysql_result($MM_Result, $i, "Usr");
					$MM_msgg = mysql_result($MM_Result, $i, "Msg");

					$MM_Message = "<span style=\"font-size: 10px;\">$MM_date</span>";
					$MM_Message .= " - <strong>$MM_user</strong>";
					$MM_Message .= "dice: $MM_msgg";

					sendMsg($MM_LastTime, $MM_Message);
				}
			}   
		}

		CloseDatabase($objConn);
	}

	return $MM_LastTime;
}

Il blocco più interessante è quello della funzione ListChat(). Questa funzione interroga il messaggio cercando tutti quei messaggi che sono stati spediti al server nel periodo compreso tra l’ultima interrogazione $MM_LastTime e l’ora corrente $MM_Time. In questo modo vengono pescati solo le ultime righe senza far perdere alcun messaggio. Quindi vengono estratti i campi, formattati in HTML e restituiti alla funzione sendMsg() che si occupa di comporre il messaggio per SSE.

File di supporto

Nell’ultimo file, chiamato chatsubmit.php, inserisco la procedura che viene attivata durante la chiamata di chatTimerHandler() nel file chat.html tramite Ajax con metodo GET. Lo script non fa altro che inserire nel database MySQL il nome e il messaggio digitato dall’utente in modo da poter essere “comunicato” a tutti quanti i client in ascolto.

if(!empty($_GET)) {
	require_once('dbFunctions.inc');

	$MM_NameUser = addslashes($_GET["NameUser"]);
	$MM_ChatString = addslashes($_GET["ChatString"]);

	if(InsertChat($MM_NameUser, $MM_ChatString)) {
		echo("Y");
	}
	else {
		echo("N");
	}
}