feat: new JSON-RPC 2.0 RPC API (#7269)

* feat: add enum for JSON-RPC error codes

* feat: new `tr_rpc_request_exec()` overload that accepts string

* feat: add JSON-RPC parse error handling

* feat: add logic for branching to JSON-RPC or legacy API

* feat: error codes for existing errors strings

* refactor: async handlers now take the done cb as parameter

* feat: support non-batch JSON-RPC requests

* feat: support batch JSON-RPC requests

* refactor: move JSON-RPC error codes to header

* test: new tests for JSON-RPC

* refactor(webui): use jsonrpc api

* docs: update docs for jsonrpc

* fix: clang-tidy warning

* perf: avoid copying callback in batch mode

* code review: don't commit to dropping old RPC

* chore: fix shadowed variable warnings
This commit is contained in:
Yat Ho
2025-12-01 00:04:40 +08:00
committed by GitHub
parent d64a1a5699
commit 1cb24a701b
16 changed files with 1410 additions and 445 deletions

View File

@@ -65,6 +65,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"clientIsChoked"sv,
"clientIsInterested"sv,
"clientName"sv,
"code"sv,
"comment"sv,
"compact-view"sv,
"complete"sv,
@@ -77,6 +78,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"creator"sv,
"cumulative-stats"sv,
"current-stats"sv,
"data"sv,
"date"sv,
"dateCreated"sv,
"default-trackers"sv,
@@ -170,6 +172,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"isStalled"sv,
"isUTP"sv,
"isUploadingTo"sv,
"jsonrpc"sv,
"labels"sv,
"lastAnnouncePeerCount"sv,
"lastAnnounceResult"sv,
@@ -200,6 +203,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"maxConnectedPeers"sv,
"memory-bytes"sv,
"memory-units"sv,
"message"sv,
"message-level"sv,
"metadataPercentComplete"sv,
"metadata_size"sv,
@@ -215,6 +219,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"nodes6"sv,
"open-dialog-dir"sv,
"p"sv,
"params"sv,
"path"sv,
"paused"sv,
"pausedTorrentCount"sv,

View File

@@ -67,6 +67,7 @@ enum // NOLINT(performance-enum-size)
TR_KEY_clientIsChoked,
TR_KEY_clientIsInterested,
TR_KEY_clientName,
TR_KEY_code,
TR_KEY_comment,
TR_KEY_compact_view,
TR_KEY_complete,
@@ -79,6 +80,7 @@ enum // NOLINT(performance-enum-size)
TR_KEY_creator,
TR_KEY_cumulative_stats,
TR_KEY_current_stats,
TR_KEY_data,
TR_KEY_date,
TR_KEY_dateCreated,
TR_KEY_default_trackers,
@@ -172,6 +174,7 @@ enum // NOLINT(performance-enum-size)
TR_KEY_isStalled,
TR_KEY_isUTP,
TR_KEY_isUploadingTo,
TR_KEY_jsonrpc,
TR_KEY_labels,
TR_KEY_lastAnnouncePeerCount,
TR_KEY_lastAnnounceResult,
@@ -202,6 +205,7 @@ enum // NOLINT(performance-enum-size)
TR_KEY_maxConnectedPeers,
TR_KEY_memory_bytes,
TR_KEY_memory_units,
TR_KEY_message,
TR_KEY_message_level,
TR_KEY_metadataPercentComplete,
TR_KEY_metadata_size,
@@ -217,6 +221,7 @@ enum // NOLINT(performance-enum-size)
TR_KEY_nodes6,
TR_KEY_open_dialog_dir,
TR_KEY_p,
TR_KEY_params,
TR_KEY_path,
TR_KEY_paused,
TR_KEY_pausedTorrentCount,

View File

@@ -356,20 +356,23 @@ void handle_web_client(struct evhttp_request* req, tr_rpc_server const* server)
void handle_rpc_from_json(struct evhttp_request* req, tr_rpc_server* server, std::string_view json)
{
if (auto otop = tr_variant_serde::json().inplace().parse(json); otop)
{
tr_rpc_request_exec(
server->session,
*otop,
[req, server](tr_session* /*session*/, tr_variant&& content)
tr_rpc_request_exec(
server->session,
json,
[req, server](tr_session* /*session*/, tr_variant&& content)
{
if (!content.has_value())
{
auto* const output_headers = evhttp_request_get_output_headers(req);
auto* const response = make_response(req, server, tr_variant_serde::json().compact().to_string(content));
evhttp_add_header(output_headers, "Content-Type", "application/json; charset=UTF-8");
evhttp_send_reply(req, HTTP_OK, "OK", response);
evbuffer_free(response);
});
}
evhttp_send_reply(req, HTTP_NOCONTENT, "OK", nullptr);
return;
}
auto* const output_headers = evhttp_request_get_output_headers(req);
auto* const response = make_response(req, server, tr_variant_serde::json().compact().to_string(content));
evhttp_add_header(output_headers, "Content-Type", "application/json; charset=UTF-8");
evhttp_send_reply(req, HTTP_OK, "OK", response);
evbuffer_free(response);
});
}
void handle_rpc(struct evhttp_request* req, tr_rpc_server* server)

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,35 @@
struct tr_session;
struct tr_variant;
namespace JsonRpc
{
auto constexpr Version = std::string_view{ "2.0" };
namespace Error
{
enum Code : int16_t
{
PARSE_ERROR = -32700,
INVALID_REQUEST = -32600,
METHOD_NOT_FOUND = -32601,
INVALID_PARAMS = -32602,
INTERNAL_ERROR = -32603,
SUCCESS = 0,
SET_ANNOUNCE_LIST,
INVALID_TRACKER_LIST,
PATH_NOT_ABSOLUTE,
UNRECOGNIZED_INFO,
SYSTEM_ERROR,
FILE_IDX_OOR,
PIECE_IDX_OOR,
HTTP_ERROR,
CORRUPT_TORRENT
};
}
} // namespace JsonRpc
using tr_rpc_response_func = std::function<void(tr_session* session, tr_variant&& response)>;
void tr_rpc_request_exec(tr_session* session, tr_variant const& request, tr_rpc_response_func&& callback = {});
void tr_rpc_request_exec(tr_session* session, std::string_view request, tr_rpc_response_func&& callback = {});

