Установка 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)

Теперь можно доработать обработчик POST формы и в нем передавать данные в 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"]