Django, socket.io, node.js - Manage private messages and group conversations - django

I am in the process of writing a back-end for a service such as Facebook Messenger or WhatsApp.
I started out following this splendid tutorial.
I do this with an API written in Python (Django).
Along side with this API, I have a Redis process and a node.js server running (localhost only). The node.js server uses the socket.io library for real-time communication through websockets
An HTTP-request containing a message can be sent from any client to the Django API, which in turn publishes the message to Redis on a certain channel.
The node.js server has subscribed to the Redis channel, and gets notified when such a message is published. Node keeps track of which sockets that are currently connected with an array of socket ids keyed with some user identifier.
I have a few questions about this:
1. Private messages
I would like to send a message targeted to a certain user. My initial approach is to have the HTTP-request (sent to Django) include which user that the message should reach. When this message reaches the node.js server (through redis), node can find that user in an array of clients. Django obviously(?) needs to know which socket.io socket belongs to which user. i.e. Django needs to know which user identifying key that node should use to grab the right socket.
Is this a good approach? Will the redis-server be a bottleneck since I only use one publishing channel? What happens if the target user of the message is offline when the source user sends the message? I would like to grab that event and send a push-notification.
2. Rooms
This service wouldn't be any good if there was not functionality for starting and maintaining group conversations. From what I have read, I should create socket.io:s rooms for this. My question is then, how do i maintain the room between sessions? What if every user participating in a group conversation goes offline and are thereby removed from the node.js server:s array of clients. Can I somehow store rooms between sessions in the Django server?.
Any help and/or feedback/thoughts is greatly appreciated.
Thanks

Private messages
Node keeps track of which sockets that are currently connected with an array of socket ids keyed with some user identifier.
You pretty much said it there. Django does not need to know the socket id of the target user, but node.js does. If you want to send a message to user 3 you would sent to message + target user (or any other meta data you want) to redis. Node.js will look up the user by reading the element with the id 3 from the socket array and send the message to this connection. If there is no element with id = 3, the user is offline and you can send your push notification. Eather in node.js or by notifying the django server.
Rooms
Since you want your rooms to be persistant you should store them in some kind of database like mysql or mongodb. The node.js server would create all the rooms and if a user connects you can look up in which rooms they participated using their user id. If they join a new room you have to update the database. If a new room is created, node.js needs to create the socket.io room and the database needs to be updated as well.

Related

Push Notifications to React Native App, based on data from 3rd party API, when app is closed

So I'm new to the whole server side of web and mobile apps. I've been doing a lot of research to try and understand how to fulfill my use case for a mobile app that I'm doing in React Native, and I've talked with folks over at firebase and AWS and still don't really have a clear path on what I should do.
My principal issue is this: I have an app that doesn't rely on anything outside the phone itself except for a few 3rd party api's. Repeat: I'm not using a server. These api's provide real-time location data for vehicles (buses). The data also provides info about how long it will be until that bus arrives at a location. I want to be able to send a notification to the user when the data says that a particular bus will be within a certain threshold of distance within a particular time frame (e.g. Send notification when Bus 1 will be at Stop 5 in 5 minutes between 10a.m. and 11a.m.). The user would be able to specify when and under what circumstances they would want to be notified and firebase would hold that information in a database.
Now, I know that Firebase provides functionality for scheduled push notifications. I want to do a notification based on a 3rd party api, and the notifications should be able to come in when the app is closed. How can I do this either with Firebase or AWS?
Side Note
I personally think that code would need to be running on a remote server constantly checking the firebase database to see if there is a user request for a notification. The code would then pull the data consistently (every 5 seconds or so) to see if the data fits the request. If it does, then it sends back to firebase saying there's a match, and firebase sends a notification to the user. If this is not how that should work, I would love to hear feedback because I'm not sure how to move forward at this point.

How to handle single websocket connection for all chats in Django Channels

