rippled
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 <ripple/basics/make_SSLContext.h>
21 #include <ripple/beast/rfc2616.h>
22 #include <ripple/beast/unit_test.h>
23 #include <ripple/core/ConfigSections.h>
24 #include <ripple/server/Server.h>
25 #include <ripple/server/Session.h>
26 #include <boost/asio.hpp>
27 #include <boost/beast/core/tcp_stream.hpp>
28 #include <boost/beast/ssl/ssl_stream.hpp>
29 #include <boost/optional.hpp>
30 #include <boost/utility/in_place_factory.hpp>
31 #include <chrono>
32 #include <stdexcept>
33 #include <test/jtx.h>
34 #include <test/jtx/envconfig.h>
35 #include <test/unit_test/SuiteJournal.h>
36 #include <thread>
37 
38 namespace ripple {
39 namespace test {
40 
41 using socket_type = boost::beast::tcp_stream;
42 using stream_type = boost::beast::ssl_stream<socket_type>;
43 
44 class Server_test : public beast::unit_test::suite
45 {
46 public:
47  class TestThread
48  {
49  private:
50  boost::asio::io_service io_service_;
51  boost::optional<boost::asio::io_service::work> work_;
53 
54  public:
56  : work_(boost::in_place(std::ref(io_service_)))
57  , thread_([&]() { this->io_service_.run(); })
58  {
59  }
60 
62  {
63  work_ = boost::none;
64  thread_.join();
65  }
66 
67  boost::asio::io_service&
69  {
70  return io_service_;
71  }
72  };
73 
74  //--------------------------------------------------------------------------
75 
77  {
78  beast::unit_test::suite& suite_;
79 
80  public:
81  explicit TestSink(beast::unit_test::suite& suite)
82  : Sink(beast::severities::kWarning, false), suite_(suite)
83  {
84  }
85 
86  void
88  override
89  {
90  if (level < threshold())
91  return;
92 
93  suite_.log << text << std::endl;
94  }
95  };
96 
97  //--------------------------------------------------------------------------
98 
99  struct TestHandler
100  {
101  bool
102  onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
103  {
104  return true;
105  }
106 
107  Handoff
109  Session& session,
111  http_request_type&& request,
112  boost::asio::ip::tcp::endpoint remote_address)
113  {
114  return Handoff{};
115  }
116 
117  Handoff
119  Session& session,
120  http_request_type&& request,
121  boost::asio::ip::tcp::endpoint remote_address)
122  {
123  return Handoff{};
124  }
125 
126  void
127  onRequest(Session& session)
128  {
129  session.write(std::string("Hello, world!\n"));
130  if (beast::rfc2616::is_keep_alive(session.request()))
131  session.complete();
132  else
133  session.close(true);
134  }
135 
136  void
140  {
141  }
142 
143  void
144  onClose(Session& session, boost::system::error_code const&)
145  {
146  }
147 
148  void
149  onStopped(Server& server)
150  {
151  }
152  };
153 
154  //--------------------------------------------------------------------------
155 
156  // Connect to an address
157  template <class Socket>
158  bool
159  connect(Socket& s, typename Socket::endpoint_type const& ep)
160  {
161  try
162  {
163  s.connect(ep);
164  pass();
165  return true;
166  }
167  catch (std::exception const& e)
168  {
169  fail(e.what());
170  }
171 
172  return false;
173  }
174 
175  // Write a string to the stream
176  template <class SyncWriteStream>
177  bool
178  write(SyncWriteStream& s, std::string const& text)
179  {
180  try
181  {
182  boost::asio::write(s, boost::asio::buffer(text));
183  pass();
184  return true;
185  }
186  catch (std::exception const& e)
187  {
188  fail(e.what());
189  }
190  return false;
191  }
192 
193  // Expect that reading the stream produces a matching string
194  template <class SyncReadStream>
195  bool
196  expect_read(SyncReadStream& s, std::string const& match)
197  {
198  boost::asio::streambuf b(1000); // limit on read
199  try
200  {
201  auto const n = boost::asio::read_until(s, b, '\n');
202  if (BEAST_EXPECT(n == match.size()))
203  {
204  std::string got;
205  got.resize(n);
206  boost::asio::buffer_copy(
207  boost::asio::buffer(&got[0], n), b.data());
208  return BEAST_EXPECT(got == match);
209  }
210  }
211  catch (std::length_error const& e)
212  {
213  fail(e.what());
214  }
215  catch (std::exception const& e)
216  {
217  fail(e.what());
218  }
219  return false;
220  }
221 
222  void
223  test_request(boost::asio::ip::tcp::endpoint const& ep)
224  {
225  boost::asio::io_service ios;
226  using socket = boost::asio::ip::tcp::socket;
227  socket s(ios);
228 
229  if (!connect(s, ep))
230  return;
231 
232  if (!write(
233  s,
234  "GET / HTTP/1.1\r\n"
235  "Connection: close\r\n"
236  "\r\n"))
237  return;
238 
239  if (!expect_read(s, "Hello, world!\n"))
240  return;
241 
242  boost::system::error_code ec;
243  s.shutdown(socket::shutdown_both, ec);
244 
246  }
247 
248  void
249  test_keepalive(boost::asio::ip::tcp::endpoint const& ep)
250  {
251  boost::asio::io_service ios;
252  using socket = boost::asio::ip::tcp::socket;
253  socket s(ios);
254 
255  if (!connect(s, ep))
256  return;
257 
258  if (!write(
259  s,
260  "GET / HTTP/1.1\r\n"
261  "Connection: Keep-Alive\r\n"
262  "\r\n"))
263  return;
264 
265  if (!expect_read(s, "Hello, world!\n"))
266  return;
267 
268  if (!write(
269  s,
270  "GET / HTTP/1.1\r\n"
271  "Connection: close\r\n"
272  "\r\n"))
273  return;
274 
275  if (!expect_read(s, "Hello, world!\n"))
276  return;
277 
278  boost::system::error_code ec;
279  s.shutdown(socket::shutdown_both, ec);
280  }
281 
282  void
284  {
285  testcase("Basic client/server");
286  TestSink sink{*this};
287  TestThread thread;
288  sink.threshold(beast::severities::Severity::kAll);
289  beast::Journal journal{sink};
290  TestHandler handler;
291  auto s = make_Server(handler, thread.get_io_service(), journal);
292  std::vector<Port> serverPort(1);
293  serverPort.back().ip =
294  beast::IP::Address::from_string(getEnvLocalhostAddr()),
295  serverPort.back().port = 0;
296  serverPort.back().protocol.insert("http");
297  auto eps = s->ports(serverPort);
298  log << "server listening on port " << eps[0].port() << std::endl;
299  test_request(eps[0]);
300  test_keepalive(eps[0]);
301  // s->close();
302  s = nullptr;
303  pass();
304  }
305 
306  void
308  {
309  testcase("stress test");
310  struct NullHandler
311  {
312  bool
313  onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
314  {
315  return true;
316  }
317 
318  Handoff
319  onHandoff(
320  Session& session,
322  http_request_type&& request,
323  boost::asio::ip::tcp::endpoint remote_address)
324  {
325  return Handoff{};
326  }
327 
328  Handoff
329  onHandoff(
330  Session& session,
331  http_request_type&& request,
332  boost::asio::ip::tcp::endpoint remote_address)
333  {
334  return Handoff{};
335  }
336 
337  void
338  onRequest(Session& session)
339  {
340  }
341 
342  void
343  onWSMessage(
346  {
347  }
348 
349  void
350  onClose(Session& session, boost::system::error_code const&)
351  {
352  }
353 
354  void
355  onStopped(Server& server)
356  {
357  }
358  };
359 
360  using namespace beast::severities;
361  SuiteJournal journal("Server_test", *this);
362 
363  NullHandler h;
364  for (int i = 0; i < 1000; ++i)
365  {
366  TestThread thread;
367  auto s = make_Server(h, thread.get_io_service(), journal);
368  std::vector<Port> serverPort(1);
369  serverPort.back().ip =
370  beast::IP::Address::from_string(getEnvLocalhostAddr()),
371  serverPort.back().port = 0;
372  serverPort.back().protocol.insert("http");
373  s->ports(serverPort);
374  }
375  pass();
376  }
377 
382  {
384 
385  public:
388  std::stringstream& strm)
389  : beast::Journal::Sink(threshold, false), strm_(strm)
390  {
391  }
392 
393  void
395  override
396  {
397  strm_ << text;
398  }
399  };
400 
407  class CaptureLogs : public Logs
408  {
411 
412  public:
413  explicit CaptureLogs(std::string& result)
414  : Logs(beast::severities::kInfo), result_(result)
415  {
416  }
417 
418  ~CaptureLogs() override
419  {
420  result_ = strm_.str();
421  }
422 
425  std::string const& partition,
427  {
428  return std::make_unique<CaptureSink>(threshold, strm_);
429  }
430  };
431 
432  void
434  {
435  testcase("Server config - invalid options");
436  using namespace test::jtx;
437 
438  std::string messages;
439 
440  except([&] {
441  Env env{
442  *this,
444  (*cfg).deprecatedClearSection("port_rpc");
445  return cfg;
446  }),
447  std::make_unique<CaptureLogs>(messages)};
448  });
449  BEAST_EXPECT(
450  messages.find("Missing 'ip' in [port_rpc]") != std::string::npos);
451 
452  except([&] {
453  Env env{
454  *this,
456  (*cfg).deprecatedClearSection("port_rpc");
457  (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
458  return cfg;
459  }),
460  std::make_unique<CaptureLogs>(messages)};
461  });
462  BEAST_EXPECT(
463  messages.find("Missing 'port' in [port_rpc]") != std::string::npos);
464 
465  except([&] {
466  Env env{
467  *this,
469  (*cfg).deprecatedClearSection("port_rpc");
470  (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
471  (*cfg)["port_rpc"].set("port", "0");
472  return cfg;
473  }),
474  std::make_unique<CaptureLogs>(messages)};
475  });
476  BEAST_EXPECT(
477  messages.find("Invalid value '0' for key 'port' in [port_rpc]") !=
478  std::string::npos);
479 
480  except([&] {
481  Env env{
482  *this,
484  (*cfg).deprecatedClearSection("port_rpc");
485  (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
486  (*cfg)["port_rpc"].set("port", "8081");
487  (*cfg)["port_rpc"].set("protocol", "");
488  return cfg;
489  }),
490  std::make_unique<CaptureLogs>(messages)};
491  });
492  BEAST_EXPECT(
493  messages.find("Missing 'protocol' in [port_rpc]") !=
494  std::string::npos);
495 
496  except(
497  [&] // this creates a standard test config without the server
498  // section
499  {
500  Env env{
501  *this,
503  cfg = std::make_unique<Config>();
504  cfg->overwrite(
505  ConfigSection::nodeDatabase(), "type", "memory");
506  cfg->overwrite(
507  ConfigSection::nodeDatabase(), "path", "main");
508  cfg->deprecatedClearSection(
510  cfg->legacy("database_path", "");
511  cfg->setupControl(true, true, true);
512  (*cfg)["port_peer"].set("ip", getEnvLocalhostAddr());
513  (*cfg)["port_peer"].set("port", "8080");
514  (*cfg)["port_peer"].set("protocol", "peer");
515  (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
516  (*cfg)["port_rpc"].set("port", "8081");
517  (*cfg)["port_rpc"].set("protocol", "http,ws2");
518  (*cfg)["port_rpc"].set("admin", getEnvLocalhostAddr());
519  (*cfg)["port_ws"].set("ip", getEnvLocalhostAddr());
520  (*cfg)["port_ws"].set("port", "8082");
521  (*cfg)["port_ws"].set("protocol", "ws");
522  (*cfg)["port_ws"].set("admin", getEnvLocalhostAddr());
523  return cfg;
524  }),
525  std::make_unique<CaptureLogs>(messages)};
526  });
527  BEAST_EXPECT(
528  messages.find("Required section [server] is missing") !=
529  std::string::npos);
530 
531  except([&] // this creates a standard test config without some of the
532  // port sections
533  {
534  Env env{
535  *this,
537  cfg = std::make_unique<Config>();
538  cfg->overwrite(
539  ConfigSection::nodeDatabase(), "type", "memory");
540  cfg->overwrite(
541  ConfigSection::nodeDatabase(), "path", "main");
542  cfg->deprecatedClearSection(
544  cfg->legacy("database_path", "");
545  cfg->setupControl(true, true, true);
546  (*cfg)["server"].append("port_peer");
547  (*cfg)["server"].append("port_rpc");
548  (*cfg)["server"].append("port_ws");
549  return cfg;
550  }),
551  std::make_unique<CaptureLogs>(messages)};
552  });
553  BEAST_EXPECT(
554  messages.find("Missing section: [port_peer]") != std::string::npos);
555  }
556 
557  void
558  run() override
559  {
560  basicTests();
561  stressTest();
562  testBadConfig();
563  }
564 };
565 
567 
568 } // namespace test
569 } // namespace ripple
ripple::test::Server_test::TestThread::TestThread
TestThread()
Definition: Server_test.cpp:55
ripple::test::Server_test::stressTest
void stressTest()
Definition: Server_test.cpp:307
beast::Journal::Sink
Abstraction for the underlying message destination.
Definition: Journal.h:74
ripple::test::Server_test::TestThread::~TestThread
~TestThread()
Definition: Server_test.cpp:61
std::string::resize
T resize(T... args)
std::this_thread::sleep_for
T sleep_for(T... args)
ripple::make_Server
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
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple)
std::string
STL class.
std::shared_ptr
STL class.
ripple::test::Server_test::TestHandler
Definition: Server_test.cpp:99
std::exception
STL class.
ripple::Logs
Manages partitions for logging.
Definition: Log.h:48
ripple::test::Server_test::basicTests
void basicTests()
Definition: Server_test.cpp:283
ripple::test::Server_test::TestHandler::onHandoff
Handoff onHandoff(Session &session, http_request_type &&request, boost::asio::ip::tcp::endpoint remote_address)
Definition: Server_test.cpp:118
ripple::test::Server_test::CaptureLogs::result_
std::string & result_
Definition: Server_test.cpp:410
ripple::http_request_type
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition: Handoff.h:31
ripple::test::socket_type
boost::beast::tcp_stream socket_type
Definition: Server_test.cpp:41
std::vector
STL class.
std::string::find
T find(T... args)
ripple::ConfigSection::importNodeDatabase
static std::string importNodeDatabase()
Definition: ConfigSections.h:43
ripple::test::Server_test::CaptureLogs::makeSink
std::unique_ptr< beast::Journal::Sink > makeSink(std::string const &partition, beast::severities::Severity threshold) override
Definition: Server_test.cpp:424
std::chrono::seconds
std::stringstream
STL class.
beast::severities
A namespace for easy access to logging severity values.
Definition: Journal.h:29
ripple::test::Server_test::TestThread::get_io_service
boost::asio::io_service & get_io_service()
Definition: Server_test.cpp:68
ripple::test::Server_test::TestHandler::onRequest
void onRequest(Session &session)
Definition: Server_test.cpp:127
ripple::test::Server_test::CaptureLogs::strm_
std::stringstream strm_
Definition: Server_test.cpp:409
boost
Definition: IPAddress.h:117
std::vector::back
T back(T... args)
ripple::test::Server_test::TestHandler::onAccept
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
Definition: Server_test.cpp:102
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:49
ripple::Session
Persistent state information for a connection session.
Definition: Session.h:40
ripple::Session::write
void write(std::string const &s)
Send a copy of data asynchronously.
Definition: Session.h:74
ripple::test::Server_test::TestSink::write
void write(beast::severities::Severity level, std::string const &text) override
Write text to the sink at the specified severity.
Definition: Server_test.cpp:87
ripple::test::Server_test::run
void run() override
Definition: Server_test.cpp:558
ripple::test::Server_test::TestSink::TestSink
TestSink(beast::unit_test::suite &suite)
Definition: Server_test.cpp:81
stdexcept
ripple::test::Server_test::CaptureLogs::CaptureLogs
CaptureLogs(std::string &result)
Definition: Server_test.cpp:413
ripple::test::getEnvLocalhostAddr
const char * getEnvLocalhostAddr()
Definition: envconfig.h:31
ripple::test::Server_test::TestSink::suite_
beast::unit_test::suite & suite_
Definition: Server_test.cpp:78
beast::rfc2616::is_keep_alive
bool is_keep_alive(boost::beast::http::message< isRequest, Body, Fields > const &m)
Definition: rfc2616.h:475
ripple::test::Server_test::CaptureLogs
Log manager for CaptureSinks.
Definition: Server_test.cpp:407
thread
chrono
ripple::test::Server_test::expect_read
bool expect_read(SyncReadStream &s, std::string const &match)
Definition: Server_test.cpp:196
std::length_error
STL class.
ripple::test::stream_type
boost::beast::ssl_stream< socket_type > stream_type
Definition: Server_test.cpp:42
beast::Journal::Sink::Sink
Sink()=delete
ripple::test::Server_test::TestThread::work_
boost::optional< boost::asio::io_service::work > work_
Definition: Server_test.cpp:51
ripple::test::Server_test::connect
bool connect(Socket &s, typename Socket::endpoint_type const &ep)
Definition: Server_test.cpp:159
ripple::Server
A multi-protocol server.
Definition: ServerImpl.h:44
ripple::Session::request
virtual http_request_type & request()=0
Returns the current HTTP request.
ripple::test::Server_test::test_keepalive
void test_keepalive(boost::asio::ip::tcp::endpoint const &ep)
Definition: Server_test.cpp:249
ripple::test::Server_test::write
bool write(SyncWriteStream &s, std::string const &text)
Definition: Server_test.cpp:178
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
ripple::Session::complete
virtual void complete()=0
Indicate that the response is complete.
beast::severities::kInfo
@ kInfo
Definition: Journal.h:36
ripple::test::Server_test::TestThread::io_service_
boost::asio::io_service io_service_
Definition: Server_test.cpp:50
ripple::test::SuiteJournal
Definition: SuiteJournal.h:88
ripple::test::Server_test::TestThread
Definition: Server_test.cpp:47
ripple::test::Server_test::CaptureSink
sink for writing all log messages to a stringstream
Definition: Server_test.cpp:381
ripple::test::Server_test::TestHandler::onClose
void onClose(Session &session, boost::system::error_code const &)
Definition: Server_test.cpp:144
ripple::test::Server_test::CaptureSink::CaptureSink
CaptureSink(beast::severities::Severity threshold, std::stringstream &strm)
Definition: Server_test.cpp:386
ripple::Logs::threshold
beast::severities::Severity threshold() const
Definition: Log.cpp:150
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
std::endl
T endl(T... args)
beast::Journal::Sink::threshold
virtual Severity threshold() const
Returns the minimum severity level this sink will report.
Definition: beast_Journal.cpp:106
beast::severities::Severity
Severity
Severity level / threshold of a Journal message.
Definition: Journal.h:31
std
STL namespace.
ripple::Session::close
virtual void close(bool graceful)=0
Close the session.
ripple::Handoff
Used to indicate the result of a server connection handoff.
Definition: Handoff.h:37
ripple::test::Server_test
Definition: Server_test.cpp:44
std::stringstream::str
T str(T... args)
ripple::test::Server_test::TestHandler::onWSMessage
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &)
Definition: Server_test.cpp:137
ripple::test::Server_test::CaptureSink::strm_
std::stringstream & strm_
Definition: Server_test.cpp:383
ripple::test::Server_test::TestSink
Definition: Server_test.cpp:76
ripple::test::Server_test::TestHandler::onHandoff
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint remote_address)
Definition: Server_test.cpp:108
ripple::test::Server_test::CaptureLogs::~CaptureLogs
~CaptureLogs() override
Definition: Server_test.cpp:418
ripple::test::Server_test::TestThread::thread_
std::thread thread_
Definition: Server_test.cpp:52
ripple::test::Server_test::test_request
void test_request(boost::asio::ip::tcp::endpoint const &ep)
Definition: Server_test.cpp:223
std::unique_ptr< stream_type >
ripple::test::Server_test::CaptureSink::write
void write(beast::severities::Severity level, std::string const &text) override
Write text to the sink at the specified severity.
Definition: Server_test.cpp:394
ripple::test::Server_test::TestHandler::onStopped
void onStopped(Server &server)
Definition: Server_test.cpp:149
ripple::test::Server_test::testBadConfig
void testBadConfig()
Definition: Server_test.cpp:433
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:114
ripple::ConfigSection::nodeDatabase
static std::string nodeDatabase()
Definition: ConfigSections.h:33
std::thread::join
T join(T... args)
std::exception::what
T what(T... args)
beast
Definition: base_uint.h:646