blob: f14a582daebe789d8304f8bb2ae39cec44cfd1bb [file] [log] [blame]
Just van Rossum40117232000-03-28 12:05:13 +00001"""PythonCGISlave.py
2
3This 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
7See 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
15LONG_RUNNING = 1 # If true, don't quit after each request.
16
17
18import MacOS
19MacOS.SchedParams(0, 0)
20from MiniAEFrame import AEServer, MiniApplication
21
22import os
23import string
24import cStringIO
25import sys
26import traceback
27import mimetools
28
29__version__ = '3.2'
30
31
32slave_dir = os.getcwd()
33
34
35# log file for errors
36sys.stderr = open(sys.argv[0] + ".errors", "a+")
37
38def convertFSSpec(fss):
39 return fss.as_pathname()
40
41
42# AE -> os.environ mappings
43ae2environ = {
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
55ERROR_MESSAGE = """\
56Content-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
71def get_cgi_code():
72 # If we're a CGI wrapper, the CGI code resides in a PYC resource.
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000073 from Carbon import Res
74 import marshal
Just van Rossum40117232000-03-28 12:05:13 +000075 try:
76 code = Res.GetNamedResource('PYC ', "CGI_MAIN")
77 except Res.Error:
78 return None
79 else:
80 return marshal.loads(code.data[8:])
81
82
83
84class PythonCGISlave(AEServer, MiniApplication):
85
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)
93
94 self.code = get_cgi_code()
95 self.long_running = LONG_RUNNING
96
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])
101
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])
110
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__
117
118 def getaboutmenutext(self):
119 return "About %s\311" % os.path.basename(sys.argv[0])
120
121 def message(self, msg):
122 import time
123 sys.stderr.write("%s (%s)\n" % (msg, time.asctime(time.localtime(time.time()))))
124
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")
132
133 def quit(self, **args):
134 self.quitting = 1
135
136 def open_app(self, **args):
137 pass
138
139 def cgihandler(self, pathargs, **args):
140 # We emulate the unix way of doing CGI: fill os.environ with stuff.
141 environ = os.environ
142
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
152
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]
159
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
164
165 # Kfrq is the complete http request.
166 infile = cStringIO.StringIO(args['Kfrq'])
167 firstline = infile.readline()
168
169 msg = mimetools.Message(infile, 0)
170
171 uri, protocol = string.split(firstline)[1:3]
172 environ['REQUEST_URI'] = uri
173 environ['SERVER_PROTOCOL'] = protocol
174
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]
178
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)
191
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)
200
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"
208
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"
229
230 # clean up
231 namespace.clear()
232 environ.clear()
233 sys.path.remove(script_dir)
234 sys.stdout = saveout
235 sys.stdin = savein
236
237 if not self.long_running:
238 # quit after each request
239 self.quitting = 1
240
241 return rv + out.getvalue()
242
243
244PythonCGISlave()