Server handles Accept headers.

This commit is contained in:
Itamar Turner-Trauring 2022-03-14 10:53:22 -04:00
parent e55c3e8acf
commit b8ab3dd6a7
2 changed files with 34 additions and 12 deletions

View File

@ -11,7 +11,11 @@ import binascii
from klein import Klein
from twisted.web import http
import attr
from werkzeug.http import parse_range_header, parse_content_range_header
from werkzeug.http import (
parse_range_header,
parse_content_range_header,
parse_accept_header,
)
from werkzeug.routing import BaseConverter, ValidationError
from werkzeug.datastructures import ContentRange
@ -243,20 +247,27 @@ class HTTPServer(object):
"""Return twisted.web ``Resource`` for this object."""
return self._app.resource()
def _cbor(self, request, data):
"""Return CBOR-encoded data."""
# TODO Might want to optionally send JSON someday, based on Accept
# headers, see https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3861
request.setHeader("Content-Type", "application/cbor")
# TODO if data is big, maybe want to use a temporary file eventually...
return dumps(data)
def _send_encoded(self, request, data):
"""Return encoded data, by default using CBOR."""
cbor_mime = "application/cbor"
accept_headers = request.requestHeaders.getRawHeaders("accept") or [cbor_mime]
accept = parse_accept_header(accept_headers[0])
if accept.best == cbor_mime:
request.setHeader("Content-Type", cbor_mime)
# TODO if data is big, maybe want to use a temporary file eventually...
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3872
return dumps(data)
else:
# TODO Might want to optionally send JSON someday:
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3861
raise _HTTPError(http.NOT_ACCEPTABLE)
##### Generic APIs #####
@_authorized_route(_app, set(), "/v1/version", methods=["GET"])
def version(self, request, authorization):
"""Return version information."""
return self._cbor(request, self._storage_server.get_version())
return self._send_encoded(request, self._storage_server.get_version())
##### Immutable APIs #####
@ -291,7 +302,7 @@ class HTTPServer(object):
storage_index, share_number, upload_secret, bucket
)
return self._cbor(
return self._send_encoded(
request,
{
"already-have": set(already_got),
@ -367,7 +378,7 @@ class HTTPServer(object):
required = []
for start, end, _ in bucket.required_ranges().ranges():
required.append({"begin": start, "end": end})
return self._cbor(request, {"required": required})
return self._send_encoded(request, {"required": required})
@_authorized_route(
_app,
@ -380,7 +391,7 @@ class HTTPServer(object):
List shares for the given storage index.
"""
share_numbers = list(self._storage_server.get_buckets(storage_index).keys())
return self._cbor(request, share_numbers)
return self._send_encoded(request, share_numbers)
@_authorized_route(
_app,

View File

@ -358,6 +358,17 @@ class GenericHTTPAPITests(SyncTestCase):
with assert_fails_with_http_code(self, http.UNAUTHORIZED):
result_of(client.get_version())
def test_unsupported_mime_type(self):
"""
The client can request mime types other than CBOR, and if they are
unsupported a NOT ACCEPTABLE (406) error will be returned.
"""
client = StorageClientGeneral(
StorageClientWithHeadersOverride(self.http.client, {"accept": "image/gif"})
)
with assert_fails_with_http_code(self, http.NOT_ACCEPTABLE):
result_of(client.get_version())
def test_version(self):
"""
The client can return the version.