xrpld
Loading...
Searching...
No Matches
Server_test.cpp
1#include <test/jtx/CaptureLogs.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/envconfig.h>
4#include <test/unit_test/SuiteJournal.h>
5
6#include <xrpld/core/Config.h>
7
8#include <xrpl/beast/rfc2616.h>
9#include <xrpl/beast/unit_test/suite.h>
10#include <xrpl/beast/utility/Journal.h>
11#include <xrpl/config/Constants.h>
12#include <xrpl/server/Handoff.h>
13#include <xrpl/server/Port.h>
14#include <xrpl/server/Server.h>
15#include <xrpl/server/Session.h>
16#include <xrpl/server/WSSession.h>
17#include <xrpl/server/detail/ServerImpl.h>
18
19#include <boost/asio/buffer.hpp>
20#include <boost/asio/executor_work_guard.hpp>
21#include <boost/asio/io_context.hpp>
22#include <boost/asio/ip/address.hpp>
23#include <boost/asio/ip/tcp.hpp>
24#include <boost/asio/streambuf.hpp>
25#include <boost/beast/core/tcp_stream.hpp>
26#include <boost/beast/ssl/ssl_stream.hpp>
27#include <boost/system/detail/error_code.hpp>
28
29#include <chrono>
30#include <exception>
31#include <memory>
32#include <optional>
33#include <ostream>
34#include <stdexcept>
35#include <string>
36#include <string_view>
37#include <thread>
38#include <utility>
39#include <vector>
40
41namespace xrpl::test {
42
43using socket_type = boost::beast::tcp_stream;
44using stream_type = boost::beast::ssl_stream<socket_type>;
45
47{
48public:
50 {
51 private:
52 boost::asio::io_context ioContext_;
56
57 public:
59 : work_(std::in_place, boost::asio::make_work_guard(ioContext_))
60 , thread_([&]() { this->ioContext_.run(); })
61 {
62 }
63
65 {
66 work_.reset();
67 thread_.join();
68 }
69
70 boost::asio::io_context&
72 {
73 return ioContext_;
74 }
75 };
76
77 //--------------------------------------------------------------------------
78
80 {
82
83 public:
85 : Sink(beast::Severity::Warning, false), suite_(suite)
86 {
87 }
88
89 void
90 write(beast::Severity level, std::string const& text) override
91 {
92 if (level < threshold())
93 return;
94
95 suite_.log << text << std::endl;
96 }
97
98 void
99 writeAlways(beast::Severity level, std::string const& text) override
100 {
101 suite_.log << text << std::endl;
102 }
103 };
104
105 //--------------------------------------------------------------------------
106
108 {
109 static bool
110 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
111 {
112 return true;
113 }
114
115 static Handoff
117 Session& session,
118 std::unique_ptr<stream_type> const& bundle,
119 http_request_type const& request,
120 boost::asio::ip::tcp::endpoint remoteAddress)
121 {
122 return Handoff{};
123 }
124
125 static Handoff
127 Session& session,
128 http_request_type const& request,
129 boost::asio::ip::tcp::endpoint remoteAddress)
130 {
131 return Handoff{};
132 }
133
134 static void
136 {
137 using namespace std::string_view_literals;
138 session.write("Hello, world!\n"sv);
140 {
141 session.complete();
142 }
143 else
144 {
145 session.close(true);
146 }
147 }
148
149 void
155
156 void
157 onClose(Session& session, boost::system::error_code const&)
158 {
159 }
160
161 void
163 {
164 }
165 };
166
167 //--------------------------------------------------------------------------
168
169 // Connect to an address
170 template <class Socket>
171 bool
172 connect(Socket& s, Socket::endpoint_type const& ep)
173 {
174 try
175 {
176 s.connect(ep);
177 pass();
178 return true;
179 }
180 catch (std::exception const& e)
181 {
182 fail(e.what());
183 }
184
185 return false;
186 }
187
188 // Write a string to the stream
189 template <class SyncWriteStream>
190 bool
191 write(SyncWriteStream& s, std::string const& text)
192 {
193 try
194 {
195 boost::asio::write(s, boost::asio::buffer(text));
196 pass();
197 return true;
198 }
199 catch (std::exception const& e)
200 {
201 fail(e.what());
202 }
203 return false;
204 }
205
206 // Expect that reading the stream produces a matching string
207 template <class SyncReadStream>
208 bool
209 expectRead(SyncReadStream& s, std::string const& match)
210 {
211 boost::asio::streambuf b(1000); // limit on read
212 try
213 {
214 auto const n = boost::asio::read_until(s, b, '\n');
215 if (BEAST_EXPECT(n == match.size()))
216 {
217 std::string got;
218 got.resize(n);
219 boost::asio::buffer_copy(boost::asio::buffer(&got[0], n), b.data());
220 return BEAST_EXPECT(got == match);
221 }
222 }
223 catch (std::length_error const& e)
224 {
225 fail(e.what());
226 }
227 catch (std::exception const& e)
228 {
229 fail(e.what());
230 }
231 return false;
232 }
233
234 void
235 testRequest(boost::asio::ip::tcp::endpoint const& ep)
236 {
237 boost::asio::io_context ios;
238 using socket = boost::asio::ip::tcp::socket;
239 socket s(ios);
240
241 if (!connect(s, ep))
242 return;
243
244 if (!write(
245 s,
246 "GET / HTTP/1.1\r\n"
247 "Connection: close\r\n"
248 "\r\n"))
249 return;
250
251 if (!expectRead(s, "Hello, world!\n"))
252 return;
253
254 boost::system::error_code ec;
255 s.shutdown(socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value)
256
258 }
259
260 void
261 testKeepalive(boost::asio::ip::tcp::endpoint const& ep)
262 {
263 boost::asio::io_context ios;
264 using socket = boost::asio::ip::tcp::socket;
265 socket s(ios);
266
267 if (!connect(s, ep))
268 return;
269
270 if (!write(
271 s,
272 "GET / HTTP/1.1\r\n"
273 "Connection: Keep-Alive\r\n"
274 "\r\n"))
275 return;
276
277 if (!expectRead(s, "Hello, world!\n"))
278 return;
279
280 if (!write(
281 s,
282 "GET / HTTP/1.1\r\n"
283 "Connection: close\r\n"
284 "\r\n"))
285 return;
286
287 if (!expectRead(s, "Hello, world!\n"))
288 return;
289
290 boost::system::error_code ec;
291 s.shutdown(socket::shutdown_both, ec); // NOLINT(bugprone-unused-return-value)
292 }
293
294 void
296 {
297 testcase("Basic client/server");
298 TestSink sink{*this};
299 TestThread thread;
301 beast::Journal const journal{sink};
302 TestHandler handler;
303 auto s = makeServer(handler, thread.getIoContext(), journal);
304 std::vector<Port> serverPort(1);
305 serverPort.back().ip = boost::asio::ip::make_address(getEnvLocalhostAddr()),
306 serverPort.back().port = 0;
307 serverPort.back().protocol.insert("http");
308 auto eps = s->ports(serverPort);
309 testRequest(eps.begin()->second);
310 testKeepalive(eps.begin()->second);
311 // s->close();
312 s = nullptr;
313 pass();
314 }
315
316 void
318 {
319 testcase("stress test");
320 struct NullHandler
321 {
322 static bool
323 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
324 {
325 return true;
326 }
327
328 static Handoff
329 onHandoff(
330 Session& session,
331 std::unique_ptr<stream_type> const& bundle,
332 http_request_type const& request,
333 boost::asio::ip::tcp::endpoint remoteAddress)
334 {
335 return Handoff{};
336 }
337
338 static Handoff
339 onHandoff(
340 Session& session,
341 http_request_type const& request,
342 boost::asio::ip::tcp::endpoint remoteAddress)
343 {
344 return Handoff{};
345 }
346
347 void
348 onRequest(Session& session)
349 {
350 }
351
352 void
353 onWSMessage(
356 {
357 }
358
359 void
360 onClose(Session& session, boost::system::error_code const&)
361 {
362 }
363
364 void
365 onStopped(Server& server)
366 {
367 }
368 };
369
370 using beast::Severity;
371 SuiteJournal journal("Server_test", *this);
372
373 NullHandler h;
374 for (int i = 0; i < 1000; ++i)
375 {
376 TestThread thread;
377 auto s = makeServer(h, thread.getIoContext(), journal);
378 std::vector<Port> serverPort(1);
379 serverPort.back().ip = boost::asio::ip::make_address(getEnvLocalhostAddr()),
380 serverPort.back().port = 0;
381 serverPort.back().protocol.insert("http");
382 s->ports(serverPort);
383 }
384 pass();
385 }
386
387 void
389 {
390 testcase("Server config - invalid options");
391 using namespace test::jtx;
392
393 std::string messages;
394
395 except([&] {
396 Env const env{
397 *this,
399 (*cfg).deprecatedClearSection(Sections::kPortRpc);
400 return cfg;
401 }),
403 });
404 BEAST_EXPECT(messages.contains("Missing 'ip' in [port_rpc]"));
405
406 except([&] {
407 Env const env{
408 *this,
410 (*cfg).deprecatedClearSection(Sections::kPortRpc);
412 return cfg;
413 }),
415 });
416 BEAST_EXPECT(messages.contains("Missing 'port' in [port_rpc]"));
417
418 except([&] {
419 Env const env{
420 *this,
422 (*cfg).deprecatedClearSection(Sections::kPortRpc);
424 (*cfg)[Sections::kPortRpc].set(Keys::kPort, "0");
425 return cfg;
426 }),
428 });
429 BEAST_EXPECT(!messages.contains("Invalid value '0' for key 'port' in [port_rpc]"));
430
431 except([&] {
432 Env const env{
433 *this,
435 (*cfg)[Sections::kServer].set(Keys::kPort, "0");
436 return cfg;
437 }),
439 });
440 BEAST_EXPECT(messages.contains("Invalid value '0' for key 'port' in [server]"));
441
442 except([&] {
443 Env const env{
444 *this,
446 (*cfg).deprecatedClearSection(Sections::kPortRpc);
448 (*cfg)[Sections::kPortRpc].set(Keys::kPort, "8081");
449 (*cfg)[Sections::kPortRpc].set(Keys::kProtocol, "");
450 return cfg;
451 }),
453 });
454 BEAST_EXPECT(messages.contains("Missing 'protocol' in [port_rpc]"));
455
456 except([&] // this creates a standard test config without the server
457 // section
458 {
459 Env const env{
460 *this,
463 cfg->overwrite(Sections::kNodeDatabase, Keys::kType, "memory");
464 cfg->overwrite(Sections::kNodeDatabase, Keys::kPath, "main");
465 cfg->deprecatedClearSection(Sections::kImportNodeDatabase);
466 cfg->legacy(Sections::kDatabasePath, "");
467 cfg->setupControl(true, true, true);
469 (*cfg)[Sections::kPortPeer].set(Keys::kPort, "8080");
470 (*cfg)[Sections::kPortPeer].set(Keys::kProtocol, "peer");
472 (*cfg)[Sections::kPortRpc].set(Keys::kPort, "8081");
473 (*cfg)[Sections::kPortRpc].set(Keys::kProtocol, "http,ws2");
476 (*cfg)[Sections::kPortWs].set(Keys::kPort, "8082");
477 (*cfg)[Sections::kPortWs].set(Keys::kProtocol, "ws");
479 return cfg;
480 }),
482 });
483 BEAST_EXPECT(messages.contains("Required section [server] is missing"));
484
485 except([&] // this creates a standard test config without some of the
486 // port sections
487 {
488 Env const env{
489 *this,
492 cfg->overwrite(Sections::kNodeDatabase, Keys::kType, "memory");
493 cfg->overwrite(Sections::kNodeDatabase, Keys::kPath, "main");
494 cfg->deprecatedClearSection(Sections::kImportNodeDatabase);
495 cfg->legacy(Sections::kDatabasePath, "");
496 cfg->setupControl(true, true, true);
499 (*cfg)[Sections::kServer].append(Sections::kPortWs);
500 return cfg;
501 }),
503 });
504 BEAST_EXPECT(messages.contains("Missing section: [port_peer]"));
505 }
506
507 void
508 run() override
509 {
510 basicTests();
511 stressTest();
513 }
514};
515
517
518} // namespace xrpl::test
T back(T... args)
Abstraction for the underlying message destination.
Definition Journal.h:51
virtual Severity threshold() const
Returns the minimum severity level this sink will report.
Sink(Sink const &sink)=default
A generic endpoint for log messages.
Definition Journal.h:38
A testsuite class.
Definition suite.h:50
bool except(F &&f, String const &reason)
Definition suite.h:433
void pass()
Record a successful test condition.
Definition suite.h:500
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:522
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
A multi-protocol server.
Definition ServerImpl.h:29
Persistent state information for a connection session.
Definition Session.h:24
virtual void close(bool graceful)=0
Close the session.
void write(std::string_view s)
Send a copy of data asynchronously.
Definition Session.h:57
virtual void complete()=0
Indicate that the response is complete.
virtual http_request_type & request()=0
Returns the current HTTP request.
TestSink(beast::unit_test::Suite &suite)
void writeAlways(beast::Severity level, std::string const &text) override
Bypass filter and write text to the sink at the specified severity.
void write(beast::Severity level, std::string const &text) override
Write text to the sink at the specified severity.
beast::unit_test::Suite & suite_
boost::asio::io_context ioContext_
boost::asio::io_context & getIoContext()
std::optional< boost::asio::executor_work_guard< boost::asio::io_context::executor_type > > work_
bool write(SyncWriteStream &s, std::string const &text)
bool connect(Socket &s, Socket::endpoint_type const &ep)
void testRequest(boost::asio::ip::tcp::endpoint const &ep)
void testKeepalive(boost::asio::ip::tcp::endpoint const &ep)
bool expectRead(SyncReadStream &s, std::string const &match)
void run() override
Runs the suite.
A transaction testing environment.
Definition Env.h:143
T endl(T... args)
T make_unique(T... args)
bool isKeepAlive(boost::beast::http::message< IsRequest, Body, Fields > const &m)
Definition rfc2616.h:361
Severity
Severity level / threshold of a Journal message.
Definition Journal.h:11
STL namespace.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:28
boost::beast::tcp_stream socket_type
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
char const * getEnvLocalhostAddr()
Definition envconfig.h:10
boost::beast::ssl_stream< socket_type > stream_type
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::unique_ptr< Server > makeServer(Handler &handler, boost::asio::io_context &ioContext, beast::Journal journal)
Create the HTTP server using the specified handler.
Definition Server.h:15
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
T resize(T... args)
T sleep_for(T... args)
Used to indicate the result of a server connection handoff.
Definition Handoff.h:18
static constexpr auto kProtocol
Definition Constants.h:144
static constexpr auto kAdmin
Definition Constants.h:85
static constexpr auto kPort
Definition Constants.h:142
static constexpr auto kType
Definition Constants.h:170
static constexpr auto kIp
Definition Constants.h:113
static constexpr auto kPath
Definition Constants.h:140
static constexpr auto kPortWs
Definition Constants.h:47
static constexpr auto kImportNodeDatabase
Definition Constants.h:20
static constexpr auto kServer
Definition Constants.h:55
static constexpr auto kPortRpc
Definition Constants.h:46
static constexpr auto kNodeDatabase
Definition Constants.h:31
static constexpr auto kDatabasePath
Definition Constants.h:13
static constexpr auto kPortPeer
Definition Constants.h:45
static void onRequest(Session &session)
static Handoff onHandoff(Session &session, std::unique_ptr< stream_type > const &bundle, http_request_type const &request, boost::asio::ip::tcp::endpoint remoteAddress)
void onClose(Session &session, boost::system::error_code const &)
static bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
static Handoff onHandoff(Session &session, http_request_type const &request, boost::asio::ip::tcp::endpoint remoteAddress)
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &)
T what(T... args)