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