bottle - Ausgabe eines Commeandlinetools anzeigen

Django, Flask, Bottle, WSGI, CGI…
Antworten
kstieger
User
Beiträge: 6
Registriert: Freitag 18. Juni 2010, 07:02

Hallo!

Ich möchte mit bootle die Ausgabe eines Commandlinetools (stdout, stderr) im Browser live ausgeben. Das Commandlinetool läuft einige Zeit weil es Dateien transferiert, Metadaten produziert, ...
Der User soll über den Status jedes Tasks an jeder Datei informiert werden. Ähnlich so wie bei Webmin wenn man ein Programmpaket installiert.
Hat sowas schon jemand gemacht? (Unter PHP gibt es die möglichkeit exec() und flush())
Gibt es bereits Beispiele?
Wie würdet ihr das anlegen? Ajax, Pipe oder Datei?
Oder denke ich zu kompliziert.

Danke im Voraus für eure Tipps.
Benutzeravatar
snafu
User
Beiträge: 6754
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

In Python werden Programme meist mit Hilfe des subprocess-Moduls gestartet. Lies dir mal die Doku (s. Link) dazu durch. Dann erfährst du, wie man Programme aufruft und auch deren Ausgabeströme ausliest. Du müsstest halt noch ein paar Kniffe anwenden, um eine "Live-Ausgabe" zu erhalten. Dafür gibt es meines Wissens keine eingebaute Funktionalität, sollte aber trotzdem relativ leicht möglich sein. Zuletzt solltest du dir noch überlegen, ob du die Original-Ausgabe zeigen willst, so wie sie auf der Shell erscheinen würde oder ob du die Ausgabe intern überwachst und zu definierten Zeitpunkten (im Sinne von "erledigter Aufgabe") entsprechende Statusmeldungen in deiner App ausgibst. Vielleicht möchtest du ja auch mit einer Prozentangabe arbeiten, welche den Fortlauf des Prozesses anzeigt.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Live-Ausgabe im Browser ist nicht so leicht. Ich hab dazu mal was geschrieben: http://bottlepy.org/docs/dev/async.html

Kurz zusammengefasst: Wenn du Daten tröpfchenweise an den Browser schickst (yield-sleep-yield-sleep) und dabei die Verbindung offen hältst, ist ein Thread deines Servers blockiert. Das geht eventuell für kleine Intranet-Anwendungen, aber nicht für echte Internet Applikationen mit hunderten von Requests. Gevent hilft da, ist aber nicht die Lösung für alle Probleme.

Nativ betrachtet könnte man es aber in etwa so lösen (ungetestet):

Code: Alles auswählen

from gevent import monkey; monkey.patch_all()
from subprocess import Popen, PIPE, STDOUT
from bottle import route, run

@route('/run')
def run_cmd():
    yield ' '*1024 # Trick browsers into rendering the page early.
    p = Popen(['command'], stdout=PIPE, stderr=STDOUT)
    for line in p.stdout:
        yield line

run(server='gevent')
Bottle: Micro Web Framework + Development Blog
deets

Ich habe ein aehnliches Problem (logfile-ausgabe) einfach mit ajax-polling geloest. Natuerlich hat das gegenueber einem event-basierten Ansatz Latenz-Problemchen, aber je nach Anwendung ist das ja ziemlich wurscht.
Benutzeravatar
snafu
User
Beiträge: 6754
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Defnull: Das ist aber sehr naiv. Die Ausgabe dürfte immer erst dann im Browser erzeugt werden, wenn das Programm beendet wurde. Du kannst es ja mal folgendermaßen testen:

Code: Alles auswählen

#!/usr/bin/env python
import time

for dummy in xrange(10):
    print 'spam'
    time.sleep(1)
Führe dies mal als Subprocess aus, ersetze yield durch print und du wirst sehen, was ich meine. Falls du in der Python-Shell arbeitest, bekommst du möglicherweise erstmal alle Zeilen auf einmal angezeigt, wenn du die von dir gezeigten Befehle per Hand eingibst. Das liegt daran, weil beim Erzeugen des Popen()-Exemplars ja bereits der Code ausgeführt wurde und daher die Ausgabe schon komplett auf STDOUT liegt, ehe du die for-Schleife geschrieben hast. Ein Nutzen der Shell-History oder ein Ausführen als Funktion zeigt dann die zu erwartende Verzögerung von etwa 10 Sekunden. Kurz gesagt: So funktioniert es leider nicht. ;)
deets

@snafu

Das stimmt so verallgemeinert nicht. Den Effekt, den du beobachtest siehst du nur, weil dein Programm genug Daten produziert, und das OS halt einen Buffer vorhaelt.

Wenn die Ausgabe gross genug ist, dann kommt die auch im server-Prozess an, und kann mittels yield ausgegeben werden.

Code: Alles auswählen

import subprocess
def test():
    p = subprocess.Popen(['/tmp/command.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    for line in p.stdout:
        yield line
for line in test():
    print line
Das dazugehoerige command.py:

Code: Alles auswählen

#!/usr/bin/python
import sys
KB = "*" * 1024
count = 0
while True:
    sys.stdout.write(KB)
    sys.stdout.write("\n")
    sys.stdout.write("KBs: %i" % count)
    count += 1
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Lese ich dann die Doku von subprocess falsch?
bufsize, if given, has the same meaning as the corresponding argument to the built-in open() function: 0 means unbuffered, 1 means line buffered, any other positive value means use a buffer of (approximately) that size. A negative bufsize means to use the system default, which usually means fully buffered. The default value for bufsize is 0 (unbuffered).
Bottle: Micro Web Framework + Development Blog
BlackJack

@Defnull: Damit ist nur gesagt was "Deine" Seite der Pipe macht. Du hast keinen Einfluss darauf was der gestartete Prozess auf seiner Seite macht. Wenn man nicht explizit etwas ändert, hat `stdout` in der Regel einen Blockpuffer (wenn es nicht mit einem Terminal verbunden ist) und `stderr` einen Zeilenpuffer.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Ahh ok, da war mein Gedankenfehler.
Bottle: Micro Web Framework + Development Blog
kstieger
User
Beiträge: 6
Registriert: Freitag 18. Juni 2010, 07:02

Vielen Dank für die Anregungen!

@snafu: Subprocess war klar das hab ich in der Eile nicht geschrieben. Aber wahrscheinlich hast du recht, wenn es erstmal mit der Ausgabe klappt will man sicher in weiterer Folge einen iInformationen vom Fortlauf des Prozesses, das wird wahrscheinlich nur mit Ajax gehen.

@Defnull: Danke für die Anregung. Leider hab ich mich (zurzeit) auf Apache+wsgi festgelegt. Auch habe ich schon bei der Installation von gevent auf zB. Mac OS 10.6 so meine Probleme. Ich brauche unbedingt eine portable Lösung zwischen Linux und Mac OS. Darum habe ich mich auch für bottle entschieden.

Wahrscheinlich ist in meinem ein Ajax Polling die beste Wahl. Ich probier mal weiter und melde mich dann wieder.

Nochmal vielen Dank.
Antworten