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 <xrpld/core/ConfigSections.h>
21#include <xrpl/basics/make_SSLContext.h>
22#include <xrpl/beast/rfc2616.h>
23#include <xrpl/beast/unit_test.h>
24#include <xrpl/server/Server.h>
25#include <xrpl/server/Session.h>
26
27#include <test/jtx.h>
28#include <test/jtx/CaptureLogs.h>
29#include <test/jtx/envconfig.h>
30#include <test/unit_test/SuiteJournal.h>
31
32#include <boost/asio.hpp>
33#include <boost/beast/core/tcp_stream.hpp>
34#include <boost/beast/ssl/ssl_stream.hpp>
35#include <boost/utility/in_place_factory.hpp>
36
37#include <chrono>
38#include <optional>
39#include <stdexcept>
40#include <thread>
41
42namespace ripple {
43namespace test {
44
45using socket_type = boost::beast::tcp_stream;
46using stream_type = boost::beast::ssl_stream<socket_type>;
47
49{
50public:
52 {
53 private:
54 boost::asio::io_service io_service_;
57
58 public:
60 : work_(std::in_place, std::ref(io_service_))
61 , thread_([&]() { this->io_service_.run(); })
62 {
63 }
64
66 {
67 work_.reset();
68 thread_.join();
69 }
70
71 boost::asio::io_service&
73 {
74 return io_service_;
75 }
76 };
77
78 //--------------------------------------------------------------------------
79
81 {
83
84 public:
86 : Sink(beast::severities::kWarning, false), suite_(suite)
87 {
88 }
89
90 void
92 override
93 {
94 if (level < threshold())
95 return;
96
97 suite_.log << text << std::endl;
98 }
99
100 void
102 override
103 {
104 suite_.log << text << std::endl;
105 }
106 };
107
108 //--------------------------------------------------------------------------
109
111 {
112 bool
113 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
114 {
115 return true;
116 }
117
118 Handoff
120 Session& session,
122 http_request_type&& request,
123 boost::asio::ip::tcp::endpoint remote_address)
124 {
125 return Handoff{};
126 }
127
128 Handoff
130 Session& session,
131 http_request_type&& request,
132 boost::asio::ip::tcp::endpoint remote_address)
133 {
134 return Handoff{};
135 }
136
137 void
139 {
140 session.write(std::string("Hello, world!\n"));
142 session.complete();
143 else
144 session.close(true);
145 }
146
147 void
151 {
152 }
153
154 void
155 onClose(Session& session, boost::system::error_code const&)
156 {
157 }
158
159 void
161 {
162 }
163 };
164
165 //--------------------------------------------------------------------------
166
167 // Connect to an address
168 template <class Socket>
169 bool
170 connect(Socket& s, typename Socket::endpoint_type const& ep)
171 {
172 try
173 {
174 s.connect(ep);
175 pass();
176 return true;
177 }
178 catch (std::exception const& e)
179 {
180 fail(e.what());
181 }
182
183 return false;
184 }
185
186 // Write a string to the stream
187 template <class SyncWriteStream>
188 bool
189 write(SyncWriteStream& s, std::string const& text)
190 {
191 try
192 {
193 boost::asio::write(s, boost::asio::buffer(text));
194 pass();
195 return true;
196 }
197 catch (std::exception const& e)
198 {
199 fail(e.what());
200 }
201 return false;
202 }
203
204 // Expect that reading the stream produces a matching string
205 template <class SyncReadStream>
206 bool
207 expect_read(SyncReadStream& s, std::string const& match)
208 {
209 boost::asio::streambuf b(1000); // limit on read
210 try
211 {
212 auto const n = boost::asio::read_until(s, b, '\n');
213 if (BEAST_EXPECT(n == match.size()))
214 {
215 std::string got;
216 got.resize(n);
217 boost::asio::buffer_copy(
218 boost::asio::buffer(&got[0], n), b.data());
219 return BEAST_EXPECT(got == match);
220 }
221 }
222 catch (std::length_error const& e)
223 {
224 fail(e.what());
225 }
226 catch (std::exception const& e)
227 {
228 fail(e.what());
229 }
230 return false;
231 }
232
233 void
234 test_request(boost::asio::ip::tcp::endpoint const& ep)
235 {
236 boost::asio::io_service 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: close\r\n"
247 "\r\n"))
248 return;
249
250 if (!expect_read(s, "Hello, world!\n"))
251 return;
252
253 boost::system::error_code ec;
254 s.shutdown(socket::shutdown_both, ec);
255
257 }
258
259 void
260 test_keepalive(boost::asio::ip::tcp::endpoint const& ep)
261 {
262 boost::asio::io_service ios;
263 using socket = boost::asio::ip::tcp::socket;
264 socket s(ios);
265
266 if (!connect(s, ep))
267 return;
268
269 if (!write(
270 s,
271 "GET / HTTP/1.1\r\n"
272 "Connection: Keep-Alive\r\n"
273 "\r\n"))
274 return;
275
276 if (!expect_read(s, "Hello, world!\n"))
277 return;
278
279 if (!write(
280 s,
281 "GET / HTTP/1.1\r\n"
282 "Connection: close\r\n"
283 "\r\n"))
284 return;
285
286 if (!expect_read(s, "Hello, world!\n"))
287 return;
288
289 boost::system::error_code ec;
290 s.shutdown(socket::shutdown_both, ec);
291 }
292
293 void
295 {
296 testcase("Basic client/server");
297 TestSink sink{*this};
299 sink.threshold(beast::severities::Severity::kAll);
300 beast::Journal journal{sink};
301 TestHandler handler;
302 auto s = make_Server(handler, thread.get_io_service(), journal);
303 std::vector<Port> serverPort(1);
304 serverPort.back().ip =
305 beast::IP::Address::from_string(getEnvLocalhostAddr()),
306 serverPort.back().port = 0;
307 serverPort.back().protocol.insert("http");
308 auto eps = s->ports(serverPort);
309 test_request(eps.begin()->second);
310 test_keepalive(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 bool
323 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
324 {
325 return true;
326 }
327
328 Handoff
329 onHandoff(
330 Session& session,
332 http_request_type&& request,
333 boost::asio::ip::tcp::endpoint remote_address)
334 {
335 return Handoff{};
336 }
337
338 Handoff
339 onHandoff(
340 Session& session,
341 http_request_type&& request,
342 boost::asio::ip::tcp::endpoint remote_address)
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 namespace beast::severities;
371 SuiteJournal journal("Server_test", *this);
372
373 NullHandler h;
374 for (int i = 0; i < 1000; ++i)
375 {
377 auto s = make_Server(h, thread.get_io_service(), journal);
378 std::vector<Port> serverPort(1);
379 serverPort.back().ip =
380 beast::IP::Address::from_string(getEnvLocalhostAddr()),
381 serverPort.back().port = 0;
382 serverPort.back().protocol.insert("http");
383 s->ports(serverPort);
384 }
385 pass();
386 }
387
388 void
390 {
391 testcase("Server config - invalid options");
392 using namespace test::jtx;
393
394 std::string messages;
395
396 except([&] {
397 Env env{
398 *this,
400 (*cfg).deprecatedClearSection("port_rpc");
401 return cfg;
402 }),
403 std::make_unique<CaptureLogs>(&messages)};
404 });
405 BEAST_EXPECT(
406 messages.find("Missing 'ip' in [port_rpc]") != std::string::npos);
407
408 except([&] {
409 Env env{
410 *this,
412 (*cfg).deprecatedClearSection("port_rpc");
413 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
414 return cfg;
415 }),
416 std::make_unique<CaptureLogs>(&messages)};
417 });
418 BEAST_EXPECT(
419 messages.find("Missing 'port' in [port_rpc]") != std::string::npos);
420
421 except([&] {
422 Env env{
423 *this,
425 (*cfg).deprecatedClearSection("port_rpc");
426 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
427 (*cfg)["port_rpc"].set("port", "0");
428 return cfg;
429 }),
430 std::make_unique<CaptureLogs>(&messages)};
431 });
432 BEAST_EXPECT(
433 messages.find("Invalid value '0' for key 'port' in [port_rpc]") ==
434 std::string::npos);
435
436 except([&] {
437 Env env{
438 *this,
440 (*cfg)["server"].set("port", "0");
441 return cfg;
442 }),
443 std::make_unique<CaptureLogs>(&messages)};
444 });
445 BEAST_EXPECT(
446 messages.find("Invalid value '0' for key 'port' in [server]") !=
447 std::string::npos);
448
449 except([&] {
450 Env env{
451 *this,
453 (*cfg).deprecatedClearSection("port_rpc");
454 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
455 (*cfg)["port_rpc"].set("port", "8081");
456 (*cfg)["port_rpc"].set("protocol", "");
457 return cfg;
458 }),
459 std::make_unique<CaptureLogs>(&messages)};
460 });
461 BEAST_EXPECT(
462 messages.find("Missing 'protocol' in [port_rpc]") !=
463 std::string::npos);
464
465 except(
466 [&] // this creates a standard test config without the server
467 // section
468 {
469 Env env{
470 *this,
472 cfg = std::make_unique<Config>();
473 cfg->overwrite(
474 ConfigSection::nodeDatabase(), "type", "memory");
475 cfg->overwrite(
476 ConfigSection::nodeDatabase(), "path", "main");
477 cfg->deprecatedClearSection(
479 cfg->legacy("database_path", "");
480 cfg->setupControl(true, true, true);
481 (*cfg)["port_peer"].set("ip", getEnvLocalhostAddr());
482 (*cfg)["port_peer"].set("port", "8080");
483 (*cfg)["port_peer"].set("protocol", "peer");
484 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
485 (*cfg)["port_rpc"].set("port", "8081");
486 (*cfg)["port_rpc"].set("protocol", "http,ws2");
487 (*cfg)["port_rpc"].set("admin", getEnvLocalhostAddr());
488 (*cfg)["port_ws"].set("ip", getEnvLocalhostAddr());
489 (*cfg)["port_ws"].set("port", "8082");
490 (*cfg)["port_ws"].set("protocol", "ws");
491 (*cfg)["port_ws"].set("admin", getEnvLocalhostAddr());
492 return cfg;
493 }),
494 std::make_unique<CaptureLogs>(&messages)};
495 });
496 BEAST_EXPECT(
497 messages.find("Required section [server] is missing") !=
498 std::string::npos);
499
500 except([&] // this creates a standard test config without some of the
501 // port sections
502 {
503 Env env{
504 *this,
506 cfg = std::make_unique<Config>();
507 cfg->overwrite(
508 ConfigSection::nodeDatabase(), "type", "memory");
509 cfg->overwrite(
510 ConfigSection::nodeDatabase(), "path", "main");
511 cfg->deprecatedClearSection(
513 cfg->legacy("database_path", "");
514 cfg->setupControl(true, true, true);
515 (*cfg)["server"].append("port_peer");
516 (*cfg)["server"].append("port_rpc");
517 (*cfg)["server"].append("port_ws");
518 return cfg;
519 }),
520 std::make_unique<CaptureLogs>(&messages)};
521 });
522 BEAST_EXPECT(
523 messages.find("Missing section: [port_peer]") != std::string::npos);
524 }
525
526 void
527 run() override
528 {
529 basicTests();
530 stressTest();
532 }
533};
534
535BEAST_DEFINE_TESTSUITE(Server, http, ripple);
536
537} // namespace test
538} // namespace ripple
T back(T... args)
Abstraction for the underlying message destination.
Definition: Journal.h:75
virtual Severity threshold() const
Returns the minimum severity level this sink will report.
A generic endpoint for log messages.
Definition: Journal.h:59
A testsuite class.
Definition: suite.h:53
log_os< char > log
Logging output stream.
Definition: suite.h:150
void pass()
Record a successful test condition.
Definition: suite.h:509
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:153
friend class thread
Definition: suite.h:305
bool except(F &&f, String const &reason)
Definition: suite.h:446
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:531
A multi-protocol server.
Definition: ServerImpl.h:48
Persistent state information for a connection session.
Definition: Session.h:41
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:74
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)
Definition: Server_test.cpp:85
void write(beast::severities::Severity level, std::string const &text) override
Write text to the sink at the specified severity.
Definition: Server_test.cpp:91
beast::unit_test::suite & suite_
Definition: Server_test.cpp:82
boost::asio::io_service & get_io_service()
Definition: Server_test.cpp:72
std::optional< boost::asio::io_service::work > work_
Definition: Server_test.cpp:55
boost::asio::io_service io_service_
Definition: Server_test.cpp:54
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:117
T endl(T... args)
T find(T... args)
T join(T... args)
bool is_keep_alive(boost::beast::http::message< isRequest, Body, Fields > const &m)
Definition: rfc2616.h:391
A namespace for easy access to logging severity values.
Definition: Journal.h:29
Severity
Severity level / threshold of a Journal message.
Definition: Journal.h:31
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
boost::beast::tcp_stream socket_type
Definition: Server_test.cpp:45
const char * getEnvLocalhostAddr()
Definition: envconfig.h:36
boost::beast::ssl_stream< socket_type > stream_type
Definition: Server_test.cpp:46
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition: Handoff.h:31
std::unique_ptr< Server > make_Server(Handler &handler, boost::asio::io_service &io_service, beast::Journal journal)
Create the HTTP server using the specified handler.
Definition: Server.h:34
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:38
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)