- new version of PythonCGISlave
- new script/applet BuildCGIApplet
This largely supercedes :Mac:Demos:cgi, except for the html doc file. Should it move here? Merged with CGI_README.txt?
Todo: fullbuild support.
(jvr)
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
new file mode 100644
index 0000000..6c9b6b5
--- /dev/null
+++ b/Mac/Tools/CGI/BuildCGIApplet.rsrc
Binary files differ
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
new file mode 100644
index 0000000..649955a
--- /dev/null
+++ b/Mac/Tools/CGI/PythonCGISlave.rsrc
Binary files differ