gpt4 book ai didi

python - 如何根据 HTTP 请求使用 Python 和 Flask 执行 shell 命令和流输出?

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:06:51 28 4
gpt4 key购买 nike

正在关注 this post ,我能够将日志文件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()

这种方法有两个问题。

  1. tail -f log 进程会在关闭网页后徘徊。访问http://localhost:5000/yield后会有n个tail进程n次
  2. 只能有1个客户端访问http://localhost:5000/yield一次

我的问题是,是否可以让 flask 在有人访问页面时执行 shell 命令,并在客户端关闭页面时终止命令?就像 tail -f log 之后的 Ctrl+C。如果没有,有哪些替代方案?为什么我一次只能让 1 个客户端访问该页面?

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

最佳答案

这里有一些代码可以完成这项工作。一些注意事项:

  1. 您需要检测请求何时断开连接,然后终止进程。下面的 try/except 代码将执行此操作。但是,在 inner() 结束后,Python 将尝试正常关闭套接字,这将引发异常(我认为它是 socket.error,根据 How to handle a broken pipe (SIGPIPE) in python? )。我找不到一种方法来干净地捕获这个异常;例如,如果我在 inner() 的末尾显式引发 StopIteration,并用 try/except socket.error block 包围它,它就不起作用。这可能是 Python 异常处理的限制。您可能可以在生成器函数中执行其他操作来告诉 Flask 中止流式传输而不尝试正常关闭套接字,但我还没有找到它。

  2. 您的主线程在 proc.stdout.readline() 期间阻塞,而 gevent.sleep() 来不及提供帮助。原则上 gevent.monkey.patch_all() 可以修补标准库,以便通常会阻塞线程的函数将控制权交给 gevent(参见 http://www.gevent.org/gevent.monkey.html)。但是,这似乎并没有修补 proc.stdout.readline()。下面的代码使用 gevent.select.select() 在产生新数据之前等待数据在 proc.stdout 或 proc.stderr 上可用。这允许 gevent 在等待时运行其他 greenlet(例如,服务其他网络客户端)。

  3. 网络服务器似乎缓冲了发送给客户端的前几 kB 数据,因此在将大量新行添加到 ./log 之前,您可能在网络浏览器中看不到任何内容。在那之后,它似乎立即发送新数据。不确定如何立即发送请求的第一部分,但这可能是流媒体服务器的一个很常见的问题,所以应该有一个解决方案。对于自行快速终止的命令,这不是问题,因为它们的完整输出将在终止后发送。

您还可以在 https://mortoray.com/2014/03/04/http-streaming-of-command-output-in-python-flask/ 找到有用的东西.

代码如下:

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()

关于python - 如何根据 HTTP 请求使用 Python 和 Flask 执行 shell 命令和流输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38630421/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com