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