| """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 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 |