Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A callback isn't envoked when handling one namespace event and emiting to another namespace #1909

Closed
valerykustov opened this issue Nov 20, 2022 · 3 comments
Assignees
Labels

Comments

@valerykustov
Copy link

Description:

I have two clients connected to a flask-socketio server. When the first one emits a message the server handles it and emits to the other client. When the second client receives the message it sends back an acknowledgement and the server invokes a callback on it. It works when both clients are connected to the same namespace. However the latest callback is not envoked when the clients are connected to different namespaces.

Steps to reproduce:

I have prepared an example consisting of four files: one python script and one template for both situations when the clients are connected to a single namespace and when they are not.

├── doesnt_work
│   ├── main.py
│   └── templates
│       └── user.html
└── works
    ├── main.py
    └── templates
        └── user.html

Versions:

bidict==0.22.0
click==8.1.3
Flask==2.2.2
Flask-SocketIO==5.3.1
gevent==22.10.2
gevent-websocket==0.10.1
greenlet==2.0.1
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
packaging==21.3
pyparsing==3.0.9
python-engineio==4.3.4
python-socketio==5.7.2
Werkzeug==2.2.2
zope.event==4.5.0
zope.interface==5.5.2

Single namespace:

works/main.py:

from gevent.monkey import patch_all
patch_all()

import logging

from flask import Flask, render_template, request
from flask_socketio import SocketIO, join_room

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
app.logger.setLevel(logging.INFO)
socketio = SocketIO(app, logger=True, engineio_logger=True)


def ack_callback(*args, **kwargs):
    res = f'ack!, {args=}, {kwargs=}'
    app.logger.info(res)


@app.route('/user/<int:user_id>')
def user(user_id):
    return render_template('user.html', user_id=user_id)


@socketio.on('join')
def on_join(room):
    join_room(room)
    socketio.emit(
        'roomEntrance', f'sid {request.sid} has entered the {room=}',
        to=room,
        callback=ack_callback
    )
    return f'entered {room=}'


@socketio.on('sendMessage')
def send_message(recipient_id: str, message: str):
    socketio.emit(
        'messageFromUser', message,
        to=f'room_{recipient_id}',
        callback=ack_callback
    )
    return f'sent to room_{recipient_id}'


if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=5000)

works/templates/user.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>User {{ user_id }}</title>
</head>
<body>
<h1>Hello, user {{ user_id }}</h1>
<script src="https://cdn.socket.io/4.4.1/socket.io.min.js" integrity="sha384-fKnu0iswBIqkjxrhQCTZ7qlLHOFEgNkRmK2vaO/LbTZSXdJfAu6ewRBdwHPhBo/H" crossorigin="anonymous"></script>
    <script>
      const socket = io();
    </script>
    <script type="text/javascript">
      (() => {
        socket.onAny((eventName, data, callback) => {
          console.log(eventName+ ': ' + data);
          if(typeof callback !== "undefined"){
            callback("Ack from JS!" + " eventName: " + eventName + ", data: " + JSON.stringify(data));
          };
        });
      })();
    </script>
    <script>
        socket.emit('join', 'room_{{ user_id }}')
    </script>
    <button onclick="socket.emit('sendMessage', '{{ 1-user_id }}', 'message from user {{ user_id }}', (response) => {console.log(response);});">Send to room_{{ 1-user_id }}</button>
  </body>
</html>

If we run this app, then open in a browser http://localhost:5000/user/0 and http://localhost:5000/user/1 and click the button on one of the pages we'll see something like that in the server logs:

vgw80MhB8FnORrEuAAAG: Received packet MESSAGE data 20["sendMessage","1","message from user 0"]
received event "sendMessage" from _k_1C_Va9R5oO6gcAAAH [/]
emitting event "messageFromUser" to room_1 [/]
xcr2FFCJ6gDwlKrxAAAE: Sending packet MESSAGE data 22["messageFromUser","message from user 0"]
vgw80MhB8FnORrEuAAAG: Sending packet MESSAGE data 30["sent to room_1"]
xcr2FFCJ6gDwlKrxAAAE: Received packet MESSAGE data 32["Ack from JS! eventName: messageFromUser, data: \"message from user 0\""]
received ack from YG5EbenDWxb4nZ9WAAAF [/]
[2022-11-20 14:06:28,462] INFO in main: ack!, args=('Ack from JS! eventName: messageFromUser, data: "message from user 0"',), kwargs={}

