radicz
radicz

Reputation: 93

Flask-SocketIO how to bridge communication of user with spawned process in event handler

My goal is to spawn process with another python script (which requires some shell interaction to enter auth code) after user clicks the button on the webpage.

The register.py script has two possible shell outcomes, either it asks to input auth code if user is not authenticated or just ends with no return message which indicates that user is already authenticated.

So far I am able to trigger this register.py file, and via socketio emit return state if the script asked for auth code or not and show it to user, but what I dont know, is how am I supposed to accept the auth code from user input on webpage and load it into register function again in case user is not authenticated already?

The code:

Flask app file - the code is a little tweaked Flask-SocketIO example

from threading import Lock
from flask import Flask, render_template, session, request
from flask_socketio import SocketIO, emit, disconnect

import pexpect


async_mode = None
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, async_mode=async_mode)
thread = None
thread_lock = Lock()


def register(username, phone):
    child = pexpect.spawn('python3 register.py -u ' + username + ' -p ' + phone )
    i = child.expect(['Please enter the code .*', pexpect.EOF])
    socketio.emit('my_response', {'data': 'pexpect result ' + str(i) + ' ' + child.after.decode(), 'count': 55555}, namespace='/test')


@app.route('/')
def index():
    return render_template('index.html')


@socketio.on("authenticate", namespace="/test")
def authenticate(message):
    global thread
    with thread_lock:
        if thread is None:
            thread = socketio.start_background_task(target=lambda: register("radicz", "+999999000999"))

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

HTML file

    <!DOCTYPE HTML>
<html>
<head>
    <title>Flask-SocketIO Test</title>
        <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>

    <script type="text/javascript" charset="utf-8">
        $(document).ready(function() {
            // Use a "/test" namespace.
            // An application can open a connection on multiple namespaces, and
            // Socket.IO will multiplex all those connections on a single
            // physical channel. If you don't care about multiple channels, you
            // can set the namespace to an empty string.
            namespace = '/test';

            // Connect to the Socket.IO server.
            // The connection URL has the following format:
            //     http[s]://<domain>:<port>[/<namespace>]
            var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);


            // Event handler for new connections.
            // The callback function is invoked when a connection with the
            // server is established.
            socket.on('connect', function() {
                socket.emit('my_event', {data: 'I\'m connected!'});
            });

            $("#register").on("click", function(){
                socket.emit("authenticate", {data: "cislo + jmeno"})
                console.log("register fired!");
            });

            // Event handler for server sent data.
            // The callback function is invoked whenever the server emits data
            // to the client. The data is then displayed in the "Received"
            // section of the page.
            socket.on('my_response', function(msg) {
                $('#log').append('<br>' + $('<div/>').text('Received #' + msg.count + ': ' + msg.data).html());
            });


            // Handlers for the different forms in the page.
            // These accept data from the user and send it to the server in a
            // variety of ways
            $('form#emit').submit(function(event) {
                socket.emit('my_event', {data: $('#emit_data').val()});
                return false;
            });
            $('form#disconnect').submit(function(event) {
                socket.emit('disconnect_request');
                return false;
            });
        });
    </script>
</head>
<body>
    <h1>Flask-SocketIO Test</h1>
    <button id="register">register</button>
    <h2>Send:</h2>
    <form id="emit" method="POST" action='#'>
        <input type="text" name="emit_data" id="emit_data" placeholder="Message">
        <input type="submit" value="Echo">
    </form>
    <form id="disconnect" method="POST" action="#">
        <input type="submit" value="Disconnect">
    </form>
    <h2>Receive:</h2>
    <div id="log"></div>
</body>
</html>

So the question is, how to get user provided auth code and load it into register function? I thought I could use some yielding technique, like emit if authentication is needed from server side, then emit event from client side, that triggers register function on backend again, but the function will continue from the point when it last time yielded, but I am not sure how to do it correctly, or if its feasible approach, or I am completely off, and there are some other techniques to accomplish this more easily?

Edit: or maybe this is the right way?

Upvotes: 0

Views: 802

Answers (1)

radicz
radicz

Reputation: 93

I didn't test it very well, but it seems that adding another socketio event handler into register function helps my purpose.

def register(username, phone):
    child = pexpect.spawn('python3 register.py -u ' + username + ' -p ' + phone )
    i = child.expect(['Please enter the code .*', pexpect.EOF])
    socketio.emit('my_response', {'data': 'pexpect result ' + str(i) + ' ' + child.after.decode(), 'count': 55555}, namespace='/test')

    @socketio.on("another_event", namespace="/test")
    def another_callback(message):
        # actual code that I wanted to run

Upvotes: 0

Related Questions