(flask + socket.IO) Result of emit callback is the response of my REST endpoint

Question:

Just to give a context here, I’m a node.JS developer, but I’m on a project that I need to work with Python using Flask framework.

The problem is, when a client request to an endpoint of my rest flask app, I need to emit an event using socket.IO, and get some data from the socket server, then this data is the response of the endpoint. But I didn’t figured out how to send this, because flask needs a “return” statement saying what is the response, and my callback is in another context.

Sample of what I’m trying to do: (There’s some comments explaining)

import socketio
import eventlet
from flask import Flask, request

sio = socketio.Server()
app = Flask(__name__)


@app.route('/test/<param>')
def get(param):
    def ack(data):
        print (data) #Should be the response
    sio.emit('event', param, callback=ack) # Socket server call my ack function
    #Without a return statement, the endpoint return 500

if __name__ == '__main__':
    app = socketio.Middleware(sio, app)
    eventlet.wsgi.server(eventlet.listen(('', 8000)), app)

Maybe, the right question here is: Is this possible?

Asked By: Pedro Kehl

||

Answers:

I’m going to give you one way to implement what you want specifically, but I believe you have an important design flaw in this, as I explain in a comment above. In the way you have this coded, your socketio.Server() object will broadcast to all your clients, so will not be able to get a callback. If you want to emit to one client (hopefully not the same one that sent the HTTP request), then you need to add a room=client_sid argument to the emit. Or, if you are contacting a Socket.IO server, then you need to use a Socket.IO client here, not a server.

In any case, to block your HTTP route until the callback function is invoked, you can use an Event object. Something like this:

from threading import Event
from flask import jsonify

@app.route('/test/<param>')
def get(param):
    ev = threading.Event()
    result = None

    def ack(data):
        nonlocal result
        nonlocal ev

        result = {'data': data}
        ev.set()  # unblock HTTP route

    sio.emit('event', param, room=some_client_sid, callback=ack)
    ev.wait()  # blocks until ev.set() is called
    return jsonify(result)
Answered By: Miguel Grinberg

I had a similar problem using FastAPI + socketIO (async version) and I was stuck at the exact same point. No eventlet so could not try out the monkey patching option.

After a lot of head bangings it turns out that, for some reason, adding asyncio.sleep(.1) just before ev.wait() made everything work smoothly. Without that, emitted event actually never reach the other side (socketio client, in my scenario)

Answered By: Henry