mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +00:00 
			
		
		
		
	- 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)
This commit is contained in:
		
							parent
							
								
									b7a40ba8d3
								
							
						
					
					
						commit
						4011723d0d
					
				
					 5 changed files with 383 additions and 0 deletions
				
			
		
							
								
								
									
										74
									
								
								Mac/Tools/CGI/BuildCGIApplet.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								Mac/Tools/CGI/BuildCGIApplet.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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()
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								Mac/Tools/CGI/BuildCGIApplet.rsrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Mac/Tools/CGI/BuildCGIApplet.rsrc
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										66
									
								
								Mac/Tools/CGI/CGI_README.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Mac/Tools/CGI/CGI_README.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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>.
 | 
			
		||||
							
								
								
									
										243
									
								
								Mac/Tools/CGI/PythonCGISlave.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								Mac/Tools/CGI/PythonCGISlave.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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()
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								Mac/Tools/CGI/PythonCGISlave.rsrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Mac/Tools/CGI/PythonCGISlave.rsrc
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue