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/server/Server.h>
23 #include <ripple/server/Session.h>
24 #include <ripple/beast/unit_test.h>
25 #include <ripple/core/ConfigSections.h>
26 #include <test/jtx.h>
27 #include <test/jtx/envconfig.h>
28 #include <test/unit_test/SuiteJournal.h>
29 #include <boost/asio.hpp>
30 #include <boost/optional.hpp>
31 #include <boost/utility/in_place_factory.hpp>
32 #include <boost/beast/core/tcp_stream.hpp>
33 #include <boost/beast/ssl/ssl_stream.hpp>
34 #include <chrono>
35 #include <stdexcept>
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 
48  class TestThread
49  {
50  private:
51  boost::asio::io_service io_service_;
52  boost::optional<boost::asio::io_service::work> work_;
54 
55  public:
57  : work_(boost::in_place(std::ref(io_service_)))
58  , thread_([&]() { this->io_service_.run(); })
59  {
60  }
61 
63  {
64  work_ = boost::none;
65  thread_.join();
66  }
67 
68  boost::asio::io_service&
70  {
71  return io_service_;
72  }
73  };
74 
75  //--------------------------------------------------------------------------
76 
78  {
79  beast::unit_test::suite& suite_;
80 
81  public:
82  explicit TestSink (beast::unit_test::suite& suite)
83  : Sink (beast::severities::kWarning, false)
84  , suite_ (suite)
85  {
86  }
87 
88  void
90  std::string const& text) override
91  {
92  if (level < threshold())
93  return;
94 
95  suite_.log << text << std::endl;
96  }
97  };
98 
99  //--------------------------------------------------------------------------
100 
101  struct TestHandler
102  {
103  bool
104  onAccept (Session& session,
105  boost::asio::ip::tcp::endpoint endpoint)
106  {
107  return true;
108  }
109 
110  Handoff
111  onHandoff (Session& session,
113  http_request_type&& request,
114  boost::asio::ip::tcp::endpoint remote_address)
115  {
116  return Handoff{};
117  }
118 
119  Handoff
120  onHandoff (Session& session, 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
139  {
140  }
141 
142  void
143  onClose (Session& session,
144  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 (boost::asio::buffer (
207  &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 (s,
233  "GET / HTTP/1.1\r\n"
234  "Connection: close\r\n"
235  "\r\n"))
236  return;
237 
238  if (! expect_read (s, "Hello, world!\n"))
239  return ;
240 
241  boost::system::error_code ec;
242  s.shutdown(socket::shutdown_both, ec);
243 
245  }
246 
247  void
248  test_keepalive(boost::asio::ip::tcp::endpoint const& ep)
249  {
250  boost::asio::io_service ios;
251  using socket = boost::asio::ip::tcp::socket;
252  socket s (ios);
253 
254  if (! connect (s, ep))
255  return;
256 
257  if (! write (s,
258  "GET / HTTP/1.1\r\n"
259  "Connection: Keep-Alive\r\n"
260  "\r\n"))
261  return;
262 
263  if (! expect_read (s, "Hello, world!\n"))
264  return ;
265 
266  if (! write (s,
267  "GET / HTTP/1.1\r\n"
268  "Connection: close\r\n"
269  "\r\n"))
270  return;
271 
272  if (! expect_read (s, "Hello, world!\n"))
273  return ;
274 
275  boost::system::error_code ec;
276  s.shutdown(socket::shutdown_both, ec);
277  }
278 
279  void basicTests()
280  {
281  testcase("Basic client/server");
282  TestSink sink {*this};
283  TestThread thread;
284  sink.threshold (beast::severities::Severity::kAll);
285  beast::Journal journal {sink};
286  TestHandler handler;
287  auto s = make_Server (handler,
288  thread.get_io_service(), journal);
289  std::vector<Port> serverPort(1);
290  serverPort.back().ip =
291  beast::IP::Address::from_string (getEnvLocalhostAddr()),
292  serverPort.back().port = 0;
293  serverPort.back().protocol.insert("http");
294  auto eps = s->ports (serverPort);
295  log << "server listening on port " << eps[0].port() << std::endl;
296  test_request(eps[0]);
297  test_keepalive(eps[0]);
298  //s->close();
299  s = nullptr;
300  pass();
301  }
302 
303  void stressTest()
304  {
305  testcase("stress test");
306  struct NullHandler
307  {
308  bool
309  onAccept (Session& session,
310  boost::asio::ip::tcp::endpoint endpoint)
311  {
312  return true;
313  }
314 
315  Handoff
316  onHandoff (Session& session,
318  http_request_type&& request,
319  boost::asio::ip::tcp::endpoint remote_address)
320  {
321  return Handoff{};
322  }
323 
324  Handoff
325  onHandoff (Session& session, http_request_type&& request,
326  boost::asio::ip::tcp::endpoint remote_address)
327  {
328  return Handoff{};
329  }
330 
331  void
332  onRequest (Session& session)
333  {
334  }
335 
336  void
337  onWSMessage(std::shared_ptr<WSSession> session,
339  {
340  }
341 
342  void
343  onClose (Session& session,
344  boost::system::error_code const&)
345  {
346  }
347 
348  void
349  onStopped (Server& server)
350  {
351  }
352  };
353 
354  using namespace beast::severities;
355  SuiteJournal journal ("Server_test", *this);
356 
357  NullHandler h;
358  for(int i = 0; i < 1000; ++i)
359  {
360  TestThread thread;
361  auto s = make_Server(h,
362  thread.get_io_service(), journal);
363  std::vector<Port> serverPort(1);
364  serverPort.back().ip =
365  beast::IP::Address::from_string (getEnvLocalhostAddr()),
366  serverPort.back().port = 0;
367  serverPort.back().protocol.insert("http");
368  s->ports (serverPort);
369  }
370  pass();
371  }
372 
377  {
379  public:
381  std::stringstream& strm)
382  : beast::Journal::Sink(threshold, false)
383  , strm_(strm)
384  {
385  }
386 
387  void
388  write(beast::severities::Severity level, std::string const& text) override
389  {
390  strm_ << text;
391  }
392  };
393 
400  class CaptureLogs : public Logs
401  {
404 
405  public:
406  explicit CaptureLogs(std::string& result)
407  : Logs (beast::severities::kInfo)
408  , result_(result)
409  {
410  }
411 
412  ~CaptureLogs() override
413  {
414  result_ = strm_.str();
415  }
416 
418  makeSink(std::string const& partition,
420  {
421  return std::make_unique<CaptureSink>(threshold, strm_);
422  }
423  };
424 
425  void
427  {
428  testcase ("Server config - invalid options");
429  using namespace test::jtx;
430 
431  std::string messages;
432 
433  except ([&]
434  {
435  Env env {*this,
437  (*cfg).deprecatedClearSection("port_rpc");
438  return cfg;
439  }),
440  std::make_unique<CaptureLogs>(messages)};
441  });
442  BEAST_EXPECT (
443  messages.find ("Missing 'ip' in [port_rpc]")
444  != std::string::npos);
445 
446  except ([&]
447  {
448  Env env {*this,
450  (*cfg).deprecatedClearSection("port_rpc");
451  (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
452  return cfg;
453  }),
454  std::make_unique<CaptureLogs>(messages)};
455  });
456  BEAST_EXPECT (
457  messages.find ("Missing 'port' in [port_rpc]")
458  != std::string::npos);
459 
460  except ([&]
461  {
462  Env env {*this,
464  (*cfg).deprecatedClearSection("port_rpc");
465  (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
466  (*cfg)["port_rpc"].set("port", "0");
467  return cfg;
468  }),
469  std::make_unique<CaptureLogs>(messages)};
470  });
471  BEAST_EXPECT (
472  messages.find ("Invalid value '0' for key 'port' in [port_rpc]")
473  != std::string::npos);
474 
475  except ([&]
476  {
477  Env env {*this,
479  (*cfg).deprecatedClearSection("port_rpc");
480  (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
481  (*cfg)["port_rpc"].set("port", "8081");
482  (*cfg)["port_rpc"].set("protocol", "");
483  return cfg;
484  }),
485  std::make_unique<CaptureLogs>(messages)};
486  });
487  BEAST_EXPECT (
488  messages.find ("Missing 'protocol' in [port_rpc]")
489  != std::string::npos);
490 
491  except ([&] //this creates a standard test config without the server
492  //section
493  {
494  Env env {*this,
496  cfg = std::make_unique<Config>();
497  cfg->overwrite (
498  ConfigSection::nodeDatabase (), "type", "memory");
499  cfg->overwrite (
500  ConfigSection::nodeDatabase (), "path", "main");
501  cfg->deprecatedClearSection (
503  cfg->legacy("database_path", "");
504  cfg->setupControl(true, true, true);
505  (*cfg)["port_peer"].set("ip", getEnvLocalhostAddr());
506  (*cfg)["port_peer"].set("port", "8080");
507  (*cfg)["port_peer"].set("protocol", "peer");
508  (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
509  (*cfg)["port_rpc"].set("port", "8081");
510  (*cfg)["port_rpc"].set("protocol", "http,ws2");
511  (*cfg)["port_rpc"].set("admin", getEnvLocalhostAddr());
512  (*cfg)["port_ws"].set("ip", getEnvLocalhostAddr());
513  (*cfg)["port_ws"].set("port", "8082");
514  (*cfg)["port_ws"].set("protocol", "ws");
515  (*cfg)["port_ws"].set("admin", getEnvLocalhostAddr());
516  return cfg;
517  }),
518  std::make_unique<CaptureLogs>(messages)};
519  });
520  BEAST_EXPECT (
521  messages.find ("Required section [server] is missing")
522  != std::string::npos);
523 
524  except ([&] //this creates a standard test config without some of the
525  //port sections
526  {
527  Env env {*this,
529  cfg = std::make_unique<Config>();
530  cfg->overwrite (ConfigSection::nodeDatabase (), "type", "memory");
531  cfg->overwrite (ConfigSection::nodeDatabase (), "path", "main");
532  cfg->deprecatedClearSection (ConfigSection::importNodeDatabase ());
533  cfg->legacy("database_path", "");
534  cfg->setupControl(true, true, true);
535  (*cfg)["server"].append("port_peer");
536  (*cfg)["server"].append("port_rpc");
537  (*cfg)["server"].append("port_ws");
538  return cfg;
539  }),
540  std::make_unique<CaptureLogs>(messages)};
541  });
542  BEAST_EXPECT (
543  messages.find ("Missing section: [port_peer]")
544  != std::string::npos);
545  }
546 
547  void
548  run() override
549  {
550  basicTests();
551  stressTest();
552  testBadConfig();
553  }
554 };
555 
557 
558 } // test
559 } // ripple
560 
ripple::test::Server_test::TestThread::TestThread
TestThread()
Definition: Server_test.cpp:56
ripple::test::Server_test::stressTest
void stressTest()
Definition: Server_test.cpp:303
beast::Journal::Sink
Abstraction for the underlying message destination.
Definition: Journal.h:76
ripple::test::Server_test::TestThread::~TestThread
~TestThread()
Definition: Server_test.cpp:62
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:101
std::exception
STL class.
ripple::Logs
Manages partitions for logging.
Definition: Log.h:49
ripple::test::Server_test::basicTests
void basicTests()
Definition: Server_test.cpp:279
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:120
ripple::test::Server_test::CaptureLogs::result_
std::string & result_
Definition: Server_test.cpp:403
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:34
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:418
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:69
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:402
boost
Definition: IPAddress.h:127
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:104
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:52
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:77
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:89
ripple::test::Server_test::run
void run() override
Definition: Server_test.cpp:548
ripple::test::Server_test::TestSink::TestSink
TestSink(beast::unit_test::suite &suite)
Definition: Server_test.cpp:82
stdexcept
ripple::test::Server_test::CaptureLogs::CaptureLogs
CaptureLogs(std::string &result)
Definition: Server_test.cpp:406
ripple::test::getEnvLocalhostAddr
const char * getEnvLocalhostAddr()
Definition: envconfig.h:32
ripple::test::Server_test::TestSink::suite_
beast::unit_test::suite & suite_
Definition: Server_test.cpp:79
beast::rfc2616::is_keep_alive
bool is_keep_alive(boost::beast::http::message< isRequest, Body, Fields > const &m)
Definition: rfc2616.h:478
ripple::test::Server_test::CaptureLogs
Log manager for CaptureSinks.
Definition: Server_test.cpp:400
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.
beast::Journal::Sink::Sink
Sink()=delete
ripple::test::Server_test::TestThread::work_
boost::optional< boost::asio::io_service::work > work_
Definition: Server_test.cpp:52
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:248
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:60
ripple::Session::complete
virtual void complete()=0
Indicate that the response is complete.
beast::severities::kInfo
@ kInfo
Definition: Journal.h:38
ripple::test::Server_test::TestThread::io_service_
boost::asio::io_service io_service_
Definition: Server_test.cpp:51
ripple::test::SuiteJournal
Definition: SuiteJournal.h:81
ripple::test::Server_test::TestThread
Definition: Server_test.cpp:48
ripple::test::Server_test::CaptureSink
sink for writing all log messages to a stringstream
Definition: Server_test.cpp:376
ripple::test::Server_test::TestHandler::onClose
void onClose(Session &session, boost::system::error_code const &)
Definition: Server_test.cpp:143
ripple::test::Server_test::CaptureSink::CaptureSink
CaptureSink(beast::severities::Severity threshold, std::stringstream &strm)
Definition: Server_test.cpp:380
ripple::Logs::threshold
beast::severities::Severity threshold() const
Definition: Log.cpp:146
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:98
beast::severities::Severity
Severity
Severity level / threshold of a Journal message.
Definition: Journal.h:32
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:378
ripple::test::Server_test::TestSink
Definition: Server_test.cpp:77
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:111
ripple::test::Server_test::CaptureLogs::~CaptureLogs
~CaptureLogs() override
Definition: Server_test.cpp:412
ripple::test::Server_test::TestThread::thread_
std::thread thread_
Definition: Server_test.cpp:53
ripple::test::Server_test::test_request
void test_request(boost::asio::ip::tcp::endpoint const &ep)
Definition: Server_test.cpp:223
ripple::test::stream_type
boost::beast::ssl_stream< socket_type > stream_type
Definition: Server_test.cpp:42
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:388
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:426
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:117
ripple::ConfigSection::nodeDatabase
static std::string nodeDatabase()
Definition: ConfigSections.h:32
std::thread::join
T join(T... args)
std::exception::what
T what(T... args)
beast
Definition: base_uint.h:582