|
| 1 | +"""PythonCGISlave.py |
| 2 | +
|
| 3 | +This program can be used in two ways: |
| 4 | +- As a Python CGI script server for web servers supporting "Actions", like WebStar. |
| 5 | +- As a wrapper for a single Python CGI script, for any "compliant" Mac web server. |
| 6 | +
|
| 7 | +See CGI_README.txt for more details. |
| 8 | +""" |
| 9 | + |
| 10 | +# |
| 11 | +# Written by Just van Rossum, but partly stolen from example code by Jack. |
| 12 | +# |
| 13 | + |
| 14 | + |
| 15 | +LONG_RUNNING = 1 # If true, don't quit after each request. |
| 16 | + |
| 17 | + |
| 18 | +import MacOS |
| 19 | +MacOS.SchedParams(0, 0) |
| 20 | +from MiniAEFrame import AEServer, MiniApplication |
| 21 | + |
| 22 | +import os |
| 23 | +import string |
| 24 | +import cStringIO |
| 25 | +import sys |
| 26 | +import traceback |
| 27 | +import mimetools |
| 28 | + |
| 29 | +__version__ = '3.2' |
| 30 | + |
| 31 | + |
| 32 | +slave_dir = os.getcwd() |
| 33 | + |
| 34 | + |
| 35 | +# log file for errors |
| 36 | +sys.stderr = open(sys.argv[0] + ".errors", "a+") |
| 37 | + |
| 38 | +def convertFSSpec(fss): |
| 39 | + return fss.as_pathname() |
| 40 | + |
| 41 | + |
| 42 | +# AE -> os.environ mappings |
| 43 | +ae2environ = { |
| 44 | + 'kfor': 'QUERY_STRING', |
| 45 | + 'Kcip': 'REMOTE_ADDR', |
| 46 | + 'svnm': 'SERVER_NAME', |
| 47 | + 'svpt': 'SERVER_PORT', |
| 48 | + 'addr': 'REMOTE_HOST', |
| 49 | + 'scnm': 'SCRIPT_NAME', |
| 50 | + 'meth': 'REQUEST_METHOD', |
| 51 | + 'ctyp': 'CONTENT_TYPE', |
| 52 | +} |
| 53 | + |
| 54 | + |
| 55 | +ERROR_MESSAGE = """\ |
| 56 | +Content-type: text/html |
| 57 | +
|
| 58 | +<html> |
| 59 | +<head> |
| 60 | +<title>Error response</title> |
| 61 | +</head> |
| 62 | +<body> |
| 63 | +<h1>Error response</h1> |
| 64 | +<p>Error code %d. |
| 65 | +<p>Message: %s. |
| 66 | +</body> |
| 67 | +</html> |
| 68 | +""" |
| 69 | + |
| 70 | + |
| 71 | +def get_cgi_code(): |
| 72 | + # If we're a CGI wrapper, the CGI code resides in a PYC resource. |
| 73 | + import Res, marshal |
| 74 | + try: |
| 75 | + code = Res.GetNamedResource('PYC ', "CGI_MAIN") |
| 76 | + except Res.Error: |
| 77 | + return None |
| 78 | + else: |
| 79 | + return marshal.loads(code.data[8:]) |
| 80 | + |
| 81 | + |
| 82 | + |
| 83 | +class PythonCGISlave(AEServer, MiniApplication): |
| 84 | + |
| 85 | + def __init__(self): |
| 86 | + self.crumblezone = 100000 * "\0" |
| 87 | + MiniApplication.__init__(self) |
| 88 | + AEServer.__init__(self) |
| 89 | + self.installaehandler('aevt', 'oapp', self.open_app) |
| 90 | + self.installaehandler('aevt', 'quit', self.quit) |
| 91 | + self.installaehandler('WWW\275', 'sdoc', self.cgihandler) |
| 92 | + |
| 93 | + self.code = get_cgi_code() |
| 94 | + self.long_running = LONG_RUNNING |
| 95 | + |
| 96 | + if self.code is None: |
| 97 | + print "%s version %s, ready to serve." % (self.__class__.__name__, __version__) |
| 98 | + else: |
| 99 | + print "%s, ready to serve." % os.path.basename(sys.argv[0]) |
| 100 | + |
| 101 | + try: |
| 102 | + self.mainloop() |
| 103 | + except: |
| 104 | + self.crumblezone = None |
| 105 | + sys.stderr.write("- " * 30 + '\n') |
| 106 | + self.message("Unexpected exception") |
| 107 | + self.dump_environ() |
| 108 | + sys.stderr.write("%s: %s\n" % sys.exc_info()[:2]) |
| 109 | + |
| 110 | + def getabouttext(self): |
| 111 | + if self.code is None: |
| 112 | + return "PythonCGISlave %s, written by Just van Rossum." % __version__ |
| 113 | + else: |
| 114 | + return "Python CGI script, wrapped by BuildCGIApplet and " \ |
| 115 | + "PythonCGISlave, version %s." % __version__ |
| 116 | + |
| 117 | + def getaboutmenutext(self): |
| 118 | + return "About %s\311" % os.path.basename(sys.argv[0]) |
| 119 | + |
| 120 | + def message(self, msg): |
| 121 | + import time |
| 122 | + sys.stderr.write("%s (%s)\n" % (msg, time.asctime(time.localtime(time.time())))) |
| 123 | + |
| 124 | + def dump_environ(self): |
| 125 | + sys.stderr.write("os.environ = {\n") |
| 126 | + keys = os.environ.keys() |
| 127 | + keys.sort() |
| 128 | + for key in keys: |
| 129 | + sys.stderr.write(" %s: %s,\n" % (repr(key), repr(os.environ[key]))) |
| 130 | + sys.stderr.write("}\n") |
| 131 | + |
| 132 | + def quit(self, **args): |
| 133 | + self.quitting = 1 |
| 134 | + |
| 135 | + def open_app(self, **args): |
| 136 | + pass |
| 137 | + |
| 138 | + def cgihandler(self, pathargs, **args): |
| 139 | + # We emulate the unix way of doing CGI: fill os.environ with stuff. |
| 140 | + environ = os.environ |
| 141 | + |
| 142 | + # First, find the document root. If we don't get a DIRE parameter, |
| 143 | + # we take the directory of this program, which may be wrong if |
| 144 | + # it doesn't live the actual http document root folder. |
| 145 | + if args.has_key('DIRE'): |
| 146 | + http_root = args['DIRE'].as_pathname() |
| 147 | + del args['DIRE'] |
| 148 | + else: |
| 149 | + http_root = slave_dir |
| 150 | + environ['DOCUMENT_ROOT'] = http_root |
| 151 | + |
| 152 | + if self.code is None: |
| 153 | + # create a Mac pathname to the Python CGI script or applet |
| 154 | + script = string.replace(args['scnm'], '/', ':') |
| 155 | + script_path = os.path.join(http_root, script) |
| 156 | + else: |
| 157 | + script_path = sys.argv[0] |
| 158 | + |
| 159 | + if not os.path.exists(script_path): |
| 160 | + rv = "HTTP/1.0 404 Not found\n" |
| 161 | + rv = rv + ERROR_MESSAGE % (404, "Not found") |
| 162 | + return rv |
| 163 | + |
| 164 | + # Kfrq is the complete http request. |
| 165 | + infile = cStringIO.StringIO(args['Kfrq']) |
| 166 | + firstline = infile.readline() |
| 167 | + |
| 168 | + msg = mimetools.Message(infile, 0) |
| 169 | + |
| 170 | + uri, protocol = string.split(firstline)[1:3] |
| 171 | + environ['REQUEST_URI'] = uri |
| 172 | + environ['SERVER_PROTOCOL'] = protocol |
| 173 | + |
| 174 | + # Make all http headers available as HTTP_* fields. |
| 175 | + for key in msg.keys(): |
| 176 | + environ['HTTP_' + string.upper(string.replace(key, "-", "_"))] = msg[key] |
| 177 | + |
| 178 | + # Translate the AE parameters we know of to the appropriate os.environ |
| 179 | + # entries. Make the ones we don't know available as AE_* fields. |
| 180 | + items = args.items() |
| 181 | + items.sort() |
| 182 | + for key, value in items: |
| 183 | + if key[0] == "_": |
| 184 | + continue |
| 185 | + if ae2environ.has_key(key): |
| 186 | + envkey = ae2environ[key] |
| 187 | + environ[envkey] = value |
| 188 | + else: |
| 189 | + environ['AE_' + string.upper(key)] = str(value) |
| 190 | + |
| 191 | + # Redirect stdout and stdin. |
| 192 | + saveout = sys.stdout |
| 193 | + savein = sys.stdin |
| 194 | + out = sys.stdout = cStringIO.StringIO() |
| 195 | + postdata = args.get('post', "") |
| 196 | + if postdata: |
| 197 | + environ['CONTENT_LENGTH'] = str(len(postdata)) |
| 198 | + sys.stdin = cStringIO.StringIO(postdata) |
| 199 | + |
| 200 | + # Set up the Python environment |
| 201 | + script_dir = os.path.dirname(script_path) |
| 202 | + os.chdir(script_dir) |
| 203 | + sys.path.insert(0, script_dir) |
| 204 | + sys.argv[:] = [script_path] |
| 205 | + namespace = {"__name__": "__main__"} |
| 206 | + rv = "HTTP/1.0 200 OK\n" |
| 207 | + |
| 208 | + try: |
| 209 | + if self.code is None: |
| 210 | + # we're a Python script server |
| 211 | + execfile(script_path, namespace) |
| 212 | + else: |
| 213 | + # we're a CGI wrapper, self.code is the CGI code |
| 214 | + exec self.code in namespace |
| 215 | + except SystemExit: |
| 216 | + # We're not exiting dammit! ;-) |
| 217 | + pass |
| 218 | + except: |
| 219 | + self.crumblezone = None |
| 220 | + sys.stderr.write("- " * 30 + '\n') |
| 221 | + self.message("CGI exception") |
| 222 | + self.dump_environ() |
| 223 | + traceback.print_exc() |
| 224 | + sys.stderr.flush() |
| 225 | + self.quitting = 1 |
| 226 | + # XXX we should return an error AE, but I don't know how to :-( |
| 227 | + rv = "HTTP/1.0 500 Internal error\n" |
| 228 | + |
| 229 | + # clean up |
| 230 | + namespace.clear() |
| 231 | + environ.clear() |
| 232 | + sys.path.remove(script_dir) |
| 233 | + sys.stdout = saveout |
| 234 | + sys.stdin = savein |
| 235 | + |
| 236 | + if not self.long_running: |
| 237 | + # quit after each request |
| 238 | + self.quitting = 1 |
| 239 | + |
| 240 | + return rv + out.getvalue() |
| 241 | + |
| 242 | + |
| 243 | +PythonCGISlave() |
0 commit comments