Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to stream output from a python subprocess to a webpage in real time?

Thanks in advance for any help. I am fairly new to python and even newer to html.

I have been trying the last few days to create a web page with buttons to perform tasks on a home server.

At the moment I have a python script that generates a page with buttons:

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

Then a python script which runs said command and outputs to an iframe on the page:

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

This does output the entire finished output after the command is finished. I have also tried adding the -u option to the python script to run it unbuffered. I have also tried using the Python subprocess as well. If it helps the types of commands I am running are apt-get update, and other Python scripts for moving files and fixing folder permissions.

And when run from normal Ubuntu server terminal it runs fine and outputs in real time and from my research it should be outputting as the command is run.

Can anyone tell me where I am going wrong? Should I be using a different language to perform this function?

EDIT Simplified example:

initial page:

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

script that creates button:

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

script that should run command:

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

I havent tested the above simplified example so unsure if its functional.

EDIT:

Simplified example should work now.

Edit:

Imrans code below if I open a browser to the ip:8000 it displays the output just like it was running in a terminal which is Exactly what I want. Except I am using Apache server for my website and an iframe to display the output. How do I do that with Apache?

edit:

I now have the output going to the iframe using Imrans example below but it still seems to buffer for example:

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.

Is that just how it is supposed to work?

EDIT 19-03-2014:

any bash / shell command I run using Imrans way seems to output to the iframe in near realtime. But if I run any kind of python script through it the output is buffered then sent to the iframe.

Do I possibly need to PIPE the output of the python script that is run by the script that runs the web server?

like image 347
ButtzyB Avatar asked Mar 15 '14 12:03

ButtzyB


People also ask

How do I get output to run from subprocess?

To capture the output of the subprocess. run method, use an additional argument named “capture_output=True”. You can individually access stdout and stderr values by using “output. stdout” and “output.

What does Python subprocess return?

The Python subprocess call() function returns the executed code of the program. If there is no program output, the function will return the code that it executed successfully. It may also raise a CalledProcessError exception.

Is subprocess thread safe Python?

This class is not thread safe. See also the Subprocess and Threads section.

How do I use OS Popen in Python?

Python method popen() opens a pipe to or from command. The return value is an open file object connected to the pipe, which can be read or written depending on whether mode is 'r' (default) or 'w'. The bufsize argument has the same meaning as in open() function.


1 Answers

You need to use HTTP chunked transfer encoding to stream unbuffered command line output. CherryPy's wsgiserver module has built-in support for chunked transfer encoding. WSGI applications can be either functions that return list of strings, or generators that produces strings. If you use a generator as WSGI application, CherryPy will use chunked transfer automatically.

Let's assume this is the program, of which the output will be streamed.

# slowprint.py

import sys
import time

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

This is our web server.

2014 Version (Older cherrpy Version)

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

#!/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())

Start the server with python webapp.py, then in another terminal make a request with curl, and watch output being printed line by line

curl 'http://localhost:8000'
like image 183
Imran Avatar answered Nov 02 '22 04:11

Imran