I want to make a chat application like WhatsApp, and I want to make the backend server using Django Channels to handle all the real-time updates.
I have been exploring various sources but I could not figure out one thing about how do i manage single websocket connection (single endpoint) for each user and still receive messages from all the chats he is part of in real time. As per my current understanding, I can add channel(web socket connection corresponding to a user) to different channel groups but what if a user is part of a lot of groups(basically is eligible to receive updates from various chats)? Should I add that channel to all the groups, he can be part of as soon as the connection is established or is there any workaround like one in my mind:
Store the list of channels corresponding to each user in a database.
Make a for loop so that whenever a message is received by server, it sends message to websocket connections corresponding to each user involved to receive that message?
Any help is appreciated. Thanks in advance.
Yes, for a simple chat system, you should just add the user's channel name to the groups he's subscribed to.
However, you definitely will need to model the chat system in the database for a more complex system. Let's say you have a model Chat, ChatMember and Message. When a user connects to the websocket, he does not need to specify any chat because it is a general connection. Any message sent by the client has to specify the chat, so you can loop through the chat members and forward the message to all who are currently connected.
How do you know who is currently connected? this is the tricky part. In my architecture, I have a group for each user, sort of like an inbox. The group name is generated from the user id. Each user can have several connections, say mobile, web etc. All the connections coming from a user is added to the users group and the user's number of active connection is saved in an Inbox model. With new connections, it is incremented and decremented during disconnections.
So to know which chat members are currently online, I can just check that the user's inbox has atleast one connection. If he is online I forward the message to his ibox group, else i store the message in his inbox. Whenever a user connects, he is sent all the messages in his inbox and the inbox is cleared.
This is just an example of a way to implement it but you can also think up a custom architecture or improve on it.

Scalable Chat server in Clojure. Problems with presence and message arrival b/w reconnections

