2021-11-12 12:51:52 -05:00
|
|
|
"""
|
|
|
|
HTTP client that talks to the HTTP storage server.
|
|
|
|
"""
|
|
|
|
|
2021-11-16 11:16:26 -05:00
|
|
|
from __future__ import absolute_import
|
|
|
|
from __future__ import division
|
|
|
|
from __future__ import print_function
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
|
|
from future.utils import PY2
|
|
|
|
|
|
|
|
if PY2:
|
|
|
|
# fmt: off
|
|
|
|
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401
|
|
|
|
# fmt: on
|
|
|
|
else:
|
2021-11-23 10:39:53 -05:00
|
|
|
# typing module not available in Python 2, and we only do type checking in
|
|
|
|
# Python 3 anyway.
|
2021-11-16 11:16:26 -05:00
|
|
|
from typing import Union
|
|
|
|
from treq.testing import StubTreq
|
|
|
|
|
2021-12-16 11:17:11 -05:00
|
|
|
from base64 import b64encode
|
2021-11-16 10:56:21 -05:00
|
|
|
|
2021-11-12 13:13:19 -05:00
|
|
|
# TODO Make sure to import Python version?
|
2021-11-16 11:28:13 -05:00
|
|
|
from cbor2 import loads
|
2021-11-12 12:51:52 -05:00
|
|
|
|
2021-11-16 10:56:21 -05:00
|
|
|
|
|
|
|
from twisted.web.http_headers import Headers
|
2021-11-12 13:13:19 -05:00
|
|
|
from twisted.internet.defer import inlineCallbacks, returnValue, fail
|
2021-11-12 12:51:52 -05:00
|
|
|
from hyperlink import DecodedURL
|
|
|
|
import treq
|
|
|
|
|
|
|
|
|
2021-11-12 13:13:19 -05:00
|
|
|
class ClientException(Exception):
|
|
|
|
"""An unexpected error."""
|
|
|
|
|
|
|
|
|
2021-11-12 12:51:52 -05:00
|
|
|
def _decode_cbor(response):
|
|
|
|
"""Given HTTP response, return decoded CBOR body."""
|
2021-11-12 13:13:19 -05:00
|
|
|
if response.code > 199 and response.code < 300:
|
|
|
|
return treq.content(response).addCallback(loads)
|
|
|
|
return fail(ClientException(response.code, response.phrase))
|
2021-11-12 12:51:52 -05:00
|
|
|
|
|
|
|
|
2021-11-16 11:09:08 -05:00
|
|
|
def swissnum_auth_header(swissnum): # type: (bytes) -> bytes
|
2021-11-16 10:56:21 -05:00
|
|
|
"""Return value for ``Authentication`` header."""
|
2021-12-16 11:17:11 -05:00
|
|
|
return b"Tahoe-LAFS " + b64encode(swissnum).strip()
|
2021-11-16 10:56:21 -05:00
|
|
|
|
|
|
|
|
2021-11-12 12:51:52 -05:00
|
|
|
class StorageClient(object):
|
|
|
|
"""
|
|
|
|
HTTP client that talks to the HTTP storage server.
|
|
|
|
"""
|
|
|
|
|
2021-11-16 11:16:26 -05:00
|
|
|
def __init__(
|
|
|
|
self, url, swissnum, treq=treq
|
|
|
|
): # type: (DecodedURL, bytes, Union[treq,StubTreq]) -> None
|
2021-11-12 12:51:52 -05:00
|
|
|
self._base_url = url
|
|
|
|
self._swissnum = swissnum
|
|
|
|
self._treq = treq
|
|
|
|
|
2021-11-16 11:09:08 -05:00
|
|
|
def _get_headers(self): # type: () -> Headers
|
2021-11-16 10:56:21 -05:00
|
|
|
"""Return the basic headers to be used by default."""
|
|
|
|
headers = Headers()
|
|
|
|
headers.addRawHeader(
|
|
|
|
"Authorization",
|
|
|
|
swissnum_auth_header(self._swissnum),
|
|
|
|
)
|
|
|
|
return headers
|
|
|
|
|
2021-12-16 11:17:11 -05:00
|
|
|
def _request(self, method, url, secrets, **kwargs):
|
|
|
|
"""
|
|
|
|
Like ``treq.request()``, but additional argument of secrets mapping
|
|
|
|
``http_server.Secret`` to the bytes value of the secret.
|
|
|
|
"""
|
|
|
|
headers = self._get_headers()
|
|
|
|
for key, value in secrets.items():
|
|
|
|
headers.addRawHeader(
|
|
|
|
"X-Tahoe-Authorization",
|
2021-12-16 11:42:06 -05:00
|
|
|
b"%s %s" % (key.value.encode("ascii"), b64encode(value).strip())
|
2021-12-16 11:17:11 -05:00
|
|
|
)
|
|
|
|
return self._treq.request(method, url, headers=headers, **kwargs)
|
|
|
|
|
2021-11-12 12:51:52 -05:00
|
|
|
@inlineCallbacks
|
|
|
|
def get_version(self):
|
|
|
|
"""
|
|
|
|
Return the version metadata for the server.
|
|
|
|
"""
|
2021-11-16 10:56:21 -05:00
|
|
|
url = self._base_url.click("/v1/version")
|
2021-12-16 11:17:11 -05:00
|
|
|
response = yield self._request("GET", url, {})
|
2021-11-12 13:13:19 -05:00
|
|
|
decoded_response = yield _decode_cbor(response)
|
|
|
|
returnValue(decoded_response)
|