| """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 |
| |
| <html> |
| <head> |
| <title>Error response</title> |
| </head> |
| <body> |
| <h1>Error response</h1> |
| <p>Error code %d. |
| <p>Message: %s. |
| </body> |
| </html> |
| """ |
| |
| |
| 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() |