rippled
Loading...
Searching...
No Matches
WSClient.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2016 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/WSClient.h>
22#include <xrpl/json/json_reader.h>
23#include <xrpl/json/to_string.h>
24#include <xrpl/protocol/jss.h>
25#include <xrpl/server/Port.h>
26
27#include <boost/beast/core/multi_buffer.hpp>
28#include <boost/beast/websocket.hpp>
29
30#include <iostream>
31#include <string>
32#include <unordered_map>
33
34namespace ripple {
35namespace test {
36
37class WSClientImpl : public WSClient
38{
39 using error_code = boost::system::error_code;
40
41 struct msg
42 {
44
45 explicit msg(Json::Value&& jv_) : jv(jv_)
46 {
47 }
48 };
49
50 static boost::asio::ip::tcp::endpoint
51 getEndpoint(BasicConfig const& cfg, bool v2)
52 {
53 auto& log = std::cerr;
54 ParsedPort common;
55 parse_Port(common, cfg["server"], log);
56 auto const ps = v2 ? "ws2" : "ws";
57 for (auto const& name : cfg.section("server").values())
58 {
59 if (!cfg.exists(name))
60 continue;
61 ParsedPort pp;
62 parse_Port(pp, cfg[name], log);
63 if (pp.protocol.count(ps) == 0)
64 continue;
65 using namespace boost::asio::ip;
66 if (pp.ip && pp.ip->is_unspecified())
67 *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()}
68 : address{address_v4::loopback()};
69
70 if (!pp.port)
71 Throw<std::runtime_error>("Use fixConfigPorts with auto ports");
72
73 return {*pp.ip, *pp.port};
74 }
75 Throw<std::runtime_error>("Missing WebSocket port");
76 return {}; // Silence compiler control paths return value warning
77 }
78
79 template <class ConstBuffers>
80 static std::string
81 buffer_string(ConstBuffers const& b)
82 {
83 using boost::asio::buffer;
84 using boost::asio::buffer_size;
86 s.resize(buffer_size(b));
87 buffer_copy(buffer(&s[0], s.size()), b);
88 return s;
89 }
90
91 boost::asio::io_service ios_;
93 boost::asio::io_service::strand strand_;
95 boost::asio::ip::tcp::socket stream_;
96 boost::beast::websocket::stream<boost::asio::ip::tcp::socket&> ws_;
97 boost::beast::multi_buffer rb_;
98
99 bool peerClosed_ = false;
100
101 // synchronize destructor
102 bool b0_ = false;
105
106 // synchronize message queue
110
111 unsigned rpc_version_;
112
113 void
115 {
116 ios_.post(strand_.wrap([this] {
117 if (!peerClosed_)
118 {
119 ws_.async_close({}, strand_.wrap([&](error_code ec) {
120 stream_.cancel(ec);
121 }));
122 }
123 }));
124 work_ = std::nullopt;
125 thread_.join();
126 }
127
128public:
130 Config const& cfg,
131 bool v2,
132 unsigned rpc_version,
134 : work_(ios_)
135 , strand_(ios_)
136 , thread_([&] { ios_.run(); })
137 , stream_(ios_)
138 , ws_(stream_)
139 , rpc_version_(rpc_version)
140 {
141 try
142 {
143 auto const ep = getEndpoint(cfg, v2);
144 stream_.connect(ep);
145 ws_.set_option(boost::beast::websocket::stream_base::decorator(
146 [&](boost::beast::websocket::request_type& req) {
147 for (auto const& h : headers)
148 req.set(h.first, h.second);
149 }));
150 ws_.handshake(
151 ep.address().to_string() + ":" + std::to_string(ep.port()),
152 "/");
153 ws_.async_read(
154 rb_,
155 strand_.wrap(std::bind(
156 &WSClientImpl::on_read_msg, this, std::placeholders::_1)));
157 }
158 catch (std::exception&)
159 {
160 cleanup();
161 Rethrow();
162 }
163 }
164
165 ~WSClientImpl() override
166 {
167 cleanup();
168 }
169
171 invoke(std::string const& cmd, Json::Value const& params) override
172 {
173 using boost::asio::buffer;
174 using namespace std::chrono_literals;
175
176 {
177 Json::Value jp;
178 if (params)
179 jp = params;
180 if (rpc_version_ == 2)
181 {
182 jp[jss::method] = cmd;
183 jp[jss::jsonrpc] = "2.0";
184 jp[jss::ripplerpc] = "2.0";
185 jp[jss::id] = 5;
186 }
187 else
188 jp[jss::command] = cmd;
189 auto const s = to_string(jp);
190 ws_.write_some(true, buffer(s));
191 }
192
193 auto jv = findMsg(5s, [&](Json::Value const& jval) {
194 return jval[jss::type] == jss::response;
195 });
196 if (jv)
197 {
198 // Normalize JSON output
199 jv->removeMember(jss::type);
200 if ((*jv).isMember(jss::status) && (*jv)[jss::status] == jss::error)
201 {
202 Json::Value ret;
203 ret[jss::result] = *jv;
204 if ((*jv).isMember(jss::error))
205 ret[jss::error] = (*jv)[jss::error];
206 ret[jss::status] = jss::error;
207 return ret;
208 }
209 if ((*jv).isMember(jss::status) && (*jv).isMember(jss::result))
210 (*jv)[jss::result][jss::status] = (*jv)[jss::status];
211 return *jv;
212 }
213 return {};
214 }
215
217 getMsg(std::chrono::milliseconds const& timeout) override
218 {
220 {
222 if (!cv_.wait_for(lock, timeout, [&] { return !msgs_.empty(); }))
223 return std::nullopt;
224 m = std::move(msgs_.back());
225 msgs_.pop_back();
226 }
227 return std::move(m->jv);
228 }
229
232 std::chrono::milliseconds const& timeout,
233 std::function<bool(Json::Value const&)> pred) override
234 {
236 {
238 if (!cv_.wait_for(lock, timeout, [&] {
239 for (auto it = msgs_.begin(); it != msgs_.end(); ++it)
240 {
241 if (pred((*it)->jv))
242 {
243 m = std::move(*it);
244 msgs_.erase(it);
245 return true;
246 }
247 }
248 return false;
249 }))
250 {
251 return std::nullopt;
252 }
253 }
254 return std::move(m->jv);
255 }
256
257 unsigned
258 version() const override
259 {
260 return rpc_version_;
261 }
262
263private:
264 void
266 {
267 if (ec)
268 {
269 if (ec == boost::beast::websocket::error::closed)
270 peerClosed_ = true;
271 return;
272 }
273
274 Json::Value jv;
275 Json::Reader jr;
276 jr.parse(buffer_string(rb_.data()), jv);
277 rb_.consume(rb_.size());
278 auto m = std::make_shared<msg>(std::move(jv));
279 {
280 std::lock_guard lock(m_);
281 msgs_.push_front(m);
282 cv_.notify_all();
283 }
284 ws_.async_read(
285 rb_,
286 strand_.wrap(std::bind(
287 &WSClientImpl::on_read_msg, this, std::placeholders::_1)));
288 }
289
290 // Called when the read op terminates
291 void
293 {
294 std::lock_guard lock(m0_);
295 b0_ = true;
296 cv0_.notify_all();
297 }
298};
299
302 Config const& cfg,
303 bool v2,
304 unsigned rpc_version,
306{
307 return std::make_unique<WSClientImpl>(cfg, v2, rpc_version, headers);
308}
309
310} // namespace test
311} // namespace ripple
T back(T... args)
T bind(T... args)
Unserialize a JSON document into a Value.
Definition: json_reader.h:39
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:78
Represents a JSON value.
Definition: json_value.h:148
Holds unparsed configuration information.
Definition: BasicConfig.h:218
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:79
unsigned version() const override
Get RPC 1.0 or RPC 2.0.
Definition: WSClient.cpp:258
WSClientImpl(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers={})
Definition: WSClient.cpp:129
boost::system::error_code error_code
Definition: WSClient.cpp:39
std::condition_variable cv0_
Definition: WSClient.cpp:104
static std::string buffer_string(ConstBuffers const &b)
Definition: WSClient.cpp:81
boost::beast::multi_buffer rb_
Definition: WSClient.cpp:97
std::optional< Json::Value > getMsg(std::chrono::milliseconds const &timeout) override
Retrieve a message.
Definition: WSClient.cpp:217
Json::Value invoke(std::string const &cmd, Json::Value const &params) override
Submit a command synchronously.
Definition: WSClient.cpp:171
static boost::asio::ip::tcp::endpoint getEndpoint(BasicConfig const &cfg, bool v2)
Definition: WSClient.cpp:51
void on_read_msg(error_code const &ec)
Definition: WSClient.cpp:265
boost::asio::io_service::strand strand_
Definition: WSClient.cpp:93
boost::beast::websocket::stream< boost::asio::ip::tcp::socket & > ws_
Definition: WSClient.cpp:96
boost::asio::io_service ios_
Definition: WSClient.cpp:91
std::optional< boost::asio::io_service::work > work_
Definition: WSClient.cpp:92
std::list< std::shared_ptr< msg > > msgs_
Definition: WSClient.cpp:109
boost::asio::ip::tcp::socket stream_
Definition: WSClient.cpp:95
std::condition_variable cv_
Definition: WSClient.cpp:108
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:231
T count(T... args)
T join(T... args)
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:301
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
void parse_Port(ParsedPort &port, Section const &section, std::ostream &log)
Definition: Port.cpp:213
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
void Rethrow()
Rethrow the exception currently being handled.
Definition: contract.h:48
T pop_back(T... args)
T push_front(T... args)
T resize(T... args)
T size(T... args)
std::optional< std::uint16_t > port
Definition: Port.h:116
std::set< std::string, boost::beast::iless > protocol
Definition: Port.h:102
std::optional< boost::asio::ip::address > ip
Definition: Port.h:115
msg(Json::Value &&jv_)
Definition: WSClient.cpp:45
T to_string(T... args)