Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/trunk/cherrypy/_cpmodpy.py

Revision 1939 (checked in by fumanchu, 1 month ago)

mod_python: send bus messages to the apache log, plus cherrypy.setup arg now can take a bare module to import (without having to call a ::function).

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

Hosted by WebFaction

Log in as guest/cpguest to create tickets