пример реализации мессенджера на PHP

This commit is contained in:
CupIvan 2018-05-09 17:26:46 +03:00
parent 856e8e42b0
commit eadc19a6d8
18 changed files with 352 additions and 0 deletions

2
php/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
db
server.conf

4
php/config.php Normal file
View File

@ -0,0 +1,4 @@
<?php
define('TIME_PING', 60); // пинг - раз в минуту
define('TIME_ONLINE', 5*60); // через сколько секунд считать, что ушёл в offline

5
php/index.php Normal file
View File

@ -0,0 +1,5 @@
<?php
require_once 'init.php';
require_once 'tpl/index.tpl';

10
php/init.php Normal file
View File

@ -0,0 +1,10 @@
<?php
require_once 'config.php';
require_once 'm/db.php';
require_once 'm/api.php';
require_once 'm/send.php';
require_once 'm/functions.php';
require_once 'post.php';

23
php/m/api.php Normal file
View File

@ -0,0 +1,23 @@
<?php
function api_get($uri, $handler)
{
if ($_SERVER['REQUEST_METHOD'] == 'GET')
api_call($uri, $handler);
}
function api_post($uri, $handler)
{
if ($_SERVER['REQUEST_METHOD'] == 'POST')
api_call($uri, $handler);
}
function api_call($uri, $handler)
{
if (preg_match("#^$uri$#", $_SERVER['REQUEST_URI'], $m))
{
foreach ($m as $k => $v) if (is_string($v)) $m[$k] = urldecode($v);
if (is_callable($handler)) $handler($m);
else require_once "tpl/$handler";
}
}

63
php/m/db.php Normal file
View File

@ -0,0 +1,63 @@
<?php
function db_insert($db, $a)
{
$data = db_get($db);
$data[] = $a;
db_save($db, $data);
}
function db_update($db, $a, $filter)
{
$data = db_get($db);
foreach (db_search($db, $filter) as $id => $_)
$data[$id] = $a + $data[$id];
db_save($db, $data);
}
function db_delete($db, $filter)
{
$data = db_get($db);
foreach (db_search($db, $filter) as $id => $_)
unset($data[$id]);
db_save($db, $data);
}
function db_search($db, $filter = [])
{
$res = [];
foreach (db_get($db) as $id => $a)
{
foreach ($filter as $k => $v)
if ($a[$k] != $v) continue 2;
$res[$id] = $a;
}
return $res;
}
function db_search_one($db, $filter)
{
$res = db_search($db, $filter);
foreach ($res as $a) return $a;
return [];
}
function db_get($db)
{
$fname = "./db/$db.json";
if (!file_exists($fname)) return [];
$db = @json_decode(@file_get_contents($fname), true);
return $db ?: [];
}
function db_save($db, $data)
{
$fname = "./db/$db.json";
if (empty($data)) unlink($fname);
else
{
$st = json_encode($data, JSON_UNESCAPED_UNICODE);
if (!file_exists($x = dirname($fname))) mkdir($x, 0777, true);
if ($st) file_put_contents($fname, $st);
}
}

13
php/m/functions.php Normal file
View File

@ -0,0 +1,13 @@
<?php
function request($x)
{
return array_intersect_key($_REQUEST, array_fill_keys(explode(',', $x), 1));
}
function redirect($x = NULL)
{
if (is_null($x)) $x = $_SERVER['REQUEST_URI'];
header('Location: '.$x);
exit;
}

14
php/m/send.php Normal file
View File

@ -0,0 +1,14 @@
<?php
function send($data)
{
$data_url = http_build_query ($data);
$data_len = strlen ($data_url);
$context = stream_context_create([
'http' => ['method'=>'POST','header'=>"Connection: close\r\nContent-Length: $data_len\r\n", 'content'=>$data_url]
]);
$res = json_decode(@file_get_contents('http://'.$data['to'].'/messages/', false, $context), true);
return !empty($res);
}

80
php/post.php Normal file
View File

