| """Native adapter for serving CherryPy via mod_python |
| |
| Basic usage: |
| |
| ########################################## |
| # Application in a module called myapp.py |
| ########################################## |
| |
| import cherrypy |
| |
| class Root: |
| @cherrypy.expose |
| def index(self): |
| return 'Hi there, Ho there, Hey there' |
| |
| |
| # We will use this method from the mod_python configuration |
| # as the entry point to our application |
| def setup_server(): |
| cherrypy.tree.mount(Root()) |
| cherrypy.config.update({'environment': 'production', |
| 'log.screen': False, |
| 'show_tracebacks': False}) |
| |
| ########################################## |
| # mod_python settings for apache2 |
| # This should reside in your httpd.conf |
| # or a file that will be loaded at |
| # apache startup |
| ########################################## |
| |
| # Start |
| DocumentRoot "/" |
| Listen 8080 |
| LoadModule python_module /usr/lib/apache2/modules/mod_python.so |
| |
| <Location "/"> |
| PythonPath "sys.path+['/path/to/my/application']" |
| SetHandler python-program |
| PythonHandler cherrypy._cpmodpy::handler |
| PythonOption cherrypy.setup myapp::setup_server |
| PythonDebug On |
| </Location> |
| # End |
| |
| The actual path to your mod_python.so is dependent on your |
| environment. In this case we suppose a global mod_python |
| installation on a Linux distribution such as Ubuntu. |
| |
| We do set the PythonPath configuration setting so that |
| your application can be found by from the user running |
| the apache2 instance. Of course if your application |
| resides in the global site-package this won't be needed. |
| |
| Then restart apache2 and access http://127.0.0.1:8080 |
| """ |
| |
| import logging |
| import sys |
| |
| import cherrypy |
| from cherrypy._cpcompat import BytesIO, copyitems, ntob |
| from cherrypy._cperror import format_exc, bare_error |
| from cherrypy.lib import httputil |
| |
| |
| # ------------------------------ Request-handling |
| |
| |
| |
| def setup(req): |
| from mod_python import apache |
| |
| # Run any setup functions defined by a "PythonOption cherrypy.setup" directive. |
| options = req.get_options() |
| if 'cherrypy.setup' in options: |
| for function in options['cherrypy.setup'].split(): |
| atoms = function.split('::', 1) |
| if len(atoms) == 1: |
| mod = __import__(atoms[0], globals(), locals()) |
| else: |
| modname, fname = atoms |
| mod = __import__(modname, globals(), locals(), [fname]) |
| func = getattr(mod, fname) |
| func() |
| |
| cherrypy.config.update({'log.screen': False, |
| "tools.ignore_headers.on": True, |
| "tools.ignore_headers.headers": ['Range'], |
| }) |
| |
| engine = cherrypy.engine |
| if hasattr(engine, "signal_handler"): |
| engine.signal_handler.unsubscribe() |
| if hasattr(engine, "console_control_handler"): |
| engine.console_control_handler.unsubscribe() |
| engine.autoreload.unsubscribe() |
| cherrypy.server.unsubscribe() |
| |
| def _log(msg, level): |
| newlevel = apache.APLOG_ERR |
| if logging.DEBUG >= level: |
| newlevel = apache.APLOG_DEBUG |
| elif logging.INFO >= level: |
| newlevel = apache.APLOG_INFO |
| elif logging.WARNING >= level: |
| newlevel = apache.APLOG_WARNING |
| # On Windows, req.server is required or the msg will vanish. See |
| # http://www.modpython.org/pipermail/mod_python/2003-October/014291.html. |
| # Also, "When server is not specified...LogLevel does not apply..." |
| apache.log_error(msg, newlevel, req.server) |
| engine.subscribe('log', _log) |
| |
| engine.start() |
| |
| def cherrypy_cleanup(data): |
| engine.exit() |
| try: |
| # apache.register_cleanup wasn't available until 3.1.4. |
| apache.register_cleanup(cherrypy_cleanup) |
| except AttributeError: |
| req.server.register_cleanup(req, cherrypy_cleanup) |
| |
| |
| class _ReadOnlyRequest: |
| expose = ('read', 'readline', 'readlines') |
| def __init__(self, req): |
| for method in self.expose: |
| self.__dict__[method] = getattr(req, method) |
| |
| |
| recursive = False |
| |
| _isSetUp = False |
| def handler(req): |
| from mod_python import apache |
| try: |
| global _isSetUp |
| if not _isSetUp: |
| setup(req) |
| _isSetUp = True |
| |
| # Obtain a Request object from CherryPy |
| local = req.connection.local_addr |
| local = httputil.Host(local[0], local[1], req.connection.local_host or "") |
| remote = req.connection.remote_addr |
| remote = httputil.Host(remote[0], remote[1], req.connection.remote_host or "") |
| |
| scheme = req.parsed_uri[0] or 'http' |
| req.get_basic_auth_pw() |
| |
| try: |
| # apache.mpm_query only became available in mod_python 3.1 |
| q = apache.mpm_query |
| threaded = q(apache.AP_MPMQ_IS_THREADED) |
| forked = q(apache.AP_MPMQ_IS_FORKED) |
| except AttributeError: |
| bad_value = ("You must provide a PythonOption '%s', " |
| "either 'on' or 'off', when running a version " |
| "of mod_python < 3.1") |
| |
| 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") |
| |
| sn = cherrypy.tree.script_name(req.uri or "/") |
| if sn is None: |
| send_response(req, '404 Not Found', [], '') |
| else: |
| app = cherrypy.tree.apps[sn] |
| method = req.method |
| path = req.uri |
| qs = req.args or "" |
| reqproto = req.protocol |
| headers = copyitems(req.headers_in) |
| rfile = _ReadOnlyRequest(req) |
| prev = None |
| |
| try: |
| redirections = [] |
| while True: |
| request, response = app.get_serving(local, remote, scheme, |
| "HTTP/1.1") |
| request.login = req.user |
| request.multithread = bool(threaded) |
| request.multiprocess = bool(forked) |
| request.app = app |
| request.prev = prev |
| |
| # Run the CherryPy Request object and obtain the response |
| try: |
| request.run(method, path, qs, reqproto, headers, rfile) |
| break |
| except cherrypy.InternalRedirect: |
| ir = sys.exc_info()[1] |
| app.release_serving() |
| prev = request |
| |
| if not recursive: |
| if ir.path in redirections: |
| raise RuntimeError("InternalRedirector visited the " |
| "same URL twice: %r" % ir.path) |
| else: |
| # Add the *previous* path_info + qs to redirections. |
| if qs: |
| qs = "?" + qs |
| redirections.append(sn + path + qs) |
| |
| # Munge environment and try again. |
| method = "GET" |
| path = ir.path |
| qs = ir.query_string |
| rfile = BytesIO() |
| |
| send_response(req, response.output_status, response.header_list, |
| response.body, response.stream) |
| finally: |
| app.release_serving() |
| except: |
| tb = format_exc() |
| cherrypy.log(tb, 'MOD_PYTHON', severity=logging.ERROR) |
| s, h, b = bare_error() |
| send_response(req, s, h, b) |
| return apache.OK |
| |
| |
| def send_response(req, status, headers, body, stream=False): |
| # Set response status |
| req.status = int(status[:3]) |
| |
| # Set response headers |
| req.content_type = "text/plain" |
| for header, value in headers: |
| if header.lower() == 'content-type': |
| req.content_type = value |
| continue |
| req.headers_out.add(header, value) |
| |
| if stream: |
| # Flush now so the status and headers are sent immediately. |
| req.flush() |
| |
| # Set response body |
| if isinstance(body, basestring): |
| req.write(body) |
| else: |
| for seg in body: |
| req.write(seg) |
| |
| |
| |
| # --------------- Startup tools for CherryPy + mod_python --------------- # |
| |
| |
| import os |
| import re |
| try: |
| import subprocess |
| def popen(fullcmd): |
| p = subprocess.Popen(fullcmd, shell=True, |
| stdout=subprocess.PIPE, stderr=subprocess.STDOUT, |
| close_fds=True) |
| return p.stdout |
| except ImportError: |
| def popen(fullcmd): |
| pipein, pipeout = os.popen4(fullcmd) |
| return pipeout |
| |
| |
| def read_process(cmd, args=""): |
| fullcmd = "%s %s" % (cmd, args) |
| pipeout = popen(fullcmd) |
| try: |
| firstline = pipeout.readline() |
| if (re.search(ntob("(not recognized|No such file|not found)"), firstline, |
| re.IGNORECASE)): |
| raise IOError('%s must be on your system path.' % cmd) |
| output = firstline + pipeout.read() |
| finally: |
| pipeout.close() |
| return output |
| |
| |
| class ModPythonServer(object): |
| |
| template = """ |
| # Apache2 server configuration file for running CherryPy with mod_python. |
| |
| DocumentRoot "/" |
| Listen %(port)s |
| LoadModule python_module modules/mod_python.so |
| |
| <Location %(loc)s> |
| SetHandler python-program |
| PythonHandler %(handler)s |
| PythonDebug On |
| %(opts)s |
| </Location> |
| """ |
| |
| def __init__(self, loc="/", port=80, opts=None, apache_path="apache", |
| handler="cherrypy._cpmodpy::handler"): |
| self.loc = loc |
| self.port = port |
| self.opts = opts |
| self.apache_path = apache_path |
| self.handler = handler |
| |
| def start(self): |
| opts = "".join([" PythonOption %s %s\n" % (k, v) |
| for k, v in self.opts]) |
| conf_data = self.template % {"port": self.port, |
| "loc": self.loc, |
| "opts": opts, |
| "handler": self.handler, |
| } |
| |
| mpconf = os.path.join(os.path.dirname(__file__), "cpmodpy.conf") |
| f = open(mpconf, 'wb') |
| try: |
| f.write(conf_data) |
| finally: |
| f.close() |
| |
| response = read_process(self.apache_path, "-k start -f %s" % mpconf) |
| self.ready = True |
| return response |
| |
| def stop(self): |
| os.popen("apache -k stop") |
| self.ready = False |
| |