I am trying to build a scalable chat server in Clojure. I am using http-kit, compojure and redis pub/sub to communicate between diffrent nodes (fan-out approach). The server will use websockets for connection b/w client-server with a fallback to long polling. A single user can have multiple connections to chat with one connection per tabs in the browser and message should be delivered to the all the connections.
So basically when the user connects I store the channel in a atom with a random uuid as
{:userid1 [{:socketuuid "random uuid#1 for uerid1" :socket "channel#1 for userid1"}
{:socketuuid "random uuid#2" :socket "channel#2"}]
:userid2 [{:socketuuid "random uuid#1 for userid2" :socket "channel#1 for userid2}]}
the message is POSTed to a common route for both websockets and long polling channels, the message structure looks like
{:from "userid1" :to "userid2" :message "message content"}
the server finds all the channels in the atom for the :from and :to user ids and send the message to the connected channels for the respective users, also it publishes the message over the redis server where the connected nodes look for channels stored in their own atom and deliver message to the respective users.
So the problem I am running into is, how to properly implement presence. Basically http-kit send you a status when a channel disconnects the status can be "server-close" or "client-close", while I can handle server disconnects (the client will reconnect automatically) but I am running into problem when the disconnect happens from client side, for eg. the user navigates to another page and will connect after a few seconds. How do I decide that the user has went offline when the client disconnects. Also I am concerned about message arrival b/w reconnects in long polling mode (my long polling timeout is 30 seconds).
Also please suggest a good presence mechanism for the above architecture. Thanks.
Please comment if you need more info. Thanks
Edit #1:
Can you recommend a good tutorial/ material on implementing presence in a chat server, I cant seem to find anything on it.
My current solution -> I am currently maintaining a global count and a last connected timestamp for the connected channels of a particular userid and when a user disconnects the count is decreased, and a timeout is implemented for 10 seconds which will check if the user has reconnected again (i.e. the last connected stamp is 10 seconds old and count is still zero), if not then the user is said to have gone offline, would you recommend this solution, If not why, or any improvements or better methods are appreciated.
Also please note I am using the timer/scheduled-task in http-kit, would these timeout significant performance effects?
There are two different cases here from client side.
Long Polling. I cannot see how this is a problem, if the client window closes, there wont be no polling anymore. One client less which asks for data.
Websockets. There is a close method available in the protocol. The client should send a notification if you implement it correctly. See here: Closing WebSocket correctly (HTML5, Javascript) for instance.
Does that answer your question?

django chat application using redis - how/where do I "listen" to messages after subscribing?

So I am trying to write a chat system using django (I am relatively new to real time system).
I did some research - there are lots of options (twisted, tornado etc) but for now I decided to to try and use nginx as the web server and redis's pubsub.
The chat would be between two users at a time.
Following is what I was thinking of:
On authentication all users issue a psubscribe chatctrl:*:. This essentially subscribes to a control channel to establish the initial conversation that is always needed
When a user u1 launches a chat with user u2, we
create a channel, say "chat:u1:u2" and subscribe to it.
The user u1 publishes a message to the control channel chatctrl:u1:u2: (a control message that would be listened to by u2) says effectively "do you want to chat with me on channel "chat:u1:u2"?
The user u2 should get this message, subscribes to the channel and responds as yes via another message on control channel (or on the newly established channel.
A session is established and both users can publish to the same channel and listen to it as well.
My question is:
1. Does the above make sense, first of all? If not how would you do it using redis?
2. The second question is where do I put the loop to listen to the messages. Since it would be "blocking" when there are no messages, it can not go in a view or in a model accessed by a view. Should it be in a spawned thread and if so how do I unsubscribe once the chat session is over?
Thanx!
See my answer here for an example of the system you describe.
In that code, the view spawns a Gevent greenlet that subscribes to Redis and pushes messages out to the client browser over socket.io.
The view then blocks until a message is recieved over socket.io, repeating for the duration of the chat session.
Hope that helps!

Distributing an application server

I have an application server. At a high level, this application server has users and groups. Users are part of one or more groups, and the server keeps all users aware of the state of their groups and other users in their groups. There are three major functions:
Updating and broadcasting meta-data relating to users and their groups; for example, a user logs in and the server updates this user's status and broadcasts it to all online users in this user's groups.
Acting as a proxy between two or more users; the client takes advantage of peer-to-peer transfer, but in the case that two users are unable to directly connect to each other, the server will act as a proxy between them.
Storing data for offline users; if a client needs to send some data to a user who isn't online, the server will store that data for a period of time and then send it when the user next comes online.
I'm trying to modify this application to allow it to be distributed across multiple servers, not necessarily all on the same local network. However, I have a requirement that backwards compatibility with old clients cannot be broken; essentially, the distribution needs to be transparent to the client.
The biggest problem I'm having is handling the case of a user connected to Server A making an update that needs to be broadcast to a user on Server B.
By extension, an even bigger problem is when a user on Server A needs the server to act as a proxy between them and a user on Server B.
My initial idea was to try to assign each user a preferred server, using some algorithm that takes which users they need to communicate with into account. This could reduce the number of users who may need to communicate with users on other servers.
However, this only minimizes how often users on different servers will need to communicate. I still have the problem of achieving the communication between users on different servers.
The only solution I could come up with for this is having the servers connect to each other, when they need to deal with a user connected to a different server.
For example, if I'm connected to Server A and I need a proxy with another user connected to Server B, I would ask Server A for a proxy connection to this user. Server A would see that the other user is connected to Server B, so it would make a 'relay' connection to Server B. This connection would just forward my requests to Server B and the responses to me.
The problem with this is that it would increase bandwidth usage, which is already extremely high. Unfortunately, I don't see any other solution.
Are there any well known or better solutions to this problem? It doesn't seem like it's very common for a distributed system to have the requirement of communication between users on different servers.
I don't know how much flexibility you have in modifying the existing server. The way I did this a long time ago was to have all the servers keep a TCP connection open to each other. I used a UDP broadcast which told the other servers about each other and allowed them to connect to new servers and remove servers that stopped sending the broadcast.
Then everytime a user connects to a server that server Unicasts a TCP message to all the servers it is connected to, and all the servers keeps a list of users and what server they are on.
Then as you suggest if you get a message from one user to another user on another server you have to relay that to the other server. The servers really need to be on the same LAN for this to work well.
You can run the server to server communications in a thread, and actually simulate the user being on the same server.
However maintaining the user lists and sending messages is prone to race conditions (like a user drops off while you are relaying the message from one server to another etc).
Maintaining the server code was a nightmare and this is really not the most efficient way to implement scalable servers. But if you have to use the legacy server code base then you really do not have too many options.
If you can look into using a language that supports remote processes and nodes like Erlang.
An alternative might be to use a message queue system like RabbitMQ or ActiveMQ, and have the servers talk to each other through that. Those system are designed to be scalable, and usually work off a Publish/Subscribe mechanism.