My favorites | Sign in
Project Logo
                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
<?php
/**
* Chat Comet / Server Push com XHR Multipart
* (long polling se não for navegador Gecko ou se for FastCGI)
*
* @author garotosopa <diogo86@gmail.com>
* @version 1.0
*/

// Conecta
mysql_connect('localhost', 'chat', 'chat');
mysql_select_db('chat');

// Cria a tabela de chat, caso ainda não exista
mysql_query("
CREATE TABLE IF NOT EXISTS chat (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
mensagem VARCHAR(400) NOT NULL,
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=MEMORY
");

// não aceitaremos multipart em fastcgi porque o flush() não funciona
$servidor_multipart = !( PHP_SAPI == 'cgi-fcgi' && isset($_ENV['FCGI_ROLE']) );

// Salva a mensagem se ela estiver sendo postada
if( isset($_POST['mensagem']) ){
// Limpa mensagens antigas
mysql_query("DELETE FROM chat WHERE timestamp < NOW() - INTERVAL 1 MINUTE");

// escapa a string
$mensagem = mysql_real_escape_string($_POST['mensagem']);

// insere na tabela chat
mysql_query("INSERT INTO chat(mensagem) VALUES('$mensagem')");

// pára o processamento
exit;
}

// Cliente quer receber mensagens mais novas que o id fornecido
elseif( isset($_GET['ultimo_id']) ){
// desabilita o time limit pra ter o loop infinito logo abaixo
set_time_limit(0);

// ve se o servidor e o cliente aceitam múltiplas respostas na requisição
$multipart = $servidor_multipart && isset($_GET['multipart']);

// se aceitarem
if( $multipart ){
// delimitador qualquer para separar cada resposta
$boundary = uniqid();

// header de que a requisição tem múltiplas respostas
header("Content-type: multipart/x-mixed-replace;boundary=\"$boundary\"");

// mandamos o delimitador para separar a resposta principal
echo "--$boundary\n";
}

// prepara o número para a query
$ultimo_id = (int) $_GET['ultimo_id'];

// guarda quando foi a última comunicação com esse cliente
$ultimo_ping = time();

// vai tentando achar mensagens
while(true){
// pega as últimas 10 mensagens depois da última que o cliente já tem
$res = mysql_query("
SELECT id, mensagem
FROM chat
WHERE id > $ultimo_id
ORDER BY id DESC
LIMIT 10
");

// se achar alguma mensagem
if( mysql_num_rows($res) ){
// array com as mensagens que serão retornadas ao cliente
$mensagens = array();

// preenchemos o array com as mensagens
while( $row = mysql_fetch_assoc($res) ){
$mensagens[] = $row;

// e atualizamos o id da última mensagem que já enviamos
if( $row['id'] > $ultimo_id ){
$ultimo_id = $row['id'];
}
}

// coloca as mensagens em ordem, já que fizemos order by id DESC
$mensagens = array_reverse($mensagens);

// se for uma requisição com várias respostas
if( $multipart ){
// enviamos o tipo desta resposta
echo "Content-type: application/json\n\n";
}

// retornamos o array das mensagens como json
echo json_encode($mensagens);

// se for uma requisição com várias respostas
if( $multipart ){
// enviamos o separador pro browser interpretar esta resposta
echo "--$boundary\n";

// forçamos o envio evitando que a resposta fique em buffer
flush();

// atualizamos a última vez que enviamos algo pra esse cliente
$ultimo_ping = time();
}
// ou finalizamos a requisição se ela não for multipart
else{
// assim o navegador lê a resposta e inicia outra conexão
exit;
}
}
// se não tiver mensagens mas for uma requisição multipart inativa
elseif( $multipart and $ultimo_ping < time() - 10 ){
// mandamos o content type desta resposta
echo "Content-type: text/plain\n\n";

// enviamos algo ao cliente para saber que a conexão está ativa
echo "ping";

// terminamos a mensagem com o separador
echo "--$boundary\n";

// forçamos o envio evitando que a resposta fique em buffer
flush();

// atualizamos a última vez que enviamos algo pra esse cliente
$ultimo_ping = time();
}
// se não tiver nada pra fazer
else{
// pausa o loop infinito só pro mysql respirar um pouco
usleep(250);
}
}
}

// se chegamos até aqui, não tem nada pra processar, é só mostrar o chat

// envia header com encoding utf-8
header("Content-type: text/html; charset=utf-8");
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>chatildis!</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="author" content="garotosopa"/>
<script type="text/javascript">
// <![CDATA[

// guarda informação se o servidor suporta ou não requisições multipart
var servidor_multipart = <?php echo (int) $servidor_multipart; ?>;

// variável que vai guardar o id da última mensagem que recebemos
var ultimo_id = 0;

// objeto que vai ser o timeout para reconectar conexões multipart inativas
var timeout;

// variável que será o elemento da div das mensagens
var chat;

// variável que será o elemento da caixa de texto de mensagem
var mensagem;

// variável que será o objeto XHR das mensagens recebidas
var xhr_chat;

// montamos o xhr para receber mensagens em browsers bacanas
if( window.XMLHttpRequest ){
xhr_chat = new XMLHttpRequest();
}
// ou montamos o xhr para o internet explorer
else if ( window.ActiveXObject ){
xhr_chat = new ActiveXObject("Microsoft.XMLHTTP");
}

// função que vai zerar o timeout de inatividade da requisição multipart
function reiniciarTimeout(){
// cancela o timeout atual
clearTimeout(timeout);

// e começa um novo timeout para refazer a conexão em 15 segundos
timeout = setTimeout( atualizar, 15000 );
}

// função callback para receber a resposta da requisição
function receber(){
// quando a requisição estiver completa
if ( xhr_chat.readyState == 4 ) {
// se houve sucesso na requisição
if ( xhr_chat.status == 200 ) {
// se for uma conexão de várias respostas
if ( xhr_chat.multipart ) {
// reinicia o timeout de inatividade
reiniciarTimeout();
}

// se a resposta do servidor for apenas um ping
if ( xhr_chat.responseText == 'ping' ){
// não fazemos nada, foi só para reiniciar o timeout
}

// senão mandamos a resposta pra função que processa as mensagens
else {
processar(xhr_chat.responseText);
}
}

// se não é multipart, vamos ter uma requisição pra cada resposta
if( ! xhr_chat.multipart ){
// sempre que completar a requisição, criaremos uma nova
setTimeout(atualizar, 250);
}
}
}

// função que vai processar as mensagens
function processar(responseText){
// transforma o texto retornado em json
var json = eval('(' + responseText + ')');

// le cada item do array retornado por json
for (i in json) {
// acessa a mensagem da iteração atual
var mensagem = json[i];

// caso não seja uma mensagem válida
if( ! mensagem.mensagem ){
// não queremos ter erro de javascript
return;
}

// escapa tags html da mensagem recebida
var texto = mensagem.mensagem.replace(/</g, '&lt;');
texto = texto.replace(/>/g, '&gt;');

// criamos um parágrafo pra cada mensagem retornada
var p = document.createElement('p');
p.innerHTML = texto;

// adicionamos o parágrafo na div de mensagens
chat.appendChild(p);
};

// id da última mensagem para depois pegar apenas as mais novas
if ( json[json.length-1] ){
// pega a propriedade id da última mensagem
ultimo_id = json[json.length-1].id;
}

// coloca a barra de rolagem no final
chat.scrollTop = chat.scrollHeight;
}

// função que começa a receber mensagens mais novas que o id que temos
function atualizar(){
// garantimos que a requisição não está aberta
xhr_chat.abort();

// enviaremos para o próprio arquivo passando o id da última mensagem
var url = location.pathname + '?ultimo_id=' + ultimo_id;

// se o servidor e o navegador suportarem requisições multipart
if( servidor_multipart && xhr_chat.multipart != null ){
// configura o request para interpretar respostas multipart
xhr_chat.multipart = true;

// avisamos que queremos uma requisição multipart
url = url + '&multipart';

// inicia o timeout para reconectar se ficar inativa por muito tempo
reiniciarTimeout();
}

// montamos a requisição assíncrona
xhr_chat.open('GET', url, true);

// associamos nossa função de callback
xhr_chat.onreadystatechange = receber;

// e então enviamos a requisição sem nenhum parâmetro extra
xhr_chat.send(null);
}

// espera carregar a página
window.onload = function(){
// guarda o elemento da div das mensagens
chat = document.getElementById('chat');

// guarda o elemento da caixa de texto de mensagem
mensagem = document.getElementById('mensagem');

// adiciona evento ao formulário para fazer o envio por ajax
document.getElementById('envio').onsubmit = function(){
// pega o texto digitado
var texto = mensagem.value;

// tira os espaços do início e fim
texto = texto.replace(/^\s*/, '');
texto = texto.replace(/\s*$/, '');

// se o texto estiver vazio, nem envia
if( texto == '' ){
return false;
}

// montamos um novo xhr de envio em browsers bacanas
if( window.XMLHttpRequest ){
var xhr_envio = new XMLHttpRequest();
}
// ou montamos um novo xhr no internet explorer
else if ( window.ActiveXObject ){
var xhr_envio = new ActiveXObject("Microsoft.XMLHTTP");
}

// enviaremos a mensagem por post para nós mesmos, assincronamente
xhr_envio.open("POST", location.pathname, true);

// definimos a requisição como envio de formulário
xhr_envio.setRequestHeader
('Content-Type', 'application/x-www-form-urlencoded');

// e então escapamos e enviamos o texto
xhr_envio.send( 'mensagem=' + encodeURIComponent(texto) );

// limpa e foca a caixa de texto
mensagem.value = '';
mensagem.focus();

// não submete o formulário tradicionalmente
return false;
};

// foca a caixa de texto ao carregar a página
mensagem.focus();

// começa a consultar as atualizações
atualizar();
};
//]]>
</script>
<style type="text/css">
body {
font-family: sans-serif;
font-size: 12px;
margin: 0;
}

h1 {
border-bottom: 1px solid #a4a4a4;
background: #eaeaea;
color: #333;
padding: 1em;
margin: 0;
}

#chat {
border: 1px solid #a4a4a4;
padding: 1em;
margin: 1em;
height: 25em;
overflow: auto;
}

#mensagem {
min-width: 50em;
}

label, input {
padding: 0.5em;
}
</style>
</head>
<body>
<h1>chatildis!</h1>
<div id="chat">
<p>It's only the chatildis ultimate chat ultra, baby!</p>
<p>
O único capaz de derrubar seu servidor com conexões abertas e
tabelas na memória... *sigh*
</p>
</div>
<form id="envio" method="post" action="">
<p>
<label for="mensagem">Mensagem:</label>
<input type="text" name="mensagem" id="mensagem" maxlength="380" />

<input type="submit" name="enviar" value="Enviar" />
</p>
</form>
</body>
</html>
Show details Hide details

Change log

r10 by diogo86 on Jun 28, 2009   Diff
Some minor changes to get it ready for a
first release.

Go to: 
Project members, sign in to write a code review

Older revisions

r9 by diogo86 on Jun 28, 2009   Diff
It turned out FastCGI doesn't respect
flush() calls. The script now checks
if it's running on FastCGI and disable
multipart responses if so.

...
r8 by diogo86 on Jun 28, 2009   Diff
First release with a multipart
response. The request remains open
receiving all messages separated by a
boundary. Only Gecko engines support
it though so the old long polling
...
All revisions of this file

File info

Size: 12422 bytes, 405 lines
Hosted by Google Code