############################################################################## # # Copyright (c) 2005 Philipp "philiKON" von Weitershausen # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """WSGI glue for mod_python This aims to implement the WSGI spec v1.0, a.k.a. PEP 333 (see http://python.org/peps/pep-0333.html) Largely based on the example by Robert Brewer, posted at http://mail.python.org/pipermail/web-sig/2004-October/000980.html $Id$ """ import sys from zope.interface import implements from modzope.interfaces import IWSGIInputStream, IWSGIErrorStream from wsgiref.handlers import BaseCGIHandler class ModPythonInputStream(object): """Wraps a mod_python request as a WSGI-compliant input stream For demonstration purposes, we use a regular StringIO object to mimick the Apache request (though we can't be sure whether that actually behaves equally)... >>> demostring = ('Garfield likes lasagna\\r\\nJon likes Garfield\\r\\n' ... 'Garfield hates Odie\\r\\n') >>> from StringIO import StringIO >>> from pprint import pprint >>> req = StringIO(demostring) >>> stream = ModPythonInputStream(req) >>> stream.read() == demostring True >>> req = StringIO(demostring) >>> stream = ModPythonInputStream(req) >>> stream.readline() 'Garfield likes lasagna\\r\\n' >>> req = StringIO(demostring) >>> stream = ModPythonInputStream(req) >>> pprint(stream.readlines()) ['Garfield likes lasagna\\r\\n', 'Jon likes Garfield\\r\\n', 'Garfield hates Odie\\r\\n'] >>> req = StringIO(demostring) >>> stream = ModPythonInputStream(req) >>> pprint(list(iter(stream))) ['Garfield likes lasagna\\r\\n', 'Jon likes Garfield\\r\\n', 'Garfield hates Odie\\r\\n'] """ implements(IWSGIInputStream) def __init__(self, request): self.request = request def read(self, size=-1): return self.request.read(size) def readline(self): return self.request.readline() def readlines(self, hint=-1): return self.request.readlines(hint) def __iter__(self): return iter(self.request.readlines()) class ModPythonErrorStream(object): """Error stream This enables logging to a custom error log file as defined with a ErrorLog directive. Errors are printed to sys.stderr end up in the global apache error log file. >>> demostring = ('Garfield likes lasagna\\r\\nJon likes Garfield\\r\\n' ... 'Garfield hates Odie\\r\\n') >>> class FakeRequest(object): ... def __init__(self): ... self.debug = False ... self.body = '' ... self.errorlog = '' ... def get_config(self): ... return {'PythonDebug': self.debug} ... def write(self, s): ... self.body += s ... def log_error(self, s): ... self.errorlog += s >>> req = FakeRequest() >>> stream = ModPythonErrorStream(req) >>> stream.write(demostring) >>> req.errorlog == demostring True >>> req.body '' >>> req = FakeRequest() >>> stream = ModPythonErrorStream(req) >>> stream.debug = True >>> stream.write(demostring) >>> req.body == demostring True >>> req = FakeRequest() >>> stream = ModPythonErrorStream(req) >>> stream.writelines([l + '\\r\\n' for l in demostring.splitlines()]) >>> req.errorlog == demostring True """ implements(IWSGIErrorStream) def __init__(self, request): self.request = request config = request.get_config() self.debug = bool(config.get("PythonDebug", False)) def flush(self): pass def write(self, msg): self.request.log_error(msg) if self.debug: self.request.write(msg) def writelines(self, seq): # PEP333 says you shouldn't add line separators self.write(''.join(seq)) def envFromApache(req): """Fetch environment from Apache request. Works like mod_python.apache.build_cgi_env, but fits better to our use.""" req.add_common_vars() env = req.subprocess_env.copy() # zope.publisher needs PATH_INFO instead of SCRIPT_NAME (the # latter confuses it badly) env['PATH_INFO'] = req.uri env['SCRIPT_NAME'] = '' if req.headers_in.has_key("authorization"): env["HTTP_AUTHORIZATION"] = req.headers_in["authorization"] return env class ModPythonHandler(BaseCGIHandler): # we're not run as a CGI script, so don't take over os.environ as # request variables os_environ = {} def __init__(self, request): from mod_python import apache options = request.get_options() try: mpm_query = apache.mpm_query except AttributeError: # Threading and forking threaded = options.get('multithread', '') forked = options.get('multiprocess', '') if not (threaded and forked): raise ValueError("You must provide 'multithread' and " "'multiprocess' PythonOptions when " "running mod_python < 3.1") threaded = threaded.lower() in ('on', 't', 'true', '1') forked = forked.lower() in ('on', 't', 'true', '1') else: threaded = mpm_query(apache.AP_MPMQ_IS_THREADED) forked = mpm_query(apache.AP_MPMQ_IS_FORKED) apache_env = envFromApache(request) BaseCGIHandler.__init__( self, stdin=ModPythonInputStream(request), stdout=None, stderr=ModPythonErrorStream(request), environ=apache_env, multiprocess=forked, multithread=threaded) self.request = request self._write = request.write def _flush(self): pass def send_headers(self): self.cleanup_headers() self.headers_sent = True self.request.status = int(self.status[:3]) for name, value in self.headers.items(): if name.lower() == 'content-length': self.request.set_content_length(int(value)) elif name.lower() == 'content-type': self.request.content_type = value else: self.request.headers_out[name] = value