OLD | NEW |
(Empty) | |
| 1 """Native adapter for serving CherryPy via mod_python |
| 2 |
| 3 Basic usage: |
| 4 |
| 5 ########################################## |
| 6 # Application in a module called myapp.py |
| 7 ########################################## |
| 8 |
| 9 import cherrypy |
| 10 |
| 11 class Root: |
| 12 @cherrypy.expose |
| 13 def index(self): |
| 14 return 'Hi there, Ho there, Hey there' |
| 15 |
| 16 |
| 17 # We will use this method from the mod_python configuration |
| 18 # as the entry point to our application |
| 19 def setup_server(): |
| 20 cherrypy.tree.mount(Root()) |
| 21 cherrypy.config.update({'environment': 'production', |
| 22 'log.screen': False, |
| 23 'show_tracebacks': False}) |
| 24 |
| 25 ########################################## |
| 26 # mod_python settings for apache2 |
| 27 # This should reside in your httpd.conf |
| 28 # or a file that will be loaded at |
| 29 # apache startup |
| 30 ########################################## |
| 31 |
| 32 # Start |
| 33 DocumentRoot "/" |
| 34 Listen 8080 |
| 35 LoadModule python_module /usr/lib/apache2/modules/mod_python.so |
| 36 |
| 37 <Location "/"> |
| 38 PythonPath "sys.path+['/path/to/my/application']" |
| 39 SetHandler python-program |
| 40 PythonHandler cherrypy._cpmodpy::handler |
| 41 PythonOption cherrypy.setup myapp::setup_server |
| 42 PythonDebug On |
| 43 </Location> |
| 44 # End |
| 45 |
| 46 The actual path to your mod_python.so is dependent on your |
| 47 environment. In this case we suppose a global mod_python |
| 48 installation on a Linux distribution such as Ubuntu. |
| 49 |
| 50 We do set the PythonPath configuration setting so that |
| 51 your application can be found by from the user running |
| 52 the apache2 instance. Of course if your application |
| 53 resides in the global site-package this won't be needed. |
| 54 |
| 55 Then restart apache2 and access http://127.0.0.1:8080 |
| 56 """ |
| 57 |
| 58 import logging |
| 59 import sys |
| 60 |
| 61 import cherrypy |
| 62 from cherrypy._cpcompat import BytesIO, copyitems, ntob |
| 63 from cherrypy._cperror import format_exc, bare_error |
| 64 from cherrypy.lib import httputil |
| 65 |
| 66 |
| 67 # ------------------------------ Request-handling |
| 68 |
| 69 |
| 70 |
| 71 def setup(req): |
| 72 from mod_python import apache |
| 73 |
| 74 # Run any setup functions defined by a "PythonOption cherrypy.setup" directi
ve. |
| 75 options = req.get_options() |
| 76 if 'cherrypy.setup' in options: |
| 77 for function in options['cherrypy.setup'].split(): |
| 78 atoms = function.split('::', 1) |
| 79 if len(atoms) == 1: |
| 80 mod = __import__(atoms[0], globals(), locals()) |
| 81 else: |
| 82 modname, fname = atoms |
| 83 mod = __import__(modname, globals(), locals(), [fname]) |
| 84 func = getattr(mod, fname) |
| 85 func() |
| 86 |
| 87 cherrypy.config.update({'log.screen': False, |
| 88 "tools.ignore_headers.on": True, |
| 89 "tools.ignore_headers.headers": ['Range'], |
| 90 }) |
| 91 |
| 92 engine = cherrypy.engine |
| 93 if hasattr(engine, "signal_handler"): |
| 94 engine.signal_handler.unsubscribe() |
| 95 if hasattr(engine, "console_control_handler"): |
| 96 engine.console_control_handler.unsubscribe() |
| 97 engine.autoreload.unsubscribe() |
| 98 cherrypy.server.unsubscribe() |
| 99 |
| 100 def _log(msg, level): |
| 101 newlevel = apache.APLOG_ERR |
| 102 if logging.DEBUG >= level: |
| 103 newlevel = apache.APLOG_DEBUG |
| 104 elif logging.INFO >= level: |
| 105 newlevel = apache.APLOG_INFO |
| 106 elif logging.WARNING >= level: |
| 107 newlevel = apache.APLOG_WARNING |
| 108 # On Windows, req.server is required or the msg will vanish. See |
| 109 # http://www.modpython.org/pipermail/mod_python/2003-October/014291.html
. |
| 110 # Also, "When server is not specified...LogLevel does not apply..." |
| 111 apache.log_error(msg, newlevel, req.server) |
| 112 engine.subscribe('log', _log) |
| 113 |
| 114 engine.start() |
| 115 |
| 116 def cherrypy_cleanup(data): |
| 117 engine.exit() |
| 118 try: |
| 119 # apache.register_cleanup wasn't available until 3.1.4. |
| 120 apache.register_cleanup(cherrypy_cleanup) |
| 121 except AttributeError: |
| 122 req.server.register_cleanup(req, cherrypy_cleanup) |
| 123 |
| 124 |
| 125 class _ReadOnlyRequest: |
| 126 expose = ('read', 'readline', 'readlines') |
| 127 def __init__(self, req): |
| 128 for method in self.expose: |
| 129 self.__dict__[method] = getattr(req, method) |
| 130 |
| 131 |
| 132 recursive = False |
| 133 |
| 134 _isSetUp = False |
| 135 def handler(req): |
| 136 from mod_python import apache |
| 137 try: |
| 138 global _isSetUp |
| 139 if not _isSetUp: |
| 140 setup(req) |
| 141 _isSetUp = True |
| 142 |
| 143 # Obtain a Request object from CherryPy |
| 144 local = req.connection.local_addr |
| 145 local = httputil.Host(local[0], local[1], req.connection.local_host or "
") |
| 146 remote = req.connection.remote_addr |
| 147 remote = httputil.Host(remote[0], remote[1], req.connection.remote_host
or "") |
| 148 |
| 149 scheme = req.parsed_uri[0] or 'http' |
| 150 req.get_basic_auth_pw() |
| 151 |
| 152 try: |
| 153 # apache.mpm_query only became available in mod_python 3.1 |
| 154 q = apache.mpm_query |
| 155 threaded = q(apache.AP_MPMQ_IS_THREADED) |
| 156 forked = q(apache.AP_MPMQ_IS_FORKED) |
| 157 except AttributeError: |
| 158 bad_value = ("You must provide a PythonOption '%s', " |
| 159 "either 'on' or 'off', when running a version " |
| 160 "of mod_python < 3.1") |
| 161 |
| 162 threaded = options.get('multithread', '').lower() |
| 163 if threaded == 'on': |
| 164 threaded = True |
| 165 elif threaded == 'off': |
| 166 threaded = False |
| 167 else: |
| 168 raise ValueError(bad_value % "multithread") |
| 169 |
| 170 forked = options.get('multiprocess', '').lower() |
| 171 if forked == 'on': |
| 172 forked = True |
| 173 elif forked == 'off': |
| 174 forked = False |
| 175 else: |
| 176 raise ValueError(bad_value % "multiprocess") |
| 177 |
| 178 sn = cherrypy.tree.script_name(req.uri or "/") |
| 179 if sn is None: |
| 180 send_response(req, '404 Not Found', [], '') |
| 181 else: |
| 182 app = cherrypy.tree.apps[sn] |
| 183 method = req.method |
| 184 path = req.uri |
| 185 qs = req.args or "" |
| 186 reqproto = req.protocol |
| 187 headers = copyitems(req.headers_in) |
| 188 rfile = _ReadOnlyRequest(req) |
| 189 prev = None |
| 190 |
| 191 try: |
| 192 redirections = [] |
| 193 while True: |
| 194 request, response = app.get_serving(local, remote, scheme, |
| 195 "HTTP/1.1") |
| 196 request.login = req.user |
| 197 request.multithread = bool(threaded) |
| 198 request.multiprocess = bool(forked) |
| 199 request.app = app |
| 200 request.prev = prev |
| 201 |
| 202 # Run the CherryPy Request object and obtain the response |
| 203 try: |
| 204 request.run(method, path, qs, reqproto, headers, rfile) |
| 205 break |
| 206 except cherrypy.InternalRedirect: |
| 207 ir = sys.exc_info()[1] |
| 208 app.release_serving() |
| 209 prev = request |
| 210 |
| 211 if not recursive: |
| 212 if ir.path in redirections: |
| 213 raise RuntimeError("InternalRedirector visited t
he " |
| 214 "same URL twice: %r" % ir.pat
h) |
| 215 else: |
| 216 # Add the *previous* path_info + qs to redirecti
ons. |
| 217 if qs: |
| 218 qs = "?" + qs |
| 219 redirections.append(sn + path + qs) |
| 220 |
| 221 # Munge environment and try again. |
| 222 method = "GET" |
| 223 path = ir.path |
| 224 qs = ir.query_string |
| 225 rfile = BytesIO() |
| 226 |
| 227 send_response(req, response.output_status, response.header_list, |
| 228 response.body, response.stream) |
| 229 finally: |
| 230 app.release_serving() |
| 231 except: |
| 232 tb = format_exc() |
| 233 cherrypy.log(tb, 'MOD_PYTHON', severity=logging.ERROR) |
| 234 s, h, b = bare_error() |
| 235 send_response(req, s, h, b) |
| 236 return apache.OK |
| 237 |
| 238 |
| 239 def send_response(req, status, headers, body, stream=False): |
| 240 # Set response status |
| 241 req.status = int(status[:3]) |
| 242 |
| 243 # Set response headers |
| 244 req.content_type = "text/plain" |
| 245 for header, value in headers: |
| 246 if header.lower() == 'content-type': |
| 247 req.content_type = value |
| 248 continue |
| 249 req.headers_out.add(header, value) |
| 250 |
| 251 if stream: |
| 252 # Flush now so the status and headers are sent immediately. |
| 253 req.flush() |
| 254 |
| 255 # Set response body |
| 256 if isinstance(body, basestring): |
| 257 req.write(body) |
| 258 else: |
| 259 for seg in body: |
| 260 req.write(seg) |
| 261 |
| 262 |
| 263 |
| 264 # --------------- Startup tools for CherryPy + mod_python --------------- # |
| 265 |
| 266 |
| 267 import os |
| 268 import re |
| 269 try: |
| 270 import subprocess |
| 271 def popen(fullcmd): |
| 272 p = subprocess.Popen(fullcmd, shell=True, |
| 273 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, |
| 274 close_fds=True) |
| 275 return p.stdout |
| 276 except ImportError: |
| 277 def popen(fullcmd): |
| 278 pipein, pipeout = os.popen4(fullcmd) |
| 279 return pipeout |
| 280 |
| 281 |
| 282 def read_process(cmd, args=""): |
| 283 fullcmd = "%s %s" % (cmd, args) |
| 284 pipeout = popen(fullcmd) |
| 285 try: |
| 286 firstline = pipeout.readline() |
| 287 if (re.search(ntob("(not recognized|No such file|not found)"), firstline
, |
| 288 re.IGNORECASE)): |
| 289 raise IOError('%s must be on your system path.' % cmd) |
| 290 output = firstline + pipeout.read() |
| 291 finally: |
| 292 pipeout.close() |
| 293 return output |
| 294 |
| 295 |
| 296 class ModPythonServer(object): |
| 297 |
| 298 template = """ |
| 299 # Apache2 server configuration file for running CherryPy with mod_python. |
| 300 |
| 301 DocumentRoot "/" |
| 302 Listen %(port)s |
| 303 LoadModule python_module modules/mod_python.so |
| 304 |
| 305 <Location %(loc)s> |
| 306 SetHandler python-program |
| 307 PythonHandler %(handler)s |
| 308 PythonDebug On |
| 309 %(opts)s |
| 310 </Location> |
| 311 """ |
| 312 |
| 313 def __init__(self, loc="/", port=80, opts=None, apache_path="apache", |
| 314 handler="cherrypy._cpmodpy::handler"): |
| 315 self.loc = loc |
| 316 self.port = port |
| 317 self.opts = opts |
| 318 self.apache_path = apache_path |
| 319 self.handler = handler |
| 320 |
| 321 def start(self): |
| 322 opts = "".join([" PythonOption %s %s\n" % (k, v) |
| 323 for k, v in self.opts]) |
| 324 conf_data = self.template % {"port": self.port, |
| 325 "loc": self.loc, |
| 326 "opts": opts, |
| 327 "handler": self.handler, |
| 328 } |
| 329 |
| 330 mpconf = os.path.join(os.path.dirname(__file__), "cpmodpy.conf") |
| 331 f = open(mpconf, 'wb') |
| 332 try: |
| 333 f.write(conf_data) |
| 334 finally: |
| 335 f.close() |
| 336 |
| 337 response = read_process(self.apache_path, "-k start -f %s" % mpconf) |
| 338 self.ready = True |
| 339 return response |
| 340 |
| 341 def stop(self): |
| 342 os.popen("apache -k stop") |
| 343 self.ready = False |
| 344 |
OLD | NEW |