Notice the last row. It is created from the ack_callback function.

Different namespaces
Below are slightly modified versions of the files from the works directory. The difference is that each user connects to its own namespace and the server emits to one of the namespaces while handling event from the other one. Also it listens to another port.

doesnt_work/main.py:

from gevent.monkey import patch_all
patch_all()

import logging

from flask import Flask, render_template, request
from flask_socketio import SocketIO, join_room

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
app.logger.setLevel(logging.INFO)
socketio = SocketIO(app, logger=True, engineio_logger=True)


def ack_callback(*args, **kwargs):
    res = f'ack!, {args=}, {kwargs=}'
    app.logger.info(res)


@app.route('/user/<int:user_id>')
def user(user_id):
    return render_template('user.html', user_id=user_id)


@socketio.on('join', namespace='/namespace_0')
@socketio.on('join', namespace='/namespace_1')
def on_join(room):
    join_room(room)
    socketio.emit(
        'roomEntrance', f'sid {request.sid} has entered the {room=}',
        to=room, namespace=request.namespace,
        callback=ack_callback
    )
    return f'entered {room=}'


@socketio.on('sendMessage', namespace='/namespace_0')
@socketio.on('sendMessage', namespace='/namespace_1')
def send_message(recipient_id: str, message: str):
    socketio.emit(
        'messageFromUser', message,
        to=f'room_{recipient_id}', namespace=f'/namespace_{recipient_id}',
        callback=ack_callback
    )
    return f'sent to room_{recipient_id}'


if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=5001)

doesnt_work/templates/user.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>User {{ user_id }}</title>
</head>
<body>
<h1>Hello, user {{ user_id }}</h1>
<script src="https://cdn.socket.io/4.4.1/socket.io.min.js" integrity="sha384-fKnu0iswBIqkjxrhQCTZ7qlLHOFEgNkRmK2vaO/LbTZSXdJfAu6ewRBdwHPhBo/H" crossorigin="anonymous"></script>
    <script>
      const socket = io('/namespace_{{ user_id }}');
    </script>
    <script type="text/javascript">
      (() => {
        socket.onAny((eventName, data, callback) => {
          console.log(eventName+ ': ' + data);
          if(typeof callback !== "undefined"){
            callback("Ack from JS!" + " eventName: " + eventName + ", data: " + JSON.stringify(data));
          };
        });
      })();
    </script>
    <script>
        socket.emit('join', 'room_{{ user_id }}')
    </script>
    <button onclick="socket.emit('sendMessage', '{{ 1-user_id }}', 'message from user {{ user_id }}', (response) => {console.log(response);});">Send to room_{{ 1-user_id }}</button>
  </body>
</html>

Same again, run this app, then open in a browser http://localhost:5001/user/0 and http://localhost:5001/user/1 and click the button on one of the pages. Then these lines appear in the server logs:

4QWbYZeo1540Yvk-AAAE: Received packet MESSAGE data 2/namespace_0,0["sendMessage","1","message from user 0"]
received event "sendMessage" from hvr4QyQFdzeFPYXhAAAF [/namespace_0]
emitting event "messageFromUser" to room_1 [/namespace_1]
YmNfuvm_48j3EBGLAAAG: Sending packet MESSAGE data 2/namespace_1,2["messageFromUser","message from user 0"]
4QWbYZeo1540Yvk-AAAE: Sending packet MESSAGE data 3/namespace_0,0["sent to room_1"]
YmNfuvm_48j3EBGLAAAG: Received packet MESSAGE data 3/namespace_1,2["Ack from JS! eventName: messageFromUser, data: \"message from user 0\""]
received ack from qFxd7MstcjebAWuRAAAH [/namespace_1]

The server states that it has got the acknowledgement from the user 1, however the ack_callback function is never called.

The expected behaviour:

Both servers envoke the callback.

@miguelgrinberg
Copy link
Owner

Thanks for the abundant details, made it much easier to locate and fix the bug!

Would you be able to install Flask-SocketIO's main branch and confirm the fix?

@miguelgrinberg miguelgrinberg self-assigned this Nov 20, 2022
@valerykustov
Copy link
Author

Wow, that was fast! It works with Flask-SocketIO-5.3.2.dev0 now.

Thank you a lot!

@miguelgrinberg
Copy link
Owner

5.3.2 is now out with this fix. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants