Делать ходы с веб-сокетами и Python / Django (/ Twisted?) - PullRequest
24 голосов
/ 06 декабря 2010

Интересная часть веб-сокетов - отправка по существу незапрошенного контента с сервера в браузер, верно?

Ну, я использую django-websocket Грегора Мюллеггера. Это действительно замечательная ранняя попытка заставить работать websockets в Django.

Я достиг "Привет, мир". Это работает так: когда запрос является веб-сокетом, объект, веб-сокет, добавляется к объекту запроса. Таким образом, я могу, с точки зрения интерпретации websocket, сделать что-то вроде:

request.websocket.send('We are the knights who say ni!')

Это прекрасно работает. Я получаю сообщение обратно в браузере как заклинание.

Но что, если я хочу сделать это, вообще не отправляя запрос из браузера?

ОК, поэтому сначала я сохраняю веб-сокет в словаре сеанса:

request.session['websocket'] = request.websocket

Затем в оболочке я иду и беру сеанс по ключу сеанса. Конечно же, в словаре сессий есть объект websocket. Happy!

Однако, когда я пытаюсь сделать:

>>> session.get_decoded()['websocket'].send('With a herring!')

Я получаю:

Traceback (most recent call last):
File "<console>", line 1, in <module>
error: [Errno 9] Bad file descriptor

Sad. : - (

ОК, поэтому я ничего не знаю о сокетах, но знаю достаточно, чтобы обнюхать в отладчике, и вот, я вижу, что сокет в моем отладчике (который связан с подлинной веб-сокет запрос) имеет fd = 6, а тот, который я взял из сохраненной в сеансе веб-сокета, имеет fd = -1.

Может ли человек, ориентированный на сокеты, помочь мне разобраться с этим?

Ответы [ 4 ]

64 голосов
/ 06 декабря 2010

Я автор django-websocket. Я не настоящий эксперт в области веб-сокетов и сетей, однако я думаю, что у меня есть приличное понимание того, что происходит. Извините, что углубился в детали. Даже если большая часть ответа не относится к вашему вопросу, это может помочь вам в какой-то другой момент. : -)


Как работают веб-сокеты

Позвольте мне кратко объяснить, что такое веб-розетка. Websocket начинается как нечто, что действительно выглядит как простой HTTP-запрос, установленный из браузера. Через заголовок HTTP он указывает, что он хочет «обновить» протокол, чтобы он стал веб-сокетом, а не HTTP-запросом. Если сервер поддерживает веб-сокеты, он соглашается на квитирование, и оба - сервер и клиент - теперь знают, что они будут использовать установленный сокет TCP, который ранее использовался для HTTP-запроса, в качестве соединения для обмена сообщениями веб-сокетов.

Помимо отправки и ожидания сообщений, они, конечно же, имеют возможность закрыть соединение в любое время.

Как django-websocket использует среду запросов Python для wsgi, чтобы захватить сокет

Теперь давайте углубимся в детали того, как django-websocket реализует «обновление» HTTP-запроса в цикле запроса-ответа django.

Django обычно использует спецификацию WSGI для общения с веб-сервером, таким как apache, gunicorn и т. Д. Эта спецификация была разработана только с учетом очень ограниченной модели обмена данными HTTP. Предполагается, что он получает HTTP-запрос (только входящие данные) и возвращает ответ (только исходящие данные). Из-за этого сложно внедрить django в концепцию веб-сокета, где разрешено двунаправленное общение.

Что я делаю в django-websocket, чтобы добиться этого, так это то, что я очень глубоко копаюсь во внутренностях WSGI и объекте запроса django, чтобы получить нижележащий сокет. Этот сокет tcp затем используется для непосредственной обработки запроса HTTP до экземпляра websocket.

Теперь к вашему первоначальному вопросу ...

Надеюсь, из вышесказанного становится очевидным, что при установке веб-сокета возвращать HttpResponse бессмысленно. Вот почему вы обычно ничего не возвращаете в представлении, которое обрабатывается django-websocket.

Однако я хотел придерживаться концепции представления, которое содержит логику и возвращает данные на основе входных данных. Вот почему вы должны использовать только код в вашем представлении для обработки веб-сокета.

После возврата из представления веб-розетка автоматически закрывается. Это сделано по причине: мы не хотим держать сокет открытым в течение неопределенного периода времени и надеемся, что клиент (браузер) закроет его.

Вот почему вы не можете получить доступ к веб-сокету с помощью django-websocket за пределами вашего обзора. Дескриптор файла тогда, конечно, устанавливается в -1, указывая, что он уже закрыт.

Отказ от ответственности

