I am trying to show visitor counter on realtime basis with django. like how many visitor are online at my webiste.
I written a websocket consumer but it alwasy give me 0 even if i open the site in multiple browser.
this is my django channel consume:
class VisitorConsumer(WebsocketConsumer):
user_count = 0
def connect(self):
self.room_name = 'visitors'
self.room_group_name = 'counter_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'send_visitor_count',
'count': self.user_count
}
)
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 room group
def send_visitor_count(self, event):
count = event['count']
# Send message to WebSocket
self.send(text_data=json.dumps({
'count': count
}))
adn this is the routing:
websocket_urlpatterns = [
re_path(r'ws/visitors/$', consumers.VisitorConsumer),
]
I am not getting why it's always firing 0.
Can anyone help to fix this?
I don't see where you are incrementing the user_count but even that may not work if you incremented it because the different instances of the consumer running in different workers won't have access to the same user_count variable. So you should store it in a cache like Redis or DB and don't forget to actually increment it
Related
I want to connect my Django WebSocket with a third-party web socket. This program is one I wrote, and it functions properly. To avoid having to re-login with the third-party API, I have now added the code to check whether the same room is present in my database. if we use the same API KEY to re-connect to the third-party API. It gives the following error:
{"event":"login","status":401,"message":"Connected from another location"}
I want to see if the same cryptocurrency coin is already connected or not. We don't want to logon with the same API KEY once we're connected. I have two issues here:
Don't send the login request to that web socket again.
Don't send the subscribe request, if the same coin already exists. Let's say BTCUSD already connected and giving me the data. I want to just connect to the next user to same room and get the data on next request.
import websocket
import time
import ssl
from channels.generic.websocket import AsyncWebsocketConsumer
from .models import Room
login = {
"event": "login",
"data": {
"apiKey": "API_KEY",
},
}
class CryptoConsumer(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
self.ws.connect("wss://crypto.financialmodelingprep.com")
async def connect(self):
self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
self.room_group_name = "crypto_%s" % self.room_name
# ****** Code Block ******
subscribe = {
"event": "subscribe",
"data": {
"ticker": self.room_name,
},
}
room = await Room.add(self.room_name) # Method in models.py to add the user and return True
if room is False:
self.ws.send(json.dumps(login))
print("The group with the name %s doesn't exist" % self.room_group_name)
time.sleep(1)
self.ws.send(json.dumps(subscribe))
# ****** End Code Block ******
# 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):
unsubscribe = {
"event": "unsubscribe",
"data": {
"ticker": self.room_name,
},
}
self.ws.send(json.dumps(unsubscribe))
self.ws.close()
# 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': 'Dummy Text'}"):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
message = str(self.ws.recv())
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name, {"type": "chat_message", "message": message}
)
# Receive message from room group
async def chat_message(self, event):
message = event["message"]
# Send message to WebSocket
await self.send(text_data=json.dumps({"message": message}))
Note: Why I want to do this entire step because we don't want the API KEY made public.
So, the code coming from the front end will connect to our Django web socket, and then we'll connect to the third-party web socket and return the data that was sent by them.
So the question is basically how would I limit the number of users to say two. I'm building a app and I basically want two users max in a game room. So at the moment on connect channel is added to group but is there a way to check if group has reached 2 users then reject web socket connection? I'm pretty much a beginner and new to channels, the documentation doesn't seem to help.
Also is there a way to list total groups users? So I can display the group name and users in a lobby.
I have not yet tried to implement this as I'm not really sure how. as you can see below I'm following a tutorial to understand and when connecting the channel is added to the group. however I would like the group to be limited to two people. Or a way to limit communication to only two clients. I added a if statement and was thinking maybe I should some how check the channel_layer object but I'm not sure how if that's the right approach.
from channels.generic.websocket import AsyncWebsocketConsumer
import json
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
class ChatRoomConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
if "some logic":
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'tester_message',
'tester': 'hello world' ,
}
)
await self.accept()
**
I am using Django Channels and RabbitMQ pika, for the first time. I am trying to consume from RabbitMQ queue. I am using Django Channels AsyncConsumer to group send it to everyone connected in the websocket.
User type 1 : Can create a task
User type 2 : Can accept the task.
Use case : When user type 1 creates the task it is published in the rabbitmq. When it is consumed from the queue, it has to be group-sent to frontend. And when the user type 2 accepts the task other instances of user type 2 cannot accept the same and we consume from the queue again and send the next task in the queue to everyone.
I have created the connection in a different thread using sync_to_async I am appending it to an in-memory list from the callback function.
And whenever someone accepts I just pop it out of the list and acknowledge the queue.
class AcceptTaskConsumer(AsyncConsumer):
body = [] #IN MEMORY LIST
delivery = {} #To store ack delivery_tag
async def websocket_connect(self, event):
print("AcceptTaskConsumer connected", event)
AcceptTaskConsumer.get_task() #STARTS Queue listener in new thread
self.room_group_name = "user_type_2"
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.send({
"type": "websocket.accept"
})
async def websocket_receive(self, event):
if event["text"] == "Hi": #If connecting first time
if AcceptTaskConsumer.body:
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "message",
"text": AcceptTaskConsumer.body[0]["body"]
}
)
else:
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "message",
"text": "No New Tasks"
}
)
else: #When someone accepts a task-> ack and send next task in queue
print(json.loads(event["text"])["id"])
AcceptTaskConsumer.channel.basic_ack(delivery_tag=AcceptTaskConsumer.delivery[json.loads(event["text"])["id"]])
AcceptTaskConsumer.delivery.pop(json.loads(event["text"])["id"])
AcceptTaskConsumer.body.pop(0)
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "message",
"text": "No New Tasks"
}
)
if AcceptTaskConsumer.body:
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "message",
"text": AcceptTaskConsumer.body[0]["body"]
}
)
async def message(self, event):
await self.send({
"type": "websocket.send",
"text": event["text"]
})
#classmethod
#sync_to_async
def get_task(cls): #pika consumer
cls.connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
cls.channel = cls.connection.channel()
cls.channel.queue_declare(queue='task_', arguments={"x-max-priority": 3})
cls.channel.basic_consume(
queue='task_', on_message_callback=AcceptTaskConsumer.callback, auto_ack=False)
cls.channel.start_consuming()
#classmethod
def callback(cls, ch, method, properties, body):
task_obj = {"body": json.dumps(body.decode("utf-8")),
"delivery_tag": method.delivery_tag}
AcceptTaskConsumer.body.append(task_obj)
AcceptTaskConsumer.delivery[json.loads(json.loads(task_obj["body"]))["id"]] = method.delivery_tag
cls.channel.stop_consuming()
async def websocket_disconnect(self, event):
print(event)
await self.send({
"type": "websocket.close"
})
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
I am pretty sure this is not the right way to do it, because it's not working as expected
I run into frequent errors like.
39 of 169 channels over capacity in group delivery
pika.exceptions.StreamLostError: Stream connection lost: BrokenPipeError(32, 'Broken pipe')
I tried running the queue listener like this answer as well. Nothing working.
Any one experienced has any thoughts about this? Is there a better way to approach this problem.?
you should move the rabitMQ cosumering logic out of the websocket consumer.
Just have a django command that runs the Rabbit Consumer, that consumer can take messages from RabbitMQ and then use send_group to send them over groups to channels.
if your django command you will need to call send_group see https://channels.readthedocs.io/en/latest/topics/channel_layers.html#using-outside-of-consumers
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
async_to_sync(
channel_layer.group_send
)(
"user_type_2",
{"type": "message", "msg": 123}
)
Then in the websocket consumer you should subscribe to the groups that the user wants/has permition to get.
Django Channels docs has following basic example of a Server Sent Events. AsyncHttpConsumer
from datetime import datetime
from channels.generic.http import AsyncHttpConsumer
class ServerSentEventsConsumer(AsyncHttpConsumer):
async def handle(self, body):
await self.send_headers(headers=[
(b"Cache-Control", b"no-cache"),
(b"Content-Type", b"text/event-stream"),
(b"Transfer-Encoding", b"chunked"),
])
while True:
payload = "data: %s\n\n" % datetime.now().isoformat()
await self.send_body(payload.encode("utf-8"), more_body=True)
await asyncio.sleep(1)
I want to accept messages sent via channel_layer and send them as events.
I changed the handle method, so it subscribes the new channel to a group. And I'm planning to send messages to the channel layer via channel_layer.group_send
But I couldn't figure out how to get the messages sent to the group, within handle method. I tried awaiting for the channel_layer.receive, it doesn't seem to work.
class ServerSentEventsConsumer(AsyncHttpConsumer):
group_name = 'my_message_group'
async def myevent(self, event):
# according to the docs, this method will be called \
# when a group received a message with type 'myevent'
# I'm not sure how to get this event within `handle` method's while loop.
pass
async def handle(self, body):
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.send_headers(headers=[
(b"Cache-Control", b"no-cache"),
(b"Content-Type", b"text/event-stream"),
(b"Transfer-Encoding", b"chunked"),
])
while True:
payload = "data: %s\n\n" % datetime.now().isoformat()
result = await self.channel_receive()
payload = "data: %s\n\n" % 'received'
I'm sending the messages to channel_layer like below: ( from a management command)
def send_event(event_data):
group_name = 'my_message_group'
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'myevent',
'data': [event_data]
}
)
I had the same issue and I even went to dig into Django Channels code but without success.
... Until I found this answer in this (still opened) issue: https://github.com/django/channels/issues/1302#issuecomment-508896846
That should solve your issue.
In your case the code would be (or something quite similar):
class ServerSentEventsConsumer(AsyncHttpConsumer):
group_name = 'my_message_group'
async def http_request(self, message):
if "body" in message:
self.body.append(message["body"])
if not message.get("more_body"):
await self.handle(b"".join(self.body))
async def myevent(self, event):
# according to the docs, this method will be called \
# when a group received a message with type 'myevent'
# I'm not sure how to get this event within `handle` method's while loop.
pass
async def handle(self, body):
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.send_headers(headers=[
(b"Cache-Control", b"no-cache"),
(b"Content-Type", b"text/event-stream"),
(b"Transfer-Encoding", b"chunked"),
])
I have a question for you please, I have an app with django channels:
THE FIRST QUESTION
the idea is to get all transaction in real time, but the transaction is growing in each moment, example the app is beginning to 10 transactions and before is 100 transactions and more, the app realtime is a little slow, how to do the app to get transactions? maybe one by one transaction if I decided to do one by one, how to implement this function:
my function to get all transaction is:
class TransactionConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'transaction_%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)
message = text_data_json['message']
# Send the message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'list_transaction',
'message': message
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
async def list_transaction(self, event):
token = event['message']
message = self.list_trans_all(token)
await self.send(text_data=json.dumps({
'massage': message
}))
# HERE RETURN ALL TRANSACTIONS BY JWT TOKEN USER LOGGED
def list_trans_all(self, token):
end_point = 'http://127.0.0.1:8000/api/v1/transactions/list-trans-users/'
headers = {'Authorization': 'Bearer ' + token}
listrans = requests.get(end_point, headers=headers)
return listrans.json()
and my client is:
<script>
var typeTransaction = 'buy';
var transaction_Socket = new WebSocket(
'ws://127.0.0.1:8001/ws/transaction/' + typeTransaction + '/');
export default {
name: 'orders',
data(){
return {
// transaction_datas: '',
}
},
mounted(){
// console.log(transaction_Socket);
// recive msg
transaction_Socket.onmessage = function(e) {
var transaction_datas = JSON.parse(e.data);
console.log(transaction_datas);
};
},
methods:{
getTransaction(){
const token_auth = window.localStorage.token_auth;
// console.log(array_msg.msg);
transaction_Socket.send(JSON.stringify({
'message': token_auth
}));
}
}
};
the code above is working well, but my problem is a load of the transaction is to begging a little slow, every time the data is an increment
THE SECOND QUESTIONS
the code above to get all transaction and give it to all users, my question is how to give each transaction for one user like this example:
|user = 2
buy = 23
product = tv
agent_id = 5
|user = 3
buy = 4
product = fp
agent_id = 7
|agent = 7
sell = 4
product = somnting
and each user and agent has to dashboard, when I click in button in the real-time the code above, the transaction gives it all users, I have a SQL with a filter by agent_id, how to gives transactions for one agent the user and agent has different JWT tokens like this
|agent = 7 // DASHBOARD AGENT ID = 7
user = 3
buy = 4
product = fp
agent_id = 7
|agent = 5 // DASHBOARD AGENT ID = 5
user = 2
buy = 34
product = somenting
agent_id = 2
I guess you understand me, thank you attention
please help me.