Update for release.
[python/dscho.git] / Mac / Tools / CGI / PythonCGISlave.py
blobf14a582daebe789d8304f8bb2ae39cec44cfd1bb
1 """PythonCGISlave.py
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.
7 See CGI_README.txt for more details.
8 """
11 # Written by Just van Rossum, but partly stolen from example code by Jack.
15 LONG_RUNNING = 1 # If true, don't quit after each request.
18 import MacOS
19 MacOS.SchedParams(0, 0)
20 from MiniAEFrame import AEServer, MiniApplication
22 import os
23 import string
24 import cStringIO
25 import sys
26 import traceback
27 import mimetools
29 __version__ = '3.2'
32 slave_dir = os.getcwd()
35 # log file for errors
36 sys.stderr = open(sys.argv[0] + ".errors", "a+")
38 def convertFSSpec(fss):
39 return fss.as_pathname()
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',
55 ERROR_MESSAGE = """\
56 Content-type: text/html
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 """
71 def get_cgi_code():
72 # If we're a CGI wrapper, the CGI code resides in a PYC resource.
73 from Carbon import Res
74 import marshal
75 try:
76 code = Res.GetNamedResource('PYC ', "CGI_MAIN")
77 except Res.Error:
78 return None
79 else:
80 return marshal.loads(code.data[8:])
84 class PythonCGISlave(AEServer, MiniApplication):
86 def __init__(self):
87 self.crumblezone = 100000 * "\0"
88 MiniApplication.__init__(self)
89 AEServer.__init__(self)
90 self.installaehandler('aevt', 'oapp', self.open_app)
91 self.installaehandler('aevt', 'quit', self.quit)
92 self.installaehandler('WWW\275', 'sdoc', self.cgihandler)
94 self.code = get_cgi_code()
95 self.long_running = LONG_RUNNING
97 if self.code is None:
98 print "%s version %s, ready to serve." % (self.__class__.__name__, __version__)
99 else:
100 print "%s, ready to serve." % os.path.basename(sys.argv[0])
102 try:
103 self.mainloop()
104 except:
105 self.crumblezone = None
106 sys.stderr.write("- " * 30 + '\n')
107 self.message("Unexpected exception")
108 self.dump_environ()
109 sys.stderr.write("%s: %s\n" % sys.exc_info()[:2])
111 def getabouttext(self):
112 if self.code is None:
113 return "PythonCGISlave %s, written by Just van Rossum." % __version__
114 else:
115 return "Python CGI script, wrapped by BuildCGIApplet and " \
116 "PythonCGISlave, version %s." % __version__
118 def getaboutmenutext(self):
119 return "About %s\311" % os.path.basename(sys.argv[0])
121 def message(self, msg):
122 import time
123 sys.stderr.write("%s (%s)\n" % (msg, time.asctime(time.localtime(time.time()))))
125 def dump_environ(self):
126 sys.stderr.write("os.environ = {\n")
127 keys = os.environ.keys()
128 keys.sort()
129 for key in keys:
130 sys.stderr.write(" %s: %s,\n" % (repr(key), repr(os.environ[key])))
131 sys.stderr.write("}\n")
133 def quit(self, **args):
134 self.quitting = 1
136 def open_app(self, **args):
137 pass
139 def cgihandler(self, pathargs, **args):
140 # We emulate the unix way of doing CGI: fill os.environ with stuff.
141 environ = os.environ
143 # First, find the document root. If we don't get a DIRE parameter,
144 # we take the directory of this program, which may be wrong if
145 # it doesn't live the actual http document root folder.
146 if args.has_key('DIRE'):
147 http_root = args['DIRE'].as_pathname()
148 del args['DIRE']
149 else:
150 http_root = slave_dir
151 environ['DOCUMENT_ROOT'] = http_root
153 if self.code is None:
154 # create a Mac pathname to the Python CGI script or applet
155 script = string.replace(args['scnm'], '/', ':')
156 script_path = os.path.join(http_root, script)
157 else:
158 script_path = sys.argv[0]
160 if not os.path.exists(script_path):
161 rv = "HTTP/1.0 404 Not found\n"
162 rv = rv + ERROR_MESSAGE % (404, "Not found")
163 return rv
165 # Kfrq is the complete http request.
166 infile = cStringIO.StringIO(args['Kfrq'])
167 firstline = infile.readline()
169 msg = mimetools.Message(infile, 0)
171 uri, protocol = string.split(firstline)[1:3]
172 environ['REQUEST_URI'] = uri
173 environ['SERVER_PROTOCOL'] = protocol
175 # Make all http headers available as HTTP_* fields.
176 for key in msg.keys():
177 environ['HTTP_' + string.upper(string.replace(key, "-", "_"))] = msg[key]
179 # Translate the AE parameters we know of to the appropriate os.environ
180 # entries. Make the ones we don't know available as AE_* fields.
181 items = args.items()
182 items.sort()
183 for key, value in items:
184 if key[0] == "_":
185 continue
186 if ae2environ.has_key(key):
187 envkey = ae2environ[key]
188 environ[envkey] = value
189 else:
190 environ['AE_' + string.upper(key)] = str(value)
192 # Redirect stdout and stdin.
193 saveout = sys.stdout
194 savein = sys.stdin
195 out = sys.stdout = cStringIO.StringIO()
196 postdata = args.get('post', "")
197 if postdata:
198 environ['CONTENT_LENGTH'] = str(len(postdata))
199 sys.stdin = cStringIO.StringIO(postdata)
201 # Set up the Python environment
202 script_dir = os.path.dirname(script_path)
203 os.chdir(script_dir)
204 sys.path.insert(0, script_dir)
205 sys.argv[:] = [script_path]
206 namespace = {"__name__": "__main__"}
207 rv = "HTTP/1.0 200 OK\n"
209 try:
210 if self.code is None:
211 # we're a Python script server
212 execfile(script_path, namespace)
213 else:
214 # we're a CGI wrapper, self.code is the CGI code
215 exec self.code in namespace
216 except SystemExit:
217 # We're not exiting dammit! ;-)
218 pass
219 except:
220 self.crumblezone = None
221 sys.stderr.write("- " * 30 + '\n')
222 self.message("CGI exception")
223 self.dump_environ()
224 traceback.print_exc()
225 sys.stderr.flush()
226 self.quitting = 1
227 # XXX we should return an error AE, but I don't know how to :-(
228 rv = "HTTP/1.0 500 Internal error\n"
230 # clean up
231 namespace.clear()
232 environ.clear()
233 sys.path.remove(script_dir)
234 sys.stdout = saveout
235 sys.stdin = savein
237 if not self.long_running:
238 # quit after each request
239 self.quitting = 1
241 return rv + out.getvalue()
244 PythonCGISlave()