能否实时将Python子进程的输出流传输到网页?

5

提前感谢您的帮助。我对Python不太熟悉,对HTML更是新手。

最近几天,我一直在尝试创建一个网页,其中包含按钮,可以在家庭服务器上执行任务。

目前,我有一个生成带有按钮的页面的Python脚本:

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

然后是一个运行该命令并将输出显示在页面上的Python脚本:iframe
(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" width="650" height="800" frameborder="0" ALLOWTRANSPARENCY="true"></iframe>
<iframe width="650" height="800" 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()

我还没有测试上述简化的例子,所以不确定其是否可用。
编辑:
简化的例子现在应该可以工作了。
编辑:
Imrans下面的代码,如果我打开一个浏览器到ip:8000,它会显示输出,就像在终端中运行一样,这正是我想要的。但是我正在使用Apache服务器作为我的网站和一个iframe来显示输出。我该怎么做?
编辑:
现在我已经使用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脚本的输出PIPE到iframe吗?


是的。这里是代码示例 - jfs
你的情况甚至比代码示例中更简单,其中消息在浏览器中的JavaScript代码和服务器上的子进程之间来回发送。回答你的问题:子进程的标准输出被发送到浏览器。 - jfs
是的,使用我在命令完成后的代码后,iframe 将填充来自命令的整个输出。我想要的是它填充每一行,就像您在终端中运行命令时看到的那样。但我不知道该怎么做。@J.F.Sebastian - ButtzyB
我提供的编程示例代码(https://dev59.com/gmgt5IYBdhLWcg3w5xg_#11729467)会在子进程刷新其内部stdout缓冲区后立即发送输出(示例Python程序使用“-u”标志,因此没有缓冲区;每个字节都会立即发送)。 - jfs
但是那个例子不是创建了一个新的 Web 服务器吗?我已经在使用 Apache 服务器运行我的网站了,那么我该如何使用 Apache 而不是运行一个单独的服务器呢?也就是说,在我的 Apache 服务器网站上转到页面 => 点击按钮(运行命令)=> 输出到我的 iframe。@J.F.Sebastian - ButtzyB
显示剩余15条评论
1个回答

7
你需要使用 HTTP分块传输编码 来流式传输无缓冲的命令行输出。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随着输出的发生而填充?@Imran - ButtzyB
1
你不必运行Apache,这个脚本本身就是一个Web服务器。如果你已经运行了它,你可以使用所需执行的命令作为查询字符串设置iframe['src']='http://localhost:8000',并在应用程序代码中从'environ'解析查询字符串。 - Imran
只是想澄清一下,我正在运行Apache,因为我正在服务器上托管一个基本网站。我试图创建一个带有一些按钮的页面,这些按钮运行特定任务(例如apt-get update),并在发生时逐行输出到iframe中,而不是在命令完成后。我能否通过Apache实现这个目标?@Imran - ButtzyB
当我在终端中运行您的示例时,输出与我在终端中输入命令时得到的相同。我希望该输出出现在 iframe 中,就像命令在终端中运行一样。如果我的表述有些混乱,请见谅。 @Imran - ButtzyB
1
刚刚尝试了你的示例并打开了浏览器到ip:8000,它完全显示了我想要的。太棒了。现在我该如何使用apache和iframe实现这个效果呢?谢谢。@Imran - ButtzyB
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接