Contact: fumanchu@aminus.org

Log in as guest/misc to create tickets

root/modpython_gateway.py

Revision 106 (checked in by fumanchu, 5 years ago)

modpython_gateway: fixed bug in cleanup and added a "wsgi.startup" PythonOption? that works similarly (but gets passed the request object).

  • Property svn:eol-style set to native
Line 
1 """
2 WSGI wrapper for mod_python. Requires Python 2.2 or greater.
3
4
5 Example httpd.conf section for a CherryPy app called "mcontrol":
6
7 <Location /mcontrol>
8     SetHandler python-program
9     PythonFixupHandler mcontrol.cherry::startup
10     PythonHandler modpython_gateway::handler
11     PythonOption wsgi.application cherrypy._cpwsgi::wsgiApp
12 </Location>
13
14 Some WSGI implementations assume that the SCRIPT_NAME environ variable will
15 always be equal to "the root URL of the app"; Apache probably won't act as
16 you expect in that case. You can add another PythonOption directive to tell
17 modpython_gateway to force that behavior:
18
19     PythonOption SCRIPT_NAME /mcontrol
20
21 Some WSGI applications need to be cleaned up when Apache exits. You can
22 register a cleanup handler with yet another PythonOption directive:
23
24     PythonOption wsgi.cleanup module::function
25
26 The module.function will be called with no arguments on server shutdown,
27 once for each child process or thread.
28 """
29
30 import traceback
31
32 from mod_python import apache
33
34
35 class InputWrapper(object):
36    
37     def __init__(self, req):
38         self.req = req
39    
40     def close(self):
41         pass
42    
43     def read(self, size=-1):
44         return self.req.read(size)
45    
46     def readline(self, size=-1):
47         return self.req.readline(size)
48    
49     def readlines(self, hint=-1):
50         return self.req.readlines(hint)
51    
52     def __iter__(self):
53         line = self.readline()
54         while line:
55             yield line
56             # Notice this won't prefetch the next line; it only
57             # gets called if the generator is resumed.
58             line = self.readline()
59
60
61 class ErrorWrapper(object):
62    
63     def __init__(self, req):
64         self.req = req
65    
66     def flush(self):
67         pass
68    
69     def write(self, msg):
70         self.req.log_error(msg)
71    
72     def writelines(self, seq):
73         self.write(''.join(seq))
74
75
76 bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', "
77              "when running a version of mod_python < 3.1")
78
79
80 class Handler:
81    
82     def __init__(self, req):
83         self.started = False
84        
85         options = req.get_options()
86        
87         # Threading and forking
88         try:
89             q = apache.mpm_query
90             threaded = q(apache.AP_MPMQ_IS_THREADED)
91             forked = q(apache.AP_MPMQ_IS_FORKED)
92         except AttributeError:
93             threaded = options.get('multithread', '').lower()
94             if threaded == 'on':
95                 threaded = True
96             elif threaded == 'off':
97                 threaded = False
98             else:
99                 raise ValueError(bad_value % "multithread")
100            
101             forked = options.get('multiprocess', '').lower()
102             if forked == 'on':
103                 forked = True
104             elif forked == 'off':
105                 forked = False
106             else:
107                 raise ValueError(bad_value % "multiprocess")
108        
109         env = self.environ = dict(apache.build_cgi_env(req))
110        
111         if 'SCRIPT_NAME' in options:
112             # Override SCRIPT_NAME and PATH_INFO if requested.
113             env['SCRIPT_NAME'] = options['SCRIPT_NAME']
114             env['PATH_INFO'] = req.uri[len(options['SCRIPT_NAME']):]
115        
116         env['wsgi.input'] = InputWrapper(req)
117         env['wsgi.errors'] = ErrorWrapper(req)
118         env['wsgi.version'] = (1,0)
119         env['wsgi.run_once'] = False
120         if env.get("HTTPS") in ('yes', 'on', '1'):
121             env['wsgi.url_scheme'] = 'https'
122         else:
123             env['wsgi.url_scheme'] = 'http'
124         env['wsgi.multithread']  = threaded
125         env['wsgi.multiprocess'] = forked
126        
127         self.request = req
128    
129     def run(self, application):
130         try:
131             result = application(self.environ, self.start_response)
132             for data in result:
133                 self.write(data)
134             if not self.started:
135                 self.request.set_content_length(0)
136             if hasattr(result, 'close'):
137                 result.close()
138         except:
139             traceback.print_exc(None, self.environ['wsgi.errors'])
140             if not self.started:
141                 self.request.status = 500
142                 self.request.content_type = 'text/plain'
143                 data = "A server error occurred. Please contact the administrator."
144                 self.request.set_content_length(len(data))
145                 self.request.write(data)
146    
147     def start_response(self, status, headers, exc_info=None):
148         if exc_info:
149             try:
150                 if self.started:
151                     raise exc_info[0], exc_info[1], exc_info[2]
152             finally:
153                 exc_info = None
154        
155         self.request.status = int(status[:3])
156        
157         for key, val in headers:
158             if key.lower() == 'content-length':
159                 self.request.set_content_length(int(val))
160             elif key.lower() == 'content-type':
161                 self.request.content_type = val
162             else:
163                 self.request.headers_out.add(key, val)
164        
165         return self.write
166    
167     def write(self, data):
168         if not self.started:
169             self.started = True
170         self.request.write(data)
171
172
173 startup = None
174 cleanup = None
175
176 def handler(req):
177     # Run a startup function if requested.
178     global startup
179     if not startup:
180         func = req.get_options().get('wsgi.startup')
181         if func:
182             module_name, object_str = func.split('::', 1)
183             module = __import__(module_name, globals(), locals(), [''])
184             startup = apache.resolve_object(module, object_str)
185             startup(req)
186    
187     # Register a cleanup function if requested.
188     global cleanup
189     if not cleanup:
190         func = req.get_options().get('wsgi.cleanup')
191         if func:
192             module_name, object_str = func.split('::', 1)
193             module = __import__(module_name, globals(), locals(), [''])
194             cleanup = apache.resolve_object(module, object_str)
195             def cleaner(data):
196                 cleanup()
197             try:
198                 # apache.register_cleanup wasn't available until 3.1.4.
199                 apache.register_cleanup(cleaner)
200             except AttributeError:
201                 req.server.register_cleanup(req, cleaner)
202    
203     # Import the wsgi 'application' callable and pass it to Handler.run
204     modname, objname = req.get_options()['wsgi.application'].split('::', 1)
205     module = __import__(modname, globals(), locals(), [''])
206     app = getattr(module, objname)
207     Handler(req).run(app)
208    
209     # status was set in Handler; always return apache.OK
210     return apache.OK
211
Note: See TracBrowser for help on using the browser.