如何在HTTP请求时使用Python和Flask执行shell命令和流输出?

如何在HTTP请求时使用Python和Flask执行shell命令和流输出?,python,linux,flask,subprocess,Python,Linux,Flask,Subprocess,接下来,我可以tail-f将日志文件添加到网页: from gevent import sleep from gevent.wsgi import WSGIServer import flask import subprocess app = flask.Flask(__name__) @app.route('/yield') def index(): def inner(): proc = subprocess.Popen( ['ta

接下来,我可以
tail-f
将日志文件添加到网页:

from gevent import sleep
from gevent.wsgi import WSGIServer
import flask
import subprocess

app = flask.Flask(__name__)

@app.route('/yield')
def index():
    def inner():
        proc = subprocess.Popen(
                ['tail -f ./log'],
                shell=True,
                stdout=subprocess.PIPE
                )
        for line in iter(proc.stdout.readline,''):
            sleep(0.1)
            yield line.rstrip() + '<br/>\n'

    return flask.Response(inner(), mimetype='text/html')

http_server = WSGIServer(('', 5000), app)
http_server.serve_forever()
从gevent导入睡眠
从gevent.wsgi导入WSGIServer
进口烧瓶
导入子流程
app=烧瓶。烧瓶(\uuuuu名称\uuuuuuu)
@应用程序路径(“/yield”)
def index():
def inner():
proc=子进程Popen(
['tail-f./log'],
shell=True,
stdout=子流程.PIPE
)
对于国际热核实验堆(iter)中的线路(程序标准读线“”):
睡眠(0.1)
屈服线.rstrip()+'
\n' return flask.Response(inner(),mimetype='text/html') http_server=WSGIServer(“”,5000),应用程序) http_server.serve_forever()
这种方法有两个问题

  • 关闭网页后,
    tail-f log
    进程将暂停。访问n次后将有n个尾进程
  • 一次只能有一个客户端访问
  • 我的问题是,当有人访问某个页面时,是否可以让flask执行shell命令,并在客户端关闭该页面时终止该命令?在
    tail-f log
    之后使用Ctrl+C。如果没有,还有什么选择? 为什么我一次只能有一个客户端访问页面


    注意:我正在研究启动/停止任意shell命令的一般方法,而不是特别跟踪文件。一些注意事项:

  • 您需要检测请求何时断开连接,然后终止进程。下面的try/except代码将执行此操作。但是,在internal()结束后,Python将尝试正常关闭套接字,这将引发一个异常(我认为是socket.error,per)。我找不到一种方法来清晰地捕捉这个异常;e、 例如,如果我在inner()的末尾显式地提出StopIteration,并用try/except socket.error块包围它,那么它就不起作用。这可能是Python异常处理的一个限制。在generator函数中,您可能还可以执行其他操作,在不尝试正常关闭套接字的情况下告诉flask中止流,但我还没有找到它

  • 您的主线程在proc.stdout.readline()期间阻塞,而gevent.sleep()来得太迟,无法提供帮助。原则上,gevent.monkey.patch_all()可以修补标准库,这样通常会阻止线程的函数将控制权交给gevent(请参阅)。但是,这似乎并没有修补proc.stdout.readline()。下面的代码使用gevent.select.select()等待proc.stdout或proc.stderr上的数据变为可用,然后生成新数据。这允许gevent在等待时运行其他Greenlet(例如,服务其他web客户端)

  • web服务器似乎对发送到客户端的前几kB数据进行缓冲,因此在向./log添加了许多新行之前,您可能无法在web浏览器中看到任何内容。之后,它似乎会立即发送新数据。不确定如何立即发送请求的第一部分,但这可能是流式服务器的一个常见问题,因此应该有一个解决方案。对于自行快速终止的命令来说,这不是问题,因为它们的完整输出将在终止后发送

  • 你也可以在网站上找到一些有用的东西

    代码如下:

    from gevent.select import select
    from gevent.wsgi import WSGIServer
    import flask
    import subprocess
    
    app = flask.Flask(__name__)
    
    @app.route('/yield')
    def index():
        def inner():
            proc = subprocess.Popen(
                    ['tail -f ./log'],
                    shell=True,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE
                    )
            # pass data until client disconnects, then terminate
            # see https://stackoverflow.com/questions/18511119/stop-processing-flask-route-if-request-aborted
            try:
                awaiting = [proc.stdout, proc.stderr]
                while awaiting:
                    # wait for output on one or more pipes, or for proc to close a pipe
                    ready, _, _ = select(awaiting, [], [])
                    for pipe in ready:
                        line = pipe.readline()
                        if line:
                            # some output to report
                            print "sending line:", line.replace('\n', '\\n')
                            yield line.rstrip() + '<br/>\n'
                        else:
                            # EOF, pipe was closed by proc
                            awaiting.remove(pipe)
                if proc.poll() is None:
                    print "process closed stdout and stderr but didn't terminate; terminating now."
                    proc.terminate()
    
            except GeneratorExit:
                # occurs when new output is yielded to a disconnected client
                print 'client disconnected, killing process'
                proc.terminate()
    
            # wait for proc to finish and get return code
            ret_code = proc.wait()
            print "process return code:", ret_code
    
        return flask.Response(inner(), mimetype='text/html')
    
    http_server = WSGIServer(('', 5000), app)
    http_server.serve_forever()
    
    从gevent.select导入选择
    从gevent.wsgi导入WSGIServer
    进口烧瓶
    导入子流程
    app=烧瓶。烧瓶(\uuuuu名称\uuuuuuu)
    @应用程序路径(“/yield”)
    def index():
    def inner():
    proc=子进程Popen(
    ['tail-f./log'],
    shell=True,
    stdout=子流程.PIPE,
    stderr=子流程.PIPE
    )
    #传递数据,直到客户端断开连接,然后终止
    #看https://stackoverflow.com/questions/18511119/stop-processing-flask-route-if-request-aborted
    尝试:
    等待=[proc.stdout,proc.stderr]
    等待时:
    #等待一个或多个管道上的输出,或等待proc关闭管道
    就绪,u,u=选择(等待,[],[])
    对于准备就绪的管道:
    line=pipe.readline()
    如果行:
    #要报告的一些输出
    打印“发送行:”,行。替换('\n','\\n')
    屈服线.rstrip()+'
    \n' 其他: #EOF,管道被proc关闭 等待。移除(管道) 如果proc.poll()为无: 打印“进程已关闭标准输出和标准输出,但未终止;正在终止。” 终止程序() 除发电退出外: #将新输出提供给断开连接的客户端时发生 打印“客户端断开连接,终止进程” 终止程序() #等待proc完成并获取返回代码 ret_code=proc.wait() 打印“流程返回代码:”,返回代码 return flask.Response(inner(),mimetype='text/html') http_server=WSGIServer(“”,5000),应用程序) http_server.serve_forever()
    以下是一些应该完成这项工作的代码。一些注意事项:

  • 您需要检测请求何时断开连接,然后终止进程。下面的try/except代码将执行此操作。但是,在internal()结束后,Python将尝试正常关闭套接字,这将引发一个异常(我认为是socket.error,per)。我找不到一种方法来清晰地捕捉这个异常;e、 例如,如果我在inner()的末尾显式地提出StopIteration,并用try/except socket.error块包围它,那么它就不起作用。这可能是Python异常处理的一个限制。在generator函数中,您可能还可以执行其他操作,在不尝试正常关闭套接字的情况下告诉flask中止流,但我还没有找到它

  • 您的主线程正在阻塞dur