summaryrefslogtreecommitdiff
path: root/paste/modpython.py
diff options
context:
space:
mode:
Diffstat (limited to 'paste/modpython.py')
-rw-r--r--paste/modpython.py252
1 files changed, 252 insertions, 0 deletions
diff --git a/paste/modpython.py b/paste/modpython.py
new file mode 100644
index 0000000..a3fef55
--- /dev/null
+++ b/paste/modpython.py
@@ -0,0 +1,252 @@
+"""WSGI Paste wrapper for mod_python. Requires Python 2.2 or greater.
+
+
+Example httpd.conf section for a Paste app with an ini file::
+
+ <Location />
+ SetHandler python-program
+ PythonHandler paste.modpython
+ PythonOption paste.ini /some/location/your/pasteconfig.ini
+ </Location>
+
+Or if you want to load a WSGI application under /your/homedir in the module
+``startup`` and the WSGI app is ``app``::
+
+ <Location />
+ SetHandler python-program
+ PythonHandler paste.modpython
+ PythonPath "['/virtual/project/directory'] + sys.path"
+ PythonOption wsgi.application startup::app
+ </Location>
+
+
+If you'd like to use a virtual installation, make sure to add it in the path
+like so::
+
+ <Location />
+ SetHandler python-program
+ PythonHandler paste.modpython
+ PythonPath "['/virtual/project/directory', '/virtual/lib/python2.4/'] + sys.path"
+ PythonOption paste.ini /virtual/project/directory/pasteconfig.ini
+ </Location>
+
+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 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:
+ raise 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