channels without channel layer or any other free hosting - django

I have a project in django 2.0 nad django-channlels 2.0 which I need to host I followed the documentation and I was able to run channels on localhost along with redis
but when I hosted on pythonanywhere,it showed it doesnot support websocket, so then I hosted on heroku,but there they were asking for verification of credit card info which i dont have to run redis.Are there additional hosting website whre I can rrun redis erver for free
Or is it poosible to implement channels without channel_layer and redis.My code is working perfectly fine on local host but can't host online for free.
class PageConsumer(WebsocketConsumer):
def connect(self, **kwargs):
self.accept()
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)("admin", {"type": "analytics.admin_message", "message": "plus"})
def disconnect(self, close_code):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)("admin", {"type": "analytics.admin_message", "message": "minus"})
its corresponidng receiver
class ChatConsumer(WebsocketConsumer):
def connect(self, **kwargs):
self.accept()
async_to_sync(self.channel_layer.group_add)("admin", self.channel_name)
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)("admin", self.channel_name)
def analytics_admin_message(self, something):
if something["message"] == "plus":
self.send(text_data=json.dumps({
'message': "plus"
}))
else:
self.send(text_data=json.dumps({
'message': "minus"
}))
def receive(self, text_data):
print("data hai bhyi", text_data)
text_data_json = json.loads(text_data)
message = text_data_json['message']
self.send(text_data=json.dumps({
'message': message
}))
settings.py
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}

from the docs
Channel layers are an entirely optional part of Channels as of version 2.0. If you don’t want to use them, just leave CHANNEL_LAYERS unset, or set it to the empty dict {}.
It will mean you will be unable to use self.channel_layer in the consumer, which you rely on.
So, it's optional but you need it.
In memory exists:
CHANNEL_LAYERS={
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}

Related

Django Websocket getting slow as the connected users increases

I am using Django Channels to send messages to the frontend. and the frequency of the messages is one message per second, everything works fine when I am working on local but on production where I used Nginx, Gunicorn and Daphne to host the server, the connection to the websocket happened after 2-3 attempts, and after the webscokets get connected the messages started coming really slow. Is this issue is related to the server or with the code ?
consumer.py
class MyConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.room_group_name = 'Live Message'
self.error_code = 4011
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, code):
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
await self.close(self.error_code)
async def send_live_data(self, event):
data = event.get('value')
additional_msg = get_additional_msg()
data['additional_msg'] = json.dumps(additional_msg, default=default)
try:
await self.send_json(data)
except Exception as e:
await self.disconnect({'code': self.error_code})
await self.close(self.error_code)
message.py
channel_layer = get_channel_layer()
async def send_data():
try:
await (channel_layer.group_send)('Live Message', {
'type': 'send_live_data',
'value': {'live_message':json.dumps(message)}})
except:
print("Error while sending message ...\n", traceback.format_exc())
class MyWorker(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
print('Worker is running .....')
while True:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(send_data())
loop.close()
time.sleep(1)
MyWorker().start()
Thank you !!!

Django Channels: Send message from outside Consumer Class

I'm new in Python and Django,
Currently, I need to setup a WebSocket server using Channels.
I follow the code in this link: Send message using Django Channels from outside Consumer class
setting.py
ASGI_APPLICATION = 'myapp.asgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
Here is the code Consumer
import json
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
class ZzConsumer(WebsocketConsumer):
def connect(self):
self.room_group_name = 'test'
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, code):
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
print("DISCONNECED CODE: ",code)
def receive(self, text_data=None, bytes_data=None):
print(" MESSAGE RECEIVED")
data = json.loads(text_data)
message = data['message']
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
"type": 'chat_message',
"message": message
}
)
def chat_message(self, event):
print("EVENT TRIGERED")
# Receive message from room group
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'type': 'chat',
'message': message
}))
And outside the Consumer:
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'test',
{
'type': 'chat_message',
'message': "event_trigered_from_views"
}
)
The expected logics is I can received the data from the group_send in the receive on the Consumer class. So that I can send message to client.
However, It's not.
Can anyone here know what's I missing?
Any help is very appreciated.
Thanks!
Updated:
routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/socket-server/', consumers.ZzConsumer.as_asgi())
]
I think you're missing type argument in chat_message method. It should match the type in group_send. I.e.:
def chat_message(self, event, type='chat_message'):
print("EVENT TRIGERED")
Matches:
async_to_sync(channel_layer.group_send)(
'test',
{
'type': 'chat_message',
'message': "event_trigered_from_views"
}
)

can't get the profile object firstname