@ -0,0 +1,80 @@
<?php
api_get('/ping', function(){
$a = ['timestamp'=>time(), 'addr'=>$_SERVER['HTTP_HOST']];
echo json_encode($a);
exit;
});
api_get('/contacts/ping', function(){
foreach (db_search('contacts') as $a)
if ($a['addr'] != $_SERVER['HTTP_HOST']) // COMMENT: сами себе не отправляем
{
if (time() - @$a['time_ping'] > TIME_PING)
{
$st = @file_get_contents('http://'.$a['addr'].'/ping');
$json = json_decode($st, true);
$x = $json ? ['time_online' => time()] : [];
db_update('contacts', ['time_ping' => time()] + $x, ['addr'=>$a['addr']]);
}
// если на связи - пытаемся дотправить сообщения
if (time() - $a['time_online'] < TIME_ONLINE)
{
$addr = $a['addr'];
foreach (db_search("undelivered/$addr") as $a)
if (send($a))
{
db_insert("messages/$addr", $a + ['received'=>time()]);
db_delete("undelivered/$addr", ['timestamp'=>$a['timestamp']]);
} else break;
}
if (!empty($data)) $a['messages'] = $data;
}
exit;
});
api_get('/messages/(?<addr>[^/]+)', 'messages.tpl');
if ($_SERVER['REQUEST_METHOD'] != 'POST') return;
api_post('/contacts/(?<addr>[^/]+)', function($m){
if (empty($_REQUEST['addr'])) die('Не указан адрес!');
$addr = $m['addr'];
$x = db_search('contacts', $addr == 'new' ? request('addr') : ['addr'=>$addr]);
if ($addr == 'new')
{
if (!empty($x)) die('Такой адрес уже есть в базе!');
db_insert('contacts', request('addr,nickname,email'));
redirect('/contacts/');
} else {
if (request('delete'))
{
db_delete('contacts', ['addr'=>$addr]);
redirect('/contacts/');
}
else
db_update('contacts', request('addr,nickname,email'), ['addr'=>$addr]);
}
});
api_post('/messages/(?<addr>[^/]+)', function($m){
$a = request('text') + ['from'=>$_SERVER['HTTP_HOST'], 'to'=>$m['addr'], 'timestamp'=>time()];
if (send($a))
db_insert('messages/'.$m['addr'], $a + ['received'=>time()]);
else
db_insert('undelivered/'.$m['addr'], $a);
redirect($_SERVER['HTTP_REFERER']);
});
api_post('/messages/', function($m){
$a = request('from,to,text,timestamp');
if ($a['to'] == $_SERVER['HTTP_HOST'])
{
db_insert('messages/'.$a['from'], $a + ['received'=>time()]);
$res = ['received'=>time()];
echo json_encode($res);
}
exit;
});
redirect();

1
php/server.conf.example Normal file
View File

@ -0,0 +1 @@
IP=127.0.0.1

4
php/server.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
source ./server.conf
php -S $IP:8000 index.php

10
php/tpl/chat.tpl Normal file
View File

@ -0,0 +1,10 @@
<h3>Сообщения</h3>
<iframe src="/messages/<?=$m['addr']?>" style="width: 600px; height: 400px;"></iframe>
<form method="POST" action="/messages/<?=$m['addr']?>">
<input name="text" style="width: 300px">
<input type="submit" value="Отправить">
</form>
<script>setInterval(function(){ document.querySelector('iframe').contentWindow.location.reload(true) }, 10000)</script>

20
php/tpl/contact.tpl Normal file
View File

@ -0,0 +1,20 @@
<?
if ($m['addr'] == 'new')
$action_name = 'Добавить';
else
{
$action_name = 'Изменить';
$a = db_search_one('contacts', ['addr'=>$m['addr']]);
}
?>
<style>
form { white-space: pre; font-family: Courier New; }
</style>
<h2><?=$action_name?> контакт</h2>
<form action="./<?=$m['addr']?>" method="POST">
Адрес: <input name="<?=$x='addr'?>" value="<?=@$a[$x]?>" required>
Ник: <input name="<?=$x='nickname'?>" value="<?=@$a[$x]?>">
email: <input name="<?=$x='email'?>" value="<?=@$a[$x]?>" type="email">
<input type="submit" value="<?=$action_name?>"> <?if ($m['addr'!='new']){?><input type="submit" name="delete" value="Удалить"><?}?>
</form>

