mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-29 17:28:53 +00:00
a helper for the ?t=json resources
This commit is contained in:
parent
7cad9e1b5d
commit
7c8c63a01f
@ -5,12 +5,24 @@ import treq
|
|||||||
from twisted.application import service
|
from twisted.application import service
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
|
||||||
from twisted.internet.task import Clock
|
from twisted.internet.task import Clock
|
||||||
from twisted.web import client, error, http
|
from twisted.web import client, error, http
|
||||||
from twisted.python import failure, log
|
from twisted.python import failure, log
|
||||||
|
|
||||||
|
from nevow.context import WebContext
|
||||||
|
from nevow.inevow import (
|
||||||
|
ICanHandleException,
|
||||||
|
IRequest,
|
||||||
|
IData,
|
||||||
|
)
|
||||||
from nevow.util import escapeToXML
|
from nevow.util import escapeToXML
|
||||||
|
from nevow.loaders import stan
|
||||||
|
from nevow.testutil import FakeRequest
|
||||||
|
from nevow.appserver import (
|
||||||
|
processingFailed,
|
||||||
|
DefaultExceptionHandler,
|
||||||
|
)
|
||||||
|
|
||||||
from allmydata import interfaces, uri, webish
|
from allmydata import interfaces, uri, webish
|
||||||
from allmydata.storage_client import StorageFarmBroker, StubServer
|
from allmydata.storage_client import StorageFarmBroker, StubServer
|
||||||
@ -19,6 +31,7 @@ from allmydata.immutable.downloader.status import DownloadStatus
|
|||||||
from allmydata.dirnode import DirectoryNode
|
from allmydata.dirnode import DirectoryNode
|
||||||
from allmydata.nodemaker import NodeMaker
|
from allmydata.nodemaker import NodeMaker
|
||||||
from allmydata.web import status
|
from allmydata.web import status
|
||||||
|
from allmydata.web.common import WebError, MultiFormatPage
|
||||||
from allmydata.util import fileutil, base32, hashutil
|
from allmydata.util import fileutil, base32, hashutil
|
||||||
from allmydata.util.consumer import download_to_data
|
from allmydata.util.consumer import download_to_data
|
||||||
from allmydata.util.encodingutil import to_str
|
from allmydata.util.encodingutil import to_str
|
||||||
@ -29,7 +42,11 @@ from ..common import FakeCHKFileNode, FakeMutableFileNode, \
|
|||||||
from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
|
from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
|
||||||
from allmydata.mutable import servermap, publish, retrieve
|
from allmydata.mutable import servermap, publish, retrieve
|
||||||
from .. import common_util as testutil
|
from .. import common_util as testutil
|
||||||
from ..common_web import HTTPClientGETFactory, do_http, Error
|
from ..common_web import (
|
||||||
|
HTTPClientGETFactory,
|
||||||
|
do_http,
|
||||||
|
Error,
|
||||||
|
)
|
||||||
from allmydata.client import Client, SecretHolder
|
from allmydata.client import Client, SecretHolder
|
||||||
from .common import unknown_rwcap, unknown_rocap, unknown_immcap, FAVICON_MARKUP
|
from .common import unknown_rwcap, unknown_rocap, unknown_immcap, FAVICON_MARKUP
|
||||||
# create a fake uploader/downloader, and a couple of fake dirnodes, then
|
# create a fake uploader/downloader, and a couple of fake dirnodes, then
|
||||||
@ -602,6 +619,117 @@ class WebMixin(testutil.TimezoneMixin):
|
|||||||
self.fail("%s was supposed to Error(302), not get '%s'" %
|
self.fail("%s was supposed to Error(302), not get '%s'" %
|
||||||
(which, res))
|
(which, res))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MultiFormatPageTests(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Tests for ``MultiFormatPage``.
|
||||||
|
"""
|
||||||
|
def resource(self):
|
||||||
|
"""
|
||||||
|
Create and return an instance of a ``MultiFormatPage`` subclass with two
|
||||||
|
formats: ``a`` and ``b``.
|
||||||
|
"""
|
||||||
|
class Content(MultiFormatPage):
|
||||||
|
docFactory = stan("doc factory")
|
||||||
|
|
||||||
|
def render_A(self, req):
|
||||||
|
return "a"
|
||||||
|
|
||||||
|
def render_B(self, req):
|
||||||
|
return "b"
|
||||||
|
return Content()
|
||||||
|
|
||||||
|
|
||||||
|
def render(self, resource, **query_args):
|
||||||
|
"""
|
||||||
|
Render a Nevow ``Page`` against a request with the given query arguments.
|
||||||
|
|
||||||
|
:param resource: The Nevow resource to render.
|
||||||
|
|
||||||
|
:param query_args: The query arguments to put into the request being
|
||||||
|
rendered. A mapping from ``bytes`` to ``list`` of ``bytes``.
|
||||||
|
|
||||||
|
:return: The rendered response body as ``bytes``.
|
||||||
|
"""
|
||||||
|
ctx = WebContext(tag=resource)
|
||||||
|
req = FakeRequest(args=query_args)
|
||||||
|
ctx.remember(DefaultExceptionHandler(), ICanHandleException)
|
||||||
|
ctx.remember(req, IRequest)
|
||||||
|
ctx.remember(None, IData)
|
||||||
|
|
||||||
|
d = maybeDeferred(resource.renderHTTP, ctx)
|
||||||
|
d.addErrback(processingFailed, req, ctx)
|
||||||
|
res = self.successResultOf(d)
|
||||||
|
if isinstance(res, bytes):
|
||||||
|
return req.v + res
|
||||||
|
return req.v
|
||||||
|
|
||||||
|
|
||||||
|
def test_select_format(self):
|
||||||
|
"""
|
||||||
|
The ``formatArgument`` attribute of a ``MultiFormatPage`` subclass
|
||||||
|
identifies the query argument which selects the result format.
|
||||||
|
"""
|
||||||
|
resource = self.resource()
|
||||||
|
resource.formatArgument = "foo"
|
||||||
|
self.assertEqual("a", self.render(resource, foo=["a"]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_format_argument(self):
|
||||||
|
"""
|
||||||
|
If a ``MultiFormatPage`` subclass does not set ``formatArgument`` then the
|
||||||
|
``t`` argument is used.
|
||||||
|
"""
|
||||||
|
resource = self.resource()
|
||||||
|
self.assertEqual("a", self.render(resource, t=["a"]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_format(self):
|
||||||
|
"""
|
||||||
|
If no value is given for the format argument and no default format has
|
||||||
|
been defined, the base Nevow rendering behavior is used
|
||||||
|
(``renderHTTP``).
|
||||||
|
"""
|
||||||
|
resource = self.resource()
|
||||||
|
self.assertEqual("doc factory", self.render(resource))
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_format(self):
|
||||||
|
"""
|
||||||
|
If no value is given for the format argument and the ``MultiFormatPage``
|
||||||
|
subclass defines a ``formatDefault``, that value is used as the format
|
||||||
|
to render.
|
||||||
|
"""
|
||||||
|
resource = self.resource()
|
||||||
|
resource.formatDefault = "b"
|
||||||
|
self.assertEqual("b", self.render(resource))
|
||||||
|
|
||||||
|
|
||||||
|
def test_explicit_none_format_renderer(self):
|
||||||
|
"""
|
||||||
|
If a format is selected which has a renderer set to ``None``, the base
|
||||||
|
Nevow rendering behavior is used (``renderHTTP``).
|
||||||
|
"""
|
||||||
|
resource = self.resource()
|
||||||
|
resource.render_FOO = None
|
||||||
|
self.assertEqual("doc factory", self.render(resource, t=["foo"]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_format(self):
|
||||||
|
"""
|
||||||
|
If a format is selected for which there is no renderer, an error is
|
||||||
|
returned.
|
||||||
|
"""
|
||||||
|
resource = self.resource()
|
||||||
|
self.assertIn(
|
||||||
|
"<title>Exception</title>",
|
||||||
|
self.render(resource, t=["foo"]),
|
||||||
|
)
|
||||||
|
self.flushLoggedErrors(WebError)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
|
class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
pass
|
pass
|
||||||
|
@ -7,6 +7,7 @@ from twisted.python import log
|
|||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
from zope.interface import Interface
|
from zope.interface import Interface
|
||||||
from nevow import loaders, appserver
|
from nevow import loaders, appserver
|
||||||
|
from nevow.rend import Page
|
||||||
from nevow.inevow import IRequest
|
from nevow.inevow import IRequest
|
||||||
from nevow.util import resource_filename
|
from nevow.util import resource_filename
|
||||||
from allmydata import blacklist
|
from allmydata import blacklist
|
||||||
@ -387,6 +388,64 @@ class RenderMixin:
|
|||||||
return m(ctx)
|
return m(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MultiFormatPage(Page):
|
||||||
|
"""
|
||||||
|
```MultiFormatPage`` is a ``rend.Page`` that can be rendered in a number
|
||||||
|
of different formats.
|
||||||
|
|
||||||
|
Rendered format is controlled by a query argument (given by
|
||||||
|
``self.formatArgument``). Different resources may support different
|
||||||
|
formats but ``json`` is a pretty common one.
|
||||||
|
"""
|
||||||
|
formatArgument = "t"
|
||||||
|
formatDefault = None
|
||||||
|
|
||||||
|
def renderHTTP(self, ctx):
|
||||||
|
"""
|
||||||
|
Dispatch to a renderer for a particular format, as selected by a query
|
||||||
|
argument.
|
||||||
|
|
||||||
|
A renderer for the format given by the query argument matching
|
||||||
|
``formatArgument`` will be selected and invoked. The default ``Page``
|
||||||
|
rendering behavior will be used if no format is selected (either by
|
||||||
|
query arguments or by ``formatDefault``).
|
||||||
|
|
||||||
|
:return: The result of the selected renderer.
|
||||||
|
"""
|
||||||
|
req = IRequest(ctx)
|
||||||
|
t = get_arg(req, self.formatArgument, self.formatDefault)
|
||||||
|
renderer = self._get_renderer(t)
|
||||||
|
result = renderer(ctx)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _get_renderer(self, fmt):
|
||||||
|
"""
|
||||||
|
Get the renderer for the indicated format.
|
||||||
|
|
||||||
|
:param bytes fmt: The format. If a method with a prefix of
|
||||||
|
``render_`` and a suffix of this format (upper-cased) is found, it
|
||||||
|
will be used.
|
||||||
|
|
||||||
|
:return: A callable which takes a Nevow context and renders a
|
||||||
|
response.
|
||||||
|
"""
|
||||||
|
if fmt is None:
|
||||||
|
return super(MultiFormatPage, self).renderHTTP
|
||||||
|
try:
|
||||||
|
renderer = getattr(self, "render_{}".format(fmt.upper()))
|
||||||
|
except AttributeError:
|
||||||
|
raise WebError(
|
||||||
|
"Unknown {} value: {!r}".format(self.formatArgument, fmt),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if renderer is None:
|
||||||
|
return super(MultiFormatPage, self).renderHTTP
|
||||||
|
return lambda ctx: renderer(IRequest(ctx))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TokenOnlyWebApi(resource.Resource):
|
class TokenOnlyWebApi(resource.Resource):
|
||||||
"""
|
"""
|
||||||
I provide a rend.Page implementation that only accepts POST calls,
|
I provide a rend.Page implementation that only accepts POST calls,
|
||||||
|
Loading…
Reference in New Issue
Block a user