Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4011723

Browse files
author
Just van Rossum
committed
- 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)
1 parent b7a40ba commit 4011723

5 files changed

Lines changed: 383 additions & 0 deletions

File tree

Mac/Tools/CGI/BuildCGIApplet.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""BuildCGIApplet.py -- Create a CGI applet from a Python script.
2+
3+
Specilized version of BuildApplet, enabling Python CGI scripts to be
4+
used under Mac web servers like WebStar. The __main__ program is
5+
PythonCGISlave.py, which provides a compatibility layer, emulating
6+
Unix-style CGI scripts. See CGI_README.txt for details.
7+
"""
8+
9+
import sys
10+
import os
11+
import macfs
12+
import MacOS
13+
import Res
14+
import EasyDialogs
15+
import buildtools
16+
import py_resource
17+
18+
19+
def main():
20+
try:
21+
buildcgiapplet()
22+
except buildtools.BuildError, detail:
23+
EasyDialogs.Message(detail)
24+
25+
26+
def buildcgiapplet():
27+
buildtools.DEBUG=1
28+
29+
# Find the template
30+
# (there's no point in proceeding if we can't find it)
31+
32+
template = buildtools.findtemplate()
33+
wrapper = os.path.join(sys.exec_prefix, ":Mac:Tools:CGI:PythonCGISlave.py")
34+
35+
# Ask for source text if not specified in sys.argv[1:]
36+
if not sys.argv[1:]:
37+
srcfss, ok = macfs.PromptGetFile('Select a CGI script:', 'TEXT', 'APPL')
38+
if not ok:
39+
return
40+
filename = srcfss.as_pathname()
41+
dstfilename = mkcgifilename(filename)
42+
dstfss, ok = macfs.StandardPutFile('Save application as:',
43+
os.path.basename(dstfilename))
44+
if not ok:
45+
return
46+
dstfilename = dstfss.as_pathname()
47+
buildone(template, wrapper, filename, dstfilename)
48+
else:
49+
# Loop over all files to be processed
50+
for filename in sys.argv[1:]:
51+
dstfilename = mkcgifilename(filename)
52+
buildone(template, wrapper, filename, dstfilename)
53+
54+
55+
def mkcgifilename(filename):
56+
if filename[-3:] == '.py':
57+
filename = filename[:-3]
58+
filename = filename + ".cgi"
59+
return filename
60+
61+
62+
def buildone(template, wrapper, src, dst):
63+
buildtools.process(template, wrapper, dst, 1)
64+
# write source as a PYC resource into dst
65+
ref = Res.OpenResFile(dst)
66+
try:
67+
Res.UseResFile(ref)
68+
py_resource.frompyfile(src, "CGI_MAIN", preload=1)
69+
finally:
70+
Res.CloseResFile(ref)
71+
72+
73+
if __name__ == '__main__':
74+
main()

Mac/Tools/CGI/BuildCGIApplet.rsrc

460 Bytes
Binary file not shown.

Mac/Tools/CGI/CGI_README.txt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
Python CGI under MacOS
2+
3+
This folder contains two tools that enable Python CGI scripts under
4+
Mac based web servers, like WebStar, Quid Quo Pro, NetPresentz or
5+
Apple's Personal Webserver.
6+
7+
Both tools emulate Unix style CGI's, allowing for cross platform
8+
CGI scripts. In short, this happens by converting an AppleEvent sent
9+
by the web server into os.environ dictionary entries. See below for more
10+
details.
11+
12+
Both tools serve slightly different purposes:
13+
- PythonCGISlave enables execution of Python scripts as plain *.py
14+
text files. The web server must be configured to handle .py requests
15+
over to PythonCGISlave. Not all web servers support that. Eg. WebStar
16+
does, but NetPresentz does not.
17+
- BuildCGIApplet wraps a Python CGI script in a compatibility layer, and
18+
creates a CGI Applet which can be executed by any web server.
19+
20+
The pros and cons of using PythonCGISlave are (+ is good, - is bad):
21+
+ support plain .py files, no need to wrap each script
22+
- not supported b all servers, requires more complicated configuration
23+
The pros and cons of using BuildCGIApplet are:
24+
+ supported by more servers
25+
+ less configuration troubles
26+
- must wrap each script
27+
28+
29+
Using BuildCGIApplet
30+
31+
Drop your CGI script onto BuildCGIApplet. An applet called <script name>.cgi
32+
will be created. Move it to the appropriate location in the HTTP document tree.
33+
Make sure your web server is configured to handle .cgi applet files. Usually
34+
it is configured correctly by default, since .cgi is a standard extension.
35+
If your CGI applet starts up for the first time, a file <applet name>.errors
36+
is created. If your CGI script causes an exception, debug info will be written
37+
to that file.
38+
39+
40+
Using PythonCGISlave
41+
42+
Place the PythonCGISlave applet somewhere in the HTTP document tree. Configure
43+
your web server so it'll pass requests for .py files to PythonCGISlave. For
44+
Webstar, this goes roughly like this:
45+
- in the WebStar Admin app, create a new "action", call it PYTHON, click the
46+
"Choose" button and select our applet. Save the settings.
47+
- go to Suffix Mappings, create a new suffix .PY, type TEXT, creator *, and
48+
choose PYTHON in the actions popup. Save the settings.
49+
50+
51+
How it works
52+
53+
For each Python CGI request, the web server will send an AppleEvent to the
54+
CGI applet. Most relevant CGI parameters are taken from the AppleEvent and
55+
get stuffed into the os.environ dictionary. Then the script gets executed.
56+
This emulates Unix-style CGI as much as possible, so CGI scripts that are
57+
written portably should now also work under a Mac web server.
58+
59+
Since the applet does not quit after each request by default, there is hardly
60+
any startup overhead except the first time it starts up. If an exception occurs
61+
in the CGI script, the applet will write a traceback to a file called
62+
<applet name>.errors, and then quit. The latter seems a good idea, just in case
63+
we leak memory. The applet will be restarted upon the next request.
64+
65+
66+
Please direct feedback to <[email protected]> and/or <[email protected]>.

Mac/Tools/CGI/PythonCGISlave.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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()

Mac/Tools/CGI/PythonCGISlave.rsrc

460 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)