Django channels async long lived events / streams - django

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)

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 !!!

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!

How to send signals to consumer in 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?

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.

Saving data Django Channels 2

I am trying to save the data which i am receiving from my client using the Django Channels.
I have read the documentation but its not very clear.
Here is my code of consumer.py
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
# Send message to WebSocket
message2 = message[1]
self.save_data(message2)
self.send(text_data=json.dumps({
'message': message2
}))
#database_sync_to_async
def save_data (self, message):
return DeviceLogs.objects.create(voltage=message)
As you may have noticed that i just want to save message2 in database.
Not sure what's the problem in your code but this should work for you.
async def chat_message(self, event):
...
message2 = message[1]
await self.save_message(message2)
...
#database_sync_to_async
def save_message(self, message):
... save message here
Looks like your consumer extends WebsocketConsumer (sync consumer). If that is the case, remove the #database_sync_to_async decorator and it should be fine. You only need to do that if your consumer is async.
From the docs:
The Django ORM is a synchronous piece of code, and so if you want to access it from asynchronous code you need to do special handling to make sure its connections are closed properly.
If you’re using SyncConsumer, or anything based on it - like JsonWebsocketConsumer - you don’t need to do anything special, as all your code is already run in a synchronous mode and Channels will do the cleanup for you as part of the SyncConsumer code.
If you are writing asynchronous code, however, you will need to call database methods in a safe, synchronous context, using database_sync_to_async.