Changeset 1949
- Timestamp:
- 04/26/08 17:50:08
- Files:
-
- trunk/cherrypy/_cperror.py (modified) (7 diffs)
- trunk/cherrypy/_cprequest.py (modified) (4 diffs)
- trunk/cherrypy/test/test_core.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cperror.py
r1839 r1949 3 3 from cgi import escape as _escape 4 4 from sys import exc_info as _exc_info 5 from traceback import format_exception as _format_exception 5 6 from urlparse import urljoin as _urljoin 6 7 from cherrypy.lib import http as _http … … 149 150 150 151 152 def clean_headers(status): 153 """Remove any headers which should not apply to an error response.""" 154 import cherrypy 155 156 response = cherrypy.response 157 158 # Remove headers which applied to the original content, 159 # but do not apply to the error page. 160 respheaders = response.headers 161 for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After", 162 "Vary", "Content-Encoding", "Content-Length", "Expires", 163 "Content-Location", "Content-MD5", "Last-Modified"]: 164 if respheaders.has_key(key): 165 del respheaders[key] 166 167 if status != 416: 168 # A server sending a response with status code 416 (Requested 169 # range not satisfiable) SHOULD include a Content-Range field 170 # with a byte-range-resp-spec of "*". The instance-length 171 # specifies the current length of the selected resource. 172 # A response with status code 206 (Partial Content) MUST NOT 173 # include a Content-Range field with a byte-range- resp-spec of "*". 174 if respheaders.has_key("Content-Range"): 175 del respheaders["Content-Range"] 176 177 151 178 class HTTPError(CherryPyException): 152 179 """ Exception used to return an HTTP error code (4xx-5xx) to the client. … … 174 201 response = cherrypy.response 175 202 176 # Remove headers which applied to the original content, 177 # but do not apply to the error page. 178 respheaders = response.headers 179 for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After", 180 "Vary", "Content-Encoding", "Content-Length", "Expires", 181 "Content-Location", "Content-MD5", "Last-Modified"]: 182 if respheaders.has_key(key): 183 del respheaders[key] 184 185 if self.status != 416: 186 # A server sending a response with status code 416 (Requested 187 # range not satisfiable) SHOULD include a Content-Range field 188 # with a byte-range-resp-spec of "*". The instance-length 189 # specifies the current length of the selected resource. 190 # A response with status code 206 (Partial Content) MUST NOT 191 # include a Content-Range field with a byte-range- resp-spec of "*". 192 if respheaders.has_key("Content-Range"): 193 del respheaders["Content-Range"] 203 clean_headers(self.status) 194 204 195 205 # In all cases, finalize will be called after this method, … … 199 209 if cherrypy.request.show_tracebacks: 200 210 tb = format_exc() 201 resp headers['Content-Type'] = "text/html"211 response.headers['Content-Type'] = "text/html" 202 212 203 213 content = self.get_error_page(self.status, traceback=tb, 204 214 message=self.message) 205 215 response.body = content 206 resp headers['Content-Length'] = len(content)216 response.headers['Content-Length'] = len(content) 207 217 208 218 _be_ie_unfriendly(self.status) … … 279 289 if kwargs.get('version') is None: 280 290 kwargs['version'] = cherrypy.__version__ 291 281 292 for k, v in kwargs.iteritems(): 282 293 if v is None: … … 285 296 kwargs[k] = _escape(kwargs[k]) 286 297 287 template = _HTTPErrorTemplate 288 289 # Replace the default template with a custom one? 290 error_page_file = cherrypy.request.error_page.get(code, '') 291 if error_page_file: 298 # Use a custom template or callable for the error page? 299 pages = cherrypy.request.error_page 300 error_page = pages.get(code) or pages.get('default') 301 if error_page: 292 302 try: 293 template = file(error_page_file, 'rb').read() 303 if callable(error_page): 304 return error_page(**kwargs) 305 else: 306 return file(error_page, 'rb').read() % kwargs 294 307 except: 308 e = _format_exception(*_exc_info())[-1] 295 309 m = kwargs['message'] 296 310 if m: 297 311 m += "<br />" 298 m += ("In addition, the custom error page " 299 "failed:\n<br />%s" % (_exc_info()[1])) 312 m += "In addition, the custom error page failed:\n<br />%s" % e 300 313 kwargs['message'] = m 301 314 302 return template % kwargs315 return _HTTPErrorTemplate % kwargs 303 316 304 317 … … 369 382 [body]) 370 383 384 trunk/cherrypy/_cprequest.py
r1938 r1949 146 146 def error_page_namespace(k, v): 147 147 """Attach error pages declared in config.""" 148 cherrypy.request.error_page[int(k)] = v 148 if k != 'default': 149 k = int(k) 150 cherrypy.request.error_page[k] = v 149 151 150 152 … … 393 395 error_page = {} 394 396 error_page__doc = """ 395 A dict of {error code: response filename} pairs. The named response 396 files should be Python string-formatting templates, and can expect by 397 default to receive the format values with the mapping keys 'status', 398 'message', 'traceback', and 'version'. The set of format mappings 399 can be extended by overriding HTTPError.set_response.""" 397 A dict of {error code: response filename or callable} pairs. 398 399 The error code must be an int representing a given HTTP error code, 400 or the string 'default', which will be used if no matching entry 401 is found for a given numeric code. 402 403 If a filename is provided, the file should contain a Python string- 404 formatting template, and can expect by default to receive format 405 values with the mapping keys %(status)s, %(message)s, %(traceback)s, 406 and %(version)s. The set of format mappings can be extended by 407 overriding HTTPError.set_response. 408 409 If a callable is provided, it will be called by default with keyword 410 arguments 'status', 'message', 'traceback', and 'version', as for a 411 string-formatting template. The callable must return a string which 412 will be set to response.body. It may also override headers or perform 413 any other processing. 414 415 If no entry is given for an error code, and no 'default' entry exists, 416 a default template will be used. 417 """ 400 418 401 419 show_tracebacks = True … … 716 734 717 735 def handle_error(self, exc): 718 """Handle the last exception. (Core)"""736 """Handle the last unanticipated exception. (Core)""" 719 737 try: 720 738 self.hooks.run("before_error_response") … … 884 902 885 903 904 trunk/cherrypy/test/test_core.py
r1921 r1949 240 240 for chunk in self.as_yield(): 241 241 yield chunk 242 243 242 243 244 def callable_error_page(status, **kwargs): 245 return "Error %s - Well, I'm very sorry but you haven't paid!" % status 246 247 244 248 class Error(Test): 245 249 … … 247 251 } 248 252 249 def custom(self): 250 raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!") 251 custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html")} 253 def custom(self, err='404'): 254 raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!") 255 custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"), 256 'error_page.401': callable_error_page, 257 } 258 259 def custom_default(self): 260 return 1 + 'a' # raise an unexpected error 261 custom_default._cp_config = {'error_page.default': callable_error_page} 252 262 253 263 def noexist(self): … … 721 731 ignore.pop() 722 732 723 # Test custom error page .733 # Test custom error page for a specific error. 724 734 self.getPage("/error/custom") 725 735 self.assertStatus(404) 726 736 self.assertBody("Hello, world\r\n" + (" " * 499)) 737 738 # Test custom error page for a specific error. 739 self.getPage("/error/custom?err=401") 740 self.assertStatus(401) 741 self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!") 742 743 # Test default custom error page. 744 self.getPage("/error/custom_default") 745 self.assertStatus(500) 746 self.assertBody("Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513)) 727 747 728 748 # Test error in custom error page (ticket #305). … … 732 752 msg = ("No, <b>really</b>, not found!<br />" 733 753 "In addition, the custom error page failed:\n<br />" 734 " [Errno 2] No such file or directory: 'nonexistent.html'")754 "IOError: [Errno 2] No such file or directory: 'nonexistent.html'") 735 755 self.assertInBody(msg) 736 756

