Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add RESTEndpoint methods that log 4xx/5xx errors and create RESTResponse object #6760

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
from aiohttp import web

from aiohttp_apispec import docs

from ipv8.REST.schema import schema

from marshmallow.fields import Integer, String

from tribler.core.components.bandwidth_accounting.community.bandwidth_accounting_community import (
BandwidthAccountingCommunity,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from tribler.core.components.libtorrent.torrentdef import TorrentDef
from tribler.core.components.restapi.rest.rest_endpoint import HTTP_BAD_REQUEST, RESTEndpoint, RESTResponse
from tribler.core.components.restapi.rest.schema import HandledErrorSchema
from tribler.core.components.restapi.rest.util import return_handled_exception
from tribler.core.utilities.path_util import Path
from tribler.core.utilities.unicode import ensure_unicode, recursive_bytes
from tribler.core.utilities.utilities import bdecode_compat, froze_it
Expand Down Expand Up @@ -69,7 +68,7 @@ async def create_torrent(self, request):
if 'files' in parameters and parameters['files']:
file_path_list = [ensure_unicode(f, 'utf-8') for f in parameters['files']]
else:
return RESTResponse({"error": "files parameter missing"}, status=HTTP_BAD_REQUEST)
return self.bad_request("files parameter missing")

if 'description' in parameters and parameters['description']:
params['comment'] = parameters['description']
Expand Down Expand Up @@ -100,7 +99,7 @@ async def create_torrent(self, request):
result = await self.download_manager.create_torrent_file(file_path_list, recursive_bytes(params))
except (OSError, UnicodeDecodeError, RuntimeError) as e:
self._logger.exception(e)
return return_handled_exception(request, e)
return self.internal_error(e)

metainfo_dict = bdecode_compat(result['metainfo'])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,10 @@
from tribler.core.components.libtorrent.download_manager.stream import STREAM_PAUSE_TIME, StreamChunk
from tribler.core.components.libtorrent.utils.libtorrent_helper import libtorrent as lt
from tribler.core.components.restapi.rest.rest_endpoint import (
HTTP_BAD_REQUEST,
HTTP_INTERNAL_SERVER_ERROR,
HTTP_NOT_FOUND,
RESTEndpoint,
RESTResponse,
RESTStreamResponse,
)
from tribler.core.components.restapi.rest.util import return_handled_exception
from tribler.core.utilities.path_util import Path
from tribler.core.utilities.simpledefs import (
DLSTATUS_CIRCUITS,
Expand Down Expand Up @@ -104,13 +100,6 @@ def setup_routes(self):
web.get('/{infohash}/files', self.get_files),
web.get('/{infohash}/stream/{fileindex}', self.stream, allow_head=False)])

@staticmethod
def return_404(request, message="this download does not exist"):
"""
Returns a 404 response code if your channel has not been created.
"""
return RESTResponse({"error": message}, status=HTTP_NOT_FOUND)

