Bis vor einiger Zeit habe ich meine Programme in Perl geschrieben, jetzt aber, in einem neuen Job, habe ich begonnen Python zu programmieren. Ich komme damit recht gut klar, nur die objektorientierte Programmierung habe ich noch nicht wirklich aufgenommen.
Ich habe versucht, eines meiner CGI-Skripte aus Perl in Python umzuschreiben. Dieses Skript lief unter Perl, so wie ich es haben wollte, aber jetzt unter Python bekomme ich es nicht richtig zum laufen. Ich bin mit meiner Weisheit am Ende und würde Euch gerne um Hilfe bitten. Das Problem, das ich habe, hängt mit einem "fork"-Befehl und den Eigenschaften von STDERR, STDIN und STDOUT auf der Betriebssystem-Ebene zusammen - und da fehlt mir doch das intimere Wissen.
Die Grundidee des CGI-Skripts ist diese: Gelegentlich setze ich Web-Services auf, die große Mengen an Daten verarbeiten müssen und daher lange Prozessierungs-Zeiten haben. Damit nun der Benutzer des Services nicht vor dem (untätigen) Bildschirm sitzt und sich fragt, ob wohl das Programm abgestürzt ist (weil schon seit Ewigkeiten nichts mehr passiert ist) baue ich eine "Bitte Warten"-Seite auf, auf der ich die abgelaufene Zeit immer wieder aktualisiere oder sogar Status-Meldungen über das, was gerade bei der Prozessierung passiert, abliefere. Diese Seite wird dann erst umgeschaltet, wenn ich die Ergebnisse präsentieren kann.
Das kann man natürlich über eine Serie von Skriptes machen. Ich wollte eine elegantere Lösung, in der alles in nur einem Skript geregelt wird. Das kann man dadurch realisieren, indem man zunächst ein Web-Formular aufbaut, in dem alle nötigen Eingaben gesammelt werden. Wenn der Benutzer die Eingaben abschickt ruft sich das Skript selbst auf - mit verändertem Parameter-Satz - "forked" den Prozess, berechnet die angefragten Daten in einem Ast des "fork" und produziert (und "refreshed") die "Bitte Warten"-Seite im anderen "fork"-Ast, bis die Daten-Produktion beendet ist. Dann erst wird die angezeigte HTML-Seite auf eine "Ergebnis"-Seite umgestellt und die "forks" beendet.
Ich hatte das Problem schon in der Perl-Version meines Demo-Skriptes (das ich auch nur mit der Hilfe eines Forums lösen konnte): die Äste des "fork" sind anscheinend nicht unabhängig, die Standard I/O muß geschlossen werden, sonst hängt die "Bitte Warten"-Seite, bis der Daten-Prozess fertig ist, fängt dann für eine Sekunde an zu zählen, um dann sofort auf die "Resultate" umzuschalten. Wenn man aber alle Standard I/O schließt und wieder öffnet (sonst kann die HTML-Seite ja nicht erzeugt werden) hängt der Daten-Prozess.
Hier der Quell-Code des Demo-Programmes (mit englischen Kommentaren):
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os, re, sys, cgi, time
import cgitb; cgitb.enable()
Change = "Apr-09-2010"
# ### START SUBROUTINE SECTION ############################################################################ #
def ErrorMsg(Stat,Mess): #** Creates an "Error" message; ***************************************************#
Html = "Content-Type: text/html\n\n"
Html += "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n\n<html>\n"
Html += "<head>\n <title>DEMO Error Message</title>\n"
Html += " <!-- Last changes: %s -->\n</head>\n\n" % (Change)
Html += "<body>\n<center>\n\n"
Html += "<span style=\"font-size:22pt; color:Green\">DEMO ERROR MESSAGE</span><br>\n<br>\n"
if (Stat == "0"):
Html += "!!! System error - could NOT fork process !!!<br>\n"
elif (Stat == "1"):
Html += "!!! System error - could NOT write \"HTML\" file '%s'!!!<br>\n" % (Mess)
Html += "</center>\n</body>\n</html>\n"
print Html
sys.exit(0);
def StartPage(): #** Creates the "Start Page"; *************************************************************#
Html = "Content-Type: text/html\n\n"
Html += "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n\n<html>\n"
Html += "<head>\n <title>DEMO START PAGE</title>\n"
Html += " <!-- Last changes: %s -->\n</head>\n\n" % (Change)
Html += "<body>\n<center>\n<br>\n"
Html += "<span style=\"font-size:22pt; color:Green\">DEMO START PAGE</span><br>\n<br>\n"
Html += "<form action=\"%s\" method=\"post\">\n" % (CGIPath)
Html += "<input type=\"hidden\" name=\"STAT\" value=\"1\">\n"
Html += "<b>TYPE A "TICK NUMBER":</b><br>\n"
Html += "<input type=\"text\" name=\"Tick\" size=\"10\"><br><br>\n"
Html += "<input type=\"submit\" value=\"PROCESS\"> <input type=\"reset\" value=\"RESET FORM\">\n"
Html += "</form>\n</center>\n</body>\n</html>\n"
return Html
def ProcessPage(): #** Creates the "Process Page"; *********************************************************#
# #
# If both STDERR calls are run, after sending the form page the program switches to the waiting page and #
# counts seconds - without switching to the "Results Page" #
# #
# The same happens if only the first STDERR call is run #
# #
# If just the second STDERR call is run after sending the form page the program switches to the initial #
# "Waiting Page", then stays there until the long process is finished, and AFTER that switches to the #
# "Waiting Page", counting seconds - without switching to the "Results Page" #
# #
# If none of them is called after sending the form page the program switches to the initial "Waiting Page", #
# then stays there until the long process is finished, and then switches to the "Results Page". This is #
# what is intended, except for that between the switch to the initial "Waiting Page" and the final switch #
# to the "Results Page" seconds should have been counted #
# #
# None of the STDERR calls makes it into the "error.log" - which doesn't surprise, because STDERR is #
# obviously closed #
# #
## sys.stderr.write("%s: CHILD: PROCESS PAGE started\n" % (CGIPath)) ### First STDERR call ###
Html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<html>\n"
Html += "<head>\n <title>DEMO PROCESS PAGE</title>\n"
Html += " <meta HTTP-EQUIV=\"Expires\" CONTENT=\"NOW\">\n</head>\n\n"
Html += "<body>\n<center>\n<br>\n"
Html += "<span style=\"font-size:22pt; color:Green\">DEMO PROCESS PAGE</span><br>\n<br>\n"
Html += "<span style=\"font-size:22pt; color:Violet\">...THAT'S THE TEST...</span><br>\n<br>\n"
Html += "...<a href=\"%s\">go back to the DEMO start page</a>...<br>\n" % (CGIPath)
Html += "</center>\n</body>\n</html>\n"
#---- >>>>> ------------------------------------------------------------------------------------------------#
time.sleep(float(Ticks)) # Simulation of a long process #
#---- <<<<< ------------------------------------------------------------------------------------------------#
## sys.stderr.write("%s: CHILD: PROCESS PAGE created\n" % (CGIPath)) ### Second STDERR call ###
return Html
def WaitingPage(Url,Tact): #** Creates the "Waiting Page"; *************************************************#
Html = "Content-Type: text/html\n\n"
Html += "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<html>\n"
Html += "<head>\n <title>...DEMO is running...</title>\n"
Html += " <META HTTP-EQUIV=\"refresh\" content=\"%s; url=%s\">\n</head>\n\n" % (Tact,Url)
Html += "<body>\n<center>\n<br>\n"
Html += "<span style=\"font-size:22pt; color:Red\">...DEMO is running...</span><br>\n"
Html += "<br>\n<span style=\"font-size:22pt\">Don't go BACK!</span><br>\n<br>\n"
Html += "<span style=\"font-size:20pt; color:Blue\">Elapsed Time: %s</span><br>\n" % (Elapsed)
Html += "</center>\n</body>\n</html>\n"
return Html
def CurrTime(): #** Creating a string with the current time; ***********************************************#
Time = time.time()
(Yr,Mo,Da,Hr,Mi,Sc,WD,DY,ST) = time.localtime(Time)
LocTime = '%02d:%02d:%02d' % (Hr,Mi,Sc)
return LocTime
def ElapsedTime(): #** Calculates the elapsed time *********************************************************#
Time = time.time()
(Yr,Mo,Da,Hr,Mi,Sc,WD,DY,ST) = time.localtime(Time)
CTime = (((Hr * 60) + Mi) * 60) + Sc
Digits = STime.split(":")
Hr = int(Digits[0])
Mi = int(Digits[1])
Sc = int(Digits[2])
ITime = (((Hr * 60) + Mi) * 60) + Sc
if (CTime < ITime):
CTime += 86400
ETime = CTime - ITime
Sc = ETime % 60
ETime = (ETime - Sc) / 60
Mi = ETime % 60
Hr = (ETime - Mi) / 60
ETime = "%02d:%02d:%02d" % (Hr,Mi,Sc)
return ETime
# ### END SUBROUTINE SECTION ############################################################################## #
# ### START MAIN PROGRAM ################################################################################## #
CGIPath = os.environ['SCRIPT_NAME']
TmpAbs = "/var/www/tmp" # Absolute 'tmp' directory path #
TmpVrt = "/tmp" # Virtual 'tmp' directory path #
HtmOut = "%s/demo.html" % (TmpAbs) # HTML Output File #
HtmTmp = "%s/demo.html.tmp" % (TmpAbs) # "Temporary" HTML Output File #
Form = cgi.FieldStorage()
STime = Form.getvalue('Time',"00:00:00") # "Process Start Time" #
Ticks = Form.getvalue('Tick',0) # "Seconds to wait" #
Status = Form.getvalue('STAT',"0") # "Process Status" #
sys.stdout = os.fdopen(sys.stdout.fileno(),'w',0)
Elapsed = "00:00:00" # Initialize "Elapsed Time" #
##### Status = 0: Create the "Start Page" ###################################################################
if (Status == "0"):
print StartPage()
##### Status = 1: Fork the program, run the long process and create the "Results Page" in CHILD #############
##### and an "Initial Waiting Page" in PARENT ###################################################
elif (Status == "1"):
if ((os.path.exists(HtmOut)) and (os.path.getsize(HtmOut) > 0)):
os.unlink(HtmOut)
try:
HTMTMP = open(HtmTmp,"w")
except IOError:
ErrorMsg("1",HtmTmp)
try:
ForkVal = os.fork()
except IOError:
ErrorMsg("0","")
if (ForkVal == 0): # CHILD process #
# If STDOUT, STDERR and STDIN are NOT closed the program remains in the "Start Page" until the long #
# process is finished, then switches to the "Waiting Page" and immediately switches to the "Results Page" #
sys.stdout.close() # Close STDOUT #
sys.stderr.close() # Close STDERR #
sys.stdin.close() # Close STDIN #
# Although STDERR is closed the attempt to write into STDERR in 'ProcessPage' function influences the #
# program behaviour drastically (look into the source code above) #
Html = ProcessPage()
HTMTMP.write(Html)
HTMTMP.close()
os.rename(HtmTmp,HtmOut)
else: # PARENT process #
Elapsed = "00:00:00"
Url = "%s?STAT=2&Time=%s" % (CGIPath,CurrTime())
print WaitingPage(Url,1)
##### Status = 2: Create the "Waiting Page" #################################################################
elif (Status == "2"):
Elapsed = ElapsedTime()
if ((os.path.exists(HtmOut)) and (os.path.getsize(HtmOut) > 0)):
Url = "%s/demo.html" % (TmpVrt)
print WaitingPage(Url,0)
else:
Url = "%s?STAT=2&Time=%s" % (CGIPath,STime)
print WaitingPage(Url,1)
sys.exit(0)
# ### END MAIN PROGRAM #################################################################################### #
Noch eine Bitte: mir wurde auch schon gesagt, dass ich das Problem mit Ajax lösen kann, aber mir geht es um dieses Skript, auch weil ich das eigentliche Problem verstehen will (zumindest soweit das meine beschränkten geistigen Mittel zulassen ), und nicht noch eine weitere Programmiersprache / Technologie lernen will. Auch habe ich nur sehr beschränkten Einblick in die objektorientierte Programmierung. Wenn mir jemand also zeigen kann, wie ich mit geringen Änderungen das hier vorgestellte Beispielskript zum Laufen bringen kann, wäre ich ihm/ihr sehr dankbar.
Mit meinen besten Grüßen und Wünschen
Euer
chanklaus