Package cherrypy :: Module _cpmodpy
[hide private]
[frames] | no frames]

Source Code for Module cherrypy._cpmodpy

  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 -def setup(req):
71 from mod_python import apache 72 73 # Run any setup functions defined by a "PythonOption cherrypy.setup" 74 # directive. 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
128 - def __init__(self, req):
129 for method in self.expose: 130 self.__dict__[method] = getattr(req, method)
131 132 133 recursive = False 134 135 _isSetUp = False 136 137
138 -def handler(req):
139 from mod_python import apache 140 try: 141 global _isSetUp 142 if not _isSetUp: 143 setup(req) 144 _isSetUp = True 145 146 # Obtain a Request object from CherryPy 147 local = req.connection.local_addr 148 local = httputil.Host( 149 local[0], local[1], req.connection.local_host or "") 150 remote = req.connection.remote_addr 151 remote = httputil.Host( 152 remote[0], remote[1], req.connection.remote_host or "") 153 154 scheme = req.parsed_uri[0] or 'http' 155 req.get_basic_auth_pw() 156 157 try: 158 # apache.mpm_query only became available in mod_python 3.1 159 q = apache.mpm_query 160 threaded = q(apache.AP_MPMQ_IS_THREADED) 161 forked = q(apache.AP_MPMQ_IS_FORKED) 162 except AttributeError: 163 bad_value = ("You must provide a PythonOption '%s', " 164 "either 'on' or 'off', when running a version " 165 "of mod_python < 3.1") 166 167 threaded = options.get('multithread', '').lower() 168 if threaded == 'on': 169 threaded = True 170 elif threaded == 'off': 171 threaded = False 172 else: 173 raise ValueError(bad_value % "multithread") 174 175 forked = options.get('multiprocess', '').lower() 176 if forked == 'on': 177 forked = True 178 elif forked == 'off': 179 forked = False 180 else: 181 raise ValueError(bad_value % "multiprocess") 182 183 sn = cherrypy.tree.script_name(req.uri or "/") 184 if sn is None: 185 send_response(req, '404 Not Found', [], '') 186 else: 187 app = cherrypy.tree.apps[sn] 188 method = req.method 189 path = req.uri 190 qs = req.args or "" 191 reqproto = req.protocol 192 headers = copyitems(req.headers_in) 193 rfile = _ReadOnlyRequest(req) 194 prev = None 195 196 try: 197 redirections = [] 198 while True: 199 request, response = app.get_serving(local, remote, scheme, 200 "HTTP/1.1") 201 request.login = req.user 202 request.multithread = bool(threaded) 203 request.multiprocess = bool(forked) 204 request.app = app 205 request.prev = prev 206 207 # Run the CherryPy Request object and obtain the response 208 try: 209 request.run(method, path, qs, reqproto, headers, rfile) 210 break 211 except cherrypy.InternalRedirect: 212 ir = sys.exc_info()[1] 213 app.release_serving() 214 prev = request 215 216 if not recursive: 217 if ir.path in redirections: 218 raise RuntimeError( 219 "InternalRedirector visited the same URL " 220 "twice: %r" % ir.path) 221 else: 222 # Add the *previous* path_info + qs to 223 # redirections. 224 if qs: 225 qs = "?" + qs 226 redirections.append(sn + path + qs) 227 228 # Munge environment and try again. 229 method = "GET" 230 path = ir.path 231 qs = ir.query_string 232 rfile = BytesIO() 233 234 send_response( 235 req, response.output_status, response.header_list, 236 response.body, response.stream) 237 finally: 238 app.release_serving() 239 except: 240 tb = format_exc() 241 cherrypy.log(tb, 'MOD_PYTHON', severity=logging.ERROR) 242 s, h, b = bare_error() 243 send_response(req, s, h, b) 244 return apache.OK
245 246
247 -def send_response(req, status, headers, body, stream=False):
248 # Set response status 249 req.status = int(status[:3]) 250 251 # Set response headers 252 req.content_type = "text/plain" 253 for header, value in headers: 254 if header.lower() == 'content-type': 255 req.content_type = value 256 continue 257 req.headers_out.add(header, value) 258 259 if stream: 260 # Flush now so the status and headers are sent immediately. 261 req.flush() 262 263 # Set response body 264 if isinstance(body, basestring): 265 req.write(body) 266 else: 267 for seg in body: 268 req.write(seg)
269 270 271 # --------------- Startup tools for CherryPy + mod_python --------------- # 272 import os 273 import re 274 try: 275 import subprocess 276
277 - def popen(fullcmd):
278 p = subprocess.Popen(fullcmd, shell=True, 279 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 280 close_fds=True) 281 return p.stdout
282 except ImportError:
283 - def popen(fullcmd):
284 pipein, pipeout = os.popen4(fullcmd) 285 return pipeout
286 287
288 -def read_process(cmd, args=""):
289 fullcmd = "%s %s" % (cmd, args) 290 pipeout = popen(fullcmd) 291 try: 292 firstline = pipeout.readline() 293 cmd_not_found = re.search( 294 ntob("(not recognized|No such file|not found)"), 295 firstline, 296 re.IGNORECASE 297 ) 298 if cmd_not_found: 299 raise IOError('%s must be on your system path.' % cmd) 300 output = firstline + pipeout.read() 301 finally: 302 pipeout.close() 303 return output
304 305
306 -class ModPythonServer(object):
307 308 template = """ 309 # Apache2 server configuration file for running CherryPy with mod_python. 310 311 DocumentRoot "/" 312 Listen %(port)s 313 LoadModule python_module modules/mod_python.so 314 315 <Location %(loc)s> 316 SetHandler python-program 317 PythonHandler %(handler)s 318 PythonDebug On 319 %(opts)s 320 </Location> 321 """ 322
323 - def __init__(self, loc="/", port=80, opts=None, apache_path="apache", 324 handler="cherrypy._cpmodpy::handler"):
325 self.loc = loc 326 self.port = port 327 self.opts = opts 328 self.apache_path = apache_path 329 self.handler = handler
330
331 - def start(self):
332 opts = "".join([" PythonOption %s %s\n" % (k, v) 333 for k, v in self.opts]) 334 conf_data = self.template % {"port": self.port, 335 "loc": self.loc, 336 "opts": opts, 337 "handler": self.handler, 338 } 339 340 mpconf = os.path.join(os.path.dirname(__file__), "cpmodpy.conf") 341 f = open(mpconf, 'wb') 342 try: 343 f.write(conf_data) 344 finally: 345 f.close() 346 347 response = read_process(self.apache_path, "-k start -f %s" % mpconf) 348 self.ready = True 349 return response
350
351 - def stop(self):
352 os.popen("apache -k stop") 353 self.ready = False
354