@staticmethod
def create_dconfig_from_params(parameters):
"""
Expand Down Expand Up @@ -376,16 +365,16 @@ async def add_download(self, request):
params = await request.json()
uri = params.get('uri')
if not uri:
return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST)
return self.bad_request("uri parameter missing")

download_config, error = DownloadsEndpoint.create_dconfig_from_params(params)
if error:
return RESTResponse({"error": error}, status=HTTP_BAD_REQUEST)
return self.bad_request(error)

try:
download = await self.download_manager.start_download_from_uri(uri, config=download_config)
except Exception as e:
return RESTResponse({"error": str(e)}, status=HTTP_INTERNAL_SERVER_ERROR)
return self.internal_error(e)

return RESTResponse({"started": True, "infohash": hexlify(download.get_def().get_infohash())})

Expand All @@ -412,18 +401,18 @@ async def add_download(self, request):
async def delete_download(self, request):
parameters = await request.json()
if 'remove_data' not in parameters:
return RESTResponse({"error": "remove_data parameter missing"}, status=HTTP_BAD_REQUEST)
return self.bad_request("parameter remove_data is missing", parameters=parameters)

infohash = unhexlify(request.match_info['infohash'])
download = self.download_manager.get_download(infohash)
if not download:
return DownloadsEndpoint.return_404(request)
return self.not_found("download does not exist")

try:
await self.download_manager.remove_download(download, remove_content=parameters['remove_data'])
except Exception as e:
self._logger.exception(e)
return return_handled_exception(request, e)
return self.internal_error(e)

return RESTResponse({"removed": True, "infohash": hexlify(download.get_def().get_infohash())})

Expand All @@ -432,8 +421,7 @@ async def vod_response(self, download, parameters, request, vod_mode):
if vod_mode:
file_index = parameters.get("fileindex")
if file_index is None:
return RESTResponse({"error": "fileindex is necessary to enable vod_mode"},
status=HTTP_BAD_REQUEST)
return self.bad_request("fileindex is necessary to enable vod_mode", parameters=parameters)
if download.stream is None:
download.add_stream()
if not download.stream.enabled or download.stream.fileindex != file_index:
Expand Down Expand Up @@ -480,34 +468,34 @@ async def update_download(self, request):
infohash = unhexlify(request.match_info['infohash'])
download = self.download_manager.get_download(infohash)
if not download:
return DownloadsEndpoint.return_404(request)
return self.not_found("download does not exist")

parameters = await request.json()
vod_mode = parameters.get("vod_mode")
if vod_mode is not None:
if not isinstance(vod_mode, bool):
return RESTResponse({"error": "vod_mode must be bool flag"},
status=HTTP_BAD_REQUEST)
return self.bad_request(f"vod_mode must be bool flag. Got: {vod_mode!r}")
return await self.vod_response(download, parameters, request, vod_mode)

if len(parameters) > 1 and 'anon_hops' in parameters:
return RESTResponse({"error": "anon_hops must be the only parameter in this request"},
status=HTTP_BAD_REQUEST)
return self.bad_request("anon_hops must be the only parameter in request", parameters=parameters)
elif 'anon_hops' in parameters:
anon_hops = int(parameters['anon_hops'])
try:
await self.download_manager.update_hops(download, anon_hops)
except Exception as e:
self._logger.exception(e)
return return_handled_exception(request, e)
return self.internal_error(e)
return RESTResponse({"modified": True, "infohash": hexlify(download.get_def().get_infohash())})

if 'selected_files' in parameters:
selected_files_list = parameters['selected_files']
selected_files = parameters['selected_files']
num_files = len(download.tdef.get_files())
if not all([0 <= index < num_files for index in selected_files_list]):
return RESTResponse({"error": "index out of range"}, status=HTTP_BAD_REQUEST)
download.set_selected_files(selected_files_list)
for index in selected_files:
if not 0 <= index < num_files:
return self.bad_request(f"index of file out of range: {index}", num_files=num_files,
selected_files=selected_files)
download.set_selected_files(selected_files)

if parameters.get('state'):
state = parameters['state']
Expand All @@ -524,7 +512,7 @@ async def update_download(self, request):
download.move_storage(dest_dir)
download.checkpoint()
else:
return RESTResponse({"error": "unknown state parameter"}, status=HTTP_BAD_REQUEST)
return self.bad_request(f"unknown state parameter: {state!r}")

return RESTResponse({"modified": True, "infohash": hexlify(download.get_def().get_infohash())})

Expand All @@ -546,11 +534,11 @@ async def get_torrent(self, request):
infohash = unhexlify(request.match_info['infohash'])
download = self.download_manager.get_download(infohash)
if not download:
return DownloadsEndpoint.return_404(request)
return self.not_found("download does not exist")

torrent = download.get_torrent_data()
if not torrent:
return DownloadsEndpoint.return_404(request)
return self.not_found("download does not exist")

return RESTResponse(lt.bencode(torrent), headers={'content-type': 'application/x-bittorrent',
'Content-Disposition': 'attachment; filename=%s.torrent'
Expand Down Expand Up @@ -580,7 +568,7 @@ async def get_files(self, request):
infohash = unhexlify(request.match_info['infohash'])
download = self.download_manager.get_download(infohash)
if not download:
return DownloadsEndpoint.return_404(request)
return self.not_found("download does not exist")
return RESTResponse({"files": self.get_files_info_json(download)})

@docs(
Expand Down Expand Up @@ -608,7 +596,7 @@ async def stream(self, request):
infohash = unhexlify(request.match_info['infohash'])
download = self.download_manager.get_download(infohash)
if not download:
return DownloadsEndpoint.return_404(request)
return self.not_found("download does not exist")

file_index = int(request.match_info['fileindex'])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ async def test_download_unknown_state(mock_dlmgr, test_download, rest_api):

await do_request(rest_api, f'downloads/{test_download.infohash}', expected_code=400,
post_data={"state": "abc"}, request_type='PATCH',
expected_json={"error": "unknown state parameter"})
expected_json={"error": "unknown state parameter: 'abc'"})


async def test_change_hops_error(mock_dlmgr, test_download, rest_api):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
from tribler.core.components.libtorrent.utils.libtorrent_helper import libtorrent as lt
from tribler.core.components.metadata_store.db.orm_bindings.torrent_metadata import tdef_to_metadata_dict
from tribler.core.components.restapi.rest.rest_endpoint import (
HTTP_BAD_REQUEST,
HTTP_INTERNAL_SERVER_ERROR,
RESTEndpoint,
RESTResponse,
)
Expand Down Expand Up @@ -82,10 +80,10 @@ async def get_torrent_info(self, request):
try:
hops = int(hops)
except ValueError:
return RESTResponse({"error": f"wrong value of 'hops' parameter: {hops}"}, status=HTTP_BAD_REQUEST)
return self.bad_request(f"wrong value of 'hops' parameter: {hops}")

if not uri:
return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST)
return self.bad_request("uri parameter missing")

metainfo = None
scheme = scheme_from_uri(uri)
Expand All @@ -95,14 +93,13 @@ async def get_torrent_info(self, request):
try:
tdef = TorrentDef.load(file)
metainfo = tdef.metainfo
except (TypeError, ValueError, RuntimeError):
return RESTResponse({"error": f"error while decoding torrent file: {file}"},
status=HTTP_INTERNAL_SERVER_ERROR)
except (TypeError, ValueError, RuntimeError) as e:
return self.internal_error(e, f"error while decoding torrent file: {file}")
elif scheme in (HTTP_SCHEME, HTTPS_SCHEME):
try:
response = await query_http_uri(uri)
except (ServerConnectionError, ClientResponseError) as e:
return RESTResponse({"error": str(e)}, status=HTTP_INTERNAL_SERVER_ERROR)
return self.internal_error(e)

if response.startswith(b'magnet'):
_, infohash, _ = parse_magnetlink(response)
Expand All @@ -113,17 +110,17 @@ async def get_torrent_info(self, request):
elif scheme == MAGNET_SCHEME:
infohash = parse_magnetlink(uri)[1]
if infohash is None:
return RESTResponse({"error": "missing infohash"}, status=HTTP_BAD_REQUEST)
return self.bad_request("missing infohash")
metainfo = await self.download_manager.get_metainfo(infohash, timeout=60, hops=hops, url=uri)
else:
return RESTResponse({"error": "invalid uri"}, status=HTTP_BAD_REQUEST)
return self.bad_request("invalid uri")

if not metainfo:
return RESTResponse({"error": "metainfo error"}, status=HTTP_INTERNAL_SERVER_ERROR)
return self.internal_error(msg="metainfo error")

if not isinstance(metainfo, dict) or b'info' not in metainfo:
self._logger.warning("Received metainfo is not a valid dictionary")
return RESTResponse({"error": "invalid response"}, status=HTTP_INTERNAL_SERVER_ERROR)
return self.internal_error(msg="Received metainfo is not a valid dictionary")

# Add the torrent to GigaChannel as a free-for-all entry, so others can search it
self.download_manager.notifier[notifications.torrent_metadata_added](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,16 +285,17 @@ async def copy_channel(self, request):
try:
request_parsed = await request.json()
except (ContentTypeError, ValueError):
return RESTResponse({"error": "Bad JSON"}, status=HTTP_BAD_REQUEST)
return self.bad_request("Bad JSON")

if not target_collection and not personal_root:
return RESTResponse({"error": "Target channel not found"}, status=HTTP_NOT_FOUND)
return self.not_found("Target channel not found")

results_list = []
for entry in request_parsed:
public_key, id_ = unhexlify(entry["public_key"]), entry["id"]
source = self.mds.ChannelNode.get(public_key=public_key, id_=id_)
if not source:
return RESTResponse({"error": "Source entry not found"}, status=HTTP_BAD_REQUEST)
return self.bad_request("Source entry not found")
# We must upgrade Collections to Channels when moving them to root channel, and, vice-versa,
# downgrade Channels to Collections when moving them into existing channels
if isinstance(source, self.mds.CollectionNode):
Expand Down Expand Up @@ -379,7 +380,7 @@ async def add_torrent_to_channel(self, request):
with db_session:
channel = self.mds.CollectionNode.get(public_key=channel_pk, id_=channel_id)
if not channel:
return RESTResponse({"error": "Unknown channel"}, status=HTTP_NOT_FOUND)
return self.not_found("Unknown channel")

parameters = await request.json()

Expand Down Expand Up @@ -407,7 +408,7 @@ async def add_torrent_to_channel(self, request):
raise RuntimeError("Metainfo timeout")
tdef = TorrentDef.load_from_dict(meta_info)
else:
return RESTResponse({"error": "unknown uri type"}, status=HTTP_BAD_REQUEST)
return self.bad_request("unknown uri type")

added = 0
if tdef:
Expand All @@ -419,23 +420,21 @@ async def add_torrent_to_channel(self, request):
if parameters.get('torrents_dir', None):
torrents_dir = parameters['torrents_dir']
if not Path(torrents_dir).is_absolute():
return RESTResponse({"error": "the torrents_dir should point to a directory"}, status=HTTP_BAD_REQUEST)
return self.bad_request("the torrents_dir should point to a directory")

recursive = False
if parameters.get('recursive'):
recursive = parameters['recursive']
if not torrents_dir:
return RESTResponse(
{"error": "the torrents_dir parameter should be provided when the recursive parameter is set"},
status=HTTP_BAD_REQUEST,
)
return self.bad_request(
"the torrents_dir parameter should be provided when the recursive parameter is set")

if torrents_dir:
torrents_list, errors_list = channel.add_torrents_from_dir(torrents_dir, recursive)
return RESTResponse({"added": len(torrents_list), "errors": errors_list})

if not parameters.get('torrent', None):
return RESTResponse({"error": "torrent parameter missing"}, status=HTTP_BAD_REQUEST)
return self.bad_request("torrent parameter missing")

# Try to parse the torrent data
# Any errors will be handled by the error_middleware
Expand All @@ -458,7 +457,7 @@ async def post_commit(self, request):
else:
coll = self.mds.CollectionNode.get(public_key=channel_pk, id_=channel_id)
if not coll:
return RESTResponse({"success": False}, status=HTTP_NOT_FOUND)
return self.not_found("Collection not found", public_key=channel_pk, id_=channel_id)
torrent_dict = coll.commit_channel_torrent()
if torrent_dict:
self.gigachannel_manager.updated_my_channel(TorrentDef.load_from_dict(torrent_dict))
Expand Down
Loading