Changeset 682
- Timestamp:
- 09/27/05 02:54:29
- Files:
-
- trunk/cherrypy/_cperror.py (modified) (2 diffs)
- trunk/cherrypy/_cphttptools.py (modified) (5 diffs)
- trunk/cherrypy/_cputil.py (modified) (3 diffs)
- trunk/cherrypy/lib/cptools.py (modified) (3 diffs)
- trunk/cherrypy/test/helper.py (modified) (1 diff)
- trunk/cherrypy/test/test_core.py (modified) (6 diffs)
- trunk/cherrypy/test/test_gzip_filter.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cperror.py
r679 r682 172 172 173 173 174 _missing = object()175 176 174 class HTTPError(Error): 177 175 """ Exception used to return an HTTP error code to the client. 178 176 This exception will automatically set the response status and body. 179 177 180 A custom body can be pased to the init method in place of the181 standard error page.178 A custom message (a long description to display in the browser) 179 can be provided in place of the default. 182 180 """ 183 181 184 def __init__(self, status=500, body=_missing):182 def __init__(self, status=500, message=None): 185 183 self.status = status = int(status) 186 184 if status < 400 or status > 599: 187 185 raise ValueError("status must be between 400 and 599.") 188 189 self.body = body 186 self.message = message 190 187 191 188 def set_response(self): 192 189 import cherrypy 193 194 # we now now have access to the traceback 195 statusString, defaultBody = cherrypy._cputil.getErrorStatusAndPage(self.status) 196 197 if self.body is _missing: 198 self.body = defaultBody 199 # try to look up a custom error page in the config map 200 # if there is no error page then use the pageGenerator 201 202 # The page generator is used because the init method is called 203 # before the exception is raised. It is impossible to embed the 204 # traceback in the error page at this piont so we use the generator 205 # to render the error page at a later point 206 207 import cherrypy 208 # try and read the page from a file 209 # we use the default if the page can't be read 210 try: 211 errorPageFile = cherrypy.config.get('errorPage.%s' % status, '') 212 self.body = file(errorPageFile, 'r') 213 except: 214 # we have alread set the body 215 pass 216 217 cherrypy.response.status = statusString 218 cherrypy.response.body = self.body 190 from cherrypy._cputil import getErrorPage, formatExc 191 192 tb = formatExc() 193 if cherrypy.config.get('server.logTracebacks', True): 194 cherrypy.log(tb) 195 196 defaultOn = (cherrypy.config.get('server.environment') == 'development') 197 if not cherrypy.config.get('server.showTracebacks', defaultOn): 198 tb = None 199 200 # In all cases, finalize will be called after this method, 201 # so don't bother cleaning up response values here. 202 cherrypy.response.status = self.status 203 cherrypy.response.body = getErrorPage(self.status, traceback=tb, 204 message=self.message) 205 206 if cherrypy.response.headerMap.has_key("Content-Encoding"): 207 del cherrypy.response.headerMap['Content-Encoding'] 219 208 220 209 def __str__(self): 221 210 import cherrypy 222 return cherrypy._cputil.getErrorStatusAndPage(self.status)[0] 211 return "%s: %s" % (self.status, self.message or "") 212 223 213 224 214 class NotFound(HTTPError): … … 227 217 def __init__(self, path): 228 218 self.args = (path,) 229 HTTPError.__init__(self, 404) 230 231 def __str__(self): 232 return self.args[0] 219 HTTPError.__init__(self, 404, "The path %s was not found." % repr(path)) trunk/cherrypy/_cphttptools.py
r679 r682 276 276 except cherrypy.RequestHandled: 277 277 pass 278 except cherrypy.HTTPRedirect, inst: 279 # For an HTTPRedirect, we don't go through the regular 280 # mechanism: we return the redirect immediately 278 except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst: 279 # For an HTTPRedirect or HTTPError (including NotFound), 280 # we don't go through the regular mechanism: 281 # we return the redirect or error page immediately 281 282 inst.set_response() 282 283 applyFilters('beforeFinalize') 283 284 finalize() 284 except cherrypy.HTTPError, inst:285 # This includes NotFound286 inst.set_response()287 applyFilters('beforeFinalize')288 finalize()289 290 285 finally: 291 286 applyFilters('onEndResource') 292 287 except: 293 # This includes HTTPError and NotFound294 288 handleError(sys.exc_info()) 295 289 … … 425 419 426 420 def handleError(exc): 427 """Set status, headers, and body when an error occurs."""421 """Set status, headers, and body when an unanticipated error occurs.""" 428 422 try: 429 423 applyFilters('beforeErrorResponse') … … 437 431 applyFilters('afterErrorResponse') 438 432 return 439 except cherrypy.HTTPRedirect, inst:433 except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst: 440 434 try: 441 435 inst.set_response() … … 520 514 return body 521 515 522 def checkStatus():523 """Test/set cherrypy.response.status. Provide Reason-phrase if missing."""524 if not cherrypy.response.status:525 cherrypy.response.status = "200 OK"526 else:527 status = str(cherrypy.response.status)528 parts = status.split(" ", 1)529 if len(parts) == 1:530 # No reason supplied.531 code, = parts532 reason = None533 else:534 code, reason = parts535 reason = reason.strip()536 537 try:538 code = int(code)539 assert code >= 100 and code < 600540 except (ValueError, AssertionError):541 code = 500542 reason = None543 544 if reason is None:545 try:546 reason = _cputil.responseCodes[code][0]547 except (KeyError, IndexError):548 reason = ""549 550 cherrypy.response.status = "%s %s" % (code, reason)551 return cherrypy.response.status552 553 516 554 517 general_header_fields = ["Cache-Control", "Connection", "Date", "Pragma", … … 580 543 """Transform headerMap (and cookies) into cherrypy.response.headers.""" 581 544 582 checkStatus()583 584 545 response = cherrypy.response 546 547 code, reason, _ = cptools.validStatus(response.status) 548 response.status = "%s %s" % (code, reason) 549 585 550 if response.body is None: 586 551 response.body = [] trunk/cherrypy/_cputil.py
r681 r682 34 34 import traceback 35 35 import time 36 #import os.path37 38 from BaseHTTPServer import BaseHTTPRequestHandler39 responseCodes = BaseHTTPRequestHandler.responses40 36 41 37 import cherrypy 38 from cherrypy.lib import cptools 39 42 40 43 41 class EmptyClass: … … 153 151 f.close() 154 152 155 def _HTTPErrorTemplate(errorString, message, traceback, version): 156 subTuple = (errorString, errorString, message, traceback, cherrypy.__version__) 157 158 return '''<?xml version="1.0" encoding="UTF-8"?> 159 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 160 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 161 <html> 162 <head> 163 <title>%s</title> 164 <style type="text/css"> 165 #poweredBy { 166 margin-top: 20px; 167 border-top: 2px solid black; 168 font-style: italic; 169 } 170 171 #traceback { 172 color: red; 173 } 174 </style> 175 </head> 176 <body> 177 <h2>%s</h2> 178 <p>%s</p> 179 <pre id="traceback">%s</pre> 180 <div id="poweredBy"> 181 <span>Powered by <a href="http://www.cherrypy.org">Cherrypy %s</a></span> 182 </div> 183 </body> 184 </html> 185 ''' % subTuple 186 187 def getErrorStatusAndPage(status, traceback = None): 188 statusString, message = responseCodes[status] 189 statusString = '%d %s' % (status, statusString) 190 191 if traceback is None: 192 traceback = '' 193 # get the traceback from formatExc 194 developmentMode = (cherrypy.config.get('server.environment') == 'development') 195 if cherrypy.config.get('server.showTracebacks') or developmentMode: 196 traceback = formatExc() 197 198 page = _HTTPErrorTemplate(statusString, message, traceback, cherrypy.__version__) 199 200 return statusString, page 153 154 _HTTPErrorTemplate = '''<?xml version="1.0" encoding="UTF-8"?> 155 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 156 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 157 <html> 158 <head> 159 <title>%(status)s</title> 160 <style type="text/css"> 161 #poweredBy { 162 margin-top: 20px; 163 border-top: 2px solid black; 164 font-style: italic; 165 } 166 167 #traceback { 168 color: red; 169 } 170 </style> 171 </head> 172 <body> 173 <h2>%(status)s</h2> 174 <p>%(message)s</p> 175 <pre id="traceback">%(traceback)s</pre> 176 <div id="poweredBy"> 177 <span>Powered by <a href="http://www.cherrypy.org">Cherrypy %(version)s</a></span> 178 </div> 179 </body> 180 </html> 181 ''' 182 183 def getErrorPage(status, **kwargs): 184 """Return an HTML page, containing a pretty error response. 185 186 status should be an int or a str. 187 kwargs will be interpolated into the page template. 188 """ 189 190 code, reason, message = cptools.validStatus(status) 191 errorPageFile = cherrypy.config.get('errorPage.%s' % code, '') 192 if errorPageFile: 193 template = file(errorPageFile, 'rb') 194 else: 195 template = _HTTPErrorTemplate 196 197 kwargs.setdefault('status', "%s %s" % (code, reason)) 198 kwargs.setdefault('message', '') 199 kwargs.setdefault('traceback', '') 200 kwargs.setdefault('version', cherrypy.__version__) 201 for k, v in kwargs.iteritems(): 202 if v is None: 203 kwargs[k] = "" 204 205 return template % kwargs 201 206 202 207 def formatExc(exc=None): 203 """formatExc(exc=None) -> exc (or sys.exc_info ), formatted."""208 """formatExc(exc=None) -> exc (or sys.exc_info if None), formatted.""" 204 209 if exc is None: 205 210 exc = sys.exc_info() 206 211 207 212 if exc == (None, None, None): 208 213 return "" … … 211 216 def _cpOnError(): 212 217 """ Default _cpOnError method """ 213 214 logTracebacks = cherrypy.config.get('server.logTracebacks', True) 215 if logTracebacks: 216 cherrypy.log(formatExc()) 217 218 response = cherrypy.response 219 220 response.status, response.body = getErrorStatusAndPage(500) 221 222 if cherrypy.response.headerMap.has_key('Content-Encoding'): 223 del cherrypy.response.headerMap['Content-Encoding'] 218 cherrypy.HTTPError(500).set_response() 224 219 225 220 _cpFilterList = [] trunk/cherrypy/lib/cptools.py
r680 r682 28 28 29 29 """ 30 Just a few convenient functions and classes 30 Tools which both the CherryPy framework and application developers invoke. 31 31 """ 32 33 from BaseHTTPServer import BaseHTTPRequestHandler 34 responseCodes = BaseHTTPRequestHandler.responses 32 35 33 36 import inspect … … 41 44 import sys 42 45 import time 46 47 43 48 import cherrypy 44 49 … … 327 332 chunk = input.read(chunkSize) 328 333 input.close() 334 335 def validStatus(status): 336 """Return legal HTTP status Code, Reason-phrase and Message. 337 338 The status arg must be an int, or a str that begins with an int. 339 340 If status is an int, or a str and no reason-phrase is supplied, 341 a default reason-phrase will be provided. 342 """ 343 344 if not status: 345 status = 200 346 347 status = str(status) 348 parts = status.split(" ", 1) 349 if len(parts) == 1: 350 # No reason supplied. 351 code, = parts 352 reason = None 353 else: 354 code, reason = parts 355 reason = reason.strip() 356 357 try: 358 code = int(code) 359 except ValueError: 360 raise cherrypy.HTTPError(500, "Illegal response status from server (non-numeric).") 361 362 if code < 100 or code > 599: 363 raise cherrypy.HTTPError(500, "Illegal response status from server (out of range).") 364 365 if code not in responseCodes: 366 # code is unknown but not illegal 367 defaultReason, message = "", "" 368 else: 369 defaultReason, message = responseCodes[code] 370 371 if reason is None: 372 reason = defaultReason 373 374 return code, reason, message trunk/cherrypy/test/helper.py
r679 r682 150 150 else: 151 151 webtest.WebCase.getPage(self, url, headers, method, body) 152 153 def assertErrorPage(self, errorCode, pattern =''):152 153 def assertErrorPage(self, status, message=None, pattern=''): 154 154 """ Compare the response body with a built in error page. 155 155 The function will optionally look for the regexp pattern, 156 156 within the exception embedded in the error page. 157 157 """ 158 159 from cherrypy._cputil import getErrorStatusAndPage 160 page = getErrorStatusAndPage(errorCode, '')[1] 161 162 # escape the question marks 163 page = page.replace('?', r'\?') 164 165 # re to find the traceback in the page 166 traceRe = re.compile('(<pre id="traceback">)(</pre>)') 167 168 # stick the pattern in the page so we can match everythign 169 # at once 170 page = traceRe.sub( '\g<1>.*%s.*\g<2>' % pattern, page) 171 172 # check if there is no exception 173 if pattern and traceRe.search(self.body): 174 msg = 'No match for %s in body' % `pattern` 175 self._handlewebError(msg) 158 159 from cherrypy._cputil import getErrorPage 160 esc = re.escape 161 page = esc(getErrorPage(status, message=message)) 162 163 # First, test the response body without checking the traceback. 164 # Stick a match-all group (.*) in to grab the traceback. 165 page = page.replace(esc('<pre id="traceback"></pre>'), 166 esc('<pre id="traceback">') + '(.*)' + esc('</pre>')) 167 m = re.match(page, self.body, re.DOTALL) 168 if not m: 169 self._handlewebError('Error page does not match') 170 return 171 172 # Now test the pattern against the traceback 173 if pattern is None: 174 # Special-case None to mean that there should be *no* traceback. 175 if m and m.group(1): 176 self._handlewebError('Error page contains traceback') 176 177 else: 177 if not re.search(page, self.body, re.DOTALL): 178 msg = 'Error page does not match' 179 self._handlewebError(msg) 178 if (m is None) or (not re.search(pattern, m.group(1))): 179 msg = 'Error page does not contain %s in traceback' 180 self._handlewebError(msg % repr(pattern)) 181 180 182 181 183 CPTestLoader = webtest.ReloadingTestLoader() trunk/cherrypy/test/test_core.py
r679 r682 295 295 '/error/page_streamed': { 296 296 'streamResponse': True, 297 }, 298 '/error/cause_err_in_finalize': { 299 'server.showTracebacks': False, 297 300 }, 298 301 }) … … 364 367 365 368 self.getPage("/status/illegal") 366 self.assertBody('oops' + (" " * 509))367 369 self.assertStatus('500 Internal error') 370 msg = "Illegal response status from server (out of range)." 371 self.assertErrorPage(500, msg) 368 372 369 373 self.getPage("/status/unknown") … … 372 376 373 377 self.getPage("/status/bad") 374 self.assertBody('hello' + (" " * 508))375 378 self.assertStatus('500 Internal error') 379 msg = "Illegal response status from server (non-numeric)." 380 self.assertErrorPage(500, msg) 376 381 377 382 def testLogging(self): … … 499 504 self.getPage("/error/missing") 500 505 self.assertStatus("404 Not Found") 501 self.assertErrorPage(404 )506 self.assertErrorPage(404, "The path '/error/missing' was not found.") 502 507 503 508 ignore = helper.webtest.ignored_exceptions … … 506 511 valerr = r'\n raise ValueError\(\)\nValueError\n' 507 512 self.getPage("/error/page_method") 508 self.assertErrorPage(500, valerr)513 self.assertErrorPage(500, pattern=valerr) 509 514 510 515 self.getPage("/error/page_yield") 511 self.assertErrorPage(500, valerr)516 self.assertErrorPage(500, pattern=valerr) 512 517 513 518 import cherrypy … … 523 528 self.assertBody("helloUnrecoverable error in the server.") 524 529 530 # No traceback should be present 525 531 self.getPage("/error/cause_err_in_finalize") 526 # We're in 'production' mode, so body should be empty527 self.assert Body("")532 msg = "Illegal response status from server (non-numeric)." 533 self.assertErrorPage(500, msg, None) 528 534 finally: 529 535 ignore.pop() trunk/cherrypy/test/test_gzip_filter.py
r679 r682 81 81 self.assertNoHeader('Content-Encoding') 82 82 self.assertStatus('500 Internal error') 83 self.assertErrorPage(500, "IndexError\n")83 self.assertErrorPage(500, pattern="IndexError\n") 84 84 85 85 # In this case, there's nothing we can do to deliver a

