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