xrpld
Loading...
Searching...
No Matches
JSONRPCClient.cpp
1#include <test/jtx/JSONRPCClient.h>
2
3#include <test/jtx/AbstractClient.h>
4
5#include <xrpld/core/Config.h>
6
7#include <xrpl/basics/contract.h>
8#include <xrpl/config/BasicConfig.h>
9#include <xrpl/config/Constants.h>
10#include <xrpl/json/json_reader.h>
11#include <xrpl/json/json_value.h>
12#include <xrpl/json/to_string.h>
13#include <xrpl/protocol/jss.h>
14#include <xrpl/server/Port.h>
15
16#include <boost/asio/buffer.hpp>
17#include <boost/asio/error.hpp>
18#include <boost/asio/io_context.hpp>
19#include <boost/asio/ip/address_v4.hpp>
20#include <boost/asio/ip/address_v6.hpp>
21#include <boost/asio/ip/tcp.hpp>
22#include <boost/beast/core/multi_buffer.hpp>
23#include <boost/beast/http/dynamic_body.hpp>
24#include <boost/beast/http/error.hpp>
25#include <boost/beast/http/message.hpp>
26#include <boost/beast/http/read.hpp>
27#include <boost/beast/http/string_body.hpp>
28#include <boost/beast/http/verb.hpp>
29#include <boost/beast/http/write.hpp>
30#include <boost/system/system_error.hpp>
31
32#include <algorithm>
33#include <array>
34#include <iostream>
35#include <memory>
36#include <sstream>
37#include <stdexcept>
38#include <string>
39
40namespace xrpl::test {
41
43{
44 static boost::asio::ip::tcp::endpoint
46 {
47 auto& log = std::cerr;
48 ParsedPort common;
49 parsePort(common, cfg[Sections::kServer], log);
50 for (auto const& name : cfg.section(Sections::kServer).values())
51 {
52 if (!cfg.exists(name))
53 continue;
54 ParsedPort pp;
55 parsePort(pp, cfg[name], log);
56 if (not pp.protocol.contains("http"))
57 continue;
58 using namespace boost::asio::ip;
59 if (pp.ip && pp.ip->is_unspecified())
60 {
61 *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()}
62 : address{address_v4::loopback()};
63 }
64
65 if (!pp.port)
66 Throw<std::runtime_error>("Use fixConfigPorts with auto ports");
67
68 return {*pp.ip, *pp.port}; // NOLINT(bugprone-unchecked-optional-access)
69 }
70 Throw<std::runtime_error>("Missing HTTP port");
71 return {}; // Silence compiler control paths return value warning
72 }
73
74 template <class ConstBufferSequence>
75 static std::string
76 bufferString(ConstBufferSequence const& b)
77 {
78 using namespace boost::asio;
80 s.resize(buffer_size(b));
81 buffer_copy(buffer(&s[0], s.size()), b);
82 return s;
83 }
84
85 boost::asio::ip::tcp::endpoint ep_;
86 boost::asio::io_context ios_;
87 boost::asio::ip::tcp::socket stream_;
88 boost::beast::multi_buffer bin_;
89 boost::beast::multi_buffer bout_;
90 unsigned rpcVersion_;
91
92 bool disconnected_ = false;
93
94 // Errors that mean the persistent keep-alive connection was dropped by the
95 // server (rather than a genuine protocol failure), so the request can be
96 // safely retried on a fresh connection.
97 static bool
98 droppedConnection(boost::system::error_code const& ec)
99 {
100 namespace error = boost::asio::error;
101 static auto const kDroppedConnectionErrors = std::to_array<boost::system::error_code>({
102 boost::beast::http::error::end_of_stream,
103 error::eof,
104 error::connection_reset,
105 error::connection_aborted,
106 error::broken_pipe,
107 error::not_connected,
108 });
109
110 return std::ranges::any_of(
111 kDroppedConnectionErrors,
112 [&ec](boost::system::error_code const& e) { return ec == e; });
113 }
114
115 // Tear down and re-establish the socket to ep_, discarding any buffered
116 // bytes left over from the dropped connection.
117 void
119 {
120 boost::system::error_code ec;
121 stream_.close(ec);
122 bin_.clear();
123 stream_.connect(ep_);
124 }
125
126public:
127 explicit JSONRPCClient(Config const& cfg, unsigned rpcVersion)
128 : ep_(getEndpoint(cfg)), stream_(ios_), rpcVersion_(rpcVersion)
129 {
130 stream_.connect(ep_);
131 }
132
133 // Return value is an Object type with up to three keys:
134 // status
135 // error
136 // result
138 invoke(std::string const& cmd, json::Value const& params) override
139 {
140 using namespace boost::beast::http;
141 using namespace boost::asio;
142 using namespace std::string_literals;
143
144 // Once disconnect() has released the slot, the client must not be
145 // reused (see AbstractClient::disconnect). Refuse rather than let the
146 // failed write/read below trip the reconnect path and silently
147 // re-consume a connection slot, which would defeat disconnectClient().
148 if (disconnected_)
149 Throw<std::logic_error>("JSONRPCClient::invoke called after disconnect()");
150
151 request<string_body> req;
152 req.method(boost::beast::http::verb::post);
153 req.target("/");
154 req.version(11);
155 req.insert("Content-Type", "application/json; charset=UTF-8");
156 {
158 ostr << ep_;
159 req.insert("Host", ostr.str());
160 }
161 {
162 json::Value jr;
163 jr[jss::method] = cmd;
164 if (rpcVersion_ == 2)
165 {
166 jr[jss::jsonrpc] = "2.0";
167 jr[jss::ripplerpc] = "2.0";
168 jr[jss::id] = 5;
169 }
170 if (params)
171 {
172 json::Value& ja = jr[jss::params] = json::ValueType::Array;
173 ja.append(params);
174 }
175 req.body() = to_string(jr);
176 }
177 req.prepare_payload();
178
179 // The client keeps a single keep-alive connection for its whole
180 // lifetime, but the server drops idle localhost connections after a few
181 // seconds (BaseHTTPPeer::kTimeoutSecondsLocal). If a slow gap between
182 // requests let the server close the socket, the write/read here fails
183 // with end_of_stream; reconnect and retry the request exactly once.
184 response<dynamic_body> res;
185 auto writeAndRead = [&] {
186 write(stream_, req);
187 read(stream_, bin_, res);
188 };
189 try
190 {
191 writeAndRead();
192 }
193 catch (boost::system::system_error const& e)
194 {
195 if (!droppedConnection(e.code()))
196 throw;
197 reconnect();
198 res = {};
199 writeAndRead();
200 }
201
202 json::Reader jr;
203 json::Value jv;
204 jr.parse(bufferString(res.body().data()), jv);
205 if (jv["result"].isMember("error"))
206 jv["error"] = jv["result"]["error"];
207 if (jv["result"].isMember("status"))
208 jv["status"] = jv["result"]["status"];
209 return jv;
210 }
211
212 [[nodiscard]] unsigned
213 version() const override
214 {
215 return rpcVersion_;
216 }
217
218 void
219 disconnect() override
220 {
221 if (disconnected_)
222 return;
223
224 disconnected_ = true;
225
226 boost::system::error_code ec;
227 stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
228 stream_.close(ec);
229 }
230};
231
233makeJSONRPCClient(Config const& cfg, unsigned rpcVersion)
234{
235 return std::make_unique<JSONRPCClient>(cfg, rpcVersion);
236}
237
238} // namespace xrpl::test
T any_of(T... args)
Unserialize a JSON document into a Value.
Definition json_reader.h:20
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
Holds unparsed configuration information.
bool exists(std::string const &name) const
Returns true if a section with the given name exists.
Section & section(std::string const &name)
Returns the section with the given name.
std::vector< std::string > const & values() const
Returns all the values in the section.
Definition BasicConfig.h:69
JSONRPCClient(Config const &cfg, unsigned rpcVersion)
static std::string bufferString(ConstBufferSequence const &b)
unsigned version() const override
Get RPC 1.0 or RPC 2.0.
static bool droppedConnection(boost::system::error_code const &ec)
boost::asio::ip::tcp::endpoint ep_
boost::asio::ip::tcp::socket stream_
boost::beast::multi_buffer bout_
boost::beast::multi_buffer bin_
boost::asio::io_context ios_
static boost::asio::ip::tcp::endpoint getEndpoint(BasicConfig const &cfg)
void disconnect() override
Close the client's connection to the server.
json::Value invoke(std::string const &cmd, json::Value const &params) override
Submit a command synchronously.
T contains(T... args)
T make_unique(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
std::unique_ptr< AbstractClient > makeJSONRPCClient(Config const &cfg, unsigned rpcVersion)
Returns a client using JSON-RPC over HTTP/S.
void parsePort(ParsedPort &port, Section const &section, std::ostream &log)
Definition Port.cpp:195
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T resize(T... args)
T size(T... args)
T str(T... args)
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:81
std::optional< boost::asio::ip::address > ip
Definition Port.h:94
std::optional< std::uint16_t > port
Definition Port.h:95
static constexpr auto kServer
Definition Constants.h:56