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   
71 -def setup(req):
72 from mod_python import apache 73 74 # Run any setup functions defined by a "PythonOption cherrypy.setup" 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 - 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 the " 214 "same URL twice: %r" % ir.path) 215 else: 216 # Add the *previous* path_info + qs to redirections. 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