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

root/branches/cherrypy-2.x/cherrypy/_cputil.py

Revision 1500 (checked in by fumanchu, 2 years ago)

2.x backport of [1154] (Quick and dirty trap of log file errors.)

  • Property svn:eol-style set to native
Line 
1 """A few utility classes/functions used by CherryPy."""
2
3 import cgi
4 import datetime
5 import sys
6 import traceback
7
8 import cherrypy
9 from cherrypy.lib import httptools
10
11 def get_object_trail(objectpath=None):
12     """
13     List of (name, object) pairs, from cherrypy.root to the current object.
14     
15     If any named objects are unreachable, (name, None) pairs are used.
16     """
17    
18     if objectpath is None:
19         try:
20             objectpath = cherrypy.request.object_path
21         except AttributeError:
22             pass
23    
24     if objectpath is not None:
25         objectpath = objectpath.strip('/')
26    
27     # Convert the objectpath into a list of names
28     if not objectpath:
29         nameList = []
30     else:
31         nameList = objectpath.split('/')
32    
33     if nameList == ['global']:
34         # Special-case a Request-URI of * to allow for our default handler.
35         root = getattr(cherrypy, 'root', None)
36         if root is None:
37             return [('root', None), ('global_', None), ('index', None)]
38         gh = getattr(root, 'global_', _cpGlobalHandler)
39         return [('root', cherrypy.root), ('global_', gh), ('index', None)]
40    
41     nameList = ['root'] + nameList + ['index']
42    
43     # Convert the list of names into a list of objects
44     node = cherrypy
45     objectTrail = []
46     for name in nameList:
47         # maps virtual names to Python identifiers (replaces '.' with '_')
48         objname = name.replace('.', '_')
49         node = getattr(node, objname, None)
50         if node is None:
51             objectTrail.append((name, node))
52         else:
53             objectTrail.append((objname, node))
54    
55     return objectTrail
56
57 def get_special_attribute(name, old_name = None):
58     """Return the special attribute. A special attribute is one that
59     applies to all of the children from where it is defined, such as
60     _cp_filters."""
61    
62     # First, we look in the right-most object to see if this special
63     # attribute is implemented. If not, then we try the previous object,
64     # and so on until we reach cherrypy.root, or a mount point.
65     # If it's still not there, we use the implementation from this module.
66     mounted_app_roots = cherrypy.tree.mount_points.values()
67     objectList = get_object_trail()
68     objectList.reverse()
69     for objname, obj in objectList:
70         if old_name and hasattr(obj, old_name):
71             return getattr(obj, old_name)
72         elif hasattr(obj, name):
73             return getattr(obj, name)
74         if obj in mounted_app_roots:
75             break
76    
77     try:
78         if old_name:
79             return globals()[old_name]
80         else:
81             return globals()[name]
82     except KeyError:
83         if old_name:
84             return get_special_attribute(name)
85         msg = "Special attribute %s could not be found" % repr(name)
86         raise cherrypy.HTTPError(500, msg)
87
88 def _cpGlobalHandler():
89     """Default handler for a Request-URI of '*'."""
90     response = cherrypy.response
91     response.headers['Content-Type'] = 'text/plain'
92    
93     # OPTIONS is defined in HTTP 1.1 and greater
94     request = cherrypy.request
95     if request.method == 'OPTIONS' and request.version >= 1.1:
96         response.headers['Allow'] = 'HEAD, GET, POST, PUT, OPTIONS'
97     else:
98         response.headers['Allow'] = 'HEAD, GET, POST'
99     return ""
100 _cpGlobalHandler.exposed = True
101
102 def logtime():
103     now = datetime.datetime.now()
104     month = httptools.monthname[now.month][:3].capitalize()
105     return '%02d/%s/%04d:%02d:%02d:%02d' % (
106         now.day, month, now.year, now.hour, now.minute, now.second)
107
108 def _cp_log_access():
109     """ Default method for logging access """
110    
111     tmpl = '%(h)s %(l)s %(u)s [%(t)s] "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
112     s = tmpl % {'h': cherrypy.request.remoteHost or cherrypy.request.remoteAddr,
113                 'l': '-',
114                 'u': getattr(cherrypy.request, "login", None) or "-",
115                 't': logtime(),
116                 'r': cherrypy.request.requestLine,
117                 's': cherrypy.response.status.split(" ", 1)[0],
118                 'b': cherrypy.response.headers.get('Content-Length', '') or "-",
119                 'f': cherrypy.request.headers.get('referer', ''),
120                 'a': cherrypy.request.headers.get('user-agent', ''),
121                 }
122    
123     if cherrypy.config.get('server.log_to_screen', True):
124         print s
125    
126     fname = cherrypy.config.get('server.log_access_file', '')
127     if fname:
128         f = open(fname, 'ab')
129         try:
130             f.write(s + '\n')
131         finally:
132             f.close()
133
134
135 _log_severity_levels = {0: "INFO", 1: "WARNING", 2: "ERROR"}
136
137 def _cp_log_message(msg, context = '', severity = 0):
138     """Default method for logging messages (error log).
139     
140     This is not just for errors! Applications may call this at any time to
141     log application-specific information.
142     """
143    
144     level = _log_severity_levels.get(severity, "UNKNOWN")
145    
146     s = ' '.join((logtime(), context, level, msg))
147    
148     if cherrypy.config.get('server.log_to_screen', True):
149         print s
150    
151     fname = cherrypy.config.get('server.log_file', '')
152     #logdir = os.path.dirname(fname)
153     #if logdir and not os.path.exists(logdir):
154     #    os.makedirs(logdir)
155     if fname:
156         f = open(fname, 'ab')
157         try:
158             f.write(s + '\n')
159         finally:
160             f.close()
161
162
163 _HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
164 <html>
165 <head>
166     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
167     <title>%(status)s</title>
168     <style type="text/css">
169     #powered_by {
170         margin-top: 20px;
171         border-top: 2px solid black;
172         font-style: italic;
173     }
174
175     #traceback {
176         color: red;
177     }
178     </style>
179 </head>
180     <body>
181         <h2>%(status)s</h2>
182         <p>%(message)s</p>
183         <pre id="traceback">%(traceback)s</pre>
184     <div id="powered_by">
185     <span>Powered by <a href="CherryPy">http://www.cherrypy.org">CherryPy %(version)s</a></span>
186     </div>
187     </body>
188 </html>
189 '''
190
191 def getErrorPage(status, **kwargs):
192     """Return an HTML page, containing a pretty error response.
193     
194     status should be an int or a str.
195     kwargs will be interpolated into the page template.
196     """
197    
198     try:
199         code, reason, message = httptools.validStatus(status)
200     except ValueError, x:
201         raise cherrypy.HTTPError(500, x.args[0])
202    
203     # We can't use setdefault here, because some
204     # callers send None for kwarg values.
205     if kwargs.get('status') is None:
206         kwargs['status'] = "%s %s" % (code, reason)
207     if kwargs.get('message') is None:
208         kwargs['message'] = message
209     if kwargs.get('traceback') is None:
210         kwargs['traceback'] = ''
211     if kwargs.get('version') is None:
212         kwargs['version'] = cherrypy.__version__
213     for k, v in kwargs.iteritems():
214         if v is None:
215             kwargs[k] = ""
216         else:
217             kwargs[k] = cgi.escape(kwargs[k])
218    
219     template = _HTTPErrorTemplate
220     error_page_file = cherrypy.config.get('error_page.%s' % code, '')
221     if error_page_file:
222         try:
223             template = file(error_page_file, 'rb').read()
224         except:
225             m = kwargs['message']
226             if m:
227                 m += "<br />"
228             m += ("In addition, the custom error page "
229                   "failed:\n<br />%s" % (sys.exc_info()[1]))
230             kwargs['message'] = m
231    
232     return template % kwargs
233
234
235 def _cp_on_http_error(status, message):
236     """ Default _cp_on_http_error method.
237     
238     status should be an int.
239     """
240     tb = formatExc()
241     logmsg = ""
242    
243     if cherrypy.config.get('server.log_tracebacks', True):
244         logmsg = tb
245     if cherrypy.config.get('server.log_request_headers', True):
246         h = ["  %s: %s" % (k, v) for k, v in cherrypy.request.header_list]
247         logmsg += 'Request Headers:\n' + '\n'.join(h)
248     if logmsg:
249         cherrypy.log(logmsg, "HTTP")
250    
251     if not cherrypy.config.get('server.show_tracebacks', False):
252         tb = None
253    
254     response = cherrypy.response
255    
256     # Remove headers which applied to the original content,
257     # but do not apply to the error page.
258     for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
259                 "Vary", "Content-Encoding", "Content-Length", "Expires",
260                 "Content-Location", "Content-MD5", "Last-Modified"]:
261         if response.headers.has_key(key):
262             del response.headers[key]
263    
264     if status != 416:
265         # A server sending a response with status code 416 (Requested
266         # range not satisfiable) SHOULD include a Content-Range field
267         # with a byte-range- resp-spec of "*". The instance-length
268         # specifies the current length of the selected resource.
269         # A response with status code 206 (Partial Content) MUST NOT
270         # include a Content-Range field with a byte-range- resp-spec of "*".
271         if response.headers.has_key("Content-Range"):
272             del response.headers["Content-Range"]
273    
274     # In all cases, finalize will be called after this method,
275     # so don't bother cleaning up response values here.
276     response.status = status
277     content = getErrorPage(status, traceback=tb, message=message)
278     response.body = content
279     response.headers['Content-Length'] = len(content)
280     response.headers['Content-Type'] = "text/html"
281    
282     be_ie_unfriendly(status)
283
284
285 _ie_friendly_error_sizes = {
286     400: 512, 403: 256, 404: 512, 405: 256,
287     406: 512, 408: 512, 409: 512, 410: 256,
288     500: 512, 501: 512, 505: 512,
289     }
290
291
292 def be_ie_unfriendly(status):
293    
294     response = cherrypy.response
295    
296     # For some statuses, Internet Explorer 5+ shows "friendly error
297     # messages" instead of our response.body if the body is smaller
298     # than a given size. Fix this by returning a body over that size
299     # (by adding whitespace).
300     # See http://support.microsoft.com/kb/q218155/
301     s = _ie_friendly_error_sizes.get(status, 0)
302     if s:
303         s += 1
304         # Since we are issuing an HTTP error status, we assume that
305         # the entity is short, and we should just collapse it.
306         content = response.collapse_body()
307         l = len(content)
308         if l and l < s:
309             # IN ADDITION: the response must be written to IE
310             # in one chunk or it will still get replaced! Bah.
311             content = content + (" " * (s - l))
312         response.body = content
313         response.headers['Content-Length'] = len(content)
314
315 def lower_to_camel(s):
316     """Turns lowercase_with_underscore into camelCase."""
317     sp = s.split('_')
318     new_sp = []
319     for i, s in enumerate(sp):
320         if i != 0:
321             s = s[0].upper() + s[1:]
322         new_sp.append(s)
323     return ''.join(new_sp)
324
325 def formatExc(exc=None):
326     """formatExc(exc=None) -> exc (or sys.exc_info if None), formatted."""
327     if exc is None:
328         exc = sys.exc_info()
329    
330     if exc == (None, None, None):
331         return ""
332    
333     page_handler_str = ""
334     args = list(getattr(exc[1], "args", []))
335     if args:
336         if len(args) > 1:
337             page_handler = args.pop()
338             page_handler_str = 'Page handler: %s\n' % repr(page_handler)
339             exc[1].args = tuple(args)
340     return page_handler_str + "".join(traceback.format_exception(*exc))
341
342 def bareError(extrabody=None):
343     """Produce status, headers, body for a critical error.
344     
345     Returns a triple without calling any other questionable functions,
346     so it should be as error-free as possible. Call it from an HTTP server
347     if you get errors after Request() is done.
348     
349     If extrabody is None, a friendly but rather unhelpful error message
350     is set in the body. If extrabody is a string, it will be appended
351     as-is to the body.
352     """
353    
354     # The whole point of this function is to be a last line-of-defense
355     # in handling errors. That is, it must not raise any errors itself;
356     # it cannot be allowed to fail. Therefore, don't add to it!
357     # In particular, don't call any other CP functions.
358    
359     body = "Unrecoverable error in the server."
360     if extrabody is not None:
361         body += "\n" + extrabody
362    
363     return ("500 Internal Server Error",
364             [('Content-Type', 'text/plain'),
365              ('Content-Length', str(len(body)))],
366             [body])
367
368 def _cp_on_error():
369     """ Default _cp_on_error method """
370     # Allow logging of only *unexpected* HTTPError's.
371     if (not cherrypy.config.get('server.log_tracebacks', True)
372         and cherrypy.config.get('server.log_unhandled_tracebacks', True)):
373         cherrypy.log(traceback=True)
374    
375     cherrypy.HTTPError(500).set_response()
376
377 def headers(headers):
378     """ Provides a simple way to add specific headers to page handler
379     Any previously set headers provided in the list of tuples will be changed
380     
381     headers - a list of tuple : (header_name, header_value)
382     """
383     def wrapper(func):
384         def inner(*args):
385             for item in headers:
386                 headername = item[0]
387                 headervalue = item[1]
388                 cherrypy.response.headerMap[headername] = headervalue
389             return func(*args)
390         return inner
391     return wrapper
392
393 _cp_filters = []
394
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets