Чат сервер з використанням фреймфорка 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"]