Emit events from flask_socketio server on set intervals - python

I have a React Client which needs to consume messages emitted by a flask_socketio server, it works fine if I emit a single message on 'connect' however if i keep emitting it in a while loop it doesn't get picked up on the client-side. I presume it could be due to action being performed in an active thread. I cannot find any posts online that talks about the issue.
Below is my React js client code.
class MyClient extends Component {
constructor() {
super();
this.state = {
endpoint:"http://127.0.0.1:5000",
response: ""
};
}
componentDidMount() {
console.log("test")
const {endpoint} = this.state;
//Very simply connect to the socket
const socket = socketIOClient(endpoint);
//Listen for data
socket.on("connect", dd => {
socket.on("log", dx => this.setState({response: dx.data}));
});
}
render() {
const {response} = this.state;
return (
<div>
<h1>Test {response}</h1>
{console.log(response)}>
</div>
)
}
}
The client looks for "log" to be emitted from the server, for now I added time to mock changing data.
When client loads i see the data, and when I disconnect and reconnect the server I get to see the new data on the client. Am wondering how do I emit the data continuously
from flask import Flask, render_template, request, jsonify
from flask_socketio import SocketIO, emit
from flask_cors import CORS
from datetime import datetime
import time
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
app = Flask(__name__)
app.config['SECRET_KEY'] = 'justasecretkeythatishouldputhere'
socketio = SocketIO(app,cors_allowed_origins="*")
CORS(app)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/api')
def api():
query = dict(request.args)
socketio.emit('log', dict(data=str(query)), broadcast=True)
return jsonify(dict(success=True, message='Received'))
#socketio.on('connect')
def on_connect():
time.sleep(1)
t = str(time.clock())
print(t)
socketio.emit('log', {'data': t, 'time': t})
if __name__ == '__main__':
socketio.run(app,port=5000)
Below loop does not work, should i run it as a background thread or is there an async property that i need to add to socketio. Any help is greatly appreciated.
#socketio.on('connect')
def on_connect():
while True:
time.sleep(1)
t = str(time.clock())
print(t)
socketio.emit('log', {'data': t, 'time': t})

In your client side you can make use of setInterval method to emit an event, after every few milliseconds.
On server side you can emit the event via which you can send the data.
So, all together, it will be something like below:
Client Side:
setInterval(function() {
socket.emit('time');
}, 1) // 1 millisecond is almost close to continues
socket.on('time', function(data) {
console.log(data)
});
Server Side:
#socket.on('time')
def time():
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
emit('time', current_time)
In the console log of your browser, you can see the continues data getting logged.
I hope this helps.
Note: Client side code, which I have provide here, is of pure JavaScript. Since, React is based on JavaScript, there will be some or the other way to use setInterval.

Related

How to broadcast a single message using Flask and angular

