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
101 //--------------------------------------------------------------------------
102
104 {
105 bool
106 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
107 {
108 return true;
109 }
110
111 Handoff
113 Session& session,
115 http_request_type&& request,
116 boost::asio::ip::tcp::endpoint remote_address)
117 {
118 return Handoff{};
119 }
120
121 Handoff
123 Session& session,
124 http_request_type&& request,
125 boost::asio::ip::tcp::endpoint remote_address)
126 {
127 return Handoff{};
128 }
129
130 void
132 {
133 session.write(std::string("Hello, world!\n"));
135 session.complete();
136 else
137 session.close(true);
138 }
139
140 void
144 {
145 }
146
147 void
148 onClose(Session& session, boost::system::error_code const&)
149 {
150 }
151
152 void
154 {
155 }
156 };
157
158 //--------------------------------------------------------------------------
159
160 // Connect to an address
161 template <class Socket>
162 bool
163 connect(Socket& s, typename Socket::endpoint_type const& ep)
164 {
165 try
166 {
167 s.connect(ep);
168 pass();
169 return true;
170 }
171 catch (std::exception const& e)
172 {
173 fail(e.what());
174 }
175
176 return false;
177 }
178
179 // Write a string to the stream
180 template <class SyncWriteStream>
181 bool
182 write(SyncWriteStream& s, std::string const& text)
183 {
184 try
185 {
186 boost::asio::write(s, boost::asio::buffer(text));
187 pass();
188 return true;
189 }
190 catch (std::exception const& e)
191 {
192 fail(e.what());
193 }
194 return false;
195 }
196
197 // Expect that reading the stream produces a matching string
198 template <class SyncReadStream>
199 bool
200 expect_read(SyncReadStream& s, std::string const& match)
201 {
202 boost::asio::streambuf b(1000); // limit on read
203 try
204 {
205 auto const n = boost::asio::read_until(s, b, '\n');
206 if (BEAST_EXPECT(n == match.size()))
207 {
208 std::string got;
209 got.resize(n);
210 boost::asio::buffer_copy(
211 boost::asio::buffer(&got[0], n), b.data());
212 return BEAST_EXPECT(got == match);
213 }
214 }
215 catch (std::length_error const& e)
216 {
217 fail(e.what());
218 }
219 catch (std::exception const& e)
220 {
221 fail(e.what());
222 }
223 return false;
224 }
225
226 void
227 test_request(boost::asio::ip::tcp::endpoint const& ep)
228 {
229 boost::asio::io_service ios;
230 using socket = boost::asio::ip::tcp::socket;
231 socket s(ios);
232
233 if (!connect(s, ep))
234 return;
235
236 if (!write(
237 s,
238 "GET / HTTP/1.1\r\n"
239 "Connection: close\r\n"
240 "\r\n"))
241 return;
242
243 if (!expect_read(s, "Hello, world!\n"))
244 return;
245
246 boost::system::error_code ec;
247 s.shutdown(socket::shutdown_both, ec);
248
250 }
251
252 void
253 test_keepalive(boost::asio::ip::tcp::endpoint const& ep)
254 {
255 boost::asio::io_service ios;
256 using socket = boost::asio::ip::tcp::socket;
257 socket s(ios);
258
259 if (!connect(s, ep))
260 return;
261
262 if (!write(
263 s,
264 "GET / HTTP/1.1\r\n"
265 "Connection: Keep-Alive\r\n"
266 "\r\n"))
267 return;
268
269 if (!expect_read(s, "Hello, world!\n"))
270 return;
271
272 if (!write(
273 s,
274 "GET / HTTP/1.1\r\n"
275 "Connection: close\r\n"
276 "\r\n"))
277 return;
278
279 if (!expect_read(s, "Hello, world!\n"))
280 return;
281
282 boost::system::error_code ec;
283 s.shutdown(socket::shutdown_both, ec);
284 }
285
286 void
288 {
289 testcase("Basic client/server");
290 TestSink sink{*this};
292 sink.threshold(beast::severities::Severity::kAll);
293 beast::Journal journal{sink};
294 TestHandler handler;
295 auto s = make_Server(handler, thread.get_io_service(), journal);
296 std::vector<Port> serverPort(1);
297 serverPort.back().ip =
298 beast::IP::Address::from_string(getEnvLocalhostAddr()),
299 serverPort.back().port = 0;
300 serverPort.back().protocol.insert("http");
301 auto eps = s->ports(serverPort);
302 test_request(eps.begin()->second);
303 test_keepalive(eps.begin()->second);
304 // s->close();
305 s = nullptr;
306 pass();
307 }
308
309 void
311 {
312 testcase("stress test");
313 struct NullHandler
314 {
315 bool
316 onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
317 {
318 return true;
319 }
320
321 Handoff
322 onHandoff(
323 Session& session,
325 http_request_type&& request,
326 boost::asio::ip::tcp::endpoint remote_address)
327 {
328 return Handoff{};
329 }
330
331 Handoff
332 onHandoff(
333 Session& session,
334 http_request_type&& request,
335 boost::asio::ip::tcp::endpoint remote_address)
336 {
337 return Handoff{};
338 }
339
340 void
341 onRequest(Session& session)
342 {
343 }
344
345 void
346 onWSMessage(
349 {
350 }
351
352 void
353 onClose(Session& session, boost::system::error_code const&)
354 {
355 }
356
357 void
358 onStopped(Server& server)
359 {
360 }
361 };
362
363 using namespace beast::severities;
364 SuiteJournal journal("Server_test", *this);
365
366 NullHandler h;
367 for (int i = 0; i < 1000; ++i)
368 {
370 auto s = make_Server(h, thread.get_io_service(), journal);
371 std::vector<Port> serverPort(1);
372 serverPort.back().ip =
373 beast::IP::Address::from_string(getEnvLocalhostAddr()),
374 serverPort.back().port = 0;
375 serverPort.back().protocol.insert("http");
376 s->ports(serverPort);
377 }
378 pass();
379 }
380
381 void
383 {
384 testcase("Server config - invalid options");
385 using namespace test::jtx;
386
387 std::string messages;
388
389 except([&] {
390 Env env{
391 *this,
393 (*cfg).deprecatedClearSection("port_rpc");
394 return cfg;
395 }),
396 std::make_unique<CaptureLogs>(&messages)};
397 });
398 BEAST_EXPECT(
399 messages.find("Missing 'ip' in [port_rpc]") != std::string::npos);
400
401 except([&] {
402 Env env{
403 *this,
405 (*cfg).deprecatedClearSection("port_rpc");
406 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
407 return cfg;
408 }),
409 std::make_unique<CaptureLogs>(&messages)};
410 });
411 BEAST_EXPECT(
412 messages.find("Missing 'port' in [port_rpc]") != std::string::npos);
413
414 except([&] {
415 Env env{
416 *this,
418 (*cfg).deprecatedClearSection("port_rpc");
419 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
420 (*cfg)["port_rpc"].set("port", "0");
421 return cfg;
422 }),
423 std::make_unique<CaptureLogs>(&messages)};
424 });
425 BEAST_EXPECT(
426 messages.find("Invalid value '0' for key 'port' in [port_rpc]") ==
427 std::string::npos);
428
429 except([&] {
430 Env env{
431 *this,
433 (*cfg)["server"].set("port", "0");
434 return cfg;
435 }),
436 std::make_unique<CaptureLogs>(&messages)};
437 });
438 BEAST_EXPECT(
439 messages.find("Invalid value '0' for key 'port' in [server]") !=
440 std::string::npos);
441
442 except([&] {
443 Env env{
444 *this,
446 (*cfg).deprecatedClearSection("port_rpc");
447 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
448 (*cfg)["port_rpc"].set("port", "8081");
449 (*cfg)["port_rpc"].set("protocol", "");
450 return cfg;
451 }),
452 std::make_unique<CaptureLogs>(&messages)};
453 });
454 BEAST_EXPECT(
455 messages.find("Missing 'protocol' in [port_rpc]") !=
456 std::string::npos);
457
458 except(
459 [&] // this creates a standard test config without the server
460 // section
461 {
462 Env env{
463 *this,
465 cfg = std::make_unique<Config>();
466 cfg->overwrite(
467 ConfigSection::nodeDatabase(), "type", "memory");
468 cfg->overwrite(
469 ConfigSection::nodeDatabase(), "path", "main");
470 cfg->deprecatedClearSection(
472 cfg->legacy("database_path", "");
473 cfg->setupControl(true, true, true);
474 (*cfg)["port_peer"].set("ip", getEnvLocalhostAddr());
475 (*cfg)["port_peer"].set("port", "8080");
476 (*cfg)["port_peer"].set("protocol", "peer");
477 (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
478 (*cfg)["port_rpc"].set("port", "8081");
479 (*cfg)["port_rpc"].set("protocol", "http,ws2");
480 (*cfg)["port_rpc"].set("admin", getEnvLocalhostAddr());
481 (*cfg)["port_ws"].set("ip", getEnvLocalhostAddr());
482 (*cfg)["port_ws"].set("port", "8082");
483 (*cfg)["port_ws"].set("protocol", "ws");
484 (*cfg)["port_ws"].set("admin", getEnvLocalhostAddr());
485 return cfg;
486 }),
487 std::make_unique<CaptureLogs>(&messages)};
488 });
489 BEAST_EXPECT(
490 messages.find("Required section [server] is missing") !=
491 std::string::npos);
492
493 except([&] // this creates a standard test config without some of the
494 // port sections
495 {
496 Env env{
497 *this,
499 cfg = std::make_unique<Config>();
500 cfg->overwrite(
501 ConfigSection::nodeDatabase(), "type", "memory");
502 cfg->overwrite(
503 ConfigSection::nodeDatabase(), "path", "main");
504 cfg->deprecatedClearSection(
506 cfg->legacy("database_path", "");
507 cfg->setupControl(true, true, true);
508 (*cfg)["server"].append("port_peer");
509 (*cfg)["server"].append("port_rpc");
510 (*cfg)["server"].append("port_ws");
511 return cfg;
512 }),
513 std::make_unique<CaptureLogs>(&messages)};
514 });
515 BEAST_EXPECT(
516 messages.find("Missing section: [port_peer]") != std::string::npos);
517 }
518
519 void
520 run() override
521 {
522 basicTests();
523 stressTest();
525 }
526};
527
528BEAST_DEFINE_TESTSUITE(Server, http, ripple);
529
530} // namespace test
531} // 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.
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)