0001"""WSGI Paste wrapper for mod_python. Requires Python 2.2 or greater.
0002
0003
0004Example httpd.conf section for a Paste app with an ini file::
0005
0006 <Location />
0007 SetHandler python-program
0008 PythonHandler paste.modpython
0009 PythonOption paste.ini /some/location/your/pasteconfig.ini
0010 </Location>
0011
0012Or if you want to load a WSGI application under /your/homedir in the module
0013``startup`` and the WSGI app is ``app``::
0014
0015 <Location />
0016 SetHandler python-program
0017 PythonHandler paste.modpython
0018 PythonPath "['/virtual/project/directory'] + sys.path"
0019 PythonOption wsgi.application startup::app
0020 </Location>
0021
0022
0023If you'd like to use a virtual installation, make sure to add it in the path
0024like so::
0025
0026 <Location />
0027 SetHandler python-program
0028 PythonHandler paste.modpython
0029 PythonPath "['/virtual/project/directory', '/virtual/lib/python2.4/'] + sys.path"
0030 PythonOption paste.ini /virtual/project/directory/pasteconfig.ini
0031 </Location>
0032
0033Some WSGI implementations assume that the SCRIPT_NAME environ variable will
0034always be equal to "the root URL of the app"; Apache probably won't act as
0035you expect in that case. You can add another PythonOption directive to tell
0036modpython_gateway to force that behavior:
0037
0038 PythonOption SCRIPT_NAME /mcontrol
0039
0040Some WSGI applications need to be cleaned up when Apache exits. You can
0041register a cleanup handler with yet another PythonOption directive:
0042
0043 PythonOption wsgi.cleanup module::function
0044
0045The module.function will be called with no arguments on server shutdown,
0046once for each child process or thread.
0047
0048This module highly based on Robert Brewer's, here:
0049http://projects.amor.org/misc/svn/modpython_gateway.py
0050"""
0051
0052import traceback
0053
0054try:
0055 from mod_python import apache
0056except:
0057 pass
0058from paste.deploy import loadapp
0059
0060class InputWrapper(object):
0061
0062 def __init__(self, req):
0063 self.req = req
0064
0065 def close(self):
0066 pass
0067
0068 def read(self, size=-1):
0069 return self.req.read(size)
0070
0071 def readline(self, size=-1):
0072 return self.req.readline(size)
0073
0074 def readlines(self, hint=-1):
0075 return self.req.readlines(hint)
0076
0077 def __iter__(self):
0078 line = self.readline()
0079 while line:
0080 yield line
0081 # Notice this won't prefetch the next line; it only
0082 # gets called if the generator is resumed.
0083 line = self.readline()
0084
0085
0086class ErrorWrapper(object):
0087
0088 def __init__(self, req):
0089 self.req = req
0090
0091 def flush(self):
0092 pass
0093
0094 def write(self, msg):
0095 self.req.log_error(msg)
0096
0097 def writelines(self, seq):
0098 self.write(''.join(seq))
0099
0100
0101bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', "
0102 "when running a version of mod_python < 3.1")
0103
0104
0105class Handler(object):
0106
0107 def __init__(self, req):
0108 self.started = False
0109
0110 options = req.get_options()
0111
0112 # Threading and forking
0113 try:
0114 q = apache.mpm_query
0115 threaded = q(apache.AP_MPMQ_IS_THREADED)
0116 forked = q(apache.AP_MPMQ_IS_FORKED)
0117 except AttributeError:
0118 threaded = options.get('multithread', '').lower()
0119 if threaded == 'on':
0120 threaded = True
0121 elif threaded == 'off':
0122 threaded = False
0123 else:
0124 raise ValueError(bad_value % "multithread")
0125
0126 forked = options.get('multiprocess', '').lower()
0127 if forked == 'on':
0128 forked = True
0129 elif forked == 'off':
0130 forked = False
0131 else:
0132 raise ValueError(bad_value % "multiprocess")
0133
0134 env = self.environ = dict(apache.build_cgi_env(req))
0135
0136 if 'SCRIPT_NAME' in options:
0137 # Override SCRIPT_NAME and PATH_INFO if requested.
0138 env['SCRIPT_NAME'] = options['SCRIPT_NAME']
0139 env['PATH_INFO'] = req.uri[len(options['SCRIPT_NAME']):]
0140 else:
0141 env['SCRIPT_NAME'] = ''
0142 env['PATH_INFO'] = req.uri
0143
0144 env['wsgi.input'] = InputWrapper(req)
0145 env['wsgi.errors'] = ErrorWrapper(req)
0146 env['wsgi.version'] = (1, 0)
0147 env['wsgi.run_once'] = False
0148 if env.get("HTTPS") in ('yes', 'on', '1'):
0149 env['wsgi.url_scheme'] = 'https'
0150 else:
0151 env['wsgi.url_scheme'] = 'http'
0152 env['wsgi.multithread'] = threaded
0153 env['wsgi.multiprocess'] = forked
0154
0155 self.request = req
0156
0157 def run(self, application):
0158 try:
0159 result = application(self.environ, self.start_response)
0160 for data in result:
0161 self.write(data)
0162 if not self.started:
0163 self.request.set_content_length(0)
0164 if hasattr(result, 'close'):
0165 result.close()
0166 except:
0167 traceback.print_exc(None, self.environ['wsgi.errors'])
0168 if not self.started:
0169 self.request.status = 500
0170 self.request.content_type = 'text/plain'
0171 data = "A server error occurred. Please contact the administrator."
0172 self.request.set_content_length(len(data))
0173 self.request.write(data)
0174
0175 def start_response(self, status, headers, exc_info=None):
0176 if exc_info:
0177 try:
0178 if self.started:
0179 raise exc_info[0], exc_info[1], exc_info[2]
0180 finally:
0181 exc_info = None
0182
0183 self.request.status = int(status[:3])
0184
0185 for key, val in headers:
0186 if key.lower() == 'content-length':
0187 self.request.set_content_length(int(val))
0188 elif key.lower() == 'content-type':
0189 self.request.content_type = val
0190 else:
0191 self.request.headers_out.add(key, val)
0192
0193 return self.write
0194
0195 def write(self, data):
0196 if not self.started:
0197 self.started = True
0198 self.request.write(data)
0199
0200
0201startup = None
0202cleanup = None
0203wsgiapps = {}
0204
0205def handler(req):
0206 options = req.get_options()
0207 # Run a startup function if requested.
0208 global startup
0209 if 'wsgi.startup' in options and not startup:
0210 func = options['wsgi.startup']
0211 if func:
0212 module_name, object_str = func.split('::', 1)
0213 module = __import__(module_name, globals(), locals(), [''])
0214 startup = apache.resolve_object(module, object_str)
0215 startup(req)
0216
0217 # Register a cleanup function if requested.
0218 global cleanup
0219 if 'wsgi.cleanup' in options and not cleanup:
0220 func = options['wsgi.cleanup']
0221 if func:
0222 module_name, object_str = func.split('::', 1)
0223 module = __import__(module_name, globals(), locals(), [''])
0224 cleanup = apache.resolve_object(module, object_str)
0225 def cleaner(data):
0226 cleanup()
0227 try:
0228 # apache.register_cleanup wasn't available until 3.1.4.
0229 apache.register_cleanup(cleaner)
0230 except AttributeError:
0231 req.server.register_cleanup(req, cleaner)
0232
0233 # Import the wsgi 'application' callable and pass it to Handler.run
0234 global wsgiapps
0235 appini = options.get('paste.ini')
0236 app = None
0237 if appini:
0238 if appini not in wsgiapps:
0239 wsgiapps[appini] = loadapp("config:%s" % appini)
0240 app = wsgiapps[appini]
0241
0242 # Import the wsgi 'application' callable and pass it to Handler.run
0243 appwsgi = options.get('wsgi.application')
0244 if appwsgi and not appini:
0245 modname, objname = appwsgi.split('::', 1)
0246 module = __import__(modname, globals(), locals(), [''])
0247 app = getattr(module, objname)
0248
0249 Handler(req).run(app)
0250
0251 # status was set in Handler; always return apache.OK
0252 return apache.OK