diff options
-rw-r--r-- | Mac/Tools/CGI/BuildCGIApplet.py | 74 | ||||
-rw-r--r-- | Mac/Tools/CGI/BuildCGIApplet.rsrc | bin | 0 -> 460 bytes | |||
-rw-r--r-- | Mac/Tools/CGI/CGI_README.txt | 66 | ||||
-rw-r--r-- | Mac/Tools/CGI/PythonCGISlave.py | 243 | ||||
-rw-r--r-- | Mac/Tools/CGI/PythonCGISlave.rsrc | bin | 0 -> 460 bytes |
5 files changed, 383 insertions, 0 deletions
diff --git a/Mac/Tools/CGI/BuildCGIApplet.py b/Mac/Tools/CGI/BuildCGIApplet.py new file mode 100644 index 0000000..29783c2 --- /dev/null +++ b/Mac/Tools/CGI/BuildCGIApplet.py @@ -0,0 +1,74 @@ +"""BuildCGIApplet.py -- Create a CGI applet from a Python script. + +Specilized version of BuildApplet, enabling Python CGI scripts to be +used under Mac web servers like WebStar. The __main__ program is +PythonCGISlave.py, which provides a compatibility layer, emulating +Unix-style CGI scripts. See CGI_README.txt for details. +""" + +import sys +import os +import macfs +import MacOS +import Res +import EasyDialogs +import buildtools +import py_resource + + +def main(): + try: + buildcgiapplet() + except buildtools.BuildError, detail: + EasyDialogs.Message(detail) + + +def buildcgiapplet(): + buildtools.DEBUG=1 + + # Find the template + # (there's no point in proceeding if we can't find it) + + template = buildtools.findtemplate() + wrapper = os.path.join(sys.exec_prefix, ":Mac:Tools:CGI:PythonCGISlave.py") + + # Ask for source text if not specified in sys.argv[1:] + if not sys.argv[1:]: + srcfss, ok = macfs.PromptGetFile('Select a CGI script:', 'TEXT', 'APPL') + if not ok: + return + filename = srcfss.as_pathname() + dstfilename = mkcgifilename(filename) + dstfss, ok = macfs.StandardPutFile('Save application as:', + os.path.basename(dstfilename)) + if not ok: + return + dstfilename = dstfss.as_pathname() + buildone(template, wrapper, filename, dstfilename) + else: + # Loop over all files to be processed + for filename in sys.argv[1:]: + dstfilename = mkcgifilename(filename) + buildone(template, wrapper, filename, dstfilename) + + +def mkcgifilename(filename): + if filename[-3:] == '.py': + filename = filename[:-3] + filename = filename + ".cgi" + return filename + + +def buildone(template, wrapper, src, dst): + buildtools.process(template, wrapper, dst, 1) + # write source as a PYC resource into dst + ref = Res.OpenResFile(dst) + try: + Res.UseResFile(ref) + py_resource.frompyfile(src, "CGI_MAIN", preload=1) + finally: + Res.CloseResFile(ref) + + +if __name__ == '__main__': + main() diff --git a/Mac/Tools/CGI/BuildCGIApplet.rsrc b/Mac/Tools/CGI/BuildCGIApplet.rsrc Binary files differnew file mode 100644 index 0000000..6c9b6b5 --- /dev/null +++ b/Mac/Tools/CGI/BuildCGIApplet.rsrc diff --git a/Mac/Tools/CGI/CGI_README.txt b/Mac/Tools/CGI/CGI_README.txt new file mode 100644 index 0000000..89f559f --- /dev/null +++ b/Mac/Tools/CGI/CGI_README.txt @@ -0,0 +1,66 @@ +Python CGI under MacOS + +This folder contains two tools that enable Python CGI scripts under +Mac based web servers, like WebStar, Quid Quo Pro, NetPresentz or +Apple's Personal Webserver. + +Both tools emulate Unix style CGI's, allowing for cross platform +CGI scripts. In short, this happens by converting an AppleEvent sent +by the web server into os.environ dictionary entries. See below for more +details. + +Both tools serve slightly different purposes: +- PythonCGISlave enables execution of Python scripts as plain *.py + text files. The web server must be configured to handle .py requests + over to PythonCGISlave. Not all web servers support that. Eg. WebStar + does, but NetPresentz does not. +- BuildCGIApplet wraps a Python CGI script in a compatibility layer, and + creates a CGI Applet which can be executed by any web server. + +The pros and cons of using PythonCGISlave are (+ is good, - is bad): + + support plain .py files, no need to wrap each script + - not supported b all servers, requires more complicated configuration +The pros and cons of using BuildCGIApplet are: + + supported by more servers + + less configuration troubles + - must wrap each script + + +Using BuildCGIApplet + +Drop your CGI script onto BuildCGIApplet. An applet called <script name>.cgi +will be created. Move it to the appropriate location in the HTTP document tree. +Make sure your web server is configured to handle .cgi applet files. Usually +it is configured correctly by default, since .cgi is a standard extension. +If your CGI applet starts up for the first time, a file <applet name>.errors +is created. If your CGI script causes an exception, debug info will be written +to that file. + + +Using PythonCGISlave + +Place the PythonCGISlave applet somewhere in the HTTP document tree. Configure +your web server so it'll pass requests for .py files to PythonCGISlave. For +Webstar, this goes roughly like this: +- in the WebStar Admin app, create a new "action", call it PYTHON, click the + "Choose" button and select our applet. Save the settings. +- go to Suffix Mappings, create a new suffix .PY, type TEXT, creator *, and + choose PYTHON in the actions popup. Save the settings. + + +How it works + +For each Python CGI request, the web server will send an AppleEvent to the +CGI applet. Most relevant CGI parameters are taken from the AppleEvent and +get stuffed into the os.environ dictionary. Then the script gets executed. +This emulates Unix-style CGI as much as possible, so CGI scripts that are +written portably should now also work under a Mac web server. + +Since the applet does not quit after each request by default, there is hardly +any startup overhead except the first time it starts up. If an exception occurs +in the CGI script, the applet will write a traceback to a file called +<applet name>.errors, and then quit. The latter seems a good idea, just in case +we leak memory. The applet will be restarted upon the next request. + + +Please direct feedback to <just@letterror.com> and/or <pythonmac-sig@python.org>. diff --git a/Mac/Tools/CGI/PythonCGISlave.py b/Mac/Tools/CGI/PythonCGISlave.py new file mode 100644 index 0000000..d2dd90f --- /dev/null +++ b/Mac/Tools/CGI/PythonCGISlave.py @@ -0,0 +1,243 @@ +"""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. + import Res, 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() diff --git a/Mac/Tools/CGI/PythonCGISlave.rsrc b/Mac/Tools/CGI/PythonCGISlave.rsrc Binary files differnew file mode 100644 index 0000000..649955a --- /dev/null +++ b/Mac/Tools/CGI/PythonCGISlave.rsrc |