| 
									
										
										
										
											2000-03-28 12:05:13 +00:00
										 |  |  | """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. | 
					
						
							| 
									
										
										
										
											2001-08-25 12:15:04 +00:00
										 |  |  | 	from Carbon import Res | 
					
						
							|  |  |  | 	import marshal | 
					
						
							| 
									
										
										
										
											2000-03-28 12:05:13 +00:00
										 |  |  | 	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() |