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/beast/core/tcp_stream.hpp>
35#include <boost/beast/ssl/ssl_stream.hpp>
36#include <boost/utility/in_place_factory.hpp>
37
38#include <chrono>
39#include <optional>
40#include <stdexcept>
41#include <thread>
42
43namespace ripple {
44namespace test {
45
46using socket_type = boost::beast::tcp_stream;
47using stream_type = boost::beast::ssl_stream<socket_type>;
48
50{
51public:
53 {
54 private:
55 boost::asio::io_service io_service_;
58
59 public:
61 : work_(std::in_place, std::ref(io_service_))
62 , thread_([&]() { this->io_service_.run(); })
63 {
64 }
65
67 {
68 work_.reset();
69 thread_.join();
70 }
71
72 boost::asio::io_service&
74 {
75 return io_service_;
76 }
77 };
78
79 //--------------------------------------------------------------------------
80
82 {
84
85 public:
87 : Sink(beast::severities::kWarning, false), suite_(suite)
88 {
89 }
90
91 void
93 override
94 {
95 if (level < threshold())
96 return;
97
98 suite_.log << text << std::endl;
99 }
100
101 void
103 override
104 {
105 suite_.log << text << std::endl;
106 }
107 };
108
109 //--------------------------------------------------------------------------
110
112 {
113 bool
114 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
115 {
116 return true;
117 }
118
119 Handoff
121 Session& session,
123 http_request_type&& request,
124 boost::asio::ip::tcp::endpoint remote_address)
125 {
126 return Handoff{};
127 }
128
129 Handoff
131 Session& session,
132 http_request_type&& request,
133 boost::asio::ip::tcp::endpoint remote_address)
134 {
135 return Handoff{};
136 }
137
138 void
140 {
141 session.write(std::string("Hello, world!\n"));
143 session.complete();
144 else
145 session.close(true);
146 }
147
148 void
152 {
153 }
154
155 void
156 onClose(Session& session, boost::system::error_code const&)
157 {
158 }
159
160 void
162 {
163 }
164 };
165
166 //--------------------------------------------------------------------------
167
168 // Connect to an address
169 template <class Socket>
170 bool
171 connect(Socket& s, typename Socket::endpoint_type const& ep)
172 {
173 try
174 {
175 s.connect(ep);
176 pass();
177 return true;
178 }
179 catch (std::exception const& e)
180 {
181 fail(e.what());
182 }
183
184 return false;
185 }
186
187 // Write a string to the stream
188 template <class SyncWriteStream>
189 bool
190 write(SyncWriteStream& s, std::string const& text)
191 {
192 try
193 {
194 boost::asio::write(s, boost::asio::buffer(text));
195 pass();
196 return true;
197 }
198 catch (std::exception const& e)
199 {
200 fail(e.what());
201 }
202 return false;
203 }
204
205 // Expect that reading the stream produces a matching string
206 template <class SyncReadStream>
207 bool
208 expect_read(SyncReadStream& s, std::string const& match)
209 {
210 boost::asio::streambuf b(1000); // limit on read
211 try
212 {
213 auto const n = boost::asio::read_until(s, b, '\n');
214 if (BEAST_EXPECT(n == match.size()))
215 {
216 std::string got;
217 got.resize(n);
218 boost::asio::buffer_copy(
219 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 test_request(boost::asio::ip::tcp::endpoint const& ep)
236 {
237 boost::asio::io_service 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 (!expect_read(s, "Hello, world!\n"))
252 return;
253
254 boost::system::error_code ec;
255 s.shutdown(socket::shutdown_both, ec);
256
258 }
259
260 void
261 test_keepalive(boost::asio::ip::tcp::endpoint const& ep)
262 {
263 boost::asio::io_service 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 (!expect_read(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 (!expect_read(s, "Hello, world!\n"))
288 return;
289
290 boost::system::error_code ec;
291 s.shutdown(socket::shutdown_both, ec);
292 }
293
294 void
296 {
297 testcase("Basic client/server");
298 TestSink sink{*this};
300 sink.threshold(beast::severities::Severity::kAll);
301 beast::Journal journal{sink};
302 TestHandler handler;
303 auto s = make_Server(handler, thread.get_io_service(), journal);
304 std::vector<Port> serverPort(1);
305 serverPort.back().ip =
306 beast::IP::Address::from_string(getEnvLocalhostAddr()),
307 serverPort.back().port = 0;
308 serverPort.back().protocol.insert("http");
309 auto eps = s->ports(serverPort);
310 test_request(eps.begin()->second);
311 test_keepalive(eps.begin()->second);
312 // s->close();
313 s = nullptr;
314 pass();
315 }
316
317 void
319 {
320 testcase("stress test");
321 struct NullHandler
322 {
323 bool
324 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
325 {
326 return true;
327 }
328
329 Handoff
330 onHandoff(
331 Session& session,
333 http_request_type&& request,
334 boost::asio::ip::tcp::endpoint remote_address)
335 {
336 return Handoff{};
337 }
338
339 Handoff
340 onHandoff(
341 Session& session,
342 http_request_type&& request,
343 boost::asio::ip::tcp::endpoint remote_address)
344 {
345 return Handoff{};
346 }
347
348 void
349 onRequest(Session& session)
350 {
351 }
352
353 void
354 onWSMessage(
357 {
358 }
359
360 void
361 onClose(Session& session, boost::system::error_code const&)
362 {
363 }
364
365 void
366 onStopped(Server& server)
367 {
368 }
369 };
370
371 using namespace beast::severities;
372 SuiteJournal journal("Server_test", *this);
373
374 NullHandler h;
375 for (int i = 0; i < 1000; ++i)
376 {
378 auto s = make_Server(h, thread.get_io_service(), journal);
379 std::vector<Port> serverPort(1);
380 serverPort.back().ip =
381 beast::IP::Address::from_string(getEnvLocalhostAddr()),
382 serverPort.back().port = 0;
383 serverPort.back().protocol.insert("http");
384 s->ports(serverPort);
385 }
386 pass();
387 }
388
389 void
391 {
392 testcase("Server config - invalid options");
393 using namespace test::jtx;
394
395 std::string messages;
396
397 except([&] {
398 Env env{
399 *this,
401 (*cfg).deprecatedClearSection("port_rpc");
402 return cfg;
403 }),
404 std::make_unique<CaptureLogs>(&messages)};
405 });
406 BEAST_EXPECT(
407 messages.find("Missing 'ip' in [port_rpc]") != std::string::npos);
408
409 except([&] {
410 Env env{
411 *this,
413 (*cfg).deprecatedClearSection("port_rpc");
414 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
415 return cfg;
416 }),
417 std::make_unique<CaptureLogs>(&messages)};
418 });
419 BEAST_EXPECT(
420 messages.find("Missing 'port' in [port_rpc]") != std::string::npos);
421
422 except([&] {
423 Env env{
424 *this,
426 (*cfg).deprecatedClearSection("port_rpc");
427 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
428 (*cfg)["port_rpc"].set("port", "0");
429 return cfg;
430 }),
431 std::make_unique<CaptureLogs>(&messages)};
432 });
433 BEAST_EXPECT(
434 messages.find("Invalid value '0' for key 'port' in [port_rpc]") ==
435 std::string::npos);
436
437 except([&] {
438 Env env{
439 *this,
441 (*cfg)["server"].set("port", "0");
442 return cfg;
443 }),
444 std::make_unique<CaptureLogs>(&messages)};
445 });
446 BEAST_EXPECT(
447 messages.find("Invalid value '0' for key 'port' in [server]") !=
448 std::string::npos);
449
450 except([&] {
451 Env env{
452 *this,
454 (*cfg).deprecatedClearSection("port_rpc");
455 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
456 (*cfg)["port_rpc"].set("port", "8081");
457 (*cfg)["port_rpc"].set("protocol", "");
458 return cfg;
459 }),
460 std::make_unique<CaptureLogs>(&messages)};
461 });
462 BEAST_EXPECT(
463 messages.find("Missing 'protocol' in [port_rpc]") !=
464 std::string::npos);
465
466 except(
467 [&] // this creates a standard test config without the server
468 // section
469 {
470 Env env{
471 *this,
473 cfg = std::make_unique<Config>();
474 cfg->overwrite(
475 ConfigSection::nodeDatabase(), "type", "memory");
476 cfg->overwrite(
477 ConfigSection::nodeDatabase(), "path", "main");
478 cfg->deprecatedClearSection(
480 cfg->legacy("database_path", "");
481 cfg->setupControl(true, true, true);
482 (*cfg)["port_peer"].set("ip", getEnvLocalhostAddr());
483 (*cfg)["port_peer"].set("port", "8080");
484 (*cfg)["port_peer"].set("protocol", "peer");
485 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
486 (*cfg)["port_rpc"].set("port", "8081");
487 (*cfg)["port_rpc"].set("protocol", "http,ws2");
488 (*cfg)["port_rpc"].set("admin", getEnvLocalhostAddr());
489 (*cfg)["port_ws"].set("ip", getEnvLocalhostAddr());
490 (*cfg)["port_ws"].set("port", "8082");
491 (*cfg)["port_ws"].set("protocol", "ws");
492 (*cfg)["port_ws"].set("admin", getEnvLocalhostAddr());
493 return cfg;
494 }),
495 std::make_unique<CaptureLogs>(&messages)};
496 });
497 BEAST_EXPECT(
498 messages.find("Required section [server] is missing") !=
499 std::string::npos);
500
501 except([&] // this creates a standard test config without some of the
502 // port sections
503 {
504 Env env{
505 *this,
507 cfg = std::make_unique<Config>();
508 cfg->overwrite(
509 ConfigSection::nodeDatabase(), "type", "memory");
510 cfg->overwrite(
511 ConfigSection::nodeDatabase(), "path", "main");
512 cfg->deprecatedClearSection(
514 cfg->legacy("database_path", "");
515 cfg->setupControl(true, true, true);
516 (*cfg)["server"].append("port_peer");
517 (*cfg)["server"].append("port_rpc");
518 (*cfg)["server"].append("port_ws");
519 return cfg;
520 }),
521 std::make_unique<CaptureLogs>(&messages)};
522 });
523 BEAST_EXPECT(
524 messages.find("Missing section: [port_peer]") != std::string::npos);
525 }
526
527 void
528 run() override
529 {
530 basicTests();
531 stressTest();
533 }
534};
535
536BEAST_DEFINE_TESTSUITE(Server, http, ripple);
537
538} // namespace test
539} // 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:48
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)
Definition: Server_test.cpp:86
void write(beast::severities::Severity level, std::string const &text) override
Write text to the sink at the specified severity.
Definition: Server_test.cpp:92
beast::unit_test::suite & suite_
Definition: Server_test.cpp:83
boost::asio::io_service & get_io_service()
Definition: Server_test.cpp:73
std::optional< boost::asio::io_service::work > work_
Definition: Server_test.cpp:56
boost::asio::io_service io_service_
Definition: Server_test.cpp:55
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 join(T... args)
bool is_keep_alive(boost::beast::http::message< isRequest, Body, Fields > const &m)
Definition: rfc2616.h:388
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
Definition: Server_test.cpp:46
boost::beast::ssl_stream< socket_type > stream_type
Definition: Server_test.cpp:47
char const * getEnvLocalhostAddr()
Definition: envconfig.h:36
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:33
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:35
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)