"""WSGI Paste wrapper for mod_python. Requires Python 2.2 or greater.
Example httpd.conf section for a Paste app with an ini file::
SetHandler python-program
PythonHandler paste.modpython
PythonOption paste.ini /some/location/your/pasteconfig.ini
Or if you want to load a WSGI application under /your/homedir in the module
``startup`` and the WSGI app is ``app``::
SetHandler python-program
PythonHandler paste.modpython
PythonPath "['/virtual/project/directory'] + sys.path"
PythonOption wsgi.application startup::app
If you'd like to use a virtual installation, make sure to add it in the path
like so::
SetHandler python-program
PythonHandler paste.modpython
PythonPath "['/virtual/project/directory', '/virtual/lib/python2.4/'] + sys.path"
PythonOption paste.ini /virtual/project/directory/pasteconfig.ini
Some WSGI implementations assume that the SCRIPT_NAME environ variable will
always be equal to "the root URL of the app"; Apache probably won't act as
you expect in that case. You can add another PythonOption directive to tell
modpython_gateway to force that behavior:
PythonOption SCRIPT_NAME /mcontrol
Some WSGI applications need to be cleaned up when Apache exits. You can
register a cleanup handler with yet another PythonOption directive:
PythonOption wsgi.cleanup module::function
The module.function will be called with no arguments on server shutdown,
once for each child process or thread.
This module highly based on Robert Brewer's, here:
http://projects.amor.org/misc/svn/modpython_gateway.py
"""
import six
import traceback
try:
from mod_python import apache
except:
pass
from paste.deploy import loadapp
class InputWrapper(object):
def __init__(self, req):
self.req = req
def close(self):
pass
def read(self, size=-1):
return self.req.read(size)
def readline(self, size=-1):
return self.req.readline(size)
def readlines(self, hint=-1):
return self.req.readlines(hint)
def __iter__(self):
line = self.readline()
while line:
yield line
# Notice this won't prefetch the next line; it only
# gets called if the generator is resumed.
line = self.readline()
class ErrorWrapper(object):
def __init__(self, req):
self.req = req
def flush(self):
pass
def write(self, msg):
self.req.log_error(msg)
def writelines(self, seq):
self.write(''.join(seq))
bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', "
"when running a version of mod_python < 3.1")
class Handler(object):
def __init__(self, req):
self.started = False
options = req.get_options()
# Threading and forking
try:
q = apache.mpm_query
threaded = q(apache.AP_MPMQ_IS_THREADED)
forked = q(apache.AP_MPMQ_IS_FORKED)
except AttributeError:
threaded = options.get('multithread', '').lower()
if threaded == 'on':
threaded = True
elif threaded == 'off':
threaded = False
else:
raise ValueError(bad_value % "multithread")
forked = options.get('multiprocess', '').lower()
if forked == 'on':
forked = True
elif forked == 'off':
forked = False
else:
raise ValueError(bad_value % "multiprocess")
env = self.environ = dict(apache.build_cgi_env(req))
if 'SCRIPT_NAME' in options:
# Override SCRIPT_NAME and PATH_INFO if requested.
env['SCRIPT_NAME'] = options['SCRIPT_NAME']
env['PATH_INFO'] = req.uri[len(options['SCRIPT_NAME']):]
else:
env['SCRIPT_NAME'] = ''
env['PATH_INFO'] = req.uri
env['wsgi.input'] = InputWrapper(req)
env['wsgi.errors'] = ErrorWrapper(req)
env['wsgi.version'] = (1, 0)
env['wsgi.run_once'] = False
if env.get("HTTPS") in ('yes', 'on', '1'):
env['wsgi.url_scheme'] = 'https'
else:
env['wsgi.url_scheme'] = 'http'
env['wsgi.multithread'] = threaded
env['wsgi.multiprocess'] = forked
self.request = req
def run(self, application):
try:
result = application(self.environ, self.start_response)
for data in result:
self.write(data)
if not self.started:
self.request.set_content_length(0)
if hasattr(result, 'close'):
result.close()
except:
traceback.print_exc(None, self.environ['wsgi.errors'])
if not self.started:
self.request.status = 500
self.request.content_type = 'text/plain'
data = "A server error occurred. Please contact the administrator."
self.request.set_content_length(len(data))
self.request.write(data)
def start_response(self, status, headers, exc_info=None):
if exc_info:
try:
if self.started:
six.reraise(exc_info[0], exc_info[1], exc_info[2])
finally:
exc_info = None
self.request.status = int(status[:3])
for key, val in headers:
if key.lower() == 'content-length':
self.request.set_content_length(int(val))
elif key.lower() == 'content-type':
self.request.content_type = val
else:
self.request.headers_out.add(key, val)
return self.write
def write(self, data):
if not self.started:
self.started = True
self.request.write(data)
startup = None
cleanup = None
wsgiapps = {}
def handler(req):
options = req.get_options()
# Run a startup function if requested.
global startup
if 'wsgi.startup' in options and not startup:
func = options['wsgi.startup']
if func:
module_name, object_str = func.split('::', 1)
module = __import__(module_name, globals(), locals(), [''])
startup = apache.resolve_object(module, object_str)
startup(req)
# Register a cleanup function if requested.
global cleanup
if 'wsgi.cleanup' in options and not cleanup:
func = options['wsgi.cleanup']
if func:
module_name, object_str = func.split('::', 1)
module = __import__(module_name, globals(), locals(), [''])
cleanup = apache.resolve_object(module, object_str)
def cleaner(data):
cleanup()
try:
# apache.register_cleanup wasn't available until 3.1.4.
apache.register_cleanup(cleaner)
except AttributeError:
req.server.register_cleanup(req, cleaner)
# Import the wsgi 'application' callable and pass it to Handler.run
global wsgiapps
appini = options.get('paste.ini')
app = None
if appini:
if appini not in wsgiapps:
wsgiapps[appini] = loadapp("config:%s" % appini)
app = wsgiapps[appini]
# Import the wsgi 'application' callable and pass it to Handler.run
appwsgi = options.get('wsgi.application')
if appwsgi and not appini:
modname, objname = appwsgi.split('::', 1)
module = __import__(modname, globals(), locals(), [''])
app = getattr(module, objname)
Handler(req).run(app)
# status was set in Handler; always return apache.OK
return apache.OK