是否可以将 python 子进程的输出实时流式传输到网页?

Posted

技术标签:

【中文标题】是否可以将 python 子进程的输出实时流式传输到网页?【英文标题】:Is it possible to stream output from a python subprocess to a webpage in real time? 【发布时间】:2014-04-20 20:38:48 【问题描述】:

提前感谢您的帮助。我对 python 还很陌生,对 html 也比较陌生。

最近几天我一直在尝试创建一个带有按钮的网页,以便在家庭服务器上执行任务。

目前我有一个 python 脚本可以生成一个带有按钮的页面:

(See the simplified example below. removed code to clean up post)

然后是运行所述命令并输出到页面上iframe 的python 脚本:

(See the simplified example below. removed code to clean up post)

这会在命令完成后输出整个完成的输出。我还尝试将 -u 选项添加到 python 脚本以无缓冲地运行它。我也尝试过使用 Python subprocess。如果有帮助,我正在运行的命令类型是 apt-get update,以及其他用于移动文件和修复文件夹权限的 Python 脚本。

当从普通的 Ubuntu 服务器终端运行时,它运行良好并实时输出,根据我的研究,它应该在命令运行时输出。

谁能告诉我哪里出错了?我应该使用其他语言来执行此功能吗?

编辑简化示例:

初始页面:

#runcmd.html

<head>
    <title>Admin Tasks</title>
</head>

<center>
<iframe src="/scripts/python/test/createbutton.py"   frameborder="0" ALLOWTRANSPARENCY="true"></iframe>
<iframe   frameborder="0" ALLOWTRANSPARENCY="true" name="display"></iframe> 
</center>

创建按钮的脚本:

cmd_page = '<form action="/scripts/python/test/runcmd.py" method="post" target="display" >' + '<label for="run_update">run updates</label><br>' + '<input align="Left" type="submit" value="runupdate" name="update" title="run_update">' + "</form><br>" + "\n"

print ("Content-type: text/html")
print ''
print cmd_page

应该运行命令的脚本:

# runcmd.py:

import os 
import pexpect
import cgi
import cgitb
import sys 

cgitb.enable()

fs = cgi.FieldStorage()

sc_command = fs.getvalue("update")

if sc_command == "runupdate":
    cmd = "/usr/bin/sudo apt-get update"

pd = pexpect.spawn(cmd, timeout=None, logfile=sys.stdout)

print ("Content-type: text/html")
print ''
print "<pre>"

line = pd.readline()  
while line:
    line = pd.readline()

我还没有测试过上面的简化示例,所以不确定它是否有效。

编辑:

简化的示例现在应该可以工作了。

编辑:

如果我打开浏览器访问 ip:8000,下面的 Imrans 代码会显示输出,就像它在终端中运行一样,这正是我想要的。除了我为我的网站使用 Apache 服务器和一个 iframe 来显示输出。我如何使用 Apache 做到这一点?

编辑:

我现在使用下面的 Imrans 示例将输出转到 iframe,但它似乎仍然在缓冲,例如:

If I have it (the script through the web server using curl ip:8000) run apt-get update in terminal it runs fine but when outputting to the web page it seems to buffer a couple of lines => output => buffer => ouput till the command is done.

But running other python scripts the same way buffer then output everything at once even with the -u flag. While again in terminal running curl ip:800 outputs like normal.

这就是它应该的工作方式吗?

编辑 2014 年 3 月 19 日:

我使用 Imrans 方式运行的任何 bash / shell 命令似乎都会近乎实时地输出到 iframe。但是,如果我通过它运行任何类型的 python 脚本,输出会被缓冲然后发送到 iframe。

我是否可能需要将运行 Web 服务器的脚本运行的 python 脚本的输出通过管道传输?

【问题讨论】:

