I want to emit message from server to client.
I have look at this but cannot use because I cannot create a namespace instance.
How to emit SocketIO event on the serverside
My use case is:
I have a database of price of product. A lot of users are currently surf my website. Some of them is viewing product X.
On the server side, the admin can edit the price of the product. If he edit the price of X, all the client must see the notification that X price change (e.x: a simple js alert).
My client javascript now:
var socket = io.connect('/product');
#notify server that this client is viewing product X
socket.emit("join", current_product.id);
#upon receive msg from server
socket.on('notification', function (data) {
alert("Price change");
});
My server code (socket.py):
#namespace('/products')
class ProductsNamespace(BaseNamespace, ProductSubscriberMixin):
def initialize(self, *args, **kwargs):
_connections[id(self)] = self
super(ProductsNamespace, self).initialize(*args, **kwargs)
def disconnect(self, *args, **kwargs):
del _connections[id(self)]
super(ProductsNamespace, self).disconnect(*args, **kwargs)
def on_join(self, *args):
print "joining"
def emit_to_subscribers(self): pass
I use the runserver_socketio.py as in this link.
(Thanks to Calvin Cheng for this excellent up-to-date example.)
I don't know how to call the emit_to_subscribers. Since I have no instance of namespace.
As I read from this doc ,
Namespaces are created only when some packets arrive that ask for the namespace.
But how can I send the packet to that namespace from the code? IF I can only create the instance when a client emit message to server, when no one is surfing the site, right after finish editing the price, the system will fail.
I am really confused about the namespace and its instance. If you have any clearer docs, please help me.
Thanks a lot!
This is my current state of understanding, hopefully it will be helpful to someone. Building up further from How to emit SocketIO event on the serverside, you now have a dictionary with ProductsNamespace objects as values. You can iterate through this dictionary to find the desired socket object. For example, if you set socket identifier upon connection, as described in the Django and Flask example apps by using on_nickname method, then you can retrieve the socket like so:
for key in _connections:
socket = _connections[key]
if 'nickname' in socket.session and socket.session['nickname'] == unicode('uniqueName'):
socket.emit('eventTag', 'message from server')
Similarly socket.session['rooms'] can be used to emit to all members of the room, and if there are multiple SocketIO namespaces, socket.ns_name can be used.
Related
I am trying to develop a web application that supports a long task at the backend. I am using flask-socketio package on my server along with celery. My workflow is following :
When a client opens the Html page -- I initiate a socket connection to the server which creates a uid for the user and emits it back.
Now once the user posts a request for the long task -- I schedule it using celery and once finished I need to emit it to the user who posted the request. (I stored the relevant userid in post request)
I have looked at #Miguels's answer for 39423646/flask-socketio-emit-to-specific-user which creates a separate room for each user and then broadcasts the message on that room. But I wanted to ask if there is some other simpler way to do this since it seems inefficient or forced way to do this.
I also came across the nodejs solution (how-to-send-a-message-to-a-particular-client-with-socket-io) which I felt to be a more natural way to accomplish this. Is there a similar solution in python-socketio too?
Update: After some more search I came across the following solution on a github gist. According to this -- ** flash-socketIO already puts all clients in the separate room given by request.sid **.
I would still wish to discuss other ways to do this. Specifically if the site traffic is quite high -- wouldn't it lead to too many rooms?
Update (2): my current (working) server code which makes use of rooms. This is borrowed and modified from flask-SocketIO celery example
#celery.task(bind=True)
def long_task(self, userid, url):
# LONG TASK
time.sleep(10)
# meta = some result
post(url, json=meta)
# It seems i can't directly emit from celery function so I mimic a post request and emit from that function
return meta
#app.route('/longtask', methods=['POST'])
def longtask():
userid = request.json['userid']
task = long_task.delay(elementid, userid, url_for('event', _external=True))
return jsonify({}), 202
#socketio.on('connect', namespace='/custom')
def events_connect():
userid = str(uuid.uuid4())
session['userid'] = userid
current_app.clients[userid] = request.sid
emit('userid', {'userid': userid})
#app.route('/event/', methods=['POST'])
def event():
userid = request.json['userid']
data = request.json
roomid = app.clients.get(userid)
socketio.emit('celerystatus', data, namespace='/custom', room=roomid)
return 'ok'
You don't have to create any rooms to address an individual user. Just set the to argument to the sid of the user you want to address:
emit('my event', my_data, to=user_sid)
The sid value that is assigned to each user is given to you in the connect event handler as request.sid.
You can make a room for every separate user. And you can emit to a particular room which user you want to share the message.
io.to(user.room).emit('specific-user', { message });
Here mean to separate user room that you need to define a room id to a chat room like a chat application (one-to-one-communication).
Room id can be concatinate of own id and user id which you want to send a message for a unique room id for each user.
I am building a chat app based on Flask SocketIO only, no database. One of the requirements is when a new user connects, the app should show previous messages. I save every message into a array on each send event. Okay, so now the problem is, when User A connects and creates some messages, and then User B connects, all the previous messages from User B is shown, but then User A also gets that messages, so User A ends up duplicated messages.
Client Side JS
function myFunction() {
document.getElementById('demo').style['text-decoration']='underline';
}
const socket = io.connect("http://127.0.0.1:5000");
socket.on('connect', function() {
socket.emit('sync_messages');
socket.emit('sync_channels');
});
Flask App Code
#socketio.on('sync_messages')
def handle_sync():
socketio.emit('show_all_messages', messages)
#socketio.on('sync_channels')
def handle_sync_channels():
socketio.emit('show_all_channels', channels)
Visual representation of what is happening
what the actual bug is
The socketio.emit() function is not context aware, it defaults to broadcasting to all connected users. Try using emit() which is the Flask-friendly wrapper:
#socketio.on('sync_messages')
def handle_sync():
emit('show_all_messages', messages)
#socketio.on('sync_channels')
def handle_sync_channels():
emit('show_all_channels', channels)
I have a python program which on certain event (for example on curl request) would calculate the function value. What I need is the moment the function executes, some data needs to be posted to tornado websocket. I have looked around internet and found examples on how to create websocket but all these examples cover scenarios where the data is invoked inside the websocket handler
Referring to this code for example:
https://github.com/benjaminmbrown/real-time-data-viz-d3-crossfilter-websocket-tutorial/blob/master/rt-data-viz/websocket_server.py
Can someone guide me on how can I post message on websocket. Basically I have tornado API where if user do a curl request I would like to log that message to websocket
You can do it by creating a registry of all active websockets and use it to send messages on a certain event.
class WebsocketRegistry:
def __init__(self):
self._active_websockets = []
def add_listener(self, listener):
self._active_websockets.append(listener)
def remove_listener(self, listener):
self._active_websockets.remove(listener)
def send_messages(self, msg_txt):
for ws in self._active_websockets:
ws.write_message(msg_txt)
registry = WebsocketRegistry()
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self, *args, **kwargs):
super(WSHandler, self).open(*args, **kwargs)
registry.add_listener(self)
def on_close(self):
super(WSHandler, self).on_close()
registry.remove_listener(self)
P.S. Take note that if you plan to scale your app with 2+ instances, this won't work and you would have to use, for example, a message queue (RabbitMQ is good) to deliver events to the all opened websockets. But overall approach would be the same: MQ would be a registry and websockets subscribe on messages (and unsubscribe on closing) on connection.
I have the following scenario I would like to implement:
User surfs to our website
User enters a bitcoin address.
A websocket is created to the server, passing the address.
The server registers a callback with Blocktrail
When the callback is triggered (a payment was seen by Blocktrail) we send a message back to the browser.
The page the user is browsing is updated to show the message recieved
I'm using webhooks from the Blocktrail API to "listen" to an event, being the reception of coins on an address.
Now, when the event happens, the API does a POST to my URL. This should send a message to the browser that is connected to my server with socket.io (such as 'payment seen on blockchain')
So the question is,
How can I send a message from a route to a socket using flask-socketio
Pseudo code:
#app.route('/callback/<address>')
def callback(id):
socketio.send('payment seen on blockchain')
#socketio.on('address',address)
def socketlisten(address):
registerCallback(address)
I'm going to describe how to solve this using Flask-SocketIO beta version 1.0b1. You can also do this with the 0.6 release, but it is a bit more complicated, the 1.0 release makes addressing individual clients easier.
Each client of a socket connection gets assigned a session id that uniquely identifies it, the so called sid. Within a socket function handler, you can access it as request.sid. Also, upon connection, each client is assigned to a private room, named with the session id.
I assume the metadata that you receive with the callback allows you to identify the user. What you need is to obtain the sid of that user. Once you have it, you can send your alert to the corresponding room.
Example (with some hand-waving regarding how you attach a sid to an address):
#app.route('/callback/<address>')
def callback(address):
sid = get_sid_from_address(address)
socketio.send('payment seen on blockchain', room=sid)
#socketio.on('address')
def socketlisten(address):
associate_address_with_sid(address, request.sid)
I'm facing problem in emiting messages from RabbitMQ to User via SocketIO.
I have Flask application with SocketIO integration.
Current user flow seems like
The problem is i'm not able to set up RabbitMQ listener which forward messages to browser via SocketIO. Every time i'm getting different error. Mostly is that connection is closed, or i'm working outside of application context.
I tried many approaches, here is my last one.
# callback
def mq_listen(uid):
rabbit = RabbitMQ()
def cb(ch, method, properties, body, mq=rabbit):
to_return = [0] # mutable
message = Message.load(body)
to_return[0] = message.get_message()
emit('report_part', {"data": to_return[0]})
rabbit.listen('results', callback=cb, id=uid)
# this is the page, which user reach
#blueprint.route('/report_result/<uid>', methods=['GET'])
def report_result(uid):
thread = threading.Thread(target=mq_listen, args=(uid,))
thread.start()
return render_template("property/report_result.html", socket_id=uid)
where rabbit.listen method is abstraction like:
def listen(self, queue_name, callback=None, id=None):
if callback is not None:
callback_function = callback
else:
callback_function = self.__callback
if id is None:
self.channel.queue_declare(queue=queue_name, durable=True)
self.channel.basic_qos(prefetch_count=1)
self.consumer_tag = self.channel.basic_consume(callback_function, queue=queue_name)
self.channel.start_consuming()
else:
self.channel.exchange_declare(exchange=queue_name, type='direct')
result = self.channel.queue_declare(exclusive=True)
exchange_name = result.method.queue
self.channel.queue_bind(exchange=queue_name, queue=exchange_name, routing_key=id)
self.channel.basic_consume(callback_function, queue=exchange_name, no_ack=True)
self.channel.start_consuming()
which resulted into
RuntimeError: working outside of request context
I will be happy for any tip or example of usage.
Thanks a lot
I had a similar issue, in the end of the day it's because when you make a request flask passes the request context to client. But the solution is NOT to add with app.app_context(). That is hackey and will definitely have errors as you're not natively sending the request context.
My solution was to create a redirect so that the request context is maintained like:
def sendToRedisFeed(eventPerson, type):
eventPerson['type'] = type
requests.get('http://localhost:5012/zmq-redirect', json=eventPerson)
This is my redirect function, so whenever there is an event I'd like to push to my PubSub it goes through this function, which then pushes to that localhost endpoint.
from flask_sse import sse
app.register_blueprint(sse, url_prefix='/stream')
#app.route('/zmq-redirect', methods=['GET'])
def send_message():
try:
sse.publish(request.get_json(), type='greeting')
return Response('Sent!', mimetype="text/event-stream")
except Exception as e:
print (e)
pass
Now, whenever an event is pushed to my /zmq-redirect endpoint, it is redirected and published via SSE.
And now finally, just to wrap everything up, the client:
var source = new EventSource("/stream");
source.addEventListener(
"greeting",
function(event) {
console.log(event)
}
)
The error message suggests that it's a Flask issue. While handling requests, Flask sets a context, but because you're using threads this context is lost. By the time it's needed, it is no longer available, so Flask gives the "working outside of request context" error.
A common way to resolve this is to provide the context manually. There is a section about this in the documentation: http://flask.pocoo.org/docs/1.0/appcontext/#manually-push-a-context
Your code doesn't show the socketio part. But I wonder if using something like flask-socketio could simplify some stuff... (https://flask-socketio.readthedocs.io/en/latest/). I would open up the RabbitMQ connection in the background (preferably once) and use the emit function to send any updates to connected SocketIO clients.