2

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?

João A. Veiga
  • 498
  • 3
  • 11

2 Answers2

0

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.

cssyphus
  • 37,875
  • 18
  • 96
  • 111
  • Unfortunatelly that didn't work, the server is still not responding to the event. I believe the issue lies in the server code. – João A. Veiga Nov 25 '21 at 14:21
  • Thanks for the headsup on using emit though – João A. Veiga Nov 25 '21 at 14:21
  • @JoãoA.Veiga I updated my answer with additional thoughts/suggestions. – cssyphus Nov 25 '21 at 14:59
  • Just tested it and still the same result. The server emits nothing back to the client. I'm starting to believe this may be a deeper issue with the flask-socketio server using eventlet. Any ideas? – João A. Veiga Nov 25 '21 at 16:22
  • No, as soon as you install (npm) eventlet, the flask-socketio server just automagically starts using it. You do not need to use both eventlet and gevent, just one of them (and eventlet is recommended). My suggestion is to create a test example that does not have all the blueprint etc in it - just a very simple flask-socketio on backend / js on front end, and get it working. Then build it back up to where you are now. Be assured: flask-socketio on backend and js/node socket.io on client side works just fine. The test will take 20mins start-to-finish, but will save you hours. – cssyphus Nov 25 '21 at 16:35
  • 1
    Thanks for the insight @cssyphus, just got it working. I'll post an answer explaining what I did – João A. Veiga Nov 25 '21 at 16:54
  • @JoãoA.Veiga Hope you get a chance to write up your solution while it's still fresh in mind... am keen to see it. Best regards – cssyphus Nov 26 '21 at 15:56
  • `socketio.send('hello')` is shorthand for `socketio.emit('message', 'hello')`. Therefore, your original answer is incorrect. See https://flask-socketio.readthedocs.io/en/latest/api.html#flask_socketio.send – Maytha8 Jun 10 '22 at 09:40
0

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.

João A. Veiga
  • 498
  • 3
  • 11