Home > Enterprise >  Flask socketio not listening to events
Flask socketio not listening to events

Time:11-26

I'm building a flask app utilizing flask socketio. I'm running it locally on Ubuntu 20.04 with gunicorn and eventlet on localhost:8000.

My server side test code looks like this:

$(document).ready(function() {
    // const socketGame = io(window.location.hostname   "/coin")
    var socket = io.connect('http://127.0.0.1:8000');

    socket.on('connect', function() {
        console.log("ok");
        socket.send('User has connected!');
    });

    socket.on('message', function(msg) {
        console.log('Received message');
    });

});

and in my server I have the following listener:

import re

from flask import render_template, request, redirect, url_for, jsonify, current_app
from flask_mail import Mail, Message
from flask_socketio import emit, join_room, leave_room, rooms
from flask_login import LoginManager, UserMixin, login_user, login_required, current_user, logout_user
from werkzeug.security import generate_password_hash, check_password_hash

from .. import socketio
from . import games_blueprint
from app.models import *
from app.utilities import sanitizeHtml

#socketio chat server
#need to create event for admin broadcasting messages
@socketio.on('message', namespace='/')
def handleMessage(msg):
    print('Message: '   msg)
    send(msg, broadcast=True)

It should simply print the msg from the client in the terminal and broadcast back a message. But, from the gunicorn logs, it looks like the server is receiving the event from the client and not passing it to the listener:

(vegazbetenv) jveiga@LAPTOP-C6PJKMM6:~/vegazbet$ gunicorn --worker-class eventlet -w 1 wsgi:application
[2021-11-24 21:00:59 -0300] [134633] [INFO] Starting gunicorn 20.1.0
[2021-11-24 21:00:59 -0300] [134633] [INFO] Listening at: http://127.0.0.1:8000 (134633)
[2021-11-24 21:00:59 -0300] [134633] [INFO] Using worker: eventlet
[2021-11-24 21:00:59 -0300] [134635] [INFO] Booting worker with pid: 134635
Server initialized for eventlet.
Server initialized for eventlet.
Server initialized for eventlet.
HYYx0QNgrNr7YodDAAAA: Sending packet OPEN data {'sid': 'HYYx0QNgrNr7YodDAAAA', 'upgrades': ['websocket'], 'pingTimeout': 20000, 'pingInterval': 25000}
HYYx0QNgrNr7YodDAAAA: Received packet MESSAGE data 0
HYYx0QNgrNr7YodDAAAA: Sending packet MESSAGE data 0{"sid":"q1NjSbTlPRYmT_TYAAAB"}
HYYx0QNgrNr7YodDAAAA: Received request to upgrade to websocket
HYYx0QNgrNr7YodDAAAA: Received packet MESSAGE data 2["message","User has connected!"]
received event "message" from q1NjSbTlPRYmT_TYAAAB [/]
HYYx0QNgrNr7YodDAAAA: Upgrade to websocket successful
HYYx0QNgrNr7YodDAAAA: Sending packet PING data None
HYYx0QNgrNr7YodDAAAA: Received packet PONG data 

As can be seen in the logs, the websocket connection is successfully upgraded and the socket-io server receives the message from the client, it just doesn't pass it to the function that was supposed to listen to the event.

Just to clarify, I'm running flask using the app factory pattern, the listener code is inside a blueprint views.py file, follows my code for app factory, and blueprint:

app/__init__.py

import os

from flask import Flask, jsonify, render_template, request, redirect, url_for
from flask_login import LoginManager, UserMixin, login_user, login_required, current_user, logout_user
from flask_mail import Mail
from flask_socketio import SocketIO
from flask_mongoengine import MongoEngine
from flask_wtf.csrf import CSRFProtect
from celery import Celery


from config import Config
from app.models import *
from config import *


### Flask extension objects instantiation ###
mail = Mail()
csrf = CSRFProtect()
login_manager = LoginManager()
# socketio = SocketIO(engineio_logger=True, logger=True, message_queue=os.getenv('CELERY_BROKER_URL'), cors_allowed_origins="*")
socketio = SocketIO(engineio_logger=True, logger=True, cors_allowed_origins="*")

### Celery instantiation ###
celery = Celery(__name__, broker=Config.CELERY_BROKER_URL, result_backend=Config.RESULT_BACKEND)


### Application Factory ###
def create_app():

    app = Flask(__name__)

    # Configure the flask app instance
    CONFIG_TYPE = os.getenv('CONFIG_TYPE', default='config.DevelopmentConfig')
    app.config.from_object(CONFIG_TYPE)

    #Configure celery
    celery.conf.update(app.config)

    # Initialize flask extension objects
    initialize_extensions(app)

    # Register blueprints
    register_blueprints(app)

    #setup db integration
    configure_db(app)

    # Configure logging
    configure_logging(app)

    # Register error handlers
    register_error_handlers(app)

    #setup db for flask-login
    @login_manager.user_loader
    def load_user(user_id):
        user = User.objects(id=user_id)[0]
        return user

    return app


