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