"""PythonCGISlave.py This program can be used in two ways: - As a Python CGI script server for web servers supporting "Actions", like WebStar. - As a wrapper for a single Python CGI script, for any "compliant" Mac web server. See CGI_README.txt for more details. """ # # Written by Just van Rossum, but partly stolen from example code by Jack. # LONG_RUNNING = 1 # If true, don't quit after each request. import MacOS MacOS.SchedParams(0, 0) from MiniAEFrame import AEServer, MiniApplication import os import string import cStringIO import sys import traceback import mimetools __version__ = '3.2' slave_dir = os.getcwd() # log file for errors sys.stderr = open(sys.argv[0] + ".errors", "a+") def convertFSSpec(fss): return fss.as_pathname() # AE -> os.environ mappings ae2environ = { 'kfor': 'QUERY_STRING', 'Kcip': 'REMOTE_ADDR', 'svnm': 'SERVER_NAME', 'svpt': 'SERVER_PORT', 'addr': 'REMOTE_HOST', 'scnm': 'SCRIPT_NAME', 'meth': 'REQUEST_METHOD', 'ctyp': 'CONTENT_TYPE', } ERROR_MESSAGE = """\ Content-type: text/html
Error code %d.
Message: %s. """ def get_cgi_code(): # If we're a CGI wrapper, the CGI code resides in a PYC resource. from Carbon import Res import marshal try: code = Res.GetNamedResource('PYC ', "CGI_MAIN") except Res.Error: return None else: return marshal.loads(code.data[8:]) class PythonCGISlave(AEServer, MiniApplication): def __init__(self): self.crumblezone = 100000 * "\0" MiniApplication.__init__(self) AEServer.__init__(self) self.installaehandler('aevt', 'oapp', self.open_app) self.installaehandler('aevt', 'quit', self.quit) self.installaehandler('WWW\275', 'sdoc', self.cgihandler) self.code = get_cgi_code() self.long_running = LONG_RUNNING if self.code is None: print "%s version %s, ready to serve." % (self.__class__.__name__, __version__) else: print "%s, ready to serve." % os.path.basename(sys.argv[0]) try: self.mainloop() except: self.crumblezone = None sys.stderr.write("- " * 30 + '\n') self.message("Unexpected exception") self.dump_environ() sys.stderr.write("%s: %s\n" % sys.exc_info()[:2]) def getabouttext(self): if self.code is None: return "PythonCGISlave %s, written by Just van Rossum." % __version__ else: return "Python CGI script, wrapped by BuildCGIApplet and " \ "PythonCGISlave, version %s." % __version__ def getaboutmenutext(self): return "About %s\311" % os.path.basename(sys.argv[0]) def message(self, msg): import time sys.stderr.write("%s (%s)\n" % (msg, time.asctime(time.localtime(time.time())))) def dump_environ(self): sys.stderr.write("os.environ = {\n") keys = os.environ.keys() keys.sort() for key in keys: sys.stderr.write(" %s: %s,\n" % (repr(key), repr(os.environ[key]))) sys.stderr.write("}\n") def quit(self, **args): self.quitting = 1 def open_app(self, **args): pass def cgihandler(self, pathargs, **args): # We emulate the unix way of doing CGI: fill os.environ with stuff. environ = os.environ # First, find the document root. If we don't get a DIRE parameter, # we take the directory of this program, which may be wrong if # it doesn't live the actual http document root folder. if args.has_key('DIRE'): http_root = args['DIRE'].as_pathname() del args['DIRE'] else: http_root = slave_dir environ['DOCUMENT_ROOT'] = http_root if self.code is None: # create a Mac pathname to the Python CGI script or applet script = string.replace(args['scnm'], '/', ':') script_path = os.path.join(http_root, script) else: script_path = sys.argv[0] if not os.path.exists(script_path): rv = "HTTP/1.0 404 Not found\n" rv = rv + ERROR_MESSAGE % (404, "Not found") return rv # Kfrq is the complete http request. infile = cStringIO.StringIO(args['Kfrq']) firstline = infile.readline() msg = mimetools.Message(infile, 0) uri, protocol = string.split(firstline)[1:3] environ['REQUEST_URI'] = uri environ['SERVER_PROTOCOL'] = protocol # Make all http headers available as HTTP_* fields. for key in msg.keys(): environ['HTTP_' + string.upper(string.replace(key, "-", "_"))] = msg[key] # Translate the AE parameters we know of to the appropriate os.environ # entries. Make the ones we don't know available as AE_* fields. items = args.items() items.sort() for key, value in items: if key[0] == "_": continue if ae2environ.has_key(key): envkey = ae2environ[key] environ[envkey] = value else: environ['AE_' + string.upper(key)] = str(value) # Redirect stdout and stdin. saveout = sys.stdout savein = sys.stdin out = sys.stdout = cStringIO.StringIO() postdata = args.get('post', "") if postdata: environ['CONTENT_LENGTH'] = str(len(postdata)) sys.stdin = cStringIO.StringIO(postdata) # Set up the Python environment script_dir = os.path.dirname(script_path) os.chdir(script_dir) sys.path.insert(0, script_dir) sys.argv[:] = [script_path] namespace = {"__name__": "__main__"} rv = "HTTP/1.0 200 OK\n" try: if self.code is None: # we're a Python script server execfile(script_path, namespace) else: # we're a CGI wrapper, self.code is the CGI code exec self.code in namespace except SystemExit: # We're not exiting dammit! ;-) pass except: self.crumblezone = None sys.stderr.write("- " * 30 + '\n') self.message("CGI exception") self.dump_environ() traceback.print_exc() sys.stderr.flush() self.quitting = 1 # XXX we should return an error AE, but I don't know how to :-( rv = "HTTP/1.0 500 Internal error\n" # clean up namespace.clear() environ.clear() sys.path.remove(script_dir) sys.stdout = saveout sys.stdin = savein if not self.long_running: # quit after each request self.quitting = 1 return rv + out.getvalue() PythonCGISlave()