是的。这是code example 这看起来很混乱。对我来说,它看起来像是向终端发送命令并在那里显示它?我想要的是在 iframe 中显示命令的输出,就像它出现在终端中一样。 @J.F.塞巴斯蒂安 您的情况比代码示例中的情况更简单,其中消息在浏览器中的javascript 代码和服务器上的子进程之间来回发送。回答你的问题:子进程的标准输出被发送到浏览器。 是的,我在命令完成后拥有的代码会在 iframe 中填充命令的整个输出。我想要的是用每一行填充它,就像你在终端中运行命令时看到的那样。但我不知道怎么做。 @J.F.塞巴斯蒂安 the code example that I provided 在子进程刷新其内部标准输出缓冲区后立即发送输出(示例 python 程序使用 -u 标志,因此没有缓冲区;每个字节都立即发送) 【参考方案1】:

您需要使用HTTP chunked transfer encoding 流式传输无缓冲的命令行输出。 CherryPy 的 wsgiserver 模块内置了对分块传输编码的支持。 WSGI 应用程序可以是返回字符串列表的函数,也可以是生成字符串的生成器。如果您使用生成器作为 WSGI 应用程序,CherryPy 将自动使用分块传输。

假设这是程序,其输出将被流式传输。

# slowprint.py

import sys
import time

for i in xrange(5):
    print i
    sys.stdout.flush()
    time.sleep(1)

这是我们的网络服务器。

2014 版(旧版 cherrpy)

# webserver.py

import subprocess
from cherrypy import wsgiserver


def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    proc = subprocess.Popen(['python', 'slowprint.py'], stdout=subprocess.PIPE)

    line = proc.stdout.readline()
    while line:
        yield line
        line = proc.stdout.readline()


server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 8000), application)
server.start()

2018版

#!/usr/bin/env python2
# webserver.py
import subprocess
import cherrypy

class Root(object):
    def index(self):
        def content():
            proc = subprocess.Popen(['python', 'slowprint.py'], stdout=subprocess.PIPE)
            line = proc.stdout.readline()
            while line:
                yield line
                line = proc.stdout.readline()
        return content()
    index.exposed = True
    index._cp_config = 'response.stream': True

cherrypy.quickstart(Root())

python webapp.py启动服务器,然后在另一个终端用curl发出请求,并观察输出逐行打印

curl 'http://localhost:8000'

【讨论】:

很抱歉,我说我对这个很陌生,所以对我来说太赤裸裸了。我在 ubuntu 服务器 13.10 上使用 apache 作为我的 Web 服务器。使用您的代码或类似的代码会输出到与按钮在同一页面上的 iframe 吗?如单击按钮 => 在服务器上运行子进程 => iframe 在发生时填充输出? @伊姆兰 你不必运行 Apache,这个脚本本身就是一个 Web 服务器。如果你有这个运行,你可以设置 iframe['src'] = 'localhost:8000' 使用你想作为查询字符串执行的命令,并在应用程序代码中从'environ'中解析查询字符串。 只是为了澄清我正在运行 apache,因为我在服务器上托管了一个基本网站。我试图有一个带有一些按钮的页面,这些按钮运行特定的任务(如 apt-get 更新)并在逐行发生时输出到 iframe。而不是在命令完成之后。有没有办法我可以用 apache 做到这一点? @伊姆兰 当我在终端中运行您的示例时,我得到的输出就像我在终端中输入了命令一样,我希望该输出出现在 iframe 中,就像命令在终端中运行一样。对不起,如果我听起来很混乱。 @伊姆兰 刚刚尝试了您的示例并将浏览器打开到 ip:8000,它完全显示了我想要的优秀。现在我如何用 apache 和 iframe 做到这一点?谢谢。 @伊姆兰

以上是关于是否可以将 python 子进程的输出实时流式传输到网页?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Python/C++/Java 将实时视频流式传输到网页的最佳方法

Python 子进程 readlines() 挂起

在将输出写入文件时使用多处理显示来自子进程的实时输出

Python:如何写入子进程的标准输入并实时读取其输出

将实时镜头从摄像机流式传输到 Unity3D

将实时镜头从摄像机流式传输到 Unity3D