29
php/tpl/contacts.tpl Normal file
View File

@ -0,0 +1,29 @@
<h3>Контакты</h3>
<style>
table { border-collapse: collapse; }
td, th { border: 1px solid #999; padding: 5px 10px; }
th { background: #EEE; }
tr:hover { background: #EEE; }
</style>
<a href="new">Добавить</a>
<table>
<tr>
<th>Адрес</th>
<th>Ник</th>
<th>email</th>
<th>online</th>
<th></th>
</tr>
<?foreach (db_search('contacts', []) as $a){?>
<tr>
<td><a href="<?=urlencode($a['addr'])?>"><?=$a['addr']?></a></td>
<td><?=$a['nickname']?></td>
<td><?=$a['email']?></td>
<td><?=(time() - @$a['time_online'] > TIME_ONLINE)?'нет':'да'?></td>
<td><a href="./<?=$a['addr']?>/messages">сообщения</a></td>
</tr>
<?}?>
</table>

16
php/tpl/dns.tpl Normal file
View File

@ -0,0 +1,16 @@
<h3>DNS записи</h3>
<a href="new">Добавить</a>
<table>
<tr>
<th>Адрес</th>
<th>Домен</th>
</tr>
<?foreach (db_search('dns', []) as $a){?>
<tr>
<td><a href="<?=urlencode($a['addr'])?>"><?=$a['addr']?></a></td>
<td><?=$a['domain']?></td>
</tr>
<?}?>
</table>

19
php/tpl/dns_edit.tpl Normal file
View File

@ -0,0 +1,19 @@
<?
if ($m['addr'] == 'new')
$action_name = 'Добавить';
else
{
$action_name = 'Изменить';
$a = db_search_one('dns', ['addr'=>$m['addr']]);
}
?>
<style>
form { white-space: pre; font-family: Courier New; }
</style>
<h2><?=$action_name?> запись</h2>
<form action="./<?=$m['addr']?>" method="POST">
Адрес: <input name="<?=$x='addr'?>" value="<?=@$a[$x]?>" required>
Домен: <input name="<?=$x='domain'?>" value="<?=@$a[$x]?>" required>
<input type="submit" value="<?=$action_name?>"> <?if ($m['addr'!='new']){?><input type="submit" name="delete" value="Удалить"><?}?>
</form>

21
php/tpl/index.tpl Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>Ваш IP: <?=$_SERVER['HTTP_HOST']?></p>
<nav>
<a href="/contacts/">Контакты</a>
</nav>
<?
api_get('/contacts/', 'contacts.tpl');
api_get('/contacts/(?<addr>[^/]+)', 'contact.tpl');
api_get('/contacts/(?<addr>[^/]+)/messages', 'chat.tpl');
?>
<iframe id="ping" style="display: none"></iframe>
<script>setInterval(function(){ document.querySelector('#ping').src = '/contacts/ping'; }, 5000)</script>
</body>
</html>

18
php/tpl/messages.tpl Normal file
View File

@ -0,0 +1,18 @@
<?php
$contacts = [];
foreach (db_search('contacts') as $a) $contacts[$a['addr']] = $a['nickname'];
$contacts[$_SERVER['HTTP_HOST']] = 'Я';
?>
<?$a = array_merge(db_search('messages/'.$m['addr']), db_search('undelivered/'.$m['addr']));?>
<?if (empty($a)) {?>
<p>Список сообщений пуст</p>
<?} else
foreach ($a as $a){?>
<?=date('H:i:s', $a['timestamp'])?> <b><?=$contacts[$a['from']]?></b>: <?=$a['text']?>
<?if (empty($a['received'])) echo ' <small><i>(не доставлено)</i></small>';?>
<br>
<?}?>
<?exit;?>