| « Sorry about the recent outages | URL-rewriting in CherryPy 2.1 » |
Update Nov 6, 2005: Finally got it to work with Apache2-prefork on Unix (it only worked on mpm_winnt until now).
Update Oct 25, 2005: I was having a problem setting up a new install of my CherryPy application, using this recipe. It turned out that I didn't have the right interpreter_name in my PythonImport directive:
PythonImport module interpreter_name
Therefore, the CherryPy server started in a different intepreter than the one being used for the requests. It must exactly match the value of req.interpreter, and is case-sensitive. I've updated the code with comments to that effect (just to have it all in one place).
Update Aug 11, 2005: I was having a problem serving .css and .js pages. CherryPy's standalone WSGI server did fine, but mod_python did not. I finally tracked it down to the fact that I was both setting apache's req.status and returning req.status from the handler. Funky. It worked when I chose to simply return the value, and not set it.
Update June 5, 2005:
Pages take forever to terminate when returning a status of 200--apache.OK must be returned instead in that case.
Added code for hotshot profiling.
Added code for using paste.lint
As I mentioned I was doing last week, I wrote a more complete WSGI wrapper for modpython. Here it is. Feedback welcome. Phil Eby told me he'd like a mod_python wrapper for inclusion in wsgiref; he should feel free to use this one however he sees fit. ![]()
"""
WSGI wrapper for mod_python. Requires Python 2.2 or greater.
Example httpd.conf section for a CherryPy app called "mcontrol":
<Directory D:\htdocs\mcontrol>
SetHandler python-program
PythonHandler wsgiref.modpy_wrapper::handler
PythonOption application cherrypy.wsgiapp::wsgiApp
PythonOption import mcontrol.cherry::startup
</Directory>
"""
import sys
from mod_python import apache
from wsgiref.handlers import BaseCGIHandler
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):
return self.req.readline()
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(BaseCGIHandler):
def __init__(self, req):
options = req.get_options()
# Threading and forking
try:
q = apache.mpm_query
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")
else:
threaded = q(apache.AP_MPMQ_IS_THREADED)
forked = q(apache.AP_MPMQ_IS_FORKED)
env = dict(apache.build_cgi_env(req))
if req.headers_in.has_key("authorization"):
env["HTTP_AUTHORIZATION"] = req.headers_in["authorization"]
BaseCGIHandler.__init__(self,
stdin=InputWrapper(req),
stdout=None,
stderr=ErrorWrapper(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
# Can't just return 200 or the page will hang until timeout
s = int(self.status[:3])
if s == 200:
self.finalstatus = apache.OK
else:
self.finalstatus = s
# the headers.Headers class doesn't have an iteritems method...
for key, val in self.headers.items():
if key.lower() == 'content-length':
if val is not None:
self.request.set_content_length(int(val))
elif key.lower() == 'content-type':
self.request.content_type = val
else:
self.request.headers_out[key] = val
_counter = 0
def profile(req):
# Call this function instead of handler
# to get profiling data for each call.
import hotshot, os.path
ppath = os.path.dirname(__file__)
if not os.path.exists(ppath):
os.makedirs(ppath)
global _counter
_counter += 1
ppath = os.path.join(ppath, "cp_%s.prof" % _counter)
prof = hotshot.Profile(ppath)
result = prof.runcall(handler, req)
prof.close()
return result
def handler(req):
config = req.get_config()
debug = int(config.get("PythonDebug", 0))
options = req.get_options()
# Because PythonImport cannot be specified per Directory or Location,
# take any 'import' PythonOption's and import them. If a function name
# in that module is provided (after the "::"), it will be called with
# the request as an argument. The module and function, if any, should
# be re-entrant (i.e., handle multiple threads), and, since they will
# be called per request, must be designed to run setup code only on the
# first request (a global 'first_request' flag is usually enough).
import_opt = options.get('import')
if import_opt:
atoms = import_opt.split('::', 1)
modname = atoms.pop(0)
module = __import__(modname, globals(), locals(), [''])
if atoms:
func = getattr(module, atoms[0])
func(req)
# Import the wsgi 'application' callable and pass it to Handler.run
modname, objname = options['application'].split('::', 1)
module = __import__(modname, globals(), locals(), [''])
app = getattr(module, objname)
h = Handler(req)
## from paste import lint
## app = lint.middleware(app)
h.run(app)
# finalstatus was set in Handler.send_headers()
return h.finalstatus
Thanks very much for this. Are you interested in working with me to get this to work with Paste? Please contact me to chat about it. (I couldn't find your e-mail address on this site.)
Bob - it's really great to understand now - what all this means...not that I really understand any of it - but now I understand why you post it. Thanks.
Hi, I'm on the developers team at mod_python, and I've found your implementation of the WSGI spec for mod_python. I've seen that there are requests to integrate your code in application frameworks, but would not it be more logical to integrate it into mod_python ? I'm just speaking on my behalf for now, but would you consider donating this code to the mod_python community (in exchange of due reference and huge thanks) ? Contact me if you're interested. Thanks !
It would be really great to include a wsgi handler in mod_python distribution!
Leandro,
Yes, it would be great! I was contacted a few weeks ago by the mod_python team, and have given them full permission to use my handler in the standard distribution. I'm just waiting now for it to show up on their site. ;)
I get Segmantation fault in apache logs. I'm using apache 2.0.49, Python 2.4.2, CherryPy 2.1.0, ModPython 3.1.4... it happens when cherrypy is imported.
In fact i tried to do a simple "import cherrypy" in modpython_handler.handler function and i get segfault. Imports work with other modules, what about?
Lethalman,
If you could make a ticket on the CherryPy Trac site, it would be a big help. http://www.cherrypy.org/newticket Please include the full text of the log when the segfault occurs.