I'm trying to get the profile firstname but it gives me this error
File "/home/marwan/Desktop/Gowd/venv/lib/python3.6/site-packages/django/utils/asyncio.py", line 24, in inner
raise SynchronousOnlyOperation(message)
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
WebSocket DISCONNECT /public_chat/1/ [127.0.0.1:53742]
INFO 2021-03-12 01:43:30,570 runserver 119412 140507688003328 WebSocket DISCONNECT /public_chat/1/ [127.0.0.1:53742]
I think that this error has something to do with trying to get the user profile in line 56
"profile": self.scope["user"].profile.firstname,
if I remove it will work fine
this is my consumers.py file
from channels.generic.websocket import AsyncJsonWebsocketConsumer
import json
from django.contrib.auth import get_user_model
from gowd_project.users.models import User, Profile
User = get_user_model()
# Example taken from:
# https://github.com/andrewgodwin/channels-examples/blob/master/multichat/chat/consumers.py
class PublicChatConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
"""
Called when the websocket is handshaking as part of initial connection.
"""
print("PublicChatConsumer: connect: " + str(self.scope["user"]))
# let everyone connect. But limit read/write to authenticated users
await self.accept()
# Add them to the group so they get room messages
await self.channel_layer.group_add(
"public_chatroom_1",
self.channel_name,
)
async def disconnect(self, code):
"""
Called when the WebSocket closes for any reason.
"""
# leave the room
print("PublicChatConsumer: disconnect")
pass
async def receive_json(self, content):
"""
Called when we get a text frame. Channels will JSON-decode the payload
for us and pass it as the first argument.
"""
# Messages will have a "command" key we can switch on
command = content.get("command", None)
print("PublicChatConsumer: receive_json: " + str(command))
print("PublicChatConsumer: receive_json: message: " + str(content["message"]))
if command == "send":
if len(content["message"].lstrip()) == 0:
raise Exception("You can't send an empty message.")
await self.send_message(content["message"])
async def send_message(self, message):
await self.channel_layer.group_send(
"public_chatroom_1",
{
"type": "chat.message",
"user_id": self.scope["user"].id,
"user_name": self.scope["user"].name,
"profile": self.scope["user"].profile.firstname,
"message": message,
}
)
async def chat_message(self, event):
"""
Called when someone has messaged our chat.
"""
# Send a message down to the client
print("PublicChatConsumer: chat_message from user #" + str(event["user_id"]))
await self.send_json(
{
"user_id": event["user_id"],
"user_name": event["user_name"],
"message": event["message"],
},
)
sync_to_async takes a callable, not the result. so, I need to do this:
#sync_to_async
def get_user_profile(self, user):
return user.profile
async def send_message(self, message):
profile = await PublicChatConsumer.get_user_profile(self.scope["user"])
profile_name = profile.firstname
await self.channel_layer.group_send(
"public_chatroom_1",
{
"type": "chat.message",
"user_id": self.scope["user"].id,
"username": profile_name,
"message": message,
}
)
full documentation
Similar problem

Django Channels: Data Going to the Wrong Socket

I am facing the same issue although the as_asgi method is called in my routing. The data are always sent to the second socket.
Channels V3.0 and Django V3.1.2 is in use.
routing.py
websocket_urlpatterns = [
re_path(WS_PREFIX + r'room/(?P<room_name>\w+)/$', RoomConsumer().as_asgi()),
]
asgi.py
import routing
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
"http": get_asgi_application(),
# Just HTTP for now. (We can add other protocols later.)
"websocket": AuthMiddlewareStack(
URLRouter(
routing.websocket_urlpatterns
)
),
})
Local server logs:
WebSocket HANDSHAKING /ws/room/2/ [127.0.0.1:63280]
WebSocket CONNECT /ws/room/2/ [127.0.0.1:63280]
WebSocket HANDSHAKING /ws/room/1/ [127.0.0.1:63288]
WebSocket CONNECT /ws/room/1/ [127.0.0.1:63288]
consumer.py:
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class RoomConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'room_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'send_data',
'message': "Message received"
}
)
# Receive message from room group
async def send_data(self, event):
# Send message to WebSocket
await self.send(text_data=json.dumps(event))
This is how I send data:
result['type'] = 'send_data'
result['test'] = 'test'
layer = get_channel_layer()
async_to_sync(layer.group_send)('room_2', result)
return HttpResponse(status=202)
Is there anything else I need to consider?
I just realized that there is a newer version of channels available. I updated to channels V3.0.2 and it is working correctly now.

Send message on model save to anyone at WebSocket URL using Channels 2.0

I'm trying to send a message to all users with an open websocket connection at a specific URL each time a model is saved. I'm using the Channels community project knocker as a reference but in doing so I have to modify it to work with Channels 2.0.
Using signals fired on a model's post_save knocker sends a notification to the Group.
In Channels 2.0, Groups are handled differently so this line Group('myGroup').send({'text': json.dumps(knock)}) in the send_knock method isn't working. Is it possible to modify this line to work with the consumer below?
class WeightConsumer(WebsocketConsumer):
def connect(self):
self.group_name = 'weight'
# Join group
async_to_sync(self.channel_layer.group_add)(
self.group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave group
async_to_sync(self.channel_layer.group_discard)(
self.group_name,
self.channel_name
)
def receive(self, text_data):
pass
Just had to make use of get_channel_layer()
def send_knock(self, created=False):
"""
Send the knock in the associated channels Group
"""
channel_layer = get_channel_layer()
group_name = 'weight'
weight = '%s' % self.get_knocker_weight()
# Send message to group
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'weight_message',
'weight': weight
}
)
Then add another method to the consumer.
class WeightConsumer(WebsocketConsumer):
...
def receive(self, text_data):
pass
def weight_message(self, event):
weight = event['weight']
# Send message to websocket
self.send(text_data=json.dumps({
'weight': weight
}))