rippled
Loading...
Searching...
No Matches
WSClient.cpp
1#include <test/jtx.h>
2#include <test/jtx/WSClient.h>
3
4#include <xrpl/json/json_reader.h>
5#include <xrpl/json/to_string.h>
6#include <xrpl/protocol/jss.h>
7#include <xrpl/server/Port.h>
8
9#include <boost/asio/executor_work_guard.hpp>
10#include <boost/asio/io_context.hpp>
11#include <boost/asio/strand.hpp>
12#include <boost/beast/core/multi_buffer.hpp>
13#include <boost/beast/websocket.hpp>
14
15#include <iostream>
16#include <string>
17#include <unordered_map>
18
19namespace xrpl {
20namespace test {
21
22class WSClientImpl : public WSClient
23{
24 using error_code = boost::system::error_code;
25
26 struct msg
27 {
29
30 explicit msg(Json::Value&& jv_) : jv(jv_)
31 {
32 }
33 };
34
35 static boost::asio::ip::tcp::endpoint
36 getEndpoint(BasicConfig const& cfg, bool v2)
37 {
38 auto& log = std::cerr;
39 ParsedPort common;
40 parse_Port(common, cfg["server"], log);
41 auto const ps = v2 ? "ws2" : "ws";
42 for (auto const& name : cfg.section("server").values())
43 {
44 if (!cfg.exists(name))
45 continue;
46 ParsedPort pp;
47 parse_Port(pp, cfg[name], log);
48 if (pp.protocol.count(ps) == 0)
49 continue;
50 using namespace boost::asio::ip;
51 if (pp.ip && pp.ip->is_unspecified())
52 *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()} : address{address_v4::loopback()};
53
54 if (!pp.port)
55 Throw<std::runtime_error>("Use fixConfigPorts with auto ports");
56
57 return {*pp.ip, *pp.port};
58 }
59 Throw<std::runtime_error>("Missing WebSocket port");
60 return {}; // Silence compiler control paths return value warning
61 }
62
63 template <class ConstBuffers>
64 static std::string
65 buffer_string(ConstBuffers const& b)
66 {
67 using boost::asio::buffer;
68 using boost::asio::buffer_size;
70 s.resize(buffer_size(b));
71 buffer_copy(buffer(&s[0], s.size()), b);
72 return s;
73 }
74
75 boost::asio::io_context ios_;
77 boost::asio::strand<boost::asio::io_context::executor_type> strand_;
79 boost::asio::ip::tcp::socket stream_;
80 boost::beast::websocket::stream<boost::asio::ip::tcp::socket&> ws_;
81 boost::beast::multi_buffer rb_;
82
83 bool peerClosed_ = false;
84
85 // synchronize destructor
86 bool b0_ = false;
89
90 // synchronize message queue
94
95 unsigned rpc_version_;
96
97 void
99 {
100 boost::asio::post(ios_, boost::asio::bind_executor(strand_, [this] {
101 if (!peerClosed_)
102 {
103 ws_.async_close({}, boost::asio::bind_executor(strand_, [&](error_code) {
104 try
105 {
106 stream_.cancel();
107 }
108 catch (boost::system::system_error const&)
109 {
110 // ignored
111 }
112 }));
113 }
114 }));
116 thread_.join();
117 }
118
119public:
121 Config const& cfg,
122 bool v2,
123 unsigned rpc_version,
125 : work_(std::in_place, boost::asio::make_work_guard(ios_))
126 , strand_(boost::asio::make_strand(ios_))
127 , thread_([&] { ios_.run(); })
128 , stream_(ios_)
129 , ws_(stream_)
130 , rpc_version_(rpc_version)
131 {
132 try
133 {
134 auto const ep = getEndpoint(cfg, v2);
135 stream_.connect(ep);
136 ws_.set_option(
137 boost::beast::websocket::stream_base::decorator([&](boost::beast::websocket::request_type& req) {
138 for (auto const& h : headers)
139 req.set(h.first, h.second);
140 }));
141 ws_.handshake(ep.address().to_string() + ":" + std::to_string(ep.port()), "/");
142 ws_.async_read(
143 rb_,
144 boost::asio::bind_executor(
145 strand_, std::bind(&WSClientImpl::on_read_msg, this, std::placeholders::_1)));
146 }
147 catch (std::exception&)
148 {
149 cleanup();
150 Rethrow();
151 }
152 }
153
154 ~WSClientImpl() override
155 {
156 cleanup();
157 }
158
160 invoke(std::string const& cmd, Json::Value const& params) override
161 {
162 using boost::asio::buffer;
163 using namespace std::chrono_literals;
164
165 {
166 Json::Value jp;
167 if (params)
168 jp = params;
169 if (rpc_version_ == 2)
170 {
171 jp[jss::method] = cmd;
172 jp[jss::jsonrpc] = "2.0";
173 jp[jss::ripplerpc] = "2.0";
174 jp[jss::id] = 5;
175 }
176 else
177 jp[jss::command] = cmd;
178 auto const s = to_string(jp);
179 ws_.write_some(true, buffer(s));
180 }
181
182 auto jv = findMsg(5s, [&](Json::Value const& jval) { return jval[jss::type] == jss::response; });
183 if (jv)
184 {
185 // Normalize JSON output
186 jv->removeMember(jss::type);
187 if ((*jv).isMember(jss::status) && (*jv)[jss::status] == jss::error)
188 {
189 Json::Value ret;
190 ret[jss::result] = *jv;
191 if ((*jv).isMember(jss::error))
192 ret[jss::error] = (*jv)[jss::error];
193 ret[jss::status] = jss::error;
194 return ret;
195 }
196 if ((*jv).isMember(jss::status) && (*jv).isMember(jss::result))
197 (*jv)[jss::result][jss::status] = (*jv)[jss::status];
198 return *jv;
199 }
200 return {};
201 }
202
204 getMsg(std::chrono::milliseconds const& timeout) override
205 {
207 {
209 if (!cv_.wait_for(lock, timeout, [&] { return !msgs_.empty(); }))
210 return std::nullopt;
211 m = std::move(msgs_.back());
212 msgs_.pop_back();
213 }
214 return std::move(m->jv);
215 }
216
218 findMsg(std::chrono::milliseconds const& timeout, std::function<bool(Json::Value const&)> pred) override
219 {
221 {
223 if (!cv_.wait_for(lock, timeout, [&] {
224 for (auto it = msgs_.begin(); it != msgs_.end(); ++it)
225 {
226 if (pred((*it)->jv))
227 {
228 m = std::move(*it);
229 msgs_.erase(it);
230 return true;
231 }
232 }
233 return false;
234 }))
235 {
236 return std::nullopt;
237 }
238 }
239 return std::move(m->jv);
240 }
241
242 unsigned
243 version() const override
244 {
245 return rpc_version_;
246 }
247
248private:
249 void
251 {
252 if (ec)
253 {
254 if (ec == boost::beast::websocket::error::closed)
255 peerClosed_ = true;
256 return;
257 }
258
259 Json::Value jv;
260 Json::Reader jr;
261 jr.parse(buffer_string(rb_.data()), jv);
262 rb_.consume(rb_.size());
263 auto m = std::make_shared<msg>(std::move(jv));
264 {
265 std::lock_guard lock(m_);
266 msgs_.push_front(m);
267 cv_.notify_all();
268 }
269 ws_.async_read(
270 rb_,
271 boost::asio::bind_executor(strand_, std::bind(&WSClientImpl::on_read_msg, this, std::placeholders::_1)));
272 }
273
274 // Called when the read op terminates
275 void
277 {
278 std::lock_guard lock(m0_);
279 b0_ = true;
280 cv0_.notify_all();
281 }
282};
283
286 Config const& cfg,
287 bool v2,
288 unsigned rpc_version,
290{
291 return std::make_unique<WSClientImpl>(cfg, v2, rpc_version, headers);
292}
293
294} // namespace test
295} // namespace xrpl
T bind(T... args)
Unserialize a JSON document into a Value.
Definition json_reader.h:18
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:131
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:59
std::list< std::shared_ptr< msg > > msgs_
Definition WSClient.cpp:93
WSClientImpl(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers={})
Definition WSClient.cpp:120
boost::beast::multi_buffer rb_
Definition WSClient.cpp:81
Json::Value invoke(std::string const &cmd, Json::Value const &params) override
Submit a command synchronously.
Definition WSClient.cpp:160
std::optional< Json::Value > getMsg(std::chrono::milliseconds const &timeout) override
Retrieve a message.
Definition WSClient.cpp:204
boost::asio::io_context ios_
Definition WSClient.cpp:75
unsigned version() const override
Get RPC 1.0 or RPC 2.0.
Definition WSClient.cpp:243
std::optional< boost::asio::executor_work_guard< boost::asio::io_context::executor_type > > work_
Definition WSClient.cpp:76
static std::string buffer_string(ConstBuffers const &b)
Definition WSClient.cpp:65
boost::asio::ip::tcp::socket stream_
Definition WSClient.cpp:79
std::optional< Json::Value > findMsg(std::chrono::milliseconds const &timeout, std::function< bool(Json::Value const &)> pred) override
Retrieve a message that meets the predicate criteria.
Definition WSClient.cpp:218
boost::beast::websocket::stream< boost::asio::ip::tcp::socket & > ws_
Definition WSClient.cpp:80
std::condition_variable cv0_
Definition WSClient.cpp:88
std::condition_variable cv_
Definition WSClient.cpp:92
void on_read_msg(error_code const &ec)
Definition WSClient.cpp:250
boost::system::error_code error_code
Definition WSClient.cpp:24
static boost::asio::ip::tcp::endpoint getEndpoint(BasicConfig const &cfg, bool v2)
Definition WSClient.cpp:36
boost::asio::strand< boost::asio::io_context::executor_type > strand_
Definition WSClient.cpp:77
T count(T... args)
T is_same_v
T join(T... args)
STL namespace.
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition WSClient.cpp:285
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
void parse_Port(ParsedPort &port, Section const &section, std::ostream &log)
Definition Port.cpp:191
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
void Rethrow()
Rethrow the exception currently being handled.
Definition contract.h:29
T push_front(T... args)
T resize(T... args)
T size(T... args)
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:83
std::optional< boost::asio::ip::address > ip
Definition Port.h:96
std::optional< std::uint16_t > port
Definition Port.h:97
msg(Json::Value &&jv_)
Definition WSClient.cpp:30
T to_string(T... args)