Данное решение подойдет для небольших проектов, так как возможность параллельно вести диалог с несколькими пользователями реализована с помощью создания нового чат-бота, то есть чем больше ботов будет, тем больше людей смогут с вами связаться в один момент времени.
При реализации было принято решение периодически опрашивать сервер Telegram, чтобы упростить развертывание и облегчить понимание материала, как альтернатива можно использовать WebHook.
Если у вас не стоит virtualenv, то необходимо его установить:
Создадим виртуальное окружение:
Активируем его:
Установим все необходимые библиотеки для работы нашего чата:
Для опроса сервера будем использовать библиотеку для работы с telegram.
Необходимо создать следующую файловую структуру:
Пришло время создать бота, данная реализация рассчитана на несколько ботов, чтобы обеспечить возможность общаться параллельно с несколькими клиентами.
Чтобы зарегистрировать бота, необходимо написать BotFather /newbot и все дальнейшие инструкции вы получите в диалоге с ним. В итоге, после успешной регистрации BotFather вернет вам токен вашего нового бота.
Теперь необходимо получить свой chat_id, чтобы бот знал, кому отправлять сообщения.
Для этого в приложении telegram находим своего бота, начинаем с ним взаимодействие командой /start, пишем ему какое-то сообщение и переходим по ссылке —
Видим примерно следующий ответ —
Чтобы обеспечить гибкость в работе чата и возможность его модернизации, необходимо использовать базу данных, я выбрал postgres.
Переключаемся на пользователя postgres:
Входим в CLI postgres:
Необходимо создать новую базу данных в кодировке Unicode;
Создадим нового пользователя в БД:
И отдадим ему все привилегии на базу:
Подключаемся к только что созданной базе:
Создадим таблицу для хранения информации ботах, она будет иметь следующую модель:
И так же дадим пользователю все привилегии на таблицу:
Теперь необходимо добавить в нее токены ботов:
Выходим из CLI:
и меняем пользователя обратно:
Первым делом вынесем настройки для работы чата в отдельный файл.
bot_settings.py
Основные функции будут находиться в файле core.py
tornadino.py
Теперь создадим статические файл:
chat.html
chat.css
Перед написанием javascript файла необходимо определиться, как будет выглядеть код для сообщении от клиента и от менеджера.
Html код для сообщении от клиента:
Html код для сообщении от менеджера:
chat.js
Для начала необходимо настроить виртуальное окружение для нашего приложения, собственно, повторить то, что мы уже делали на локальной машине в пункте реализация.
После того, как мы настроили окружение, нужно перенести туда наш проект, проще всего это сделать, используя git, предварительно необходимо загрузить код в свой репозиторий и оттуда уже клонировать его на сервер.
Если у вас на сервере не установлен postgres, то установить его можно так:
Запускаем postgres:
Добавляем автозапуск:
После чего необходимо перейти в psql под пользователем postgres и повторить все, что мы делали на локальной машине.
Будем запускать наше tornado приложение с помощью supervisor в фоне.
Для начала установим supervisor:
Теперь откроем конфигурационный файл супервизора, который будет находится в /etc/supervisor.conf
Не забудьте поменять пути в конфигурационном файле!
Перед тем, как запускать supervisor, необходимо создать папку /var/log/supervisord/ в ней будут собираться логи торнадо, так что, если supervisor запустил tornado-8004, но чат не работает, то ошибку стоит искать там.
Запускаем супервизор:
Проверяем, что все в порядке:
Должны получить что-то подобное:
На локальной машине вносим изменения в chat.js:
и открываем в браузере chat.html.
Готово!
Можно без особых телодвижений прикручивать такой чат к своим проектам, так же достаточно удобно использовать для сбора feedback.
Внутреннее устройство чата
Рис.1 UML диаграмма последовательностей
Рис.1 UML диаграмма последовательностей
Реализация
При реализации было принято решение периодически опрашивать сервер Telegram, чтобы упростить развертывание и облегчить понимание материала, как альтернатива можно использовать WebHook.
Подготовка виртуального окружения
Если у вас не стоит virtualenv, то необходимо его установить:
pip install virtualenv
Создадим виртуальное окружение:
virtualenv --no-site-packages -p python3.4 chat
Активируем его:
source chat/bin/activate
Установим все необходимые библиотеки для работы нашего чата:
pip install tornado==4.4.2 psycopg2==2.7.3 pyTelegramBotAPI==2.2.3
Для опроса сервера будем использовать библиотеку для работы с telegram.
Необходимо создать следующую файловую структуру:
Создание бота
Пришло время создать бота, данная реализация рассчитана на несколько ботов, чтобы обеспечить возможность общаться параллельно с несколькими клиентами.
Чтобы зарегистрировать бота, необходимо написать BotFather /newbot и все дальнейшие инструкции вы получите в диалоге с ним. В итоге, после успешной регистрации BotFather вернет вам токен вашего нового бота.
Теперь необходимо получить свой chat_id, чтобы бот знал, кому отправлять сообщения.
Для этого в приложении telegram находим своего бота, начинаем с ним взаимодействие командой /start, пишем ему какое-то сообщение и переходим по ссылке —
https://api.telegram.org/bot<токен_вашего_бота>/getUpdates
Видим примерно следующий ответ —
{"id":555455667,"first_name":"Иван","last_name":"Иванович","username":"kamrus","language_code":"ru-RU"}
id и есть ваш chat_id
Настройка postgres
Чтобы обеспечить гибкость в работе чата и возможность его модернизации, необходимо использовать базу данных, я выбрал postgres.
Переключаемся на пользователя postgres:
sudo su - postgres
Входим в CLI postgres:
psql
Необходимо создать новую базу данных в кодировке Unicode;
CREATE DATABASE habr_chat ENCODING 'UNICODE';
Создадим нового пользователя в БД:
CREATE USER habr_user WITH PASSWORD '12345';
И отдадим ему все привилегии на базу:
GRANT ALL PRIVILEGES ON DATABASE habr_chat TO habr_user;
Подключаемся к только что созданной базе:
\c habr_chat
Создадим таблицу для хранения информации ботах, она будет иметь следующую модель:
Физическая модель
Рис.2 Физическая модель таблицы chat
Рис.2 Физическая модель таблицы chat
CREATE TABLE chat (
id SERIAL NOT NULL PRIMARY KEY,
token character varying(300) NOT NULL UNIQUE,
ready BOOLEAN NOT NULL DEFAULT True,
last_message TEXT,
customer_asked BOOLEAN NOT NULL DEFAULT False,
remote_ip character varying(100)
)
И так же дадим пользователю все привилегии на таблицу:
GRANT ALL PRIVILEGES ON TABLE chat TO habr_user;
Теперь необходимо добавить в нее токены ботов:
INSERT INTO chat (token) VALUES ('your_bot_token');
Выходим из CLI:
\q
и меняем пользователя обратно:
exit
Написание кода
Первым делом вынесем настройки для работы чата в отдельный файл.
bot_settings.py
CHAT_ID = Вставить ваш chat_id
db = {
'db_name': 'habr_chat',
'user': 'habr_user',
'password': '12345',
'host': '',
'port': ''
}
Основные функции будут находиться в файле core.py
from telebot import apihelper
from bot_settings import db
import psycopg2
import datetime
def get_updates(token, conn, cur, offset=None, limit=None, timeout=20):
''' Возвращает сообщение из телеграма '''
json_updates = apihelper.get_updates(token, offset, limit, timeout)
try:
answer = json_updates[-1]['message']['text']
except IndexError:
answer = ''
# если не проверять приходило ли сообщение от пользователя, то
# функция будет просто возвращать последнее сообщение от менеджера,
# которое в свою очередь могло предназначаться предыдущему клиенту
if is_customer_asked(conn, cur, token):
# необходимо проверять предыдущее сообщение, так как запрос к серверу
# повторяется через константное время и клиенту будет отправляться одно и тоже сообщение
if not is_last_message(conn, cur, token, answer):
# если сообщение прошло обе проверки то обновить это сообщение
# в базе данных
update_last_message(conn, cur, token, answer)
return answer
else:
# если пользователь еще ничего не спросил, то необходимо все равно обновить
# предыдущее сообщение менеджера, на случай если предыдущии пользовватель отключится,
# но менеджер все равно отправит сообщение
update_last_message(conn, cur, token, answer)
def send_message(token, chat_id, text):
'''Отправить сообщение менеджеру в телеграм'''
apihelper.send_message(token, chat_id, text)
def connect_postgres(**kwargs):
try:
conn = psycopg2.connect(dbname=db['db_name'],
user=db['user'],
password=db['password'],
host=db['host'],
port=db['port'])
except Exception as e:
print(e, 'Ошибка при подключении к posqgres')
raise e
cur = conn.cursor()
return conn, cur
def update_last_message(conn, cur, token, message, **kwargs):
''' Обновляет последнее сообщение, присланное менеджером '''
query = "UPDATE chat SET last_message = %s WHERE token = %s"
data = [message, token]
try:
cur.execute(query, data)
conn.commit()
except Exception as e:
print(e, 'Ошибка при попытке обновить последнее сообщение на %s' %message)
raise e
def add_remote_ip(conn, cur, token, ip):
''' Функция добавляет ip адрес пользователя '''
query = "UPDATE chat SET remote_ip = %s WHERE token = %s"
data = [ip, token]
try:
cur.execute(query, data)
conn.commit()
except Exception as e:
print(e, 'Ошибка при попытке добавить ip адрес')
raise e
def delete_remote_ip(conn, cur, token):
''' Удалить ip адрес у бота по переданному токену '''
query = "UPDATE chat SET remote_ip = %s WHERE token = %s"
data = ['', token]
try:
cur.execute(query, data)
conn.commit()
except Exception as e:
print(e, 'Ошибка при попытке удалить ip адрес')
raise e
def is_last_message(conn, cur, token, message, **kwargs):
''' Проверить является ли переданное сообщение последним сообщением менеджера '''
query = "SELECT last_message FROM chat WHERE token = %s"
data = [token, ]
try:
cur.execute(query, data)
last_message = cur.fetchone()
if last_message:
if last_message[0] == message:
return True
return False
except Exception as e:
print(e, 'Ошибка при определении последнего сообщения')
raise e
def update_customer_asked(conn, cur, token, to_value):
''' Обновить статус ответа клиента '''
query = "UPDATE chat SET customer_asked = %s WHERE token = %s"
# to_value = True/False
data = [to_value, token]
try:
cur.execute(query, data)
conn.commit()
except Exception as e:
print(e, 'Ошибка при попытке обновить "customer_asked" на %s' %to_value)
raise e
def is_customer_asked(conn, cur, token):
''' Если клиент уже написал сообщение, то функция вернет True '''
query = "SELECT customer_asked FROM chat WHERE token = %s"
data = [token, ]
try:
cur.execute(query, data)
customer_asked = cur.fetchone()
return customer_asked[0]
except Exception as e:
print(e, "Ошибка при попытке узнать написал ли пользователь сообщение или еще нет")
raise e
def get_bot(conn, cur):
'''
Функция берет из базы свободного бота, у которого ready = True.
Возвращает (id, token, ready, last_message, customer_asked) для свободного бота
'''
query = "SELECT * FROM chat WHERE ready = True"
try:
cur.execute(query)
bot = cur.fetchone()
if bot:
return bot
else:
return None
except Exception as e:
print(e, "Ошибка при попытке найти свободного бота")
raise e
def make_bot_busy(conn, cur, token):
''' Меняет значение ready на False, тем самым делая бота занятым '''
query = "UPDATE chat SET ready = False WHERE token = %s"
data = [token,]
try:
cur.execute(query, data)
conn.commit()
except Exception as e:
print(e, 'Ошибка при попытке изменить значение "ready" на False')
raise e
def make_bot_free(conn, cur, token):
''' Меняет значение ready на False, тем самым делая бота свободным '''
update_customer_asked(conn, cur, token, False)
delete_remote_ip(conn, cur, token)
query = "UPDATE chat SET ready = True WHERE token = %s"
data = [token,]
try:
cur.execute(query, data)
conn.commit()
except Exception as e:
print(e, 'Ошибка при попытке изменить значение "ready" на True')
raise e
tornadino.py
import tornado.ioloop
import tornado.web
import tornado.websocket
import core
from bot_settings import CHAT_ID
import datetime
class WSHandler(tornado.websocket.WebSocketHandler):
def __init__(self, application, request, **kwargs):
super(WSHandler, self).__init__(application, request, **kwargs)
# При создании нового подключения с пользователем подключимся к postgres
self.conn, self.cur = core.connect_postgres()
self.get_bot(self.conn, self.cur, request.remote_ip)
def get_bot(self, conn, cur, ip):
while True:
bot = core.get_bot(conn, cur)
if bot:
self.bot_token = bot[1]
self.customer_asked = bot[4]
# занять бота
core.make_bot_busy(self.conn, self.cur, self.bot_token)
# добавить боту ip адрес
core.add_remote_ip(self.conn, self.cur, self.bot_token, ip)
break
def check_origin(self, origin):
''' Дает возможность подключаться с различных адресов '''
return True
def bot_callback(self):
''' Функция вызывается PeriodicCallback и проверяет сервер Telegram на
наличие новых сообщений от менеджера
'''
ans_telegram = core.get_updates(self.bot_token, self.conn, self.cur)
if ans_telegram:
# если пришло сообщение от менеджера, то отправить его в браузер клиенту
self.write_message(ans_telegram)
def open(self):
''' Функция вызываемая при открытии сокета с клиентом '''
# Запускает опрос сервера Telegram каждые 3сек
self.telegram_loop = tornado.ioloop.PeriodicCallback(self.bot_callback, 3000)
self.telegram_loop.start()
def on_message(self, message):
''' Функция вызываемая, когда по сокету приходит сообщение '''
if not self.customer_asked:
self.customer_asked = True
# обновить значение в бд, что клиент задал вопрос
core.update_customer_asked(self.conn, self.cur, self.bot_token, True)
core.send_message(self.bot_token, CHAT_ID, message)
def on_close(self):
''' Функция вызываемая при закрытии соединения '''
core.send_message(self.bot_token, CHAT_ID, "Пользователь закрыл чат")
# остановить PeriodicCallback
self.telegram_loop.stop()
# освободить бота
core.make_bot_free(self.conn, self.cur, self.bot_token)
# WebSocket будет доступен по адресу ws://127.0.0.1:8080/ws
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
application.listen(8080)
tornado.ioloop.IOLoop.current().start()
Теперь создадим статические файл:
chat.html
Посмотреть код
<div class="chatbox chatbox-down chatbox--empty">
<div class="chatbox__title">
<h5><a href="#">Tornado-Telegram-chat</a></h5>
<button class="chatbox__title__close">
<span>
<svg viewBox="0 0 12 12" width="12px" height="12px">
<line stroke="#FFFFFF" x1="11.75" y1="0.25" x2="0.25" y2="11.75"></line>
<line stroke="#FFFFFF" x1="11.75" y1="11.75" x2="0.25" y2="0.25"></line>
</svg>
</span>
</button>
</div>
<div id="messages__box" class="chatbox__body">
<!-- сюда будут добавляться сообщения от клиента и менеджера -->
</div>
<button id="start-ws" type="button" class="btn btn-success btn-block">Начать чат</button>
<form>
<textarea id="message" class="chatbox__message" placeholder="Ваше сообщение..."></textarea>
<input id="sendmessage" type="hidden">
</form>
</div>
chat.css
Посмотреть код
.chatbox {
position: fixed;
bottom: 0;
right: 30px;
height: 400px;
background-color: #fff;
font-family: Arial, sans-serif;
-webkit-transition: all 600ms cubic-bezier(0.19, 1, 0.22, 1);
transition: all 600ms cubic-bezier(0.19, 1, 0.22, 1);
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
.chatbox-down {
bottom: -350px;
}
.chatbox--closed {
bottom: -400px;
}
.chatbox .form-control:focus {
border-color: #1f2836;
}
.chatbox__title,
.chatbox__body {
border-bottom: none;
}
.chatbox__title {
min-height: 50px;
padding-right: 10px;
background-color: #1f2836;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
cursor: pointer;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
}
.chatbox__title h5 {
height: 50px;
margin: 0 0 0 15px;
line-height: 50px;
position: relative;
padding-left: 20px;
-webkit-flex-grow: 1;
flex-grow: 1;
}
.chatbox__title h5 a {
color: #fff;
max-width: 195px;
display: inline-block;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chatbox__title h5:before {
content: '';
display: block;
position: absolute;
top: 50%;
left: 0;
width: 12px;
height: 12px;
background: #4CAF50;
border-radius: 6px;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
}
.chatbox__title__tray,
.chatbox__title__close {
width: 24px;
height: 24px;
outline: 0;
border: none;
background-color: transparent;
opacity: 0.5;
cursor: pointer;
-webkit-transition: opacity 200ms;
transition: opacity 200ms;
}
.chatbox__title__tray:hover,
.chatbox__title__close:hover {
opacity: 1;
}
.chatbox__title__tray span {
width: 12px;
height: 12px;
display: inline-block;
border-bottom: 2px solid #fff
}
.chatbox__title__close svg {
vertical-align: middle;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 1.2px;
}
.chatbox__body,
.chatbox__credentials {
padding: 15px;
border-top: 0;
background-color: #f5f5f5;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
-webkit-flex-grow: 1;
flex-grow: 1;
}
.chatbox__credentials {
display: none;
}
.chatbox__credentials .form-control {
-webkit-box-shadow: none;
box-shadow: none;
}
.chatbox__body {
overflow-y: auto;
}
.chatbox__body__message {
position: relative;
}
.chatbox__body__message p {
padding: 15px;
border-radius: 4px;
font-size: 14px;
background-color: #fff;
-webkit-box-shadow: 1px 1px rgba(100, 100, 100, 0.1);
box-shadow: 1px 1px rgba(100, 100, 100, 0.1);
}
.chatbox__body__message img {
width: 40px;
height: 40px;
border-radius: 4px;
border: 2px solid #fcfcfc;
position: absolute;
top: 15px;
}
.chatbox__body__message--left p {
margin-left: 15px;
padding-left: 30px;
text-align: left;
}
.chatbox__body__message--left img {
left: -5px;
}
.chatbox__body__message--right p {
margin-right: 15px;
padding-right: 30px;
text-align: right;
}
.chatbox__body__message--right img {
right: -5px;
}
.chatbox__message {
padding: 15px;
min-height: 50px;
outline: 0;
resize: none;
border: none;
font-size: 12px;
border: 1px solid #ddd;
border-bottom: none;
background-color: #fefefe;
width: 100%;
}
.chatbox--empty {
height: 262px;
}
.chatbox--empty.chatbox-down {
bottom: -212px;
}
.chatbox--empty.chatbox--closed {
bottom: -262px;
}
.chatbox--empty .chatbox__body,
.chatbox--empty .chatbox__message {
display: none;
}
.chatbox--empty .chatbox__credentials {
display: block;
}
.description {
font-family: Arial, sans-serif;
font-size: 12px;
}
#start-ws {
margin-top: 30px;
}
.no-visible {
display: none;
}
Перед написанием javascript файла необходимо определиться, как будет выглядеть код для сообщении от клиента и от менеджера.
Html код для сообщении от клиента:
Посмотреть код
<div class="chatbox__body__message chatbox__body__message--right">
<img src="../static/user.png" alt="">
<p></p>
</div>
Html код для сообщении от менеджера:
Посмотреть код
<div class="chatbox__body__message chatbox__body__message--right">
<img src="../static/user.png" alt="">
<p></p>
</div>
chat.js
Посмотреть код
(function($) {
$(document).ready(function() {
var $chatbox = $('.chatbox'),
$chatboxTitle = $('.chatbox__title'),
$chatboxTitleClose = $('.chatbox__title__close'),
$chatboxWs = $('#start-ws');
// Свернуть чат при нажатии на заголовок и наоборот
$chatboxTitle.on('click', function() {
$chatbox.toggleClass('chatbox-down');
});
// Закрыть чат
$chatboxTitleClose.on('click', function(e) {
e.stopPropagation();
$chatbox.addClass('chatbox--closed');
// Если на момент закрытия был открыт сокет, то
// следует закрыть его
if (window.sock) {
window.sock.close();
}
});
// Подключиться к сокету
$chatboxWs.on('click', function(e) {
e.preventDefault();
// сделать диалог видимым
$chatbox.removeClass('chatbox--empty');
// сделать кнопку начала чата невидимой
$chatboxWs.addClass('no-visible');
if (!("WebSocket" in window)) {
alert("Ваш браузер не поддерживает web sockets");
}
else {
alert("Начало соединения");
setup();
}
});
});
})(jQuery);
// Функция создания соединения по WebSocket
function setup(){
var host = "ws://62.109.2.175:8084/ws";
var socket = new WebSocket(host);
window.sock = socket;
var $txt = $("#message");
var $btnSend = $("#sendmessage");
// Отслеживать изменения в textarea
$txt.focus();
$btnSend.on('click',function(){
var text = $txt.val();
if(text == ""){return}
// отправить сообщение по сокету
socket.send(text);
// отобразить в дилоге сообщение
clientRequest(text);
$txt.val("");
// $('#send')
});
// отслеживать нажатие enter
$txt.keypress(function(evt){
// если был нажат enter
if(evt.which == 13){
$btnSend.click();
}
});
if(socket){
// действие на момент открытия сокета
socket.onopen = function(){
}
// действие на момент получения сообщения по сокету
socket.onmessage = function(msg){
// отобразить сообщение в диалоге
managerResponse(msg.data);
}
// действия на момент закрытия сокета
socket.onclose = function(){
webSocketClose("The connection has been closed.");
window.sock = false;
}
}else{
console.log("invalid socket");
}
}
function webSocketClose(txt){
var p = document.createElement('p');
p.innerHTML = txt;
document.getElementById('messages__box').appendChild(p);
}
//функция для ответов клиента
function clientRequest(txt) {
$("#messages__box").append("<div class='chatbox__body__message chatbox__body__message--right'> <img src='../static/user.png' alt=''> <p>" + txt + "</p> </div>");
}
// Функция для ответов менеджера
function managerResponse(txt) {
$("#messages__box").append("<div class='chatbox__body__message chatbox__body__message--left'> <img src='../static/user.png' alt=''> <p>" + txt + "</p> </div>");
}
Развертывание на centos7
Для начала необходимо настроить виртуальное окружение для нашего приложения, собственно, повторить то, что мы уже делали на локальной машине в пункте реализация.
После того, как мы настроили окружение, нужно перенести туда наш проект, проще всего это сделать, используя git, предварительно необходимо загрузить код в свой репозиторий и оттуда уже клонировать его на сервер.
Настраиваем postgres
Если у вас на сервере не установлен postgres, то установить его можно так:
sudo yum install postgresql-server postgresql-devel postgresql-contrib
Запускаем postgres:
sudo postgresql-setup initdb
sudo systemctl start postgresql
Добавляем автозапуск:
sudo systemctl enable postgresql
После чего необходимо перейти в psql под пользователем postgres и повторить все, что мы делали на локальной машине.
Будем запускать наше tornado приложение с помощью supervisor в фоне.
Для начала установим supervisor:
sudo yum install supervisor
Теперь откроем конфигурационный файл супервизора, который будет находится в /etc/supervisor.conf
[unix_http_server]
file=/path/to/supervisor.sock ; (the path to the socket file)
[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=error ; (log level;default info; others: debug,warn,trace)
pidfile=/path/to/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
user=root
childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP)
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///path/to/supervisor.sock ; use a unix:// URL for a unix socket
[program:tornado-8004]
environment=PATH="/path/to/chat/bin"
command=/path/to/chat/bin/python3.4 /path/to/tornadino.py --port=8084
stopsignal=KILL
stderr_logfile=/var/log/supervisord/tornado-stderr.log
stdout_logfile=/var/log/supervisord/tornado-stdout.log
[include]
files = supervisord.d/*.ini
Не забудьте поменять пути в конфигурационном файле!
Перед тем, как запускать supervisor, необходимо создать папку /var/log/supervisord/ в ней будут собираться логи торнадо, так что, если supervisor запустил tornado-8004, но чат не работает, то ошибку стоит искать там.
Запускаем супервизор:
sudo supervisorctl start tornado-8004
Проверяем, что все в порядке:
sudo supervisorctl status
Должны получить что-то подобное:
tornado-8004 RUNNING pid 32139, uptime 0:08:10
На локальной машине вносим изменения в chat.js:
var host = "ws://адресс_вашего_сервера:8084/ws";
и открываем в браузере chat.html.
Готово!
Можно без особых телодвижений прикручивать такой чат к своим проектам, так же достаточно удобно использовать для сбора feedback.