[Web-SIG] [WSGI] mod_python wrapper: minimal first attempt

Robert Brewer fumanchu at amor.org
Thu Oct 14 07:13:21 CEST 2004


Phillip J. Eby wrote:
> 
> At 02:06 PM 10/13/04 -0700, Robert Brewer wrote:
> >In order to test my application's WSGI interface, I wrote a quick
> >mod_python server interface for WSGI. It's not bulletproof, but the
> >parts I use work. Sorry, Phillip, I didn't subclass
> >wsgiref.handlers.BaseHandler yet. ;(
> 
> That's okay; you've given me several of the pieces I would 
> need to do it myself.  :)

I was hoping someone would say that. :)

> Anyway, a mod_python handler would probably look something like:
> 
>      from wsgiref.handlers import BaseCGIHandler
> 
>      class ModPyHandler(BaseCGIHandler):
> 
>          def __init__(self,req):
>              req.add_common_vars()
>              BaseCGIHandler.__init__(self,
>                  stdin = ModPythonInputWrapper(req),
>                  stdout = None,
>                  stderr = ModPythonErrorWrapper(req),
>                  environ = dict(req.subprocess_env.items()),
>                  multiprocess = True,  # XXX
>                  multithread  = True,  # XXX
>              )

1. I found apache.build_cgi_env(req) tonight, which does the
add_common_vars() and dict() shoving for you. Unfortunately, it's got a
bug. So I just stole code from it.

2. I think apache.mpm_query() is what we want for
multithreading/process. But it was introduced in version 3.1, so that
needs to be trapped. I went with optional arguments to
ModPyHandler.__init__

>      def wsgi_handler(req):
>          handler = ModPyHandler(req)
>          options = req.get_options()
>          appmod,appname = options['application'].split('::')
>          d = {}
>          exec ("from %(appmod)s import %(appname) as application" % 
> locals()) in d
>          handler.run(d[application])
>          from mod_python import apache
>          return apache.OK

Eeew. exec. Smelly. :) I'll stick with plain Python code over
PythonOption, thanks, and make my app developers do a few lines of extra
work *once* instead of every deployer on every install. To each his
own... :P

> Let me know if this code works for you, and if so I'll add it to the 
> wsgiref library.

Here's the revised version. I haven't tested everything; for example,
reading straight from wsgi.input or writing to .errors. I'll wait for
the bug reports. :)


class ModPythonInputWrapper(object):
    
    def __init__(self, req):
        self.req = req
    
    def read(self, size=-1):
        return self.req.read(size)
    
    def readline(self):
        return self.req.readline()
    
    def readlines(self, hint=-1):
        return self.req.readlines(hint)
    
    def __iter__(self):
        return iter(self.req.readlines())


class ModPythonErrorWrapper(object):
    
    def __init__(self, req):
        self.req = req
    
    def flush(self):
        pass
    
    def write(self, content):
        self.req.log_error(content)
    
    def writelines(self, seq):
        for content in seq:
            self.req.log_error(content)


from wsgiref.handlers import BaseCGIHandler

class ModPyHandler(BaseCGIHandler):
    
    def __init__(self, req, threaded=None, forked=None):
        from mod_python import apache
        try:
            q = apache.mpm_query
        except AttributeError:
             if (threaded is None) or (forked is None):
                 m = ("You must provide 'threaded' and 'forked' args to
"
                      "ModPyHandler when running mod_python < 3.1")
                 raise ValueError(m)
        else:
            threaded = apache.mpm_query(apache.AP_MPMQ_IS_THREADED)
            forked = apache.mpm_query(apache.AP_MPMQ_IS_FORKED)
        
        req.add_common_vars()
        env = req.subprocess_env.copy()
        
        if req.path_info:
            env["SCRIPT_NAME"] = req.uri[:-len(req.path_info)]
        else:
            env["SCRIPT_NAME"] = req.uri
        
        env["GATEWAY_INTERFACE"] = "Python-CGI/1.1"
        
        # you may want to comment this out for better security
        if req.headers_in.has_key("authorization"):
            env["HTTP_AUTHORIZATION"] = req.headers_in["authorization"]
        
        BaseCGIHandler.__init__(self,
                                stdin=ModPythonInputWrapper(req),
                                stdout=None,
                                stderr=ModPythonErrorWrapper(req),
                                environ=env,
                                multiprocess=forked,
                                multithread=threaded
                                )
        self.request = req
        self._write = req.write
    
    def _flush(self):
        pass
    
    def send_headers(self):
        self.cleanup_headers()
        self.headers_sent = True
        self.request.status = int(self.status[:3])
        for key, val in self.headers.items():
            self.request.headers_out[key] = val



Robert Brewer
MIS
Amor Ministries
fumanchu at amor.org


More information about the Web-SIG mailing list