problem with channel_name outside from consumer

I need to setup a WebSocket server using Channels ==3.0.4 and Django ==3.2

First of all, I’m using the default channel “InMemoryChannelLayer”, I created a consumer class and I tested my connection by using a simple javascript file as a client and it worked well.
I can send and receive messages between my consumer and this javascript file.
Now, I want to send a message through this channel outside my consumer.
The function is outside the consumer

async_to_sync(channel_layer.send)(
“my_channel”,
{
“type”: “notify”,
…}

. I want to call the async_to_sync outside the consumer. But the problem is that this function does not invoke the method “notify” in the consumer.

it can not know the consumer that should be invoked.

It seems that the problem is with the channel_name .Can anyone here know what I 'm missing?
this is my consumer.py

from channels.generic.websocket import AsyncWebsocketConsumer
from channels.layers import InMemoryChannelLayer

channel_layer = InMemoryChannelLayer()
class Consumer(AsyncWebsocketConsumer):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    async def connect(self, *args, **kwargs):
        await super().accept()

    async def receive(self, text_data=None, bytes_data=None, **kwargs):
        message = json.loads(text_data)
        await self.send(json.dumps({'message': message}))

    async def disconnect(self, *args, **kwargs):
        await self.close(*args)

    # @channel_layer.receive
    async def notify(self, event):
        await self.send(text_data=json.dumps(event["text"]))
channel_name = "my_channel"

My function out side consumer.py

channel_layer = get_channel_layer()
async_to_sync(channel_layer.send)(
            "my_channel",
            {
                "type": "notify",
                "text": "Hello,Listen here world!"
            },
        )

and in my asgi.py:

application = ProtocolTypeRouter({
    'http': get_asgi_application(),
    "websocket": URLRouter(routing.websocket_urlpatterns),
    "channel": ChannelNameRouter({
        "my_channel": Consumer,
    })

thanks

There are actually two things to address here:

Change that now, switch to a different Channel layer. (I use Redis, it really is easy to use, unless you’re on Windows.)

Why?

This:

This implies to me that you’ve created a Channels Worker.

This means that you need to be using manage.py runworker to have this task run. And since this is an external process to your Django process, it does not have access to the same InMemoryChannelLayer instance as Django does. The two cannot communicate with each other.

Then for your other question:

If it doesn’t know who the consumer is, how does it know which person to send the notification to?

If the process knows the “person” but not the “channel name”, there are a couple different ways of handling this.

Probably the easiest way that I can think of is to have the consumer for the person connecting create a group for itself with a well-defined group-name pattern. (Yes, it is “valid” or “proper” to have a group with only one channel connected.) Then, your external process uses the information it has on the person to send the message to that group name.

1 Like

i’m using Lunix not Windows. Actually i’m beginner with django websocket. At this stage, I only want to test it and make sure that it works. That’s why i did’nt use Redis and also I don’t need to use users “person” .
In fact my main need is if i have a specific action somewhere in my code (like update object), i should send a data “payload” to my consumer by calling this function async_to_sync(channel_layer.send). I’m not comfortable with channel workers, I thought that it would help me to create a channel_name linked to my consumer.

Yes, but you want to make sure it “works” for real. That’s why even the Channels Tutorial shows using Redis as the channel layer. Don’t try to short-cut this.

Yes you do. You need some way to identify what person you want to send the notification to.

Ok, so it sounds like you’re talking about the situation documented at Using Outside of Consumers. That’s great - you can do that, but then you don’t need to use this:

That’s the purpose of Groups.

Channels itself does not “track” or “manage” individual channels for applications to interrogate. Either a consumer needs to let another consumer know what channel to use to send replies, or they both need to agree upon a common link. In the default case within Channels, Groups serve as that common link.

so you think using Redis that make my consumer receive data from outise by using async_to_sync(channel_layer.send) ?

That’s not the point here. That’s just an issue of getting things right for whatever you want to do with it.

The root issue here is that the sender of the message needs to know who the recipient is going to be. The function, channel_layer.send is going to send a message to a channel. It’s up to you to know how to determine what the destination channel is going to be.

Also keep in mind that send is going to send a message to the consumer, not the browser. It’s up to the consumer to know what to do with that message to forward it back out to a browser.

i forgot to tell you that i make print debug in consumer.py.

async def notify(self, event):
        print(event) <---- does not invoked
        await self.send(text_data=json.dumps(event["text"]))

so when i update an object and by calling async_to_sync(channel_layer.send)(
“my_channel”,
{
“type”: “notify”,
“text”: “Hello,Listen here world!”
},
)
the “channel_layer.send” was called well but i can not see any bebug logging in “notify” method because “channel_layer.send” does not invoke the correct consumer.

Correct. You don’t have things wired together correctly yet.

You need to make all the adjustments identified above.

You have not yet posted here any information about how you’re trying to link the consumer to the Django process trying to contact it.

i added the channel name to group in my consumer.py

class Consumer(AsyncWebsocketConsumer):
    async def connect(self, *args, **kwargs):
        self.group_name = 'my_group'
        self.channel_name = 'my_channel'
        await self.channel_layer.group_add(self.group_name, self.channel_name)
        await self.accept()

the function in other module outside the consumer like this:

channel_layer = get_channel_layer()
def websocket_send(data):
    payload = json.dumps(data)
    async_to_sync(channel_layer.group_send)('my_group', {'type': 'notify', "text": "Hello,Listen here world!"})
def sendmsg():
    websocket_send(data)

I also added a line debuging in the method group_send() exist in file layers.py provided by Library channels

async def group_send(self, group, message):
        print('SEND TO GROUP')
        print( self.groups) <--- here i added a print
        # Check types
        assert isinstance(message, dict), "Message is not a dict"
        assert self.valid_group_name(group), "Invalid group name"
        # # Run clean
        # self._clean_expired()
        # Send to each channel
        for channel in self.groups.get(group, set()): <-- does not enter here because self.groups.get(group, set()) is Emty !!!
            try:
                await self.send(channel, message)
            except ChannelFull:
                pass

async_to_sync(channel_layer.group_send) did not work .
and i got as log :
SEND TO GROUP
so the problem is the self.groups is empty so no channel to send through :slightly_frowning_face:
so how can i passed correctly the name of group ?

First, you will want to remove this line:

You do not set self.channel_name within a consumer. That is set and managed by Channels. Consider it a read-only value.

Do you have a notify function in your consumer? That’s the function that is going to be called by your external websocket_send function.

Also, you may want to rename that function. You’re not actually sending data to a websocket. You’re sending a message through Channels. It’s up to the receiver of that message to determine what to do with it.

Finally in your external module (outside the consumer), remove the sendmsg function. You do not send directly to a websocket.

Regarding your debugging output, you’ll want to confirm that a websocket connection is actually being made. You might want to add print statements in your connect function to see when that happens.

this is my notify function in consumer.py

async def notify(self, event):
        await self.send(text_data=json.dumps(event["text"]))

i added a print inside the connect() function it seems the connection not established when calling async_to_sync(channel_layer.group_send) outside the consumer.py
should i added some thing like this to established connection between external module and my consumer to send and recieve Data

 websocket = websockets.connect("ws://localhost:8000/realtime")

actually my main goal is to send data as real time through websocket to my client when some model changed .
Do you have any recommendations for best scenario or steps to achieve my goal ?

No, the connection to a consumer is made by the browser. You do not directly connect to a consumer from anywhere else. All data is passed through the channel layer.

Absolutely not

Your browser establishes a websocket connection with a consumer. The consumer creates a channel and connects to the channel_layer (redis).

Everything else communicates with that consumer through the channel_layer.

In my DjangoCon tutorial, I talked about this diagram - maybe it might help you understand the relationships between the components:

Architecture

This isn’t the only way to connect all these things together, but it’s what we do.