I'm trying to broadcast a message to all the users using flask and angular, I'm trying to achieve this using socketio but since I'm new to socketio, I'm unable to implement it as expected.
Basic idea is to pick a message from db table and broadcast to all users using via API call.
The server should show the new message without the website being refreshed by the user.
Python Code:
import socketio
from aiohttp import web
app = Flask(__name__)
sio = socketio.AsyncServer(async_mode='aiohttp', logger=True, engineio_logger=True)
sio.attach(app)
#sio.on('join_room', namespace='/maintenance')
def handle_message(sid, room):
sio.enter_room(sid, room=room, namespace='/maintenance')
#sio.on('getMessage', namespace='/maintenance')
async def handle_message(sid, maintenance_room):
await sio.emit('getMaintenanceResponse', 'json_object', namespace='/maintenance', room=maintenance_room)
#sio.on('addMessage', namespace='/maintenance')
async def add_message(sid, message_data, maintenance_room):
await sio.emit('getMaintenanceResponse', 'json_object', namespace='/maintenance', room=maintenance_room)
Angular Code:
this.socket.createMaintenanceRoom('maintenance');
this.socket.getMaintenance('maintenance').subscribe(response => {
if (response) {
this.maintenanceNotesData = response;
}
},
err => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification'));

Django channels - unable to subscribe to groups

I'm attempting to send consumers.py information to display on the client end outside of consumers.py.
I've referenced Send message using Django Channels from outside Consumer class this previous question, but the sub process .group_send or .group_add don't seem to exist, so I feel it's possible I'm missing something very easy.
Consumers.py
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
class WSConsumer(WebsocketConsumer):
def connect(self):
async_to_sync(self.channel_layer.group_add)("appDel", self.channel_name)
self.accept()
self.render()
appAlarm.py
def appFunc(csvUpload):
#csvUpload=pd.read_csv(request.FILES['filename'])
csvFile = pd.DataFrame(csvUpload)
colUsernames = csvFile.usernames
print(colUsernames)
channel_layer = get_channel_layer()
for user in colUsernames:
req = r.get('https://reqres.in/api/users/2')
print(req)
t = req.json()
data = t['data']['email']
print(user + " " + data)
message = user + " " + data
async_to_sync(channel_layer.group_send)(
'appDel',
{'type': 'render', 'message': message}
)
It's throwing this error:
async_to_sync(channel_layer.group_send)(
AttributeError: 'NoneType' object has no attribute 'group_send'
and will throw the same error for group_add when stripping it back more to figure out what's going on, but per the documentation HERE I feel like this should be working.
To anyone looking at this in the future, I was not able to use redis or even memurai in Windows OS due to cost. I ended up using server side events (SSE), specifically django-eventstream, and so far it's worked great as I didn't need the client to interact with the server, for a chat application this would not work.
Eventstream creates an endpoint at /events/ the client can connect to and receive a streaming http response.
Sending data from externalFunc.py:
send_event('test', 'message', {'text': 'Hello World'})
Event listener in HTML page:
var es = new ReconnectingEventSource('/events/');
es.addEventListener('message', function (e) {
console.log(e.data);
var source = new EventSource("/events/")
var para = document.createElement("P");
const obj = JSON.parse(event.data)
para.innerText = obj.text;
document.body.appendChild(para)
}, false);
es.addEventListener('stream-reset', function (e) {
}, false);

Flask Socketio not updating data

I am working with Flask Socketio. My code right now console logs every time a user opens a page. However when I open the page in a new tab/window the console of the original user in not updated. Below is the code, have a look at it
let socket = io.connect("http://127.0.0.1:5000/")
socket.on("connect",() => {
socket.emit("my custom event",{text:"I have joined"})
})
socket.on("my response",function(msg) {
console.log(msg)
})
And here is the python code for flask
from flask import Flask, render_template, request
import requests
from flask_socketio import SocketIO, emit, send
app = Flask(__name__)
app.config["SECRET_KEY"] = "hope"
socketio = SocketIO(app)
#app.route('/')
def hello_world():
return render_template("index.html")
#app.route('/1')
def random_route():
return render_template("index2.html")
#socketio.on('message')
def message(data):
print(data)
#socketio.on('my custom event')
def handle_custom_event(data):
emit("my response", data)
if __name__ == "__main__":
socketio.run(app, debug=True)
The default for the emit function is to address the event only to the sender. If you want to address all your connected clients, you have to indicate so with the broadcast option:
#socketio.on('my custom event')
def handle_custom_event(data):
emit("my response", data, broadcast=True)

How to send messages to all clients with Flask-SocketIO?

I'm trying to learn Flask-SocketIO, Now i'm trying to do simple chat, but no one receives messages except the user in browser. Python/Flask code:
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = '?-.'
socketio = SocketIO(app)
#socketio.on('joined')
def handle_message(who):
emit('back', who)
#app.route("/")
def main_page():
return render_template("index.html")
if __name__ == '__main__':
socketio.run(app)
and Javascript:
var socket = io.connect('http://' + document.domain + ':' + location.port);
$('#mybut').on("click", function(){
socket.emit('joined', {who:'someone'});
});
socket.on('back', function(data) {
console.log(data['who'] + ' joined.')
});
You need to set the broadcast parameter in this case. Check the Flask-SocketIO docs on broadcasting.
I.e. change your handler to the following:
#socketio.on('joined')
def handle_message(who):
emit('back', who, broadcast=True)

Django/gevent socket.IO with redis pubsub. Where do I put things?

I have an isolated python script that simply captures data from Twitter's streaming API and then on the receipt of each message, using redis pubsub it publishes to the channel "tweets". Here is that script:
def main():
username = "username"
password = "password"
track_list = ["apple", "microsoft", "google"]
with tweetstream.FilterStream(username, password, track=track_list) as stream:
for tweet in stream:
text = tweet["text"]
user = tweet["user"]["screen_name"]
message = {"text": text, "user": user}
db.publish("tweets", message)
if __name__ == '__main__':
try:
print "Started..."
main()
except KeyboardInterrupt:
print '\nGoodbye!'
My server side socket.io implementation is done using django-socketio (based off of gevent-socketio) https://github.com/stephenmcd/django-socketio which simply provides a few helper decorators as well as a broadcast_channel method. Because it's done in django, I've simply put this code in views.py simply so that they're imported. My views.py code:
def index(request):
return render_to_response("twitter_app/index.html", {
}, context_instance=RequestContext(request))
def _listen(socket):
db = redis.Redis(host="localhost", port=6379, db=0)
client = db.pubsub()
client.subscribe("tweets")
tweets = client.listen()
while True:
tweet = tweets.next()
tweet_data = ast.literal_eval(tweet["data"])
message = {"text": tweet_data["text"], "user": tweet_data["user"], "type": "tweet"}
socket.broadcast_channel(message)
#on_subscribe(channel="livestream")
def subscribe(request, socket, context, channel):
g = Greenlet.spawn(_listen, socket)
The client side socket.io JavaScript simply connects and subscribes to the channel "livestream" and captures any received messages to that channel:
var socket = new io.Socket();
socket.connect();
socket.on('connect', function() {
socket.subscribe("livestream");
});
socket.on('message', function(data) {
console.log(data);
});
The obvious problem with this code is that each time a new user or browser window is opened to the page, a new _listen method is spawned and the tweets get subscribed to and broadcast for each user resulting in duplicate messages being received on the client. My question is, where would the proper place be to put the _listen method so that it's only created once regardless of the # of clients? Also, keeping in mind that the broadcast_channel method is a method of a socket instance.
The problem was that I was using socket.broadcast_channel when I should have been using socket.send.

Categories

Resources