Страницы

Поиск по вопросам

Показаны сообщения с ярлыком tornado. Показать все сообщения
Показаны сообщения с ярлыком tornado. Показать все сообщения

воскресенье, 12 января 2020 г.

Сессия пользователя

#python #веб_программирование #flask #tornado


Пишу небольшой проект на связке flask + tornado. На flask написан общий функционал
(на данный момент авторизация + регистрация пользователей, также создана модель), на
торнадо пытаюсь написать чат. 

Как tornado научить понимать, под каким пользователем мы сейчас авторизованы, и от
его имени писать в чат сообщения?

P.S. Если кто желает поучастовать в разработке данного проекта - милости прошу. Хочу
сделать нечто для голосового общения в веб. Цель - получения опыта в подобного рода
разработке.
    


Ответы

Ответ 1



Очень интересный и актуальный вопрос для Tornado. Способов реализации много, торнадо тем и хорош что из коробки нет никакого навязанного решения, можно дать волю фантазии и заточить все максимально вкусно для своего проекта. Предлагаю такой вариант (опускаю лишний код, чтобы показать только суть): class BaseHandler(tornado.web.RequestHandler): ... def get_current_user(self): user = None user_json = self.get_secure_cookie("mysite_cookie_user") if not user_json: return None logging.info("User ID:%s is get response", str(user_json)) try: user = tornado.escape.json_decode(user_json) except Exception: logging.info("Error get user cookie") return user def get_visiter_settings(self): settings_json = self.get_secure_cookie("mysite_cookie_settings") try: cur_settings = tornado.escape.json_decode(settings_json) logging.info("Visiter settings:%s is get response from cookie.", str(cur_settings)) except Exception: logging.info("Error get settings from cookie.") logging.info("Init new cookie settings.") cur_settings = { "visitor_uuid": str(uuid.uuid4()), "option1": "foo", ... "option10": "bar"} self.set_secure_cookie("mysite_cookie_settings", json.dumps(cur_settings)) logging.info("New cookie settings initialize.") finally: return cur_settings class MainHandler(BaseHandler): @tornado.web.asynchronous @tornado.gen.coroutine def get(self,*args, **kwargs): current_user = self.get_current_user() visiter_settings = self.get_visiter_settings() Хеши сессий можно хранить например в Redis. В хендлерах состояние сессий можно получать и обновлять по ключам сформированным от current_user и visiter_settings["visitor_uuid"]. Например, для гостей сессии определять по visitor_uuid, для авторизованных по current_user + можно их линковать к их уже существующей сессии по visitor_uuid.

Ответ 2



Можно, например, по cookies Если у вас общая база логично передавать логин в secure_cookies. На этом принципе основана аутентификация tornado из коробки Про фласк не скажу, увы с ним не сталкивался, но думаю там есть что-то похожее или в крайнем случаем можно написать самому.

воскресенье, 8 декабря 2019 г.

Одна переменная на несколько процессов в Python

#python #многопоточность #websocket #tornado #celery


По совету @andreymal из моего предыдущего вопроса:  


  Если разными частями приложения являются разные процессы, то для этого надо организовывать
межпроцессное взаимодействие, чтобы один процесс работал с соединениями, а другие процессы
отправляли этому процессу сообщения. Можно для этого написать свой велосипед на сокетах,
можно для этого использовать готовые решения вроде RabbitMQ и Redis.  


Пытаюсь реализовать межпроцессное взаимодействие при помощи celery.  

Исходные данные и какую задачу нужно решить:
К серверу по вебсокетам должны подключаться клиенты (люди, использующие браузер)
и ожидать сообщений от сервера.
На сервере время от времени срабатывает процесс, генерирующий это самое сообщение.
Это сообщение (которое нужно отдать клиенту) можно попросить у процесса передать в
указанный скрипт.  

Что для этого сделано:  


Обработчик сокет-соединений на Python tornado;
.py скрипт, которому на stdin подается сообщение от процесса, который генерирует
само сообщение;
функция-таск для celery, которая должна отправить сообщение в браузер.


То есть, процесс генерирует сообщение -> вызывает скрипт, передавая ему сообщение
на stdin -> скрипт вызывает celery таск -> таск берет массив соединений и рассылает
им сообщение.

Суть проблемы:
Сокет сервер складывает все соединения в массив, но этот массив оказывается пустым
для celery таска.   

Собственно, вопрос: как завести один массив на несколько процессов, или где я налажал,
и как это делать правильно? 

UPD добавил упрощенный код для понимания того, как и что есть.
Сокет-сервер на tornado: 

class SocketHandler(tornado.websocket.WebSocketHandler):
    connections = []

    def open(self):
        self.__class__.connections.append(self)

    @classmethod
    def get_connections(cls):
        return cls.connections


и celery таск, который должен отправлять сообщения клиентам: 

from handlers import SocketHandler

@celery.task
def send_msg(msg):
    for conn in SocketHandler.get_connections():
        conn.write_message(msg)


Но список полученный путем SocketHandler.get_connections() в файле с celery тасками
оказывается пустым.
Запускаю все это в разных терминалах так:   

(venv)$ python app.py  # это торнадо апп, импортирующий приведенный handlers
(venv)$ celery -A tasks worker

    


Ответы

Ответ 1



Из описания проблемы и документации Celery я так понимаю, что таски выполняются в отдельных воркерах, которые может запускать сам Celery, но суть в том, что таск должен выполняться в одном процессе с сокет-сервером, тогда массив с сокетами ему будет виден. Пробежавшсь по диагонали по документации Celery, я подобного не нашёл; если это на нём и правда невозможно, то, видимо, стоит взять что-то попроще. Вообще я представляю это как-то так (несколько кривенько, но, думаю, суть должна быть понятна): def queue_thread(): while thread_should_work(): msg = redis.blpop("websocket_queue", timeout=5) if not msg: continue data = pickle.loads(msg[1]) send_message_to_all_sockets(data) Threading.thread(target=queue_thread).start()

