Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/Paste/paste/modpython.py
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

Top