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#include <boost/beast/core/multi_buffer.hpp>
27#include <boost/beast/websocket.hpp>
28
29#include <condition_variable>
30#include <string>
31#include <unordered_map>
32
33#include <iostream>
34
35#include <xrpl/beast/unit_test.h>
36
37namespace ripple {
38namespace test {
39
40class WSClientImpl : public WSClient
41{
42 using error_code = boost::system::error_code;
43
44 struct msg
45 {
47
48 explicit msg(Json::Value&& jv_) : jv(jv_)
49 {
50 }
51 };
52
53 static boost::asio::ip::tcp::endpoint
54 getEndpoint(BasicConfig const& cfg, bool v2)
55 {
56 auto& log = std::cerr;
57 ParsedPort common;
58 parse_Port(common, cfg["server"], log);
59 auto const ps = v2 ? "ws2" : "ws";
60 for (auto const& name : cfg.section("server").values())
61 {
62 if (!cfg.exists(name))
63 continue;
64 ParsedPort pp;
65 parse_Port(pp, cfg[name], log);
66 if (pp.protocol.count(ps) == 0)
67 continue;
68 using namespace boost::asio::ip;
69 if (pp.ip && pp.ip->is_unspecified())
70 *pp.ip = pp.ip->is_v6() ? address{address_v6::loopback()}
71 : address{address_v4::loopback()};
72
73 if (!pp.port)
74 Throw<std::runtime_error>("Use fixConfigPorts with auto ports");
75
76 return {*pp.ip, *pp.port};
77 }
78 Throw<std::runtime_error>("Missing WebSocket port");
79 return {}; // Silence compiler control paths return value warning
80 }
81
82 template <class ConstBuffers>
83 static std::string
84 buffer_string(ConstBuffers const& b)
85 {
86 using boost::asio::buffer;
87 using boost::asio::buffer_size;
89 s.resize(buffer_size(b));
90 buffer_copy(buffer(&s[0], s.size()), b);
91 return s;
92 }
93
94 boost::asio::io_service ios_;
96 boost::asio::io_service::strand strand_;
98 boost::asio::ip::tcp::socket stream_;
99 boost::beast::websocket::stream<boost::asio::ip::tcp::socket&> ws_;
100 boost::beast::multi_buffer rb_;
101
102 bool peerClosed_ = false;
103
104 // synchronize destructor
105 bool b0_ = false;
108
109 // synchronize message queue
113
114 unsigned rpc_version_;
115
116 void
118 {
119 ios_.post(strand_.wrap([this] {
120 if (!peerClosed_)
121 {
122 ws_.async_close({}, strand_.wrap([&](error_code ec) {
123 stream_.cancel(ec);
124 }));
125 }
126 }));
127 work_ = std::nullopt;
128 thread_.join();
129 }
130
131public:
133 Config const& cfg,
134 bool v2,
135 unsigned rpc_version,
137 : work_(ios_)
138 , strand_(ios_)
139 , thread_([&] { ios_.run(); })
140 , stream_(ios_)
141 , ws_(stream_)
142 , rpc_version_(rpc_version)
143 {
144 try
145 {
146 auto const ep = getEndpoint(cfg, v2);
147 stream_.connect(ep);
148 ws_.set_option(boost::beast::websocket::stream_base::decorator(
149 [&](boost::beast::websocket::request_type& req) {
150 for (auto const& h : headers)
151 req.set(h.first, h.second);
152 }));
153 ws_.handshake(
154 ep.address().to_string() + ":" + std::to_string(ep.port()),
155 "/");
156 ws_.async_read(
157 rb_,
158 strand_.wrap(std::bind(
159 &WSClientImpl::on_read_msg, this, std::placeholders::_1)));
160 }
161 catch (std::exception&)
162 {
163 cleanup();
164 Rethrow();
165 }
166 }
167
168 ~WSClientImpl() override
169 {
170 cleanup();
171 }
172
174 invoke(std::string const& cmd, Json::Value const& params) override
175 {
176 using boost::asio::buffer;
177 using namespace std::chrono_literals;
178
179 {
180 Json::Value jp;
181 if (params)
182 jp = params;
183 if (rpc_version_ == 2)
184 {
185 jp[jss::method] = cmd;
186 jp[jss::jsonrpc] = "2.0";
187 jp[jss::ripplerpc] = "2.0";
188 jp[jss::id] = 5;
189 }
190 else
191 jp[jss::command] = cmd;
192 auto const s = to_string(jp);
193 ws_.write_some(true, buffer(s));
194 }
195
196 auto jv = findMsg(5s, [&](Json::Value const& jval) {
197 return jval[jss::type] == jss::response;
198 });
199 if (jv)
200 {
201 // Normalize JSON output
202 jv->removeMember(jss::type);
203 if ((*jv).isMember(jss::status) && (*jv)[jss::status] == jss::error)
204 {
205 Json::Value ret;
206 ret[jss::result] = *jv;
207 if ((*jv).isMember(jss::error))
208 ret[jss::error] = (*jv)[jss::error];
209 ret[jss::status] = jss::error;
210 return ret;
211 }
212 if ((*jv).isMember(jss::status) && (*jv).isMember(jss::result))
213 (*jv)[jss::result][jss::status] = (*jv)[jss::status];
214 return *jv;
215 }
216 return {};
217 }
218
220 getMsg(std::chrono::milliseconds const& timeout) override
221 {
223 {
225 if (!cv_.wait_for(lock, timeout, [&] { return !msgs_.empty(); }))
226 return std::nullopt;
227 m = std::move(msgs_.back());
228 msgs_.pop_back();
229 }
230 return std::move(m->jv);
231 }
232
235 std::chrono::milliseconds const& timeout,
236 std::function<bool(Json::Value const&)> pred) override
237 {
239 {
241 if (!cv_.wait_for(lock, timeout, [&] {
242 for (auto it = msgs_.begin(); it != msgs_.end(); ++it)
243 {
244 if (pred((*it)->jv))
245 {
246 m = std::move(*it);
247 msgs_.erase(it);
248 return true;
249 }
250 }
251 return false;
252 }))
253 {
254 return std::nullopt;
255 }
256 }
257 return std::move(m->jv);
258 }
259
260 unsigned
261 version() const override
262 {
263 return rpc_version_;
264 }
265
266private:
267 void
269 {
270 if (ec)
271 {
272 if (ec == boost::beast::websocket::error::closed)
273 peerClosed_ = true;
274 return;
275 }
276
277 Json::Value jv;
278 Json::Reader jr;
279 jr.parse(buffer_string(rb_.data()), jv);
280 rb_.consume(rb_.size());
281 auto m = std::make_shared<msg>(std::move(jv));
282 {
283 std::lock_guard lock(m_);
284 msgs_.push_front(m);
285 cv_.notify_all();
286 }
287 ws_.async_read(
288 rb_,
289 strand_.wrap(std::bind(
290 &WSClientImpl::on_read_msg, this, std::placeholders::_1)));
291 }
292
293 // Called when the read op terminates
294 void
296 {
297 std::lock_guard lock(m0_);
298 b0_ = true;
299 cv0_.notify_all();
300 }
301};
302
305 Config const& cfg,
306 bool v2,
307 unsigned rpc_version,
309{
310 return std::make_unique<WSClientImpl>(cfg, v2, rpc_version, headers);
311}
312
313} // namespace test
314} // namespace ripple
T back(T... args)
T bind(T... args)
Unserialize a JSON document into a Value.
Definition: json_reader.h:37
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:73
Represents a JSON value.
Definition: json_value.h:147
Holds unparsed configuration information.
Definition: BasicConfig.h:219
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:80
unsigned version() const override
Get RPC 1.0 or RPC 2.0.
Definition: WSClient.cpp:261
WSClientImpl(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers={})
Definition: WSClient.cpp:132
boost::system::error_code error_code
Definition: WSClient.cpp:42
std::condition_variable cv0_
Definition: WSClient.cpp:107
static std::string buffer_string(ConstBuffers const &b)
Definition: WSClient.cpp:84
boost::beast::multi_buffer rb_
Definition: WSClient.cpp:100
std::optional< Json::Value > getMsg(std::chrono::milliseconds const &timeout) override
Retrieve a message.
Definition: WSClient.cpp:220
Json::Value invoke(std::string const &cmd, Json::Value const &params) override
Submit a command synchronously.
Definition: WSClient.cpp:174
static boost::asio::ip::tcp::endpoint getEndpoint(BasicConfig const &cfg, bool v2)
Definition: WSClient.cpp:54
void on_read_msg(error_code const &ec)
Definition: WSClient.cpp:268
boost::asio::io_service::strand strand_
Definition: WSClient.cpp:96
boost::beast::websocket::stream< boost::asio::ip::tcp::socket & > ws_
Definition: WSClient.cpp:99
boost::asio::io_service ios_
Definition: WSClient.cpp:94
std::optional< boost::asio::io_service::work > work_
Definition: WSClient.cpp:95
std::list< std::shared_ptr< msg > > msgs_
Definition: WSClient.cpp:112
boost::asio::ip::tcp::socket stream_
Definition: WSClient.cpp:98
std::condition_variable cv_
Definition: WSClient.cpp:111
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:234
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:304
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:199
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
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:115
std::set< std::string, boost::beast::iless > protocol
Definition: Port.h:101
std::optional< boost::asio::ip::address > ip
Definition: Port.h:114
msg(Json::Value &&jv_)
Definition: WSClient.cpp:48
T to_string(T... args)