I'm trying to create a python app that can send message from server to client. Currently I'm using this sample code from here. It's a chat app and it's working fine. I tried to modified the app and add a new function in the server side python code that will print a message "Dummy" into the client but seems like it didn't work.
Here's my html code:
index.html
<body>
<ul id="messages"></ul>
<ul id="output"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="{{url_for('static', filename='assets/vendor/socket.io.min.js')}}"></script>
<script src="{{url_for('static', filename='assets/vendor/jquery.js')}}"></script>
<script>
var socket = io.connect('http://127.0.0.1:5000/chat');
$('form').submit(function(){
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').html($('<li>').text(msg));
});
socket.on('output', function(msg){
alert(msg)
$('#messages').html($('<li>').text(msg));
});
</script>
Here's my backend code:
web_app.py
from flask import Flask
from flask import render_template
from flask_socketio import SocketIO
from flask_socketio import emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
connected = False
def socket_onload(json):
socketio.emit('output', str(json), namespace='/chat')
print('received message: ' + str(json))
@socketio.on('chat message', namespace='/chat')
def handle_chat_message(json):
print('received message: ' + str(json))
emit('chat message', str(json), broadcast=True)
@socketio.on('connect') # global namespace
def handle_connect():
global connected
connected = True
print('Client connected')
@socketio.on('connect', namespace='/chat')
def handle_chat_connect():
print('Client connected to chat namespace')
emit('chat message', 'welcome!')
@socketio.on('disconnect', namespace='/chat')
def test_disconnect():
print('Client disconnected')
@app.route('/')
def index():
return render_template('index.html')
@app.route('/blah/')
def blah():
return render_template('blah.html')
main.py
import web_app
import threading
import time
def main():
import web_app
webapp_thread = threading.Thread(target=run_web_app)
webapp_thread.start()
# webapp_thread = threading.Thread(target=run_web_app, args=(i,))
while web_app.connected==False:
print "waiting for client to connect"
time.sleep(1)
pass
print "Connected..."
time.sleep(3)
print "Trying to print dummy message..."
web_app.socket_onload("Dummy")
def run_web_app():
web_app.socketio.run(web_app.app)
if __name__ == '__main__':
main()
I can see "received message: Dummy" in the terminal but nothing's change on the web browser.
You have two mistakes which prevent you from doing so:
First, you are trying to emit an event with socket.io outside from the socket context.
When a function is wraped with @socketio.on
decorator, it becomes an Event-Handlers
.
While an event is fired on the server-side it will search for the right handler to handle the event and initialize the context to the specific client that emitted the event.
Without this context initializing, your socketio.emit('output', str(json), namespace='/chat')
will do nothing because the server doesn't know to whom it should emit back the response.
Anyway, there is a little trick for emitting events manually to a specific client (even if you are not in its context). Each time a socket has opened, the server assign it to a "private" room with the same name as the socket id (sid
). So in order to send a message to the client outside from the client context, you can create a list of connected client's ids and call the emit function with the room=<id>
argument.
For example:
web_app.py:
...
from flask import Flask, request
clients = []
@socketio.on('connect')
def handle_connect():
print('Client connected')
clients.append(request.sid)
@socketio.on('disconnect')
def handle_disconnect():
print('Client disconnected')
clients.remove(request.sid)
def send_message(client_id, data):
socketio.emit('output', data, room=client_id)
print('sending message "{}" to client "{}".'.format(data, client_id))
...
Then you would probably use this as follow:
main.py:
import web_app
import threading
import time
def main():
webapp_thread = threading.Thread(target=run_web_app)
webapp_thread.start()
while not web_app.clients:
print "waiting for client to connect"
time.sleep(1)
print "Connected..."
time.sleep(3)
print "Trying to print dummy message..."
web_app.send_message(web_app.clients[0], "Dummy")
...
But even if you try this, it will not work (which brings us to the second mistake).
Second, you are mixing eventlet with regular Python threads and it's not a good idea. the green threads that eventlet uses do not work well with regular threads. Instead, you should use green threads for all your threading needs.
One option which I found in the internet, is to monkey patch the Python standard library, so that threading, sockets, etc. are replaced with eventlet friendly versions. You can do this at the very top of your main.py script:
import eventlet
eventlet.monkey_patch()
After that it should work fine (I tried it on my own). Let me know if you have another problems...