Чат сервер з використанням фреймфорка Tornado.
Основи Python и Django. -> Використання сервера REDIS.
Встановлення Redis сервера.
sudo apt-get install redis-server
Установка бібліотеки python для роботи з сервером.
pip install tornado-redis redis
перед установкою переконайтеся, що ви активували віртуальне оточення
Створення тестового скрипту надсилання даних до каналу redis.
test-redis.py
#!/usr/bin/env python
import redis
print('Testing redis connection')
redis_client = redis.Redis(host='localhost', port=6379, db=0)
print("Sending message to channel")
out = redis_client.publish('chat-channel', 'my data')
print(out)
Клієнтська частина
var ws = new WebSocket("ws://localhost:8888/websocket");
ws.onopen = function() {
message = {'action': 'connect', 'message': 'new connection'}
ws.send(JSON.stringify(message));
};
ws.onmessage = function (evt) {
jdata = JSON.parse(evt.data);
if( jdata['action'] == 'set_sign'){
console.log(`Set sign ${jdata['message']}`);
localStorage.setItem('chat-connection-id', jdata['message']);
} else {
$('#message_box').append('<li>'+jdata.message+'</li>');
}
}
$('#submit_button').on('click',function(e){
e.preventDefault();
data = {
'action': 'message',
'message': $('#chat_message').val()
};
ws.send(JSON.stringify(data));
});
Використання бібліотеки tornado-redis
import tornadoredis
...
class WebsocketHandler(tornado.websocket.WebSocketHandler):
def __init__(self, *args, **kwargs):
super(WebsocketHandler, self).__init__(*args, **kwargs)
self.listen_redis()
def listen_redis(self):
self.client = tornadoredis.Client()
self.client.connect()
self.client.subscribe('chat-channel')
self.client.listen(self.on_message)
При такому розкладі не працюватиме т.к. процес підписки на канал має бути асинхронним у циклі подій Tornado. Для цього використовуватимемо декоратор @tornado.gen.coroutine. Він створює асинхронну корутину (генератор). Це зручніший спосіб організації послідовності асинхронних дій порівняно із створенням ланцюга коллбеків.
Приклад асинхронної корутини.
class GenAsyncHandler(RequestHandler):
@gen.coroutine
def get(self):
http_client = AsyncHTTPClient()
response = yield http_client.fetch("http://example.com")
do_something_with_response(response)
self.render("template.html")
Як видно, код виглядає синхронно (без колбеків), однак виконується асинхронно, зберігаючи послідовність виконання.
response = yield http_client.fetch("http://example.com")
Після оператора yield код припиниться чекаючи отримати результат у response.
У нашому випадку ми асинхронно підписуємося на канал і очікуємо результату, після чого слухаємо канал.
@tornado.gen.coroutine
def subscribe_redis(self):
self.client = tornadoredis.Client()
self.client.connect()
yield tornado.gen.Task(self.client.subscribe, 'chat-channel')
self.client.listen(self.on_message)
Цей функціонал можна реалізувати за допомогою звичайних колбеків так:
class WebsocketHandler(tornado.websocket.WebSocketHandler):
def __init__(self, *args, **kwargs):
super(WebsocketHandler, self).__init__(*args, **kwargs)
self.subscribe_redis()
def listen_redis(self,rez):
self.client.listen(self.on_message)
def subscribe_redis(self):
self.client = tornadoredis.Client()
self.client.connect()
self.client.subscribe('chat-channel',self.listen_redis)
Цей функціонал можна реалізувати за допомогою звичайних колбеків так: redis_client = redis.Redis(host=’localhost’, port=6379, db=0)
...
class FormHandler(tornado.web.RequestHandler):
def post(self):
data = {"action": "onmessage", "message": self.get_argument('message')}
redis_client.publish('chat-channel',json.dumps(data))
self.write("OK")
Далі перепишемо код клієнта для надсилання даних із форми AJAX запитом (без перевантаження сторінки).
$('#submit_button').on('click',function(e){
e.preventDefault();
data = {
'action': 'message',
'message': $('#chat_message').val()
};
//ws.send(JSON.stringify(data));
$.post('/submit',data,function(r){
console.log(r);
})
});
Раніше ми передавали повідомлення до веб-сокету.
Повідомлення, яке опускається на сервер з redis виглядає таким об’єктом.
Message(kind='message', channel='chat-channel', body='{"message": "test", "action": "onmessage"}', pattern='chat-channel')
Для доступу до параметра body використовується наступний синтаксис:
message.body["message|action"]