8

I'm trying to get server messages pushed out to the end user that's logged into our flask website.

I've done some research and it seems that the best solution is to use socket-io.

My attempts at this don't seem to be working, I must also indicate that my knowledge of javascript is very basic.

Any assistance / guidance will be highly appreciated.

See my code below:

python - app.py

from flask_socketio import SocketIO, emit
from flask import Flask, render_template, url_for, request
from time import sleep

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
app.config['DEBUG'] = True

# turn the flask app into a socketio app
socketio = SocketIO(app, async_mode=None, logger=True, engineio_logger=True)

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        if request.form['submit_button'] == 'Do Stuff':
            # server doing things....
            # the below method will make calls to emit socket messages
            # based on actions / outcome of actions.
            serverActions()
    return render_template('index.html')

@socketio.on('connect')
def connect():
    print('Client connected')

@socketio.on('display_message')
def displayMessage(message):
    socketio.emit('newmessage', {'message': message})
    socketio.sleep(2)

def serverActions():
    # get connection to DB
    message = "connecting to DB"
    # show message to user on flask page
    displayMessage(message)

    # test successful connection to DB
    message = "successfully connected to DB"
    displayMessage(message)

    # query the DB
    message = "querying the DB"
    displayMessage(message)

    # update DB
    message = "updating the DB"
    displayMessage(message)

    # etc......

if __name__ == '__main__':
    socketio.run(app)

HTML - templates/index.html

<!DOCTYPE html>
<html>
<head>
    <script src="//code.jquery.com/jquery-3.3.1.min.js"></script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
    <script src="static/js/application.js"></script>
</head>
<body>
  <form method="POST">
  <div>
    <div>
      <h1>Asynchronous Flask Communication</h1>
      <p>Messages generated by the Flask server should appear below, asynchronously.</p>
    </div>
  </div>

  <div>
      <p>Asynchronous page updates will appear here:</p>
      <div>
        <input type="submit" value="Do Stuff" name="submit_button">
      </div>
      <div>
        <h3>Server Messages:</h3>
        <div id="message">
        </div>
      </div>
  </div>

  </form>
</body>
</html>

javascript - static/js/application.js

$(document).ready(function(){
    //connect to the socket server.
    var socket = io.connect('http://' + document.domain + ':' + location.port);

    //receive message details from server
    socket.on('display_message', function(msg) {
        console.log("Received message" + msg.message);
        message_string = '<p>' + msg.message + '</p>';
        $('#message').html(message_string);
    });

});

3 Answers 3

6
+50

You emit events that you listen for with on event handler. Also I don't think it makes sense for your event listener display_message to be inside a rest endpoint. Here is a solution with pypubsub for convenience so you can easily subscribe for all events on the server. It can work without it too but here it is

server.py

from flask_socketio import SocketIO, emit
from flask import Flask, render_template, url_for, request
from time import sleep
from pubsub import pub


app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
app.config['DEBUG'] = True

# turn the flask app into a socketio app
socketio = SocketIO(app, async_mode=None, logger=True, engineio_logger=True)

def listener(arg1):
    print('Function listener1 received:')
    print('  arg1 =', arg1)
    socketio.emit('newmessage',{'message':arg1})


pub.subscribe(listener, 'rootTopic')

@app.route('/', methods=['GET', 'POST'])
def index():
    return render_template('index.html')

@app.route('/post', methods=['POST'])
def post():
    pub.sendMessage('rootTopic', arg1='post')

@socketio.on('connect')
def connect():
    pub.sendMessage('rootTopic', arg1='connected to socket')
    print('Client connected')



if __name__ == '__main__':
    socketio.run(app)

index.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <div id="message"></div>
    <button id="btn">CLICK</button>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script>
$(document).ready(function(){
    //connect to the socket server.
    var socket = io.connect('http://' + document.domain + ':' + location.port);
    $('#btn').on('click', function() {
      fetch('http://' + document.domain + ':' + location.port + '/post', {method:"POST"})
    })
    //receive message details from server
    socket.on('newmessage', function(msg) {
        console.log("Received message" + msg.message);
        message = '<p>' + msg.message + '</p>';

        $('#message').append(message);
    });

});
    </script>
  </body>
</html>
Sign up to request clarification or add additional context in comments.

8 Comments

This doesn't work. I want to be able to trigger the emit event asynchronously, as the server is about to do something / has done something, in my serverActions method. I need this to keep my end user up to date with what's happening on the server.
I think that you are over complicating things. Just emit those serverActions when they happen. Also connecting to the db will not happen in a route scope just call sendMessage when an event happens with the corresponding message and use async await when needed. I don't think your function serverActions is needed.
I am making calls to sendMessage, I see the below being done on the backend: emitting event "newmessage" to all [/] xxxx: Sending packet MESSAGE data 2["newmessage",{"message":"connecting to DB"}] But it doesn't display on the frontend, in my html. I don't see received packet message data on the backend, which is what the problem is I believe.
But when I place this: message = "show message..." socketio.emit('newmessage', {'message': message}) inside: @socketio.on('connect') def connect(): then it displays on the frontend, backend looks like:
That sounds like something I could use. An example will be highly helpful and appreciated. Thanking you in advance.
|
1

Heres the trick to it: emit messages to a group. So, could invoke join group and add everyone to a group on log in that way you can send messages to everyone connected.

from flask import Flask
from flask_socketio import SocketIO, send, emit, join_room, leave_room

app = Flask('__name__')
socket = SocketIO(app, cors_allowed_origins="*", SameSite=None)


@socket.on('connect', namespace='/optional')
def connect():
    join_room('connected')

@app.route('/message')
def some_name():
    emit('message', 'your actual message', room='connected', namespace='/optional')

Now when you hit the url it should send a message to everyone connected. Remember the namespace is optional, but i recommend you use one.

Comments

0

The problem from the example that you have given appears to be that the client, i.e. your JS in the browser, is listening for the event display_message. However your server is not emitting that event by the looks of it. Every call to the displayMethod is emitting a newmessage event. Update your client to add an event listener for that event.

    socket.on('newmessage', function(msg) {
        console.log("Received message" + msg.message);
        message_string = '<p>' + msg.message + '</p>';
        $('#message').html(message_string);
    });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.