How to send signals to consumer in django? - django

I'm developing WebSocket programs in Django. I want to receive the request from the client(ReactJS) and send signals to WebSocket consumer, and then WebSocket consumer sends messages to the client.
How to solve such a problem?
I tried as following.
urls.py
urlpatterns = [
url(r'^updatesignal/$',updatesignal),
url(r'^stopsignal/$',stopsignal),
]
views.py
#api_view(['POST'])
def updatesignal(request):
print("update")
consumers.isconnected = 1
return Response("update signal")
#api_view(['POST'])
def stopsignal(request):
print("stop signal")
consumers.isconnected = 0
return Response("stop signal")
consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
import json
import asyncio
isconnected = 0
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
print("connect")
await self.accept()
while isconnected == 1:
await asyncio.sleep(2)
print("true")
# obj = # do_something (Ex: constantly query DB...)
await self.send(text_data=json.dumps({
'message': "aaa"
}))
async def disconnect(self, close_code):
# Leave room group
pass
async def receive(self, text_data):
# Receive message from WebSocket
.....
But WebSocket consumer is fired after 10 seconds after receiving update signal request. How to solve this problem?

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 async long lived events / streams

How would a continuous stream be implemented with websockets (preferably with django-channels)?
My code continuously sends a message. I would like to be able to start/stop it but at the moment doesn't respond on the serverside to the start/stop event (the print statement doesnt show up). It's like the whole consumer is blocking the thread even though it is async. I can't seem to figure out how to implement this.
import asyncio
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
pause = False
async def connect(self):
await self.accept()
await self._main_loop()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
text_data_json = json.loads(text_data)
print(text_data_json)
pause = text_data_json["pause"]
self.pause = bool(pause)
async def _main_loop(self):
while True:
if not self.pause:
await self.send(text_data=json.dumps({"message": "play"}))
else:
await self.send(text_data=json.dumps({"message": "pause"}))
await asyncio.sleep(1)

Send value from __init__.py to consumer.py websocket Django channels

I want to send data from the firebase stream(pyrebase) via django channels websocket. Web scoket is working fine, But I can't imagine how I can use consumer.py file send message function in init_.py.
Here my django channel web socket Consumer.py file.
import json
from random import randint
from asyncio import sleep
from channels.generic.websocket import AsyncWebsocketConsumer
from django.conf import settings
class WSConsumer(AsyncWebsocketConsumer):
group_name = settings.STREAM_SOCKET_GROUP_NAME
async def connect(self):
# Joining group
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave group
await self.channel_layer.group_discard(
self.group_name,
self.channel_name
)
async def receive(self, text_data):
print(text_data)
# Send data to group
await self.channel_layer.group_send(
self.group_name,
{
'type': 'system_load',
'data': text_data
}
)
async def system_load(self, event):
# Receive data from group
print('sending message to client')
await self.send(text_data=json.dumps(event['data']))
This file works fine.
This is my inti.py file. In this file, I want to send data to the WebSocket in this stream_handler function.
import pyrebase
from configFiles.config import config
firebase = pyrebase.initialize_app(config)
authe = firebase.auth()
database = firebase.database()
safe_temperature = 20
safe_humidity = 40
def stream_handler(message):
if "Humidity" in message["path"]:
if message["data"] > safe_humidity:
database.child("Controller").child("Light").set(1)
else:
database.child("Controller").child("Light").set(0)
if "Temperature" in message["path"]:
if message["data"] > safe_humidity:
database.child("Controller").child("Fan").set(1)
else:
database.child("Controller").child("Fan").set(0)
# Here place I want to send data which is message["data"] to the WebSocket
mystream = database.child("Sensor").stream(stream_handler)
Thank you!

Django channels live chat save sent messages