### Helper Functions ###
def register_blueprints(app):
    from app.auth import auth_blueprint
    from app.main import main_blueprint
    from app.games import games_blueprint
    from app.admin import admin_blueprint

    app.register_blueprint(auth_blueprint, url_prefix='/users')
    app.register_blueprint(main_blueprint)
    app.register_blueprint(games_blueprint, url_prefix='/games')
    app.register_blueprint(admin_blueprint, url_prefix='/admin')

def initialize_extensions(app):
    mail.init_app(app)
    login_manager.init_app(app)
    csrf.init_app(app)
    socketio.init_app(app)

def register_error_handlers(app):

    # 400 - Bad Request
    @app.errorhandler(400)
    def bad_request(e):
        return render_template('400.html'), 400

    # 401 - Unauthorized
    @app.errorhandler(401)
    def bad_request(e):
        return render_template('401.html'), 401

    # 403 - Forbidden
    @app.errorhandler(403)
    def forbidden(e):
        return render_template('403.html'), 403

    # 404 - Page Not Found
    @app.errorhandler(404)
    def page_not_found(e):
        return render_template('404.html'), 404

    # 405 - Method Not Allowed
    @app.errorhandler(405)
    def method_not_allowed(e):
        return render_template('405.html'), 405

    # 500 - Internal Server Error
    @app.errorhandler(500)
    def server_error(e):
        return render_template('500.html'), 500

def configure_logging(app):
    import logging
    from flask.logging import default_handler
    from logging.handlers import RotatingFileHandler

    # Deactivate the default flask logger so that log messages don't get duplicated
    app.logger.removeHandler(default_handler)

    # Create a file handler object
    file_handler = RotatingFileHandler('flaskapp.log', maxBytes=16384, backupCount=20)

    # Set the logging level of the file handler object so that it logs INFO and up
    file_handler.setLevel(logging.INFO)

    # Create a file formatter object
    file_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(filename)s: %(lineno)d]')

    # Apply the file formatter object to the file handler object
    file_handler.setFormatter(file_formatter)

    # Add file handler object to the logger
    app.logger.addHandler(file_handler)

def configure_db(app):
    db = MongoEngine(app)

app/games/__init__.py

#This bluepint will deal with all site functionality

from flask import Blueprint

games_blueprint = Blueprint('games', __name__, template_folder='templates')

from . import views

Any thoughts on why this is happening and how to solve it?

CodePudding user response:

Your client is looking for socket.on('message', incoming_message_as_a_string) but your server is sending socket.send('just the string without an event')

Try:

socket.on('connect', function() {
    socket.send('message', 'User has connected!');
});

but I would make a habit of always using emit, on both client and server:

socket.on('connect', function() {
    console.log("ok");
    socket.emit('message', 'User has connected!');
});

The reason being that emit can send both objects or strings, whereas send can only send strings.


Update:

Looking again at your question, I don't think your two sides are matching up. When a socket message is sent, the first parameter is the "event name" - basically a trigger that gives the other side something to watch for (so that the socket listeners can differentiate between different types of messages).

The server is only listening for a message event, and then sending out its own message - which means the client side has to send something first. Usually, that is a "connect" message -- and it does send one of those when the client connects, but the server side isn't listening for that event. It is only listening for a "message" event - and the client side only sends a message event after it first receives a message event from the server.

On the server side, try changing this:

@socketio.on('message', namespace='/')
def handleMessage(msg):
    print('Message: '   msg)
    send(msg, broadcast=True)

to this:

@socketio.on('connect')
def handleConnect():
    print('Someone connected')
    send('message', 'A user has joined', broadcast=True)

@socketio.on('message')
def handleMessage(msg):
    print('Message: '   msg)
    send(msg, broadcast=True)

PS - I removed the namespace stuff because I got things working without it and I'm unsure how it might effect results. You can add it back in once you've gotten a little further on.

CodePudding user response:

Not really sure on the fine details here, but the following alteration made the server work.

In the original app factory function, I was calling socketio.init_app(app) before registering the blueprints. I just moved it to be the last operation in the app factory:

def create_app():

    app = Flask(__name__)

    # Configure the flask app instance
    CONFIG_TYPE = os.getenv('CONFIG_TYPE', default='config.DevelopmentConfig')
    app.config.from_object(CONFIG_TYPE)

    #Configure celery
    celery.conf.update(app.config)

    # Register blueprints
    register_blueprints(app)

    #setup db integration
    configure_db(app)

    # Configure logging
    configure_logging(app)

    # Register error handlers
    register_error_handlers(app)
    
    ##########################################
    # HERE
    # Initialize flask extension objects
    initialize_extensions(app)
    ##########################################

    #setup db for flask-login
    @login_manager.user_loader
    def load_user(user_id):
        user = User.objects(id=user_id)[0]
        return user

    return app

I'm guessing that the operation order somehow stops socketio from actually registering the event handlers. Would appreciate if anyone more experienced with flask-socketio could dive deeper in the issue.

  • Related