четверг, 7 марта 2019 г.

Сессия пользователя

Пишу небольшой проект на связке flask + tornado. На flask написан общий функционал (на данный момент авторизация + регистрация пользователей, также создана модель), на торнадо пытаюсь написать чат.
Как tornado научить понимать, под каким пользователем мы сейчас авторизованы, и от его имени писать в чат сообщения?
P.S. Если кто желает поучастовать в разработке данного проекта - милости прошу. Хочу сделать нечто для голосового общения в веб. Цель - получения опыта в подобного рода разработке.


Ответ

Очень интересный и актуальный вопрос для Tornado. Способов реализации много, торнадо тем и хорош что из коробки нет никакого навязанного решения, можно дать волю фантазии и заточить все максимально вкусно для своего проекта.
Предлагаю такой вариант (опускаю лишний код, чтобы показать только суть):
class BaseHandler(tornado.web.RequestHandler):
...
def get_current_user(self): user = None user_json = self.get_secure_cookie("mysite_cookie_user") if not user_json: return None logging.info("User ID:%s is get response", str(user_json)) try: user = tornado.escape.json_decode(user_json) except Exception: logging.info("Error get user cookie") return user
def get_visiter_settings(self): settings_json = self.get_secure_cookie("mysite_cookie_settings") try: cur_settings = tornado.escape.json_decode(settings_json) logging.info("Visiter settings:%s is get response from cookie.", str(cur_settings)) except Exception: logging.info("Error get settings from cookie.") logging.info("Init new cookie settings.") cur_settings = { "visitor_uuid": str(uuid.uuid4()), "option1": "foo",
...
"option10": "bar"} self.set_secure_cookie("mysite_cookie_settings", json.dumps(cur_settings)) logging.info("New cookie settings initialize.") finally: return cur_settings
class MainHandler(BaseHandler):
@tornado.web.asynchronous @tornado.gen.coroutine def get(self,*args, **kwargs): current_user = self.get_current_user() visiter_settings = self.get_visiter_settings()
Хеши сессий можно хранить например в Redis.
В хендлерах состояние сессий можно получать и обновлять по ключам сформированным от current_user и visiter_settings["visitor_uuid"].
Например, для гостей сессии определять по visitor_uuid, для авторизованных по current_user + можно их линковать к их уже существующей сессии по visitor_uuid.

пятница, 12 октября 2018 г.

Одна переменная на несколько процессов в Python

По совету @andreymal из моего предыдущего вопроса:
Если разными частями приложения являются разные процессы, то для этого надо организовывать межпроцессное взаимодействие, чтобы один процесс работал с соединениями, а другие процессы отправляли этому процессу сообщения. Можно для этого написать свой велосипед на сокетах, можно для этого использовать готовые решения вроде RabbitMQ и Redis.
Пытаюсь реализовать межпроцессное взаимодействие при помощи celery.
Исходные данные и какую задачу нужно решить: К серверу по вебсокетам должны подключаться клиенты (люди, использующие браузер) и ожидать сообщений от сервера. На сервере время от времени срабатывает процесс, генерирующий это самое сообщение. Это сообщение (которое нужно отдать клиенту) можно попросить у процесса передать в указанный скрипт.
Что для этого сделано:
Обработчик сокет-соединений на Python tornado; .py скрипт, которому на stdin подается сообщение от процесса, который генерирует само сообщение; функция-таск для celery, которая должна отправить сообщение в браузер.
То есть, процесс генерирует сообщение -> вызывает скрипт, передавая ему сообщение на stdin -> скрипт вызывает celery таск -> таск берет массив соединений и рассылает им сообщение.
Суть проблемы: Сокет сервер складывает все соединения в массив, но этот массив оказывается пустым для celery таска.
Собственно, вопрос: как завести один массив на несколько процессов, или где я налажал, и как это делать правильно?
UPD добавил упрощенный код для понимания того, как и что есть. Сокет-сервер на tornado:
class SocketHandler(tornado.websocket.WebSocketHandler): connections = []
def open(self): self.__class__.connections.append(self)
@classmethod def get_connections(cls): return cls.connections
и celery таск, который должен отправлять сообщения клиентам:
from handlers import SocketHandler
@celery.task def send_msg(msg): for conn in SocketHandler.get_connections(): conn.write_message(msg)
Но список полученный путем SocketHandler.get_connections() в файле с celery тасками оказывается пустым. Запускаю все это в разных терминалах так:
(venv)$ python app.py # это торнадо апп, импортирующий приведенный handlers (venv)$ celery -A tasks worker


Ответ

Из описания проблемы и документации Celery я так понимаю, что таски выполняются в отдельных воркерах, которые может запускать сам Celery, но суть в том, что таск должен выполняться в одном процессе с сокет-сервером, тогда массив с сокетами ему будет виден. Пробежавшсь по диагонали по документации Celery, я подобного не нашёл; если это на нём и правда невозможно, то, видимо, стоит взять что-то попроще.
Вообще я представляю это как-то так (несколько кривенько, но, думаю, суть должна быть понятна):
def queue_thread(): while thread_should_work(): msg = redis.blpop("websocket_queue", timeout=5) if not msg: continue data = pickle.loads(msg[1]) send_message_to_all_sockets(data)
Threading.thread(target=queue_thread).start()