Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
This repository was archived by the owner on Feb 15, 2020. It is now read-only.

spuf/DemoChat

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Демо-чат на WebSockets, Tornado, MongoDB

Читая статьи на хабре о веб-сокетах, сильно захотелось что-нибудь написать на них. После долгих поисков я выбрал Tornado Web Server, в качестве сервера, ведь он написан на Питоне, да и он похож на GAE.

Подготовка

Для начала нам понадобятся сам Python c Tornado и AsyncMongo, и MongoDB. У меня всё с успехом установилось как на Windows 7 x64, так и на Debian 6 x86. Для установки библиотек для питона помогает easy_install. Кстати, в зависимостях asyncmongo есть pymongo, которому понадобятся при установке компилятор (gcc) и заголовочные файлы (python-dev).

Сервер

Наш сервер будет выполнять следующие вещи:

  • Выдача главной страницы
  • Выдача статики
  • Обслуживание сокета
  • Соединение с базой
  • Выдача сообщения о том, что у нашего пользователя нет флэша/js/ws
class Application(tornado.web.Application):
    def __init__(self):
        self.db = asyncmongo.Client(pool_id='mydb', host='127.0.0.1', port=27017, dbname='test')
        handlers = [
                (r'/', MainHandler),
                (r'/add_message', AddHandler),
                (r'/chatsocket', ChatSocketHandler)
        ]
        settings = dict(
            template_path=os.path.join(os.path.dirname(__file__), 'templates'),
            static_path=os.path.join(os.path.dirname(__file__), 'static'),
        )
        tornado.web.Application.__init__(self, handlers, **settings)

/add_message - это action у формы сообщения, ведь именно туда пойдет наш консерватор.

Главная берет последние 50 сообщений из БД, и после их получения парсит шаблон.

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.application.db.messages.find({}, limit=50, sort=[('time', -1)], callback=self.on_response)

    def on_response(self, response, error):
        if error:
            raise tornado.web.HTTPError(500)
        self.render('index.html', messages=response[::-1])

Для нашего нерадивого пользователя мы покажем это:

class AddHandler(tornado.web.RequestHandler):
    def post(self):
        logging.info('Post message')
        self.write('Your browser doesn\'t support JavaScript or WebSockets or Flash.');

С сокетом кода выйдет чуть поболее. Для началам нам понадобиться переменная для хранения всех подключенных,

class ChatSocketHandler(tornado.websocket.WebSocketHandler):
    waiters = set()

    def open(self):
        ChatSocketHandler.waiters.add(self)
        logging.info('Add waiter')

    def on_close(self):
        ChatSocketHandler.waiters.remove(self)
        logging.info('Remove waiter')

При получении сообщения, мы создаем объект с датой, текстом, а также HTML представлением (которое вполне логично засунуть в клиент). Затем запускаем операцию добавления, заранее запихав в callback объект с сообщением.

    def on_message(self, message):
        logging.info('Got message %r', message)
        parsed = tornado.escape.json_decode(message)
        chat = {
            'body': parsed['body'],
            'time': time.time()
        }
        chat['html'] = self.render_string('message.html', message=chat)
        callback = functools.partial(ChatSocketHandler.send_updates, chat=chat)
        self.application.db.messages.insert(chat, callback=callback)

И как только сообщение добавится в БД, рассылаем его всем клиентам.

    @classmethod
    def send_updates(cls, result, error, chat):
        if error:
            raise tornado.web.HTTPError(500)
        logging.info('Sending message to %d waiters', len(cls.waiters))
        for waiter in cls.waiters:
            try:
                waiter.write_message(chat)
            except:
                logging.error('Error sending message', exc_info=True)

Клиент

Клиентский html состоит из простенькой формы. Javscript же вешает свой обработчик на событие submit и нажатие Enter, для отправки сообщения в сокет.

updater.socket.send(JSON.stringify(message));

Подключение к сокету, который ожидает на том же хосте и порту.

updater.socket = new WebSocket('ws://'+document.location.host+'/chatsocket');

Обработка входящих сообщений.

updater.socket.onmessage = function(event) {
  updater.showMessage(JSON.parse(event.data));
}

Обработка закрытия сокета.

updater.socket.onclose = function () {
	if (confirm('Socket disconnected.\nReload this page?'))
		document.location.reload();
}

Клиент без поддержки WebSocket

Для него на стороне клиента мы воспользуемся web-socket-js. А на стороне сервер нам придется повесить сервер для статической страницы на 843 порт, отдающий crossdomain.xml:

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
	<allow-access-from domain="*" secure="false" to-ports="*"/>
	<site-control permitted-cross-domain-policies="master-only" />
</cross-domain-policy>

А какой сервер лучше всех отдает статику? nginx, примерно такого конфига:

server {
	listen 843;
	location / {
		rewrite ^(.*)$ /crossdomain.xml;
	}
	error_page 400 /crossdomain.xml;
	location = /crossdomain.xml {
		root /var/www/static;
	}
}

About

Демо-чат на WebSockets, Tornado, MongoDB

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
Morty Proxy This is a proxified and sanitized view of the page, visit original site.