15

TL;DR

Want this flow:

                     ws://...
websocket client 1 <-----------> websocket client 2
                         ^
                         |
                       server (send messages via views)

So I have the following:

views.py

def alarm(request):
    layer = get_channel_layer()
    async_to_sync(layer.group_send)('events', {
    'type': 'events.alarm',
    'content': 'triggered'
    })
    return HttpResponse('<p>Done</p>')

consumers.py

class EventConsumer(JsonWebsocketConsumer):
  def connect(self):
    print('inside EventConsumer connect()')
    async_to_sync(self.channel_layer.group_add)(
        'events',
        self.channel_name
    )
    self.accept()

  def disconnect(self, close_code):
    print('inside EventConsumer disconnect()')
    print("Closed websocket with code: ", close_code)
    async_to_sync(self.channel_layer.group_discard)(
        'events',
        self.channel_name
    )
    self.close()

  def receive_json(self, content, **kwargs):
    print('inside EventConsumer receive_json()')
    print("Received event: {}".format(content))
    self.send_json(content)

  def events_alarm(self, event):
    print('inside EventConsumer events_alarm()')
    self.send_json(
        {
            'type': 'events.alarm',
            'content': event['content']
        }
    )

in routing.py

application = ProtocolTypeRouter({
    'websocket': AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter(
                chat.routing.websocket_urlpatterns,

            )
        )
    ),
})

where websocket_urlpatterns is

websocket_urlpatterns = [
url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
url(r'^ws/event/$', consumers.EventConsumer),
]

urls.py

urlpatterns = [
    url(r'^alarm/$', alarm, name='alarm'),
]

when I call /alarm/ , only the HTTP request is made and the message is not sent to the websocket

The following are the logs:

[2018/09/26 18:59:12] HTTP GET /alarm/ 200 [0.07, 127.0.0.1:60124]

My intention is to make django view send to a group (use case would be for a server to send notification to all connected members in the group).

What setting am I missing here.

I am running django channels 2.1.3 with redis as backend. The CHANNELS_LAYERS etc. have all been setup.

Reference Links:

  1. sending messages to groups in django channels 2
  2. github issue
  3. channels docs

EDIT: I could send the message using the websocket-client from the view

from websocket import create_connection
ws = create_connection("ws://url")
ws.send("Hello, World")

But is it possible to send without using the above (donot wan't to create a connection)?

Source code: chat-app

Adarsh
  • 3,273
  • 3
  • 20
  • 44
  • a few things come to mind: 1) did you check in redis if an item is created? 2) if you (just for testing) use the InMemoryLayer does it work? – Matthaus Woolard Sep 27 '18 at 08:58
  • Yes i can see sorted sets created in redis for each chat room (groups) and using InMemoryLayer isn't supported officially in channels 2 as per this discussion: https://github.com/django/channels/issues/1039 – Adarsh Sep 27 '18 at 09:17
  • for local dev the InMemory is supported (it is after all what is used for unit Tests) but yes not supported for production. so just to be clear 1) you connect to your consumer with your WebSocket client 2) you hit the alarm endpoint 3) no message comes done your already open WebSocket connections? – Matthaus Woolard Sep 27 '18 at 10:27
  • Yes. Done is returned as HttpResponse from the view. The "content" however is not propagated to already open websocket connection. Have added the flow diagram for clarity – Adarsh Sep 27 '18 at 10:40
  • how is the HTTP alarm view linked up, is it running within the channels/daphne runtime? have you tried linking up the http view through the channels application? https://channels.readthedocs.io/en/latest/topics/routing.html#protocoltyperouter – Matthaus Woolard Sep 27 '18 at 10:44
  • view is linked up in the urls.py and I am running the command, python manage.py runserver to launch daphne and worker in seperate threads. (more info: https://github.com/django/channels/issues/142) – Adarsh Sep 27 '18 at 10:55
  • added a small project on github for your reference: https://github.com/adihat/django-channels-chat – Adarsh Sep 27 '18 at 13:46
  • nice, so does the frontend connect to the `ws/event/` consumer? – Matthaus Woolard Sep 28 '18 at 09:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/180928/discussion-between-adarsh-and-matthaus-woolard). – Adarsh Sep 28 '18 at 09:54

1 Answers1

9

credits to @Matthaus Woolard for making the concept pretty clear.

So this was the problem:

The client had disconnected when I tried to send the message from the django view. This happened as the server restarted upon code change. I refreshed the browser and it started to work.

Silly mistake

So to summarize:

Add the following in connect() in case of Synchronous consumer:

async_to_sync(self.channel_layer.group_add)('events', self.channel_name)

or add this incase of Async Consumer:

await self.channel_layer.group_add('events', self.channel_name)

create a view as follows:

def alarm(request):
   layer = get_channel_layer()
   async_to_sync(layer.group_send)('events', {
           'type': 'events.alarm',
           'content': 'triggered'
        })
   return HttpResponse('<p>Done</p>')
Adarsh
  • 3,273
  • 3
  • 20
  • 44