JayBeams  0.1
Another project to have fun coding.
ut_acceptor.cpp
Go to the documentation of this file.
1 #include <jb/ehs/acceptor.hpp>
2 
3 #include <thread>
4 
5 #include <boost/asio/io_service.hpp>
6 #include <boost/test/unit_test.hpp>
7 
8 /// Wait for a connection close event in the dispatcher
9 namespace {
10 void wait_for_connection_close(
11  std::shared_ptr<jb::ehs::request_dispatcher> d, long last_count) {
12  using namespace std::chrono_literals;
13  // .. wait until the connection is closed ...
14  for (int c = 0; c != 100 and last_count == d->get_close_connection(); ++c) {
15  std::this_thread::sleep_for(10ms);
16  }
17  BOOST_CHECK_EQUAL(d->get_close_connection(), last_count + 1);
18 }
19 } // anonymous namespace
20 
21 /**
22  * @test Verify that jb::ehs::connection + jb::ehs::acceptor work as expected.
23  */
24 BOOST_AUTO_TEST_CASE(acceptor_base) {
25  // Let the operating system pick a listening address ...
26  boost::asio::ip::tcp::endpoint ep{boost::asio::ip::address_v4(), 0};
27 
28  // ... create a dispatcher and a handler for / ...
29  auto dispatcher = std::make_shared<jb::ehs::request_dispatcher>("test");
32  dispatcher->add_handler("/", [](request_type const&, response_type& res) {
33  res.insert("Content-type", "text/plain");
34  res.body = "OK\n";
35  });
36 
37  // ... create a IO service, and an acceptor in the default address ...
38  boost::asio::io_service io_service;
39  jb::ehs::acceptor acceptor(io_service, ep, dispatcher);
40 
41  // ... run a separate thread with the io_service so we can write
42  // synchronous code in the test, which is easier to follow ...
43  std::thread t([&io_service]() { io_service.run(); });
44 
45  // ... get the local listening endpoint ...
46  auto listen = acceptor.local_endpoint();
47  // ... create a separate io service for the client side ...
48  boost::asio::io_service io;
49  // ... open a socket to send the HTTP request ...
50  boost::asio::ip::tcp::socket sock{io};
51  sock.connect(listen);
52 
53  // ... prepare and send the request ...
56  request_type req{beast::http::verb::get, "/", 11};
57  req.set(beast::http::field::host, "0.0.0.0");
58  req.set(beast::http::field::user_agent, "acceptor_base");
59  // req.set(beast::http::field::connection, "close");
60  beast::http::write(sock, req);
61 
62  // ... receive the reply ...
63  beast::flat_buffer sb{8192};
64  response_type res;
65  beast::http::read(sock, sb, res);
66 
67  BOOST_CHECK_EQUAL(res.result_int(), 200);
68  BOOST_CHECK_EQUAL(res.version, 11);
69  BOOST_CHECK_EQUAL(res["server"], "test");
70  BOOST_CHECK_EQUAL(res.body, "OK\n");
71 
72  // ... closing the socket triggers more behaviors in the acceptor
73  // and connector classes ...
74  auto current = dispatcher->get_close_connection();
75  sock.close();
76  BOOST_TEST_CHECKPOINT("closing connection in acceptor_base");
77  wait_for_connection_close(dispatcher, current);
78 
79  // ... shutdown the acceptor and stop the io_service ...
80  io_service.dispatch([&acceptor, &io_service] {
81  acceptor.shutdown();
82  io_service.stop();
83  });
84  // ... wait for the acceptor thread ...
85  t.join();
86 }
87 
88 /**
89  * @test Verify that jb::ehs::connection handles read errors.
90  */
91 BOOST_AUTO_TEST_CASE(connection_read_error) {
92  using namespace std::chrono_literals;
93  // Let the operating system pick a listening address ...
94  boost::asio::ip::tcp::endpoint ep{boost::asio::ip::address_v4(), 0};
95 
96  // ... create a dispatcher and a handler for / ...
97  auto dispatcher = std::make_shared<jb::ehs::request_dispatcher>("test");
100  dispatcher->add_handler("/", [](request_type const&, response_type& res) {
101  res.insert("Content-type", "text/plain");
102  res.body = "OK\n";
103  });
104 
105  // ... create a IO service, and an acceptor in the default address ...
106  boost::asio::io_service io_service;
107  jb::ehs::acceptor acceptor(io_service, ep, dispatcher);
108 
109  // ... run a separate thread with the io_service so we can write
110  // synchronous code in the test, which is easier to follow ...
111  std::thread t([&io_service]() { io_service.run(); });
112 
113  // ... get the local listening endpoint ...
114  auto listen = acceptor.local_endpoint();
115 
116  // ... create a separate io service for the client side ...
117  boost::asio::io_service io;
118  // ... open a socket to send the HTTP request ...
119  boost::asio::ip::tcp::socket sock{io};
120  sock.connect(listen);
121  // ... prepare and send the request ...
122  using jb::ehs::request_type;
124  request_type req{beast::http::verb::get, "/", 11};
125  req.set("host", "0.0.0.0");
126  req.set("user-agent", "acceptor_base");
127  // ... we lie about the content length to make the server wait for
128  // more data ...
129  req.set(beast::http::field::content_length, "1000000");
130  beast::http::write(sock, req);
131 
132  // ... fetch the number of closed connections ...
133  long c = dispatcher->get_close_connection();
134 
135  // ... and now close the socket before the long message is sent ...
136  sock.close();
137 
138  // ... wait a few milliseconds for the server to detect the closed
139  // connection ...
140  for (int i = 0; i != 10 and c == dispatcher->get_close_connection(); ++i) {
141  std::this_thread::sleep_for(10ms);
142  }
143  BOOST_CHECK_EQUAL(c, 0);
144  BOOST_CHECK_EQUAL(dispatcher->get_close_connection(), 1);
145  BOOST_CHECK_EQUAL(dispatcher->get_read_error(), 1);
146 
147  // ... shutdown the acceptor and stop the io_service ...
148  io_service.dispatch([&acceptor, &io_service] {
149  acceptor.shutdown();
150  io_service.stop();
151  });
152  // ... wait for the acceptor thread ...
153  t.join();
154 }
155 
156 namespace {
157 /// Open and close a connection to @a ep
158 void cycle_connection(
159  std::shared_ptr<jb::ehs::request_dispatcher> d, boost::asio::io_service& io,
160  boost::asio::ip::tcp::endpoint const& ep, long expected_open_count,
161  long expected_close_count) {
162  using namespace std::chrono_literals;
163 
164  // ... get the number of open connections so far ...
165  auto open_count = d->get_open_connection();
166  BOOST_CHECK_EQUAL(open_count, expected_open_count);
167 
168  // ... open a socket to send the HTTP request ...
169  boost::asio::ip::tcp::socket sock{io};
170  sock.connect(ep);
171 
172  // ... wait until the connection is received ...
173  for (int c = 0; c != 10 and open_count == d->get_open_connection(); ++c) {
174  std::this_thread::sleep_for(10ms);
175  }
176  BOOST_CHECK_EQUAL(d->get_open_connection(), expected_open_count + 1);
177 
178  // ... get the number of closed connections so far ...
179  auto close_count = d->get_close_connection();
180  BOOST_CHECK_EQUAL(close_count, expected_close_count);
181 
182  // ... close the socket ...
183  sock.close();
184  BOOST_TEST_CHECKPOINT("closing connection in cycle_connection");
185  wait_for_connection_close(d, close_count);
186 }
187 
188 } // anonymous namespace
189 
190 /**
191  * @test Verify that jb::ehs::acceptor accepts multiple connections.
192  */
193 BOOST_AUTO_TEST_CASE(acceptor_multiple_connections) {
194  // Let the operating system pick a listening address ...
195  boost::asio::ip::tcp::endpoint ep{boost::asio::ip::address_v4(), 0};
196 
197  // ... create a dispatcher, with no handlers ...
198  auto dispatcher = std::make_shared<jb::ehs::request_dispatcher>("test");
199  // ... create a IO service, and an acceptor in the default address ...
200  boost::asio::io_service io_service;
201  jb::ehs::acceptor acceptor(io_service, ep, dispatcher);
202  // ... run a separate thread with the io_service so we can write
203  // synchronous code in the test, which is easier to follow ...
204  std::thread t([&io_service]() { io_service.run(); });
205 
206  // ... get the local listening endpoint ...
207  auto listen = acceptor.local_endpoint();
208 
209  // ... create a separate io service for the client side ...
210  boost::asio::io_service io;
211  cycle_connection(dispatcher, io, listen, 0, 0);
212  cycle_connection(dispatcher, io, listen, 1, 1);
213 
214  // ... shutdown the acceptor and stop the io_service ...
215  io_service.dispatch([&acceptor, &io_service] {
216  acceptor.shutdown();
217  io_service.stop();
218  });
219  // ... wait for the acceptor thread ...
220  t.join();
221 }
222 
223 /**
224  * @test Verify that jb::ehs::connection can handle multiple requests.
225  */
226 BOOST_AUTO_TEST_CASE(connection_multiple_requests) {
227  // Let the operating system pick a listening address ...
228  boost::asio::ip::tcp::endpoint ep{boost::asio::ip::address_v4(), 0};
229 
230  // ... create a dispatcher, with no handlers ...
231  auto dispatcher = std::make_shared<jb::ehs::request_dispatcher>("test");
232  // ... create a IO service, and an acceptor in the default address ...
233  boost::asio::io_service io_service;
234  jb::ehs::acceptor acceptor(io_service, ep, dispatcher);
235 
236  // ... run a separate thread with the io_service so we can write
237  // synchronous code in the test, which is easier to follow ...
238  std::thread t([&io_service]() { io_service.run(); });
239 
240  // ... get the local listening endpoint ...
241  auto listen = acceptor.local_endpoint();
242  // ... create a separate io service for the client side ...
243  boost::asio::io_service io;
244  // ... open a socket to send the HTTP request ...
245  boost::asio::ip::tcp::socket sock{io};
246  sock.connect(listen);
247 
248  // ... prepare and send the request ...
249  using jb::ehs::request_type;
251  request_type req{beast::http::verb::get, "/", 11};
252  req.set("host", "0.0.0.0");
253  req.set("user-agent", "acceptor_base");
254  beast::http::write(sock, req);
255 
256  // ... receive the reply ...
257  beast::flat_buffer sb;
258  response_type res;
259  beast::http::read(sock, sb, res);
260 
261  BOOST_CHECK_EQUAL(res.result_int(), 404);
262  BOOST_CHECK_EQUAL(res.version, 11);
263  BOOST_CHECK_EQUAL(res["server"], "test");
264 
265  // ... do the request+reply thing again ...
266  beast::http::write(sock, req);
267  beast::http::read(sock, sb, res);
268  BOOST_CHECK_EQUAL(res.result_int(), 404);
269  BOOST_CHECK_EQUAL(res.version, 11);
270  BOOST_CHECK_EQUAL(res["server"], "test");
271 
272  // ... closing the socket triggers more behaviors in the acceptor
273  // and connector classes ...
274  auto close_count = dispatcher->get_close_connection();
275  sock.close();
276  BOOST_TEST_CHECKPOINT("closing connection in connection_multiple_requests");
277  wait_for_connection_close(dispatcher, close_count);
278 
279  // ... shutdown the acceptor and stop the io_service ...
280  io_service.dispatch([&acceptor, &io_service] {
281  acceptor.shutdown();
282  io_service.stop();
283  });
284  // ... wait for the acceptor thread ...
285  t.join();
286 }
287 
288 /**
289  * @test Verify that jb::ehs::acceptor shutdown is safe to call twice.
290  */
291 BOOST_AUTO_TEST_CASE(acceptor_double_shutdown) {
292  // Let the operating system pick a listening address ...
293  boost::asio::ip::tcp::endpoint ep{boost::asio::ip::address_v4(), 0};
294 
295  // ... create a dispatcher, with no handlers ...
296  auto dispatcher = std::make_shared<jb::ehs::request_dispatcher>("test");
297  // ... create a IO service, and an acceptor in the default address ...
298  boost::asio::io_service io_service;
299  jb::ehs::acceptor acceptor(io_service, ep, dispatcher);
300 
301  acceptor.shutdown();
302  acceptor.shutdown();
303 
304  BOOST_CHECK_EQUAL(dispatcher->get_accept_error(), 0);
305 }
306 
307 /**
308  * @test Improve coverage forjb::ehs::acceptor.
309  */
310 BOOST_AUTO_TEST_CASE(acceptor_on_accept_closed) {
311  using namespace std::chrono_literals;
312  // Let the operating system pick a listening address ...
313  boost::asio::ip::tcp::endpoint ep{boost::asio::ip::address_v4(), 0};
314 
315  // ... create a dispatcher, with no handlers ...
316  auto dispatcher = std::make_shared<jb::ehs::request_dispatcher>("test");
317  // ... create a IO service, and an acceptor in the default address ...
318  boost::asio::io_service io_service;
319 
320  // ... the acceptor will register for the on_accept() callback ...
321  jb::ehs::acceptor acceptor(io_service, ep, dispatcher);
322 
323  // ... close the acceptor before it has a chance to do anything ...
324  acceptor.shutdown();
325 
326  // ... the thread will call on_accept() just because it is closed,
327  // and that will hit one more path ...
328  std::thread t([&io_service]() { io_service.run(); });
329 
330  for (int c = 0; c != 100 and 0 == dispatcher->get_accept_closed(); ++c) {
331  std::this_thread::sleep_for(10ms);
332  }
333  BOOST_CHECK_EQUAL(dispatcher->get_accept_closed(), 1);
334 
335  // ... shutdown everything ...
336  io_service.dispatch([&acceptor, &io_service] { io_service.stop(); });
337  t.join();
338 }
void shutdown()
Gracefully shutdown the acceptor.
Definition: acceptor.cpp:26
beast::http::request< beast::http::string_body > request_type
The request type used for JayBeams Embedded HTTP Servers.
Definition: base_types.hpp:17
beast::http::response< beast::http::string_body > response_type
The response type used for JayBeams Embedded HTTP Servers.
Definition: base_types.hpp:20
boost::asio::ip::tcp::endpoint local_endpoint() const
Return the local listening endpoint.
Definition: acceptor.hpp:32
BOOST_AUTO_TEST_CASE(acceptor_base)
Definition: ut_acceptor.cpp:24
Create a control server for the program.
Definition: acceptor.hpp:17