可以实时将Python子进程的输出流式传输到网页吗?

5 投票
1 回答
10599 浏览
提问于 2025-04-17 22:26

非常感谢大家的帮助。我对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)

这个脚本在命令执行完后会输出所有的结果。我还尝试过给Python脚本加上-u选项,这样可以让它不缓存输出。我也试过使用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()

我还没有测试上面的简化例子,所以不确定它是否能正常工作。

编辑:

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

编辑:

Imran的代码,如果我在浏览器中打开ip:8000,它的输出就像在终端中运行一样,这正是我想要的。不过我在我的网站上使用的是Apache服务器,并且用iframe来显示输出。我该如何在Apache中做到这一点呢?

编辑:

现在我已经通过Imran的例子让输出显示在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日:

我用Imran的方法运行的任何bash/shell命令似乎都能在接近实时的情况下输出到iframe。但如果我通过它运行任何Python脚本,输出就会被缓存,然后再发送到iframe。

我是否需要把运行的Python脚本的输出通过管道传递给运行网页服务器的脚本呢?

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版本(旧版CherryPy)

# 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'

撰写回答