Я объяснил выше, что копаюсь в окружающей среде django, чтобы каким-то образом - очень хакерским способом - получить доступ к нижележащему сокету. Это очень хрупко и также не должно работать, поскольку WSGI не предназначен для этого! Я также объяснил выше, что веб-сокет закрывается после завершения представления - однако после закрытия веб-сокета (И закрыл сокет tcp) реализация WSGI django пытается отправить HTTP-ответ - он не знает о веб-сокетах и ​​думает, что нормальный цикл запроса-ответа HTTP. Но сокет уже закрыт и отправка не удастся. Это обычно вызывает исключение в django.

Это не повлияло на мои тесты с сервером разработки. Браузер никогда не заметит (вы знаете ... сокет уже закрыт ;-) - но возникновение необработанной ошибки в каждом запросе не очень хорошая концепция и может привести к утечке памяти, неправильно обрабатывает отключение соединения с базой данных и многие другие неприятные вещи что будет сломаться в какой-то момент, если вы используете django-websocket не только для экспериментов.

Вот почему я бы действительно советовал вам пока не использовать веб-сокеты с django .Это не работает по дизайну.Django и особенно WSGI потребуется полный пересмотр для решения этих проблем (см. это обсуждение для веб-сокетов и WSGI ).С тех пор я бы предложил использовать что-то вроде eventlet .Eventlet имеет работающую реализацию websocket (я позаимствовал некоторый код из eventlet для начальной версии django-websocket) и, поскольку он представляет собой простой код на python, вы можете импортировать свои модели и все остальное из django.Единственный недостаток - вам нужен второй веб-сервер, работающий только для обработки веб-сокетов.

6 голосов
/ 26 января 2014

Как отметил Грегор Мюллеггер, WSGI не может должным образом обрабатывать Websockets, потому что этот протокол никогда не был разработан для обработки такой функции. uWSGI , начиная с версии 1.9.11, может обрабатывать Websockets из коробки.Здесь uWSGI связывается с сервером приложений, используя необработанный HTTP, а не протокол WSGI.Таким образом, сервер, созданный таким образом, может обрабатывать внутренние компоненты протокола и поддерживать соединение открытым в течение длительного периода.Использование долгосрочных соединений, обрабатываемых представлением Django, также не является хорошей идеей, поскольку в этом случае они блокировали бы рабочий поток, который является ограниченным ресурсом.

Основная цель Websockets - это отправка на сервер push-сообщенийклиенту в асинхронном режиме.Это может быть представление Django, запускаемое другими браузерами (например, клиенты чата, многопользовательские игры), или событие, запускаемое, скажем, django-celery (например, спортивные результаты).Поэтому для этих сервисов Django крайне важно использовать очередь сообщений для отправки сообщений клиенту.

Чтобы обработать это масштабируемым образом, я написал django-websocket-redis ,Модуль Django, который может держать открытыми все эти долгоживущие соединения Websocket в одном потоке / процессе, используя Redis в качестве очереди внутренних сообщений.

2 голосов
/ 31 января 2011

Вы могли бы дать Stargate bash: http://boothead.github.com/stargate/ и http://pypi.python.org/pypi/stargate/.

Он построен на основе пирамиды и eventlet (я также внес значительную часть поддержки websocket и тестов в eventlet),Большим преимуществом пирамиды для такого рода вещей является то, что у нее есть концепция ресурса, на который ссылается URL, а не просто результат вызываемого объекта.Таким образом, вы получаете график постоянных ресурсов, который сопоставляется с вашей структурой URL, и соединения веб-сокетов просто маршрутизируются и подключаются к этим ресурсам.

Таким образом, в итоге вам нужно выполнить только две вещи:

class YourView(WebSocketView):

    def handler(self, websocket):
        self.request.context.add_listener(websocket)
        while True:
            msg = websocket.wait()
            # Do something with message

Для получения сообщений и

resource.send(some_other_message)

Здесь ресурс является экземпляром stargate.resource.WebSocketAwareContext (как и self.request.context) выше, а метод send отправляет сообщение всем подключенным клиентамс помощью метода add_listener.

Чтобы опубликовать сообщение для всех подключенных клиентов, вы просто позвоните node.send(message)

Я надеюсь, что на следующей неделе я напишу небольшой пример приложения илидва, чтобы продемонстрировать это немного лучше.

Не стесняйтесь пинговать меня на github, если вам нужна помощь с этим.

0 голосов
/ 06 декабря 2010

request.websocket вероятно закрывается, когда вы возвращаетесь из обработчика запроса (просмотр). Простое решение - сохранить обработчик живым (не возвращаясь из вида). Если ваш сервер не является многопоточным, вы не сможете принимать другие одновременные запросы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...