So I have a Django app in which I used channels to implement live chat. My consumer looks like this:
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
username = self.scope["user"]
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'user': username.username
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
user=event['user']
print(user)
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message,
'user':user
}))
So I am looking for a way to save the sent messages (because currently they are lost on refresh). I have created a Messages model that has a CharField for the message text. I think I can save new messages if I do this in my chat_message function:
new_message=Messages(text=message)
new_nessage.save()
My question is how do I preload the last 10 messages whenever a user gets connected to the chat?
So in order to do this, I ended up saving my message to the database in the "receive" function (because if you save it in the chat_message one it's saved 1 time for each active user). Then in order to preload messages I used an AJAX call every time a websocket is opened in order to get the 10 most recent messages using a python function and then I passed the messages back as a JsonResponse (https://simpleisbetterthancomplex.com/tutorial/2016/08/29/how-to-work-with-ajax-request-with-django.html scroll down to the AJAX requests part of this article and take a look at signup.html, urls.py and views.py files in this order for reference).

How to create task which sends data continuously and disconnect safely in consumers, django channels 2?

From this answer, which helps to send data from consumers for every n seconds.
Tried to handle the disconnection properly, using creat_task method, tried to stop while-loop(which is used to send data for every n seconds) by sending a flag=False(Assuming, this flag is not sent to the same instance which is created the task).
consumers.py:
class AutoUpdateConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print("connected", event)
await self.send({
"type": "websocket.accept"
})
await self.create_task(True)
async def websocket_receive(self, event):
print("receive", event)
async def websocket_disconnect(self, event):
await self.create_task(False)
print("disconnected", event)
async def create_task(self, flag=True):
while flag:
await asyncio.sleep(2)
df= pd.DataFrame(data=[random.sample(range(100), 4) for _ in range(5)])
await self.send({
'type': 'websocket.send',
'text': df.to_html(),
})
Warning:
2019-09-11 14:40:06,400 - WARNING - server - Application instance
<Task pending coro=<SessionMiddlewareInstance.__call__() running at
D:\Django\Django channels\django_channels_env\lib\site-packages\channels\sessions.py:175>
wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at
0x000001870E06C618>()] for connection <WebSocketProtocol client=
['127.0.0.1', 63789] path=b'/ws/home'> took too long to shut down and was
killed.
How to stop_task safely instead of waiting for channels to kill task?
Or
How to stop infinite while loop running in a method, from another method in same class?
Versions:
Django == 2.0.7
channels == 2.1.2
I would suggest creating a group when connecting to the consumer. That way you can trigger a message from anywhere in your django project as long as you know the group name (auto_update).
from channels.generic.websocket import AsyncWebsocketConsumer
class AutoUpdateConsumer(AsyncWebsocketConsumer):
async def connect(self):
print('connect')
# join the group
self.group_name = 'auto_update'
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, event):
print('disconnect')
# leave the group
await self.channel_layer.group_discard(
self.group_name,
self.channel_name
)
async def receive(self, event):
print('receive')
async def auto_update(self, event):
print('sending df')
df = event['df']
await self.send({
'text': df
})
To send the message I would use a custom management command. To stop the command I would create a singleton model (a model with only one instance) that has a boolean field that can be periodically checked to see if the loop should be stopped.
First use get_channel_layer() to get the active layer that communicates with redis, then in the loop call group_send to invoke a consumer method specified by the type key.
# /project/app/management/commands/auto_update.py
from django.core.management.base import BaseCommand
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from config.models import AutoUpdateSettings
class Command(BaseCommand):
help = 'Command to start auto updating'
def handle(self, *args, **kwargs):
settings = AutoUpdateSettings.objects.first()
settings.keep_running = True
settings.save()
group_name = 'auto_update'
channel_layer = get_channel_layer()
while True:
settings.refresh_from_db()
if not settings.keep_running:
break
df= pd.DataFrame(data=[random.sample(range(100), 4) for _ in range(5)])
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'auto_update', # this is the name of your consumer method
'df': df.to_html()
}
)
To start the loop that sends the message to the group you would call the command python manage.py auto_update. To stop the command you would use the admin page and set keep_running to false.