View File

@@ -2438,7 +2438,7 @@ void renameTorrentFileString(tr_torrent* tor, std::string_view oldpath, std::str
void tr_torrent::rename_path_in_session_thread(
std::string_view const oldpath,
std::string_view const newname,
tr_torrent_rename_done_func const callback,
tr_torrent_rename_done_func const& callback,
void* const callback_user_data)
{
using namespace rename_helpers;
@@ -2482,19 +2482,19 @@ void tr_torrent::rename_path_in_session_thread(
{
auto const szold = tr_pathbuf{ oldpath };
auto const sznew = tr_pathbuf{ newname };
(*callback)(this, szold.c_str(), sznew.c_str(), error, callback_user_data);
callback(this, szold.c_str(), sznew.c_str(), error, callback_user_data);
}
}
void tr_torrent::rename_path(
std::string_view oldpath,
std::string_view newname,
tr_torrent_rename_done_func callback,
tr_torrent_rename_done_func&& callback,
void* callback_user_data)
{
this->session->run_in_session_thread(
[this, oldpath = std::string(oldpath), newname = std::string(newname), callback, callback_user_data]()
{ rename_path_in_session_thread(oldpath, newname, callback, callback_user_data); });
[this, oldpath = std::string(oldpath), newname = std::string(newname), cb = std::move(callback), callback_user_data]()
{ rename_path_in_session_thread(oldpath, newname, std::move(cb), callback_user_data); });
}
void tr_torrentRenamePath(
@@ -2507,7 +2507,7 @@ void tr_torrentRenamePath(
oldpath = oldpath != nullptr ? oldpath : "";
newname = newname != nullptr ? newname : "";
tor->rename_path(oldpath, newname, callback, callback_user_data);
tor->rename_path(oldpath, newname, std::move(callback), callback_user_data);
}
// ---

View File

@@ -183,7 +183,7 @@ struct tr_torrent
void rename_path(
std::string_view oldpath,
std::string_view newname,
tr_torrent_rename_done_func callback,
tr_torrent_rename_done_func&& callback,
void* callback_user_data);
// these functions should become private when possible,
@@ -1323,7 +1323,7 @@ private:
void rename_path_in_session_thread(
std::string_view oldpath,
std::string_view newname,
tr_torrent_rename_done_func callback,
tr_torrent_rename_done_func const& callback,
void* callback_user_data);
void start_in_session_thread();

View File

@@ -15,6 +15,7 @@
#include <time.h> // time_t
#ifdef __cplusplus
#include <functional>
#include <string>
#include <string_view>
#else
@@ -853,12 +854,8 @@ void tr_torrentStart(tr_torrent* torrent);
/** @brief Stop (pause) a torrent */
void tr_torrentStop(tr_torrent* torrent);
using tr_torrent_rename_done_func = void (*)( //
tr_torrent* torrent,
char const* oldpath,
char const* newname,
int error,
void* user_data);
using tr_torrent_rename_done_func = std::function<
void(tr_torrent* torrent, char const* oldpath, char const* newname, int error, void* user_data)>;
/**
* @brief Rename a file or directory in a torrent.