mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-31 00:24:13 +00:00
handle Deferred from render
This commit is contained in:
parent
fd70715a8b
commit
69c7c40510
@ -18,6 +18,10 @@ from allmydata.storage.shares import get_share_file
|
||||
from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
|
||||
from allmydata.immutable import upload
|
||||
from allmydata.mutable import publish
|
||||
|
||||
from ...web.common import (
|
||||
render_exception,
|
||||
)
|
||||
from .. import common_util as testutil
|
||||
from ..common import WebErrorMixin, ShouldFailMixin
|
||||
from ..no_network import GridTestMixin
|
||||
@ -34,6 +38,7 @@ class CompletelyUnhandledError(Exception):
|
||||
pass
|
||||
|
||||
class ErrorBoom(object, resource.Resource):
|
||||
@render_exception
|
||||
def render(self, req):
|
||||
raise CompletelyUnhandledError("whoops")
|
||||
|
||||
|
@ -3,15 +3,43 @@ import time
|
||||
import json
|
||||
from functools import wraps
|
||||
|
||||
from hyperlink import (
|
||||
DecodedURL,
|
||||
)
|
||||
|
||||
from twisted.web import (
|
||||
http,
|
||||
resource,
|
||||
server,
|
||||
template,
|
||||
)
|
||||
from twisted.web.template import (
|
||||
tags,
|
||||
)
|
||||
from twisted.web.util import (
|
||||
FailureElement,
|
||||
redirectTo,
|
||||
)
|
||||
from twisted.python.reflect import (
|
||||
fullyQualifiedName,
|
||||
)
|
||||
from twisted.python.urlpath import (
|
||||
URLPath,
|
||||
)
|
||||
from twisted.python import log
|
||||
from twisted.python.failure import (
|
||||
Failure,
|
||||
)
|
||||
from twisted.internet.defer import (
|
||||
Deferred,
|
||||
maybeDeferred,
|
||||
)
|
||||
from twisted.web.resource import (
|
||||
IResource,
|
||||
)
|
||||
from nevow import appserver
|
||||
from nevow.inevow import IRequest
|
||||
|
||||
from allmydata import blacklist
|
||||
from allmydata.interfaces import (
|
||||
EmptyPathnameComponentError,
|
||||
@ -320,48 +348,10 @@ def humanize_failure(f):
|
||||
|
||||
|
||||
class MyExceptionHandler(appserver.DefaultExceptionHandler, object):
|
||||
def simple(self, ctx, text, code=http.BAD_REQUEST):
|
||||
req = IRequest(ctx)
|
||||
req.setResponseCode(code)
|
||||
#req.responseHeaders.setRawHeaders("content-encoding", [])
|
||||
#req.responseHeaders.setRawHeaders("content-disposition", [])
|
||||
req.setHeader("content-type", "text/plain;charset=utf-8")
|
||||
if isinstance(text, unicode):
|
||||
text = text.encode("utf-8")
|
||||
req.setHeader("content-length", b"%d" % len(text))
|
||||
req.write(text)
|
||||
# TODO: consider putting the requested URL here
|
||||
req.finishRequest(False)
|
||||
|
||||
def renderHTTP_exception(self, ctx, f):
|
||||
try:
|
||||
text, code = humanize_failure(f)
|
||||
except:
|
||||
log.msg("exception in humanize_failure")
|
||||
log.msg("argument was %s" % (f,))
|
||||
log.err()
|
||||
text, code = str(f), None
|
||||
if code is not None:
|
||||
return self.simple(ctx, text, code)
|
||||
if f.check(server.UnsupportedMethod):
|
||||
# twisted.web.server.Request.render() has support for transforming
|
||||
# this into an appropriate 501 NOT_IMPLEMENTED or 405 NOT_ALLOWED
|
||||
# return code, but nevow does not.
|
||||
req = IRequest(ctx)
|
||||
method = req.method
|
||||
return self.simple(ctx,
|
||||
"I don't know how to treat a %s request." % method,
|
||||
http.NOT_IMPLEMENTED)
|
||||
req = IRequest(ctx)
|
||||
accept = req.getHeader("accept")
|
||||
if not accept:
|
||||
accept = "*/*"
|
||||
if "*/*" in accept or "text/*" in accept or "text/html" in accept:
|
||||
super = appserver.DefaultExceptionHandler
|
||||
return super.renderHTTP_exception(self, ctx, f)
|
||||
# use plain text
|
||||
traceback = f.getTraceback()
|
||||
return self.simple(ctx, traceback, http.INTERNAL_SERVER_ERROR)
|
||||
req.write(_renderHTTP_exception(req, f))
|
||||
req.finishRequest(False)
|
||||
|
||||
|
||||
class NeedOperationHandleError(WebError):
|
||||
@ -481,16 +471,130 @@ def exception_to_child(f):
|
||||
return g
|
||||
|
||||
|
||||
def render_exception(f):
|
||||
def render_exception(render):
|
||||
"""
|
||||
Decorate a ``render_*`` method with exception handling behavior to render
|
||||
an error page reflecting the exception.
|
||||
"""
|
||||
@wraps(f)
|
||||
@wraps(render)
|
||||
def g(self, request):
|
||||
try:
|
||||
return f(self, request)
|
||||
except Exception as e:
|
||||
description, status = humanize_exception(e)
|
||||
return resource.ErrorPage(status, "Error", description).render(request)
|
||||
bound_render = render.__get__(self, type(self))
|
||||
result = maybeDeferred(bound_render, request)
|
||||
if isinstance(result, Deferred):
|
||||
result.addBoth(_finish, bound_render, request)
|
||||
return server.NOT_DONE_YET
|
||||
elif isinstance(result, bytes):
|
||||
return result
|
||||
elif result == server.NOT_DONE_YET:
|
||||
return server.NOT_DONE_YET
|
||||
else:
|
||||
raise Exception("{!r} returned unusable {!r}".format(
|
||||
fullyQualifiedName(bound_render),
|
||||
result,
|
||||
))
|
||||
|
||||
return g
|
||||
|
||||
|
||||
def _finish(result, render, request):
|
||||
if isinstance(result, Failure):
|
||||
_finish(
|
||||
_renderHTTP_exception(request, result),
|
||||
render,
|
||||
request,
|
||||
)
|
||||
elif IResource.providedBy(result):
|
||||
# If result is also using @render_exception then we don't want to
|
||||
# double-apply the logic. This leads to an attempt to double-finish
|
||||
# the request. If it isn't using @render_exception then you should
|
||||
# fix it so it is.
|
||||
result.render(request)
|
||||
elif isinstance(result, bytes):
|
||||
request.write(result)
|
||||
request.finish()
|
||||
elif isinstance(result, URLPath):
|
||||
if result.netloc == b"":
|
||||
root = URLPath.fromRequest(request)
|
||||
result.scheme = root.scheme
|
||||
result.netloc = root.netloc
|
||||
_finish(redirectTo(str(result), request), render, request)
|
||||
elif isinstance(result, DecodedURL):
|
||||
_finish(redirectTo(str(result), request), render, request)
|
||||
elif result is None:
|
||||
request.finish()
|
||||
elif result == server.NOT_DONE_YET:
|
||||
pass
|
||||
else:
|
||||
log.err("Request for {!r} handled by {!r} returned unusable {!r}".format(
|
||||
request.uri,
|
||||
fullyQualifiedName(render),
|
||||
result,
|
||||
))
|
||||
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
|
||||
_finish(b"", render, request)
|
||||
|
||||
|
||||
def _renderHTTP_exception(request, failure):
|
||||
try:
|
||||
text, code = humanize_failure(failure)
|
||||
except:
|
||||
log.msg("exception in humanize_failure")
|
||||
log.msg("argument was %s" % (failure,))
|
||||
log.err()
|
||||
text = str(failure)
|
||||
code = None
|
||||
|
||||
if code is not None:
|
||||
return _renderHTTP_exception_simple(request, text, code)
|
||||
|
||||
if failure.check(server.UnsupportedMethod):
|
||||
# twisted.web.server.Request.render() has support for transforming
|
||||
# this into an appropriate 501 NOT_IMPLEMENTED or 405 NOT_ALLOWED
|
||||
# return code, but nevow does not.
|
||||
method = request.method
|
||||
return _renderHTTP_exception_simple(
|
||||
request,
|
||||
"I don't know how to treat a %s request." % (method,),
|
||||
http.NOT_IMPLEMENTED,
|
||||
)
|
||||
|
||||
accept = request.getHeader("accept")
|
||||
if not accept:
|
||||
accept = "*/*"
|
||||
if "*/*" in accept or "text/*" in accept or "text/html" in accept:
|
||||
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
|
||||
return template.renderElement(
|
||||
request,
|
||||
tags.html(
|
||||
tags.head(
|
||||
tags.title(u"Exception"),
|
||||
),
|
||||
tags.body(
|
||||
FailureElement(failure),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# use plain text
|
||||
traceback = failure.getTraceback()
|
||||
return _renderHTTP_exception_simple(
|
||||
request,
|
||||
traceback,
|
||||
http.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
|
||||
def _renderHTTP_exception_simple(request, text, code):
|
||||
request.setResponseCode(code)
|
||||
request.setHeader("content-type", "text/plain;charset=utf-8")
|
||||
if isinstance(text, unicode):
|
||||
text = text.encode("utf-8")
|
||||
request.setHeader("content-length", b"%d" % len(text))
|
||||
return text
|
||||
|
||||
|
||||
def handle_when_done(req, d):
|
||||
when_done = get_arg(req, "when_done", None)
|
||||
if when_done:
|
||||
d.addCallback(lambda res: DecodedURL.from_text(when_done.decode("utf-8")))
|
||||
return d
|
||||
|
@ -58,6 +58,7 @@ from allmydata.web.common import (
|
||||
SlotsSequenceElement,
|
||||
exception_to_child,
|
||||
render_exception,
|
||||
handle_when_done,
|
||||
)
|
||||
from allmydata.web.filenode import ReplaceMeMixin, \
|
||||
FileNodeHandler, PlaceHolderNodeHandler
|
||||
@ -206,6 +207,7 @@ class DirectoryNodeHandler(ReplaceMeMixin, Resource, object):
|
||||
)
|
||||
return make_handler_for(node, self.client, self.node, name)
|
||||
|
||||
@render_exception
|
||||
def render_DELETE(self, req):
|
||||
assert self.parentnode and self.name
|
||||
d = self.parentnode.delete(self.name)
|
||||
@ -310,13 +312,7 @@ class DirectoryNodeHandler(ReplaceMeMixin, Resource, object):
|
||||
else:
|
||||
raise WebError("POST to a directory with bad t=%s" % t)
|
||||
|
||||
when_done = get_arg(req, "when_done", None)
|
||||
if when_done:
|
||||
def done(res):
|
||||
req.redirect(when_done)
|
||||
return res
|
||||
d.addCallback(done)
|
||||
return d
|
||||
return handle_when_done(req, d)
|
||||
|
||||
def _POST_mkdir(self, req):
|
||||
name = get_arg(req, "name", "")
|
||||
|
@ -1,6 +1,10 @@
|
||||
|
||||
import json
|
||||
|
||||
from hyperlink import (
|
||||
DecodedURL,
|
||||
)
|
||||
|
||||
from twisted.web import http, static
|
||||
from twisted.internet import defer
|
||||
from twisted.web.resource import (
|
||||
@ -8,8 +12,6 @@ from twisted.web.resource import (
|
||||
ErrorPage,
|
||||
)
|
||||
|
||||
from nevow import url
|
||||
|
||||
from allmydata.interfaces import ExistingChildError
|
||||
from allmydata.monitor import Monitor
|
||||
from allmydata.immutable.upload import FileHandle
|
||||
@ -34,8 +36,8 @@ from allmydata.web.common import (
|
||||
render_exception,
|
||||
should_create_intermediate_directories,
|
||||
text_plain,
|
||||
MyExceptionHandler,
|
||||
WebError,
|
||||
handle_when_done,
|
||||
)
|
||||
from allmydata.web.check_results import (
|
||||
CheckResultsRenderer,
|
||||
@ -150,10 +152,7 @@ class PlaceHolderNodeHandler(Resource, ReplaceMeMixin):
|
||||
# placeholder.
|
||||
raise WebError("POST to a file: bad t=%s" % t)
|
||||
|
||||
when_done = get_arg(req, "when_done", None)
|
||||
if when_done:
|
||||
d.addCallback(lambda res: when_done)
|
||||
return d
|
||||
return handle_when_done(req, d)
|
||||
|
||||
|
||||
class FileNodeHandler(Resource, ReplaceMeMixin, object):
|
||||
@ -315,10 +314,7 @@ class FileNodeHandler(Resource, ReplaceMeMixin, object):
|
||||
else:
|
||||
raise WebError("POST to file: bad t=%s" % t)
|
||||
|
||||
when_done = get_arg(req, "when_done", None)
|
||||
if when_done:
|
||||
d.addCallback(lambda res: url.URL.fromString(when_done))
|
||||
return d
|
||||
return handle_when_done(req, d)
|
||||
|
||||
def _maybe_literal(self, res, Results_Class):
|
||||
if res:
|
||||
@ -485,23 +481,11 @@ class FileDownloader(Resource, object):
|
||||
if req.method == "HEAD":
|
||||
return ""
|
||||
|
||||
finished = []
|
||||
def _request_finished(ign):
|
||||
finished.append(True)
|
||||
req.notifyFinish().addBoth(_request_finished)
|
||||
|
||||
d = self.filenode.read(req, first, size)
|
||||
|
||||
def _finished(ign):
|
||||
if not finished:
|
||||
req.finish()
|
||||
def _error(f):
|
||||
lp = log.msg("error during GET", facility="tahoe.webish", failure=f,
|
||||
level=log.UNUSUAL, umid="xSiF3w")
|
||||
if finished:
|
||||
log.msg("but it's too late to tell them", parent=lp,
|
||||
level=log.UNUSUAL, umid="j1xIbw")
|
||||
return
|
||||
req._tahoe_request_had_error = f # for HTTP-style logging
|
||||
if req.startedWriting:
|
||||
# The content-type is already set, and the response code has
|
||||
@ -513,15 +497,16 @@ class FileDownloader(Resource, object):
|
||||
# error response be shorter than the intended results.
|
||||
#
|
||||
# We don't have a lot of options, unfortunately.
|
||||
req.write("problem during download\n")
|
||||
req.finish()
|
||||
return b"problem during download\n"
|
||||
else:
|
||||
# We haven't written anything yet, so we can provide a
|
||||
# sensible error message.
|
||||
eh = MyExceptionHandler()
|
||||
eh.renderHTTP_exception(req, f)
|
||||
d.addCallbacks(_finished, _error)
|
||||
return req.deferred
|
||||
return f
|
||||
d.addCallbacks(
|
||||
lambda ignored: None,
|
||||
_error,
|
||||
)
|
||||
return d
|
||||
|
||||
|
||||
def _file_json_metadata(req, filenode, edge_metadata):
|
||||
|
@ -1,10 +1,15 @@
|
||||
|
||||
import time
|
||||
from nevow import url
|
||||
from hyperlink import (
|
||||
DecodedURL,
|
||||
)
|
||||
from twisted.web.template import (
|
||||
renderer,
|
||||
tags as T,
|
||||
)
|
||||
from twisted.python.urlpath import (
|
||||
URLPath,
|
||||
)
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.web import resource
|
||||
@ -84,17 +89,14 @@ class OphandleTable(resource.Resource, service.Service):
|
||||
"""
|
||||
:param allmydata.webish.MyRequest req:
|
||||
"""
|
||||
ophandle = get_arg(req, "ophandle")
|
||||
ophandle = get_arg(req, "ophandle").decode("utf-8")
|
||||
assert ophandle
|
||||
target = get_root(req) + "/operations/" + ophandle
|
||||
root = DecodedURL.from_text(unicode(URLPath.fromRequest(req)))
|
||||
target = root.click(u"/").child(u"operations", ophandle)
|
||||
output = get_arg(req, "output")
|
||||
if output:
|
||||
target = target + "?output=%s" % output
|
||||
|
||||
# XXX: We have to use nevow.url here because nevow.appserver
|
||||
# is unhappy with anything else; so this gets its own ticket.
|
||||
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3314
|
||||
return url.URL.fromString(target)
|
||||
target = target.add(u"output", output.decode("utf-8"))
|
||||
return target
|
||||
|
||||
@exception_to_child
|
||||
def getChild(self, name, req):
|
||||
|
@ -2,6 +2,9 @@
|
||||
import urllib
|
||||
from twisted.web import http
|
||||
from twisted.internet import defer
|
||||
from twisted.python.urlpath import (
|
||||
URLPath,
|
||||
)
|
||||
from twisted.python.filepath import FilePath
|
||||
from twisted.web.resource import Resource
|
||||
from twisted.web.template import (
|
||||
@ -66,7 +69,7 @@ def POSTUnlinkedCHK(req, client):
|
||||
def _done(upload_results, redir_to):
|
||||
if "%(uri)s" in redir_to:
|
||||
redir_to = redir_to.replace("%(uri)s", urllib.quote(upload_results.get_uri()))
|
||||
return url.URL.fromString(redir_to)
|
||||
return URLPath.fromString(redir_to)
|
||||
d.addCallback(_done, when_done)
|
||||
else:
|
||||
# return the Upload Results page, which includes the URI
|
||||
|
Loading…
x
Reference in New Issue
Block a user