Я пытаюсь настроить приложение, использующее django2.0.2 и channels2.1.1. Я хотел бы достичь использования фоновой / рабочей задачи для выполнения некоторой работы, которая будет производить данные, которые должны динамически появляться на веб-сайте. Моя проблема, связанная в первую очередь с каналами, заключается в следующем: как мне правильно установить связь между исполнителем и потребителем, подключенным к веб-сокету?
Ниже приведен минимальный пример, подчеркивающий проблему: идея состоит в том, что пользователь запускает воркера, воркер создает некоторые данные и отправляет их через канальный уровень потребителю, который подключен к веб-сокету.
#routing.py
from channels.routing import ChannelNameRouter, ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from django.urls import path
from testApp.consumers import *
application = ProtocolTypeRouter({
"websocket":AuthMiddlewareStack(
URLRouter([
path("wspath",TestConsumer),
]),
),
"channel":ChannelNameRouter({
"test_worker": TestWorker,
}),
})
Потребители:
#consumers.py
from channels.consumer import SyncConsumer
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
class TestConsumer(WebsocketConsumer):
def websocket_connect(self,message):
async_to_sync(self.channel_layer.group_add)("testGroup",self.channel_name)
self.connect()
#I understand this next part is a bit weird, but I figured it
#is the most concise way to explain my problem
async_to_sync(self.channel_layer.group_send)(
"testGroup",
{
'type':"echo_msg",
'msg':"sent from WebsocketConsumer",
})
def echo_msg(self, message):
print("Message to WebsocketConsumer", message)
class TestWorker(SyncConsumer):
def triggerWorker(self, message):
async_to_sync(self.channel_layer.group_add)("testGroup",self.channel_name)
async_to_sync(self.channel_layer.group_send)(
"testGroup",
{
'type':"echo_msg",
'msg':"sent from worker",
})
def echo_msg(self, message):
print("Message to worker ", message)
Вид
#views.py
from django.shortcuts import render
import channels.layers
from asgiref.sync import async_to_sync
def index(request):
if request.method == "POST":
channel_layer = channels.layers.get_channel_layer()
async_to_sync(channel_layer.send)('test_worker',{
'type':'triggerWorker',
})
return render(
request,
"index.html",
{})
и html:
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "utf-8">
<script>
console.info('ws://' + window.location.host)
var socket = new WebSocket(
'ws://' + window.location.host + "/wspath"
);
</script>
</head>
<div>Click to run worker</div>
<body>
<form action = "" method = "POST">
{% csrf_token %}
<button type = "submit">Start</button>
</form>
</body>
Теперь, когда я запускаю это, выполняя (на отдельных консолях)
python3 manage.py runserver
а также
python3 manage.py runworker test_worker
а затем запускайте рабочий, консоль сервера запуска выводит:
Message to WebsocketConsumer {'type': 'echo_msg', 'msg': 'sent from WebsocketConsumer'}
где как выводит консоль runworker:
Message to worker {'type': 'echo_msg', 'msg': 'sent from worker'}
Message to worker {'type': 'echo_msg', 'msg': 'sent from WebsocketConsumer'}
Итак, я могу отправить материал worker -> worker, WebsocketConsumer -> WebsocketConsumer, WebsocketConsumer -> worker.
Насколько я понимаю (что, очевидно, неверно), также должен был быть работник сообщения -> WebsocketConsumer, потому что я добавил и то, и другое в "testGroup".
Итак, у меня вопрос: почему WebsocketConsumer ничего не получил от рабочего (это то, что меня интересует, чтобы в конечном итоге установить связь с javaScript)? Или, другими словами, почему я могу отправлять материал только из WebsocketConsumer рабочему, а не наоборот?





Ваш веб-сокет никогда не используется. Вы отправляете сообщение со своего представления работнику, а не от потребителя к работнику. Представление отправляет это сообщение, когда вы отправляете ему данные.
URL-адрес, который у вас есть в вашем JavaScript, - /wspath/, а URL-адрес, который вы зарегистрировали на своем потребителе, - chat/stream. Веб-сокет никогда не подключается.
Кроме того, ваш рабочий будет добавлен в одну и ту же группу снова и снова с вашей текущей настройкой. Вам нужно всего лишь один раз добавить воркера в группу.
Если этот ответ помог вам, отметьте его правильным.
Я запускаю ваш код. Я вижу, что все работает.
У вас есть начальное сообщение, отправляемое в POST - оно работает - вы добавляете его в группу. Когда веб-сокет подключается, он отправляет сообщение работнику. Вы можете видеть это в своем терминале runworker. Это возвращает его вашему потребителю и распечатывается в терминале, на котором вы запускаете runserver. Чтобы вернуть его в свой веб-браузер, вам нужно написать:
def echo_msg(self, message):
print("Message to WebsocketConsumer", message)
self.send(json.dumps(message))
Откройте инструменты разработчика в Chrome, чтобы увидеть, как он вернется. Перейдите в сеть> выберите подключение к веб-сокету> затем щелкните фреймы.
Кстати, вам не нужно снова и снова добавлять тестировщика в одну и ту же группу. Рабочий всегда работает.
Еще один кстати: если ваши группы будут называться одинаково для всех пользователей, вам не нужно добавлять своего работника в группу. Вы можете отправлять сообщения прямо своему воркеру по его имени маршрута (send вместо group_send). Рабочий может отправлять сообщения обратно в группу без добавления в группу. Вам нужно только добавить потребителей веб-сокетов в группы.
Кроме того, если вы не хотите, чтобы несколько пользователей видели одни и те же сообщения, вам вообще не нужны группы. Просто отправьте сообщение работнику с именем канала (self.channel_name), чтобы отправить его обратно.
Кроме того, вы, вероятно, захотите работать с потребителями json, а не разбирать сообщения самостоятельно, но это зависит от вас.
Большое спасибо. Теперь у меня все работает. Также благодарю за прояснение этих моментов, ясно, что мое понимание некоторых концепций в каналах все еще немного шаткое.
Я исправил эту довольно глупую ошибку. Но в любом случае, после исправления я наблюдаю то же поведение