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