Package gavo :: Package web :: Module weberrors
[frames] | no frames]

Source Code for Module gavo.web.weberrors

  1  """ 
  2  Default error displays for the data center and error helper code. 
  3   
  4  Everything in here must render synchronuosly. 
  5   
  6  You probably should not construct anything in this module directly 
  7  but rather just raise the appropriate exceptions from svcs. 
  8  """ 
  9   
 10  #c Copyright 2008-2019, the GAVO project 
 11  #c 
 12  #c This program is free software, covered by the GNU GPL.  See the 
 13  #c COPYING file in the source distribution. 
 14   
 15   
 16  import urlparse 
 17   
 18  from nevow import inevow 
 19  from nevow import rend 
 20  from nevow import tags as T 
 21  from twisted.internet import defer 
 22  from twisted.python import failure 
 23  from twisted.python import log 
 24  from zope.interface import implements 
 25   
 26  from gavo import base 
 27  from gavo import svcs 
 28  from gavo import utils 
 29  from gavo.base import config 
 30  from gavo.web import common 
 31   
 32   
33 -def getContactEmail():
34 mailMeta = config.getMeta("contact.email") 35 if mailMeta: 36 return mailMeta.getContent() 37 else: 38 return "unconfigured@invalid.address"
39 40
41 -class ErrorPage(rend.Page, common.CommonRenderers):
42 """A base for error handling pages. 43 44 The idea is that you set the "handles" class attribute to 45 the exception you handle. The exception has to match exactly, i.e., 46 no isinstancing is done. 47 48 You also must set status to the HTTP status code the error should 49 return. 50 51 All error pages have a failure attribute that's a twisted failure 52 with all the related mess (e.g., tracebacks). 53 54 You have the status and message data methods. 55 """ 56 handles = None 57 status = 500 58 titleMessage = "Unspecified Error" 59 beforeMessage = "We're sorry, but something didn't work out:" 60 afterMessage = T.p["This generic text shouldn't be here. The" 61 " child class should override afterMessage."] 62 _footer = "delete this when done" 63
64 - def __init__(self, error):
65 self.failure = error
66
67 - def data_status(self, ctx, data):
68 return str(self.status)
69
70 - def data_message(self, ctx, data):
71 return self.failure.getErrorMessage()
72
73 - def render_beforeMessage(self, ctx, data):
74 return ctx.tag[self.beforeMessage]
75
76 - def render_afterMessage(self, ctx, data):
77 return ctx.tag[self.afterMessage]
78
79 - def render_message(self, ctx, data):
80 return ctx.tag(class_="errmsg")[self.failure.getErrorMessage()]
81
82 - def render_hint(self, ctx, data):
83 if (hasattr(self.failure.value, "hint") and self.failure.value.hint): 84 return ctx.tag[T.strong["Hint: "], 85 self.failure.value.hint] 86 return ""
87 96
97 - def render_titlemessage(self, ctx, data):
98 return ctx.tag["%s -- %s"%( 99 base.getConfig("web", "sitename"), self.titleMessage)]
100 105
106 - def renderHTTP(self, ctx):
107 request = inevow.IRequest(ctx) 108 request.setResponseCode(self.status) 109 return rend.Page.renderHTTP(self, ctx)
110 111 docFactory = common.doctypedStan(T.html[ 112 T.head(render=T.directive("commonhead"))[ 113 T.title(render=T.directive("titlemessage"))], 114 T.body[ 115 T.img(src="/static/img/logo_medium.png", class_="headlinelogo", 116 style="position:absolute;right:5pt"), 117 T.h1[ 118 T.invisible(render=T.directive("titlemessage")), 119 " (", 120 T.invisible(data=T.directive("status"), render=T.directive("string")), 121 ")"], 122 T.p(render=T.directive("beforeMessage")), 123 T.div(class_="errors", render=T.directive("message")), 124 T.div(render=T.directive("afterMessage")), 125 T.invisible(render=T.directive("footer"))]])
126 127 128
129 -class NotFoundPage(ErrorPage):
130 handles = svcs.UnknownURI 131 status = 404 132 titleMessage = "Not Found" 133 beforeMessage = ("We're sorry, but the resource you" 134 " requested could not be located.") 135 afterMessage = [ 136 T.p["If this message resulted from following a link from ", 137 T.strong["within the data center"], 138 ", you have discovered a bug, and we would be" 139 " extremely grateful if you could notify us."], 140 T.p["If you got here following an ", 141 T.strong["external link"], 142 ", we would be" 143 " grateful for a notification as well. We will ask the" 144 " external operators to fix their links or provide" 145 " redirects as appropriate."], 146 T.p["In either case, you may find whatever you were looking" 147 " for by inspecting our ", 148 T.a(href="/")["list of published services"], "."], 149 T.p(render=T.directive("rdlink"))] 150
151 - def renderHTTP_notFound(self, ctx):
152 return self.renderHTTP(ctx)
153 154
155 -class NotFoundPageWithFancyMessage(NotFoundPage):
156 """A NotFoundPage with a message that's taken from a piece of stan. 157 """
158 - def __init__(self, message):
159 self.message = message
160
161 - def render_message(self, ctx, data):
162 return ctx.tag[self.message]
163
166 167
168 -class OtherNotFoundPage(NotFoundPage):
169 handles = base.NotFoundError
170 171
172 -class RDNotFoundPage(NotFoundPage):
173 handles = base.RDNotFound
174 175
176 -class ForbiddenPage(ErrorPage):
177 handles = svcs.ForbiddenURI 178 status = 403 179 titleMessage = "Forbidden" 180 beforeMessage = "We're sorry, but the resource you requested is forbidden." 181 afterMessage = T.div[ 182 T.p["This usually means you tried to use a renderer on a service" 183 " that does not support it. If you did not come up with the" 184 " URL in question yourself, complain fiercely to the staff of ", 185 T.invisible(render=T.directive("getconfig"))["[web]sitename"], 186 "."], 187 T.p(render=T.directive("rdlink"))]
188 189
190 -class RedirectBase(ErrorPage):
191 - def renderHTTP(self, ctx):
192 request = inevow.IRequest(ctx) 193 # add request arguments if they are not already included in the 194 # URL we're redirecting to: 195 self.destURL = self.failure.value.dest 196 if '?' not in self.destURL: 197 args = urlparse.urlparse(request.uri).query 198 if args: 199 self.destURL = self.failure.value.dest+"?"+args 200 request.setHeader("location", str(self.destURL)) 201 return ErrorPage.renderHTTP(self, ctx)
202 205 206 afterMessage = T.p["You should not see this page -- either your browser or" 207 " our site is broken. Complain."]
208 209
210 -class RedirectPage(RedirectBase):
211 handles = svcs.WebRedirect 212 status = 301 213 titleMessage = "Moved Permanently" 214 beforeMessage = ["The resource you requested is available from a ", 215 T.a(render=T.directive("destLink"))[ 216 "different URL"], 217 "."]
218 219
220 -class FoundPage(RedirectBase):
221 handles = svcs.Found 222 status = 302 223 titleMessage = "Found" 224 beforeMessage = ["The resource you requested can be found at ", 225 T.a(render=T.directive("destLink"))[ 226 T.invisible(render=T.directive("destLink"))], 227 "."]
228 229
230 -class SeeOtherPage(RedirectBase):
231 handles = svcs.SeeOther 232 status = 303 233 titleMessage = "See Other" 234 beforeMessage = ["Please turn to a ", 235 T.a(render=T.directive("destLink"))[ 236 "different URL"], 237 " to go on."]
238 239
240 -class AuthenticatePage(ErrorPage):
241 handles = svcs.Authenticate 242 status = 401 243 titleMessage = "Authentication Required" 244
245 - def renderHTTP(self, ctx):
246 request = inevow.IRequest(ctx) 247 request.setHeader('WWW-Authenticate', 248 'Basic realm="%s"'%str(self.failure.value.realm)) 249 return ErrorPage.renderHTTP(self, ctx)
250 251 docFactory = svcs.loadSystemTemplate("unauth.html")
252 253
254 -class BadMethodPage(ErrorPage):
255 handles = svcs.BadMethod 256 status = 405 257 titleMessage = "Bad Method" 258 beforeMessage = ( 259 "You just tried to use some HTTP method to access this resource" 260 " that this resource does not support. This probably means that" 261 " this resource is for exclusive use for specialized clients.") 262 afterMessage = T.p["You may find whatever you were really looking" 263 " for by inspecting our ", 264 T.a(href="/")["list of published services"], 265 "."]
266 267
268 -class NotAcceptable(ErrorPage):
269 handles = base.DataError 270 status = 406 271 titleMessage = "Not Acceptable" 272 beforeMessage = ("The server cannot generate the data you requested." 273 " The associated message is:") 274 afterMessage = ""
275 276
277 -class ErrorDisplay(ErrorPage):
278 handles = base.ReportableError 279 status = 500 280 titleMessage = "Error" 281 beforeMessage = ("A piece of code failed:") 282 afterMessage = [T.p["Problems of this sort usually mean we considered" 283 " the possibility of something like this happening; if the above" 284 " doesn't give you sufficient hints to fix the problem, please" 285 " complain to the address given below."], 286 T.p(render=T.directive("hint"))]
287 288 # HTML mess for last-resort type error handling. 289 errorTemplate = ( 290 '<body><div style="position:fixed;left:4px;top:4px;' 291 'visibility:visible;overflow:visible !important;' 292 'max-width:600px !important;z-index:500">' 293 '<div style="border:2px solid red;' 294 'width:400px !important;background:white">' 295 '%s' 296 '</div></div></body></html>') 297
298 -def _formatFailure(failure):
299 res = errorTemplate%( 300 "<h1>Internal Error</h1><p>A(n)" 301 " %s exception occurred. The" 302 " accompanying message is: '%s'</p>" 303 "<p>If you are seeing this, it is always a bug in our code" 304 " or the data descriptions, and we would be extremely grateful" 305 " for a report at" 306 " %s</p>"%(failure.value.__class__.__name__, 307 common.escapeForHTML(failure.getErrorMessage()), 308 getContactEmail())) 309 return res.encode("ascii", "ignore")
310 311
312 -class InternalServerErrorPage(ErrorPage):
313 """A catch-all page served when no other error page seemed responsible. 314 """ 315 handles = base.Error # meaningless, no isinstance done here 316 status = 500 317 titleMessage = "Uncaught Exception" 318 beforeMessage = T.p["Your action has caused a(n) ", 319 T.span(render=str, data=T.directive("excname")), 320 " exception to occur. As additional info, the failing code" 321 " gave:"], 322 afterMessage = T.p["This is always a bug in our software, and we would really" 323 " be grateful for a report to the contact address below," 324 " preferably with a description of what you were trying to do," 325 " including any data pieces if applicable. Thanks."] 326
327 - def data_excname(self, ctx, data):
328 return self.failure.value.__class__.__name__
329
330 - def renderInnerException(self, ctx):
331 """called when rendering already has started. 332 333 We don't know where we're sitting, so we try to break out as well 334 as we can. 335 """ 336 request = inevow.IRequest(ctx) 337 request.setResponseCode(500) # probably too late, but log still profits. 338 data = _formatFailure(self.failure) 339 if isinstance(data, unicode): 340 data = data.encode("utf-8", "ignore") 341 request.write(data) 342 request.finishRequest(False) 343 return ""
344
345 - def renderHTTP(self, ctx):
346 request = inevow.IRequest(ctx) 347 base.ui.notifyFailure(self.failure) 348 base.ui.notifyInfo("Arguments of failed request: %s"% 349 repr(request.args)[:2000]) 350 if getattr(self.failure.value, "hint", None): 351 base.ui.notifyDebug("Exception hint: %s"%self.failure.value.hint) 352 if getattr(request, "startedWriting", False): 353 # exception happened while rendering a page. 354 return self.renderInnerException(ctx) 355 else: 356 return ErrorPage.renderHTTP(self, ctx)
357 358
359 -def _writePanicInfo(ctx, failure, secErr=None):
360 """write some panic-type stuff for failure and finishes the request. 361 """ 362 request = inevow.IRequest(ctx) 363 request.setResponseCode(500) 364 base.ui.notifyFailure(failure) 365 base.ui.notifyInfo("Arguments were %s"%request.args) 366 # write out some HTML and hope 367 # for the best (it might well turn up in the middle of random output) 368 request.write( 369 "<html><head><title>Severe Error</title></head><body>") 370 try: 371 request.write(_formatFailure(failure)) 372 except: 373 request.write("<h1>Ouch</h1><p>There has been an error that in" 374 " addition breaks the toplevel error catching code. Complain.</p>") 375 base.ui.notifyError("Error while processing failure: %s"%secErr) 376 request.write("</body></html>") 377 request.finishRequest(False)
378 379 380 getErrorPage = utils.buildClassResolver( 381 baseClass=ErrorPage, 382 objects=globals().values(), 383 instances=False, 384 key=lambda obj: obj.handles, 385 default=InternalServerErrorPage) 386 387
388 -def getDCErrorPage(error):
389 """returns stuff for root.ErrorCatchingNevowSite. 390 """ 391 # This should be replaced by remembering DCExceptionHandler when 392 # some day we fix nevow. 393 if error is None: 394 error = failure.Failure() 395 return getErrorPage(error.value.__class__)(error)
396 397
398 -def _finishErrorProcessing(ctx, error):
399 """finishes ctx's request. 400 """ 401 # this is also intended as a hook when something weird happens during 402 # error processing. When everything's fine, you should end up here. 403 request = inevow.IRequest(ctx) 404 request.finishRequest(False) 405 return ""
406 407
408 -class DCExceptionHandler(object):
409 """The toplevel exception handler. 410 """ 411 # Since something here is broken in nevow, this isn't really used. 412 implements(inevow.ICanHandleException, inevow.ICanHandleNotFound) 413
414 - def renderHTTP_exception(self, ctx, error):
415 try: 416 handler = getDCErrorPage(error) 417 return defer.maybeDeferred(handler.renderHTTP, ctx 418 ).addCallback(lambda ignored: _finishErrorProcessing(ctx, error) 419 ).addErrback(lambda secErr: _writePanicInfo(ctx, error, secErr)) 420 except: 421 base.ui.notifyError("Error while handling %s error:"%error) 422 _writePanicInfo(ctx, error)
423
424 - def renderHTTP_notFound(self, ctx):
425 try: 426 raise svcs.UnknownURI("locateChild returned None") 427 except svcs.UnknownURI: 428 return NotFoundPage(failure.Failure())
429
430 - def renderInlineException(self, ctx, error):
431 # We can't really do that. Figure out how to break out of this. 432 log.err(error, _why="Inline exception") 433 return ('<div style="border: 1px dashed red; color: red; clear: both">' 434 '[[ERROR]]</div>')
435