mirror of https://github.com/evilhero/mylar
354 lines
11 KiB
Python
Executable File
354 lines
11 KiB
Python
Executable File
"""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()
|
|
cmd_not_found = re.search(
|
|
ntob("(not recognized|No such file|not found)"),
|
|
firstline,
|
|
re.IGNORECASE
|
|
)
|
|
if cmd_not_found:
|
|
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
|