Flask、flask-socketio を使用して、ブラウザーで「コマンド文字列」(ping 8.8.8.8 など) を受け入れるアプリケーションを実装し、サーバーがそれを実行して、コマンドの出力をリアルタイムでブラウザーに返します。
私の実装は以下の通りです。
クライアント側は「exec_event」イベントをサーバーに発行し、サーバーはコマンドを実行し、コマンドによって新しい出力が生成されると、クライアントに「exec_response」イベントを継続的に発行します。
コマンドを終了したい場合、クライアントは「ctrlc_event」をサーバーに送信することもできます。
私が得た問題は次のとおりです。
サーバーは、前の「exec_event」を処理しているとき、「ctrlc_event」を処理できません。サーバーで「ping 8.8.8.8」などの長時間実行コマンドを直接強制終了すると、サーバーは「ctrlc_event」の処理を開始します。
誰かが私に理由を指摘したり、方向性を教えてもらえますか?
サーバー側コード
from gevent import monkey
monkey.patch_all()
from flask import Flask, render_template, session, Response
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask.ext.socketio import SocketIO, emit
import os
import time
from subprocess import Popen, PIPE
class ExecForm(Form):
command = StringField('Command: ', validators=[DataRequired()])
submit = SubmitField('Execute')
class CtrlCForm(Form):
submit = SubmitField('Ctrl C')
app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'hard-to-guess'
socketio = SocketIO(app)
@app.route('/')
def index():
exec_form = ExecForm()
ctrlc_form = CtrlCForm()
return render_template('index.html',
exec_form=exec_form,
ctrlc_form=ctrlc_form)
@socketio.on('exec_event', namespace='/test')
def execute(message):
command = message['data']
process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE,
close_fds=True, preexec_fn=os.setsid)
session['pid'] = process.pid
for out in iter(process.stdout.readline, ''):
out = '<pre>' + out + '</pre>'
emit('exec_response', {'data': out})
time.sleep(0.001)
out = "Command finished." + '<br /><br />'
emit('exec_terminate', {'data': out})
@socketio.on('ctrlc_event', namespace='/test')
def ctrlc(message):
print("Received ctrlc")
# do something to kill the process
# finally emit an event.
out = "Process killed." + '<br /><br/>'
emit('ctrlc_response', {'data': out})
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', port=8080)
クライアントコードは
<html>
<head>
<title>Example</title>
<script type="text/javascript" src='//code.jquery.com/jquery-1.11.0.min.js'></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/0.9.16/socket.io.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
var socket = io.connect('http://' + document.domain + ':' + location.port + '/test');
$('#exec_form').submit(function(event) {
socket.emit('exec_event', {data: $('#exec_input').val()});
return false;
});
$('#ctrlc_form').submit(function(event) {
socket.emit('ctrlc_event', {data: 'nothing'});
return false;
});
socket.on('exec_response', function(msg) {
$('#result').append(msg.data);
});
socket.on('exec_terminate', function(msg) {
$('#result').append(msg.data);
});
socket.on('ctrlc_response', function(msg) {
$('#result').append(msg.data);
});
});
</script>
<style>
.result {
overflow-y: scroll;
width: 100%;
height: 400px;
border: 4px solid red;
}
.result pre {
margin: 2px;
}
</style>
</head>
<body>
<h3>Flask Example</h3>
<form id="exec_form" method='POST' action='#' target='result'>
{{ exec_form.command.label }}
{{ exec_form.command(id="exec_input", placeholder="command", size=100) }}
{{ exec_form.submit(id="exec_btn") }}
</form>
<form id="ctrlc_form" method='POST' action='#' target='result'>
{{ ctrlc_form.submit(id="ctrlc_btn") }}
</form>
<div id="result" class="result"></div>
</body>
</html>