Quick update to the README file. For intros and books we now point to
[python/dscho.git] / Mac / Tools / CGI / PythonCGISlave.py
blobd2dd90ff7647f66c9c4c00ad1c7f7b4add58a7e0
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 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:])
83 class PythonCGISlave(AEServer, MiniApplication):
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)
93 self.code = get_cgi_code()
94 self.long_running = LONG_RUNNING
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])
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])
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__
117 def getaboutmenutext(self):
118 return "About %s\311" % os.path.basename(sys.argv[0])
120 def message(self, msg):
121 import time
122 sys.stderr.write("%s (%s)\n" % (msg, time.asctime(time.localtime(time.time()))))
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")
132 def quit(self, **args):
133 self.quitting = 1
135 def open_app(self, **args):
136 pass
138 def cgihandler(self, pathargs, **args):
139 # We emulate the unix way of doing CGI: fill os.environ with stuff.
140 environ = os.environ
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
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]
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
164 # Kfrq is the complete http request.
165 infile = cStringIO.StringIO(args['Kfrq'])
166 firstline = infile.readline()
168 msg = mimetools.Message(infile, 0)
170 uri, protocol = string.split(firstline)[1:3]
171 environ['REQUEST_URI'] = uri
172 environ['SERVER_PROTOCOL'] = protocol
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]
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)
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)
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"
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"
229 # clean up
230 namespace.clear()
231 environ.clear()
232 sys.path.remove(script_dir)
233 sys.stdout = saveout
234 sys.stdin = savein
236 if not self.long_running:
237 # quit after each request
238 self.quitting = 1
240 return rv + out.getvalue()
243 PythonCGISlave()