I am using channels in my project which made django to use websockets very easy to setup and use.
In my application every user that logs in opens a Group to which we can send information if there is any activity.
def ws_connect(message):
Group("%s" % message.user.id).add(message.reply_channel)
so whenever I want to send a message to that user, I use
Group('%s' % user.id).send(
{
'text': json.dumps({
'message': 'Some message'
})
}
)
But this fails silently if there is any error.
So question is there way to check if it failed or if there is any way we can check if the Group exists (live or listening ), even before I send data in order to handle it better.
Related
I would like to write a bot that will listen to the specific chat and save messages to the local storage.
After starting the bot app, everything works properly, but after some time I can see such information in the logs:
pyrogram.client {
'_': 'types.UpdatesTooLong'
}
Immediately after this log, my userbot stops hearing any message from any user.
How can I fix this and handle such case updatesTooLong ?
I want to send a message to a websocket client when it connects to the server on AWS lambda and API gateway. Currently, I use wscat as a client. Since the response 'connected' is not shown on the wscat console when I connect to the server, I added post_to_connection to send a message 'hello world' to the client. However, it raises GoneException.
An error occurred (GoneException) when calling the PostToConnection
operation
How can I solve this problem and send some message to wscat when connecting to the server?
My python code is below. I use Python 3.8.5.
import os
import boto3
import botocore
dynamodb = boto3.resource('dynamodb')
connections = dynamodb.Table(os.environ['TABLE_NAME'])
def lambda_handler(event, context):
domain_name = event.get('requestContext',{}).get('domainName')
stage = event.get('requestContext',{}).get('stage')
connection_id = event.get('requestContext',{}).get('connectionId')
result = connections.put_item(Item={ 'id': connection_id })
apigw_management = boto3.client('apigatewaymanagementapi',
endpoint_url=F"https://{domain_name}/{stage}")
ret = "hello world";
try:
_ = apigw_management.post_to_connection(ConnectionId=connection_id,
Data=ret)
except botocore.exceptions.ClientError as e:
print(e);
return { 'statusCode': 500,
'body': 'something went wrong' }
return { 'statusCode': 200,
"body": 'connected'};
Self-answer: you cannot post_to_connection to the connection itself in onconnect.
I have found that the GoneException can occur when the client that initiated the websocket has disconnected from the socket and its connectionId can no longer be found. Is there something causing the originating client to disconnect from the socket before it can receive your return message?
My use case is different but I am basically using a DB to check the state of a connection before replying to it, and not using the request context to do that. This error's appearance was reduced by writing connectionIds to DynamoDB on connect, and deleting them from the table upon disconnect events. Messaging now writes to connectionIds in the table instead of the id in the request context. Most messages go through but some errors are still emitted when the client leaves the socket but does not emit a proper disconnect event which leaves orphans in the table. The next step is to enforce item deletes when irregular disconnections occur. Involving a DB may be overkill for your situation, just sharing what helped me make progress on the GoneException error.
We need to post to connection after connecting (i.e. when the routeKey is not $connect)
routeKey = event.get('requestContext', {}).get('routeKey')
print(routeKey) # for debugging
if routeKey != '$connect': # if we have defined multiple route keys we can choose the right one here
apigw_management.post_to_connection(ConnectionId=connection_id, Data=ret)
#nemy's answer is totally true but it doesn't explain the reason. So, I just want to explain...
So, first of all What is GoneException or GoneError 410 ?
A 410 Gone error occurs when a user tries to access an asset which no longer exists on the requested server. In order for a request to return a 410 Gone status, the resource must also have no forwarding address and be considered to be gone permanently.
you can find more details about GoneException in this article.
In here, GoneException has occured; it means that the POST connection we are trying to make, doesn't exist - which fits perfectly in the scenario. Because we still haven't established the connection between Client and Server. The way APIGatewayWebsocketAPIs work is that you request an Endpoint(Route) and that Endpoint will invoke that Lambda Function (In our case it is ConnectionLambdaFunction for $connect Route).
Now, if The Lambda function resolves with statusCode: 200 then and only then the API Gateway will allow the connection to be established. So, basically untill we return statusCode: 200 from our Lambda Function we are not connected and untill then we are totally unknown to server and thats why the Post call that has been made before the return statement itself will throw an error.
I was wondering, if it is possible to connect (as an admin) with a user and to write messages? I already modified the message-table in the database, however, the message does only appear after reloading the whole website - which is not the optimal outcome. I appreciate every hint. Thank you.
You have to use a pub/sub service to get notification on the client side when a message is sent in server side
A good start is pusher.com service, in your server side code, you will run this example code whenever you send a message to your user:
pusher.trigger('my-channel', 'my-event', {
'message': 'hello world'
})
and in your client side, you add a code that listen to the above channel/topic and act accordingly
var channel = pusher.subscribe('my-channel');
channel.bind('my-event', function(data) {
alert('Received my-event with message: ' + data.message);
});
I am using Django 1.10 as the backend of a warehousing app we built.
I am adding another new feature where a webpage that will be displayed on a giant monitor. This webpage will show nothing except for 1 giant number.
I have a RFID device that once detects a RFID tag, will send a http request to my Django server.
When that happens, I want the number from the RFID tag to be displayed on the webpage mentioned earlier.
I have read briefly about socket.io but I want to as much as possible keep within the Django universe. I have read briefly about Django Channels as well.
My questions are:
should I use Django Channels for this use case?
if so, how do i do that with my use case above?
It really depends on the usage of the information you're displaying, if you don't need that number to be in real-time then you can opt for the regular AJAX poll once every X seconds like mentionned in zwer's comment.
Now if you need that number to be in real-time, then you should go for websockets and django channels, it's really easy to setup a code base that does what you want.
Assuming you installed django channels and configured your settings.
First you need to setup the consumers.py and routing.py logic that manages the websockets (think of those as views.py and urls.py but for websocket logic).
consumers.py
from channels import Group
from channels.auth import channel_session_user_from_http, channel_session_user
#channel_session_user_from_http
def ws_add(message):
# Authenticates the client
# ASGI WebSocket packet-received and send-packet message types
# both have a "text" key for their textual data.
message.reply_channel.send({
"accept": True,
})
Group("rfid-group").add(message.reply_channel)
#channel_session_user
def ws_message(message):
# You can process messages you receive from the socket here
# Apply w/e logic validation
#channel_session_user
def ws_disconnect(message):
Group("rfid-group").discard(message.reply_channel)
routing.py
from channels.routing import route
from .consumers import ws_message, ws_add, ws_disconnect
routing_routing = [
route("websocket.connect", ws_add),
route("websocket.receive", ws_message),
route("websocket.disconnect", ws_disconnect),
]
Now you need to write the front-end websocket logic:
<script>
socket = new WebSocket("ws://" + window.location.host);
socket.onmessage = function(e) {
console.log("Message received");
// Process the received number here
console.log(e.data);
}
</script>
This will establish a websocket connection, subscribes the client to a group called "rfid-group", now any message sent to that Group will be echoed to all subscribers of that Group, this can handle multiple clients.
Now we need the part that will listen to a request from the rfid device, process it, and send the result to the display, this should be a simple view as the RFID device will send regular HTTP information.
from django.http import HttpResponse
from channels import Group
def rfid_processor(request):
'''
Consider authenticating your rfid_num producer to prevent
someone messing with your api
'''
rfid_num = request.GET.get("rfid", "")
if rfid_num:
Group("rfid-group").send({"text": rfid_num})
return HttpResponse(status=200)
return HttpResponse(status=500)
Hook it to a url:
from app.views import rfid_processor
urlpatterns = [
url(r'^rfid/$', rfid_processor),
]
This is all you need to setup a minimal working django channels project that will echo the number received from the RFID device to the display screen(s).
Hope this helps.
Try reading on this website. https://realpython.com/blog/python/django-and-ajax-form-submissions/
I think this might help you. Also, HassenPy has a pretty good answer. Be sure to read that answer.
So I've got redis feature and tornado running on my server and whenever I open my websocket chat through a login, the terminal displays the following message
Error: Authentication missing
I'm not sure why this is happening because there are cookies in the authentication part of the app,
# Save user when authentication was successful.
def on_user_find(result, user=user):
##todo: We should check if email is given even though we can assume.
if result == "null" or not result:
# If user does not exist, create a new entry.
self.application.client.set("user:" + user["email"], tornado.escape.json_encode(user))
else:
# Update existing user.
# #todo: Should use $set to update only needed attributes?
dbuser = tornado.escape.json_decode(result)
dbuser.update(user)
user = dbuser
self.application.client.set("user:" + user["email"], tornado.escape.json_encode(user))
# Save user id in cookie.
self.set_secure_cookie("user", user["email"])
self.application.usernames[user["email"]] = user.get("name") or user["email"]
And in the websocket.py (where I run the script) I've made it so that the websocket handle checks if there are cookies available first before user access the app,
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
def open(self, user):
self.login = self.get_secure_cookie("user")
if not self.login:
# self.login = "anonymous"
print "Not authorized"
self.disconnect()
return
Yet it's still displaying the error, I've searched online and checked several SO answers but they don't show any solid solution in regards to this question. So far the most I've gotten is that I have to access the websocket header to put the above code inside, but I have no clue how I would do that. Help?