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_;
40
41 public:
43 : work_(std::in_place, boost::asio::make_work_guard(io_context_))
44 , thread_([&]() { this->io_context_.run(); })
45 {
46 }
47
49 {
50 work_.reset();
51 thread_.join();
52 }
53
54 boost::asio::io_context&
56 {
57 return io_context_;
58 }
59 };
60
61 //--------------------------------------------------------------------------
62
64 {
66
67 public:
69 {
70 }
71
72 void
73 write(beast::severities::Severity level, std::string const& text) override
74 {
75 if (level < threshold())
76 return;
77
78 suite_.log << text << std::endl;
79 }
80
81 void
83 {
84 suite_.log << text << std::endl;
85 }
86 };
87
88 //--------------------------------------------------------------------------
89
91 {
92 bool
93 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
94 {
95 return true;
96 }
97
100 Session& session,
102 http_request_type&& request,
103 boost::asio::ip::tcp::endpoint remote_address)
104 {
105 return Handoff{};
106 }
107
108 Handoff
109 onHandoff(Session& session, http_request_type&& request, boost::asio::ip::tcp::endpoint remote_address)
110 {
111 return Handoff{};
112 }
113
114 void
116 {
117 session.write(std::string("Hello, world!\n"));
119 session.complete();
120 else
121 session.close(true);
122 }
123
124 void
128
129 void
130 onClose(Session& session, boost::system::error_code const&)
131 {
132 }
133
134 void
136 {
137 }
138 };
139
140 //--------------------------------------------------------------------------
141
142 // Connect to an address
143 template <class Socket>
144 bool
145 connect(Socket& s, typename Socket::endpoint_type const& ep)
146 {
147 try
148 {
149 s.connect(ep);
150 pass();
151 return true;
152 }
153 catch (std::exception const& e)
154 {
155 fail(e.what());
156 }
157
158 return false;
159 }
160
161 // Write a string to the stream
162 template <class SyncWriteStream>
163 bool
164 write(SyncWriteStream& s, std::string const& text)
165 {
166 try
167 {
168 boost::asio::write(s, boost::asio::buffer(text));
169 pass();
170 return true;
171 }
172 catch (std::exception const& e)
173 {
174 fail(e.what());
175 }
176 return false;
177 }
178
179 // Expect that reading the stream produces a matching string
180 template <class SyncReadStream>
181 bool
182 expect_read(SyncReadStream& s, std::string const& match)
183 {
184 boost::asio::streambuf b(1000); // limit on read
185 try
186 {
187 auto const n = boost::asio::read_until(s, b, '\n');
188 if (BEAST_EXPECT(n == match.size()))
189 {
190 std::string got;
191 got.resize(n);
192 boost::asio::buffer_copy(boost::asio::buffer(&got[0], n), b.data());
193 return BEAST_EXPECT(got == match);
194 }
195 }
196 catch (std::length_error const& e)
197 {
198 fail(e.what());
199 }
200 catch (std::exception const& e)
201 {
202 fail(e.what());
203 }
204 return false;
205 }
206
207 void
208 test_request(boost::asio::ip::tcp::endpoint const& ep)
209 {
210 boost::asio::io_context ios;
211 using socket = boost::asio::ip::tcp::socket;
212 socket s(ios);
213
214 if (!connect(s, ep))
215 return;
216
217 if (!write(
218 s,
219 "GET / HTTP/1.1\r\n"
220 "Connection: close\r\n"
221 "\r\n"))
222 return;
223
224 if (!expect_read(s, "Hello, world!\n"))
225 return;
226
227 boost::system::error_code ec;
228 s.shutdown(socket::shutdown_both, ec);
229
231 }
232
233 void
234 test_keepalive(boost::asio::ip::tcp::endpoint const& ep)
235 {
236 boost::asio::io_context ios;
237 using socket = boost::asio::ip::tcp::socket;
238 socket s(ios);
239
240 if (!connect(s, ep))
241 return;
242
243 if (!write(
244 s,
245 "GET / HTTP/1.1\r\n"
246 "Connection: Keep-Alive\r\n"
247 "\r\n"))
248 return;
249
250 if (!expect_read(s, "Hello, world!\n"))
251 return;
252
253 if (!write(
254 s,
255 "GET / HTTP/1.1\r\n"
256 "Connection: close\r\n"
257 "\r\n"))
258 return;
259
260 if (!expect_read(s, "Hello, world!\n"))
261 return;
262
263 boost::system::error_code ec;
264 s.shutdown(socket::shutdown_both, ec);
265 }
266
267 void
269 {
270 testcase("Basic client/server");
271 TestSink sink{*this};
273 sink.threshold(beast::severities::Severity::kAll);
274 beast::Journal journal{sink};
275 TestHandler handler;
276 auto s = make_Server(handler, thread.get_io_context(), journal);
277 std::vector<Port> serverPort(1);
278 serverPort.back().ip = boost::asio::ip::make_address(getEnvLocalhostAddr()), serverPort.back().port = 0;
279 serverPort.back().protocol.insert("http");
280 auto eps = s->ports(serverPort);
281 test_request(eps.begin()->second);
282 test_keepalive(eps.begin()->second);
283 // s->close();
284 s = nullptr;
285 pass();
286 }
287
288 void
290 {
291 testcase("stress test");
292 struct NullHandler
293 {
294 bool
295 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
296 {
297 return true;
298 }
299
300 Handoff
301 onHandoff(
302 Session& session,
304 http_request_type&& request,
305 boost::asio::ip::tcp::endpoint remote_address)
306 {
307 return Handoff{};
308 }
309
310 Handoff
311 onHandoff(Session& session, http_request_type&& request, boost::asio::ip::tcp::endpoint remote_address)
312 {
313 return Handoff{};
314 }
315
316 void
317 onRequest(Session& session)
318 {
319 }
320
321 void
322 onWSMessage(std::shared_ptr<WSSession> session, std::vector<boost::asio::const_buffer> const& buffers)
323 {
324 }
325
326 void
327 onClose(Session& session, boost::system::error_code const&)
328 {
329 }
330
331 void
332 onStopped(Server& server)
333 {
334 }
335 };
336
337 using namespace beast::severities;
338 SuiteJournal journal("Server_test", *this);
339
340 NullHandler h;
341 for (int i = 0; i < 1000; ++i)
342 {
344 auto s = make_Server(h, thread.get_io_context(), journal);
345 std::vector<Port> serverPort(1);
346 serverPort.back().ip = boost::asio::ip::make_address(getEnvLocalhostAddr()), serverPort.back().port = 0;
347 serverPort.back().protocol.insert("http");
348 s->ports(serverPort);
349 }
350 pass();
351 }
352
353 void
355 {
356 testcase("Server config - invalid options");
357 using namespace test::jtx;
358
359 std::string messages;
360
361 except([&] {
362 Env env{
363 *this,
365 (*cfg).deprecatedClearSection("port_rpc");
366 return cfg;
367 }),
369 });
370 BEAST_EXPECT(messages.find("Missing 'ip' in [port_rpc]") != std::string::npos);
371
372 except([&] {
373 Env env{
374 *this,
376 (*cfg).deprecatedClearSection("port_rpc");
377 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
378 return cfg;
379 }),
381 });
382 BEAST_EXPECT(messages.find("Missing 'port' in [port_rpc]") != std::string::npos);
383
384 except([&] {
385 Env env{
386 *this,
388 (*cfg).deprecatedClearSection("port_rpc");
389 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
390 (*cfg)["port_rpc"].set("port", "0");
391 return cfg;
392 }),
394 });
395 BEAST_EXPECT(messages.find("Invalid value '0' for key 'port' in [port_rpc]") == std::string::npos);
396
397 except([&] {
398 Env env{
399 *this,
401 (*cfg)["server"].set("port", "0");
402 return cfg;
403 }),
405 });
406 BEAST_EXPECT(messages.find("Invalid value '0' for key 'port' in [server]") != std::string::npos);
407
408 except([&] {
409 Env env{
410 *this,
412 (*cfg).deprecatedClearSection("port_rpc");
413 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
414 (*cfg)["port_rpc"].set("port", "8081");
415 (*cfg)["port_rpc"].set("protocol", "");
416 return cfg;
417 }),
419 });
420 BEAST_EXPECT(messages.find("Missing 'protocol' in [port_rpc]") != std::string::npos);
421
422 except([&] // this creates a standard test config without the server
423 // section
424 {
425 Env env{
426 *this,
429 cfg->overwrite(ConfigSection::nodeDatabase(), "type", "memory");
430 cfg->overwrite(ConfigSection::nodeDatabase(), "path", "main");
431 cfg->deprecatedClearSection(ConfigSection::importNodeDatabase());
432 cfg->legacy("database_path", "");
433 cfg->setupControl(true, true, true);
434 (*cfg)["port_peer"].set("ip", getEnvLocalhostAddr());
435 (*cfg)["port_peer"].set("port", "8080");
436 (*cfg)["port_peer"].set("protocol", "peer");
437 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
438 (*cfg)["port_rpc"].set("port", "8081");
439 (*cfg)["port_rpc"].set("protocol", "http,ws2");
440 (*cfg)["port_rpc"].set("admin", getEnvLocalhostAddr());
441 (*cfg)["port_ws"].set("ip", getEnvLocalhostAddr());
442 (*cfg)["port_ws"].set("port", "8082");
443 (*cfg)["port_ws"].set("protocol", "ws");
444 (*cfg)["port_ws"].set("admin", getEnvLocalhostAddr());
445 return cfg;
446 }),
448 });
449 BEAST_EXPECT(messages.find("Required section [server] is missing") != std::string::npos);
450
451 except([&] // this creates a standard test config without some of the
452 // port sections
453 {
454 Env env{
455 *this,
458 cfg->overwrite(ConfigSection::nodeDatabase(), "type", "memory");
459 cfg->overwrite(ConfigSection::nodeDatabase(), "path", "main");
460 cfg->deprecatedClearSection(ConfigSection::importNodeDatabase());
461 cfg->legacy("database_path", "");
462 cfg->setupControl(true, true, true);
463 (*cfg)["server"].append("port_peer");
464 (*cfg)["server"].append("port_rpc");
465 (*cfg)["server"].append("port_ws");
466 return cfg;
467 }),
469 });
470 BEAST_EXPECT(messages.find("Missing section: [port_peer]") != std::string::npos);
471 }
472
473 void
474 run() override
475 {
476 basicTests();
477 stressTest();
479 }
480};
481
482BEAST_DEFINE_TESTSUITE(Server, server, xrpl);
483
484} // namespace test
485} // namespace xrpl
T back(T... args)
Abstraction for the underlying message destination.
Definition Journal.h:57
virtual Severity threshold() const
Returns the minimum severity level this sink will report.
A generic endpoint for log messages.
Definition Journal.h:41
A testsuite class.
Definition suite.h:52
log_os< char > log
Logging output stream.
Definition suite.h:145
void pass()
Record a successful test condition.
Definition suite.h:495
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:148
friend class thread
Definition suite.h:296
bool except(F &&f, String const &reason)
Definition suite.h:432
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:517
A multi-protocol server.
Definition ServerImpl.h:30
Persistent state information for a connection session.
Definition Session.h:24
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:57
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:98
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:357
A namespace for easy access to logging severity values.
Definition Journal.h:11
Severity
Severity level / threshold of a Journal message.
Definition Journal.h:13
STL namespace.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
boost::beast::tcp_stream socket_type
char const * getEnvLocalhostAddr()
Definition envconfig.h:17
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:6
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:13
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:16
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:19
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint remote_address)
void onClose(Session &session, boost::system::error_code const &)
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
Handoff onHandoff(Session &session, http_request_type &&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)