]>
Commit | Line | Data |
---|---|---|
20effc67 TL |
1 | // Copyright (c) 2019 Sorin Fetche |
2 | ||
3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
5 | ||
6 | // PLEASE NOTE: This example requires the Boost 1.70 version of Asio and Beast, which at the time of this | |
7 | // writing is in beta. | |
8 | ||
9 | // Example of a composed asynchronous operation which uses the LEAF library for error handling and reporting. | |
10 | // | |
11 | // Examples of running: | |
12 | // - in one terminal (re)run: ./asio_beast_leaf_rpc_v3 0.0.0.0 8080 | |
13 | // - in another run: | |
14 | // curl localhost:8080 -v -d "sum 0 1 2 3" | |
15 | // generating errors returned to the client: | |
16 | // curl localhost:8080 -v -X DELETE -d "" | |
17 | // curl localhost:8080 -v -d "mul 1 2x3" | |
18 | // curl localhost:8080 -v -d "div 1 0" | |
19 | // curl localhost:8080 -v -d "mod 1" | |
20 | // | |
21 | // Runs that showcase the error handling on the server side: | |
22 | // - error starting the server: | |
23 | // ./asio_beast_leaf_rpc_v3 0.0.0.0 80 | |
24 | // - error while running the server logic: | |
25 | // ./asio_beast_leaf_rpc_v3 0.0.0.0 8080 | |
26 | // curl localhost:8080 -v -d "error-quit" | |
27 | // | |
28 | #include <boost/algorithm/string/replace.hpp> | |
29 | #include <boost/asio/io_context.hpp> | |
30 | #include <boost/asio/ip/tcp.hpp> | |
31 | #include <boost/beast/core.hpp> | |
32 | #include <boost/beast/http.hpp> | |
33 | #include <boost/beast/version.hpp> | |
34 | #include <boost/format.hpp> | |
35 | #include <boost/leaf.hpp> | |
36 | #include <boost/spirit/include/qi_numeric.hpp> | |
37 | #include <boost/spirit/include/qi_parse.hpp> | |
38 | #include <deque> | |
39 | #include <iostream> | |
40 | #include <list> | |
41 | #include <optional> | |
42 | #include <string> | |
43 | ||
44 | namespace beast = boost::beast; | |
45 | namespace http = beast::http; | |
46 | namespace leaf = boost::leaf; | |
47 | namespace net = boost::asio; | |
48 | ||
49 | namespace { | |
50 | using error_code = boost::system::error_code; | |
51 | } // namespace | |
52 | ||
53 | // The operation being performed when an error occurs. | |
54 | struct e_last_operation { | |
55 | std::string_view value; | |
56 | }; | |
57 | ||
58 | // The HTTP request type. | |
59 | using request_t = http::request<http::string_body>; | |
60 | // The HTTP response type. | |
61 | using response_t = http::response<http::string_body>; | |
62 | ||
63 | response_t handle_request(request_t &&request); | |
64 | ||
65 | // A composed asynchronous operation that implements a basic remote calculator over HTTP. | |
66 | // It receives from the remote side commands such as: | |
67 | // sum 1 2 3 | |
68 | // div 3 2 | |
69 | // mod 1 0 | |
70 | // in the body of POST requests and sends back the result. | |
71 | // | |
72 | // Besides the calculator related commands, it also offer a special command: | |
73 | // - `error_quit` that asks the server to simulate a server side error that leads to the connection being dropped | |
74 | // . | |
75 | // | |
76 | // From the error handling perspective there are three parts of the implementation: | |
77 | // - the handling of an HTTP request and creating the response to send back | |
78 | // (see handle_request) | |
79 | // - the parsing and execution of the remote command we received as the body of an an HTTP POST request | |
80 | // (see execute_command()) | |
81 | // - this composed asynchronous operation which calls them | |
82 | // . | |
83 | // | |
84 | // This example operation is based on: | |
85 | // - https://github.com/boostorg/beast/blob/b02f59ff9126c5a17f816852efbbd0ed20305930/example/echo-op/echo_op.cpp | |
86 | // - part of | |
87 | // https://github.com/boostorg/beast/blob/b02f59ff9126c5a17f816852efbbd0ed20305930/example/advanced/server/advanced_server.cpp | |
88 | // | |
89 | template <class AsyncStream, typename ErrorContext, typename CompletionToken> | |
90 | auto async_demo_rpc(AsyncStream &stream, ErrorContext &error_context, CompletionToken &&token) -> | |
91 | typename net::async_result<typename std::decay<CompletionToken>::type, void(leaf::result<void>)>::return_type { | |
92 | ||
93 | static_assert(beast::is_async_stream<AsyncStream>::value, "AsyncStream requirements not met"); | |
94 | ||
95 | using handler_type = | |
96 | typename net::async_completion<CompletionToken, void(leaf::result<void>)>::completion_handler_type; | |
97 | using base_type = beast::stable_async_base<handler_type, beast::executor_type<AsyncStream>>; | |
98 | struct internal_op : base_type { | |
99 | // This object must have a stable address | |
100 | struct temporary_data { | |
101 | beast::flat_buffer buffer; | |
102 | std::optional<http::request_parser<request_t::body_type>> parser; | |
103 | std::optional<response_t> response; | |
104 | }; | |
105 | ||
106 | AsyncStream &m_stream; | |
107 | ErrorContext &m_error_context; | |
108 | temporary_data &m_data; | |
109 | bool m_write_and_quit; | |
110 | ||
111 | internal_op(AsyncStream &stream, ErrorContext &error_context, handler_type &&handler) | |
112 | : base_type{std::move(handler), stream.get_executor()}, m_stream{stream}, m_error_context{error_context}, | |
113 | m_data{beast::allocate_stable<temporary_data>(*this)}, m_write_and_quit{false} { | |
114 | start_read_request(); | |
115 | } | |
116 | ||
117 | void operator()(error_code ec, std::size_t /*bytes_transferred*/ = 0) { | |
118 | leaf::result<bool> result_continue_execution; | |
119 | { | |
120 | auto active_context = activate_context(m_error_context); | |
121 | auto load = leaf::on_error(e_last_operation{m_data.response ? "async_demo_rpc::continuation-write" | |
122 | : "async_demo_rpc::continuation-read"}); | |
123 | if (ec == http::error::end_of_stream) { | |
124 | // The remote side closed the connection. | |
125 | result_continue_execution = false; | |
126 | } else if (ec) { | |
127 | result_continue_execution = leaf::new_error(ec); | |
128 | } else { | |
129 | result_continue_execution = leaf::exception_to_result([&]() -> leaf::result<bool> { | |
130 | if (!m_data.response) { | |
131 | // Process the request we received. | |
132 | m_data.response = handle_request(std::move(m_data.parser->release())); | |
133 | m_write_and_quit = m_data.response->need_eof(); | |
134 | http::async_write(m_stream, *m_data.response, std::move(*this)); | |
135 | return true; | |
136 | } | |
137 | ||
138 | // If getting here, we completed a write operation. | |
139 | m_data.response.reset(); | |
140 | // And start reading a new message if not quitting | |
141 | // (i.e. the message semantics of the last response we sent required an end of file) | |
142 | if (!m_write_and_quit) { | |
143 | start_read_request(); | |
144 | return true; | |
145 | } | |
146 | ||
147 | // We didn't initiate any new async operation above, so we will not continue the execution. | |
148 | return false; | |
149 | }); | |
150 | } | |
151 | // The activation object and load_last_operation need to be reset before calling the completion handler | |
152 | // This is because, in general, the completion handler may be called directly or posted and if posted, | |
153 | // it could execute in another thread. This means that regardless of how the handler gets to be actually | |
154 | // called we must ensure that it is not called with the error context active. | |
155 | // Note: An error context cannot be activated twice | |
156 | } | |
157 | if (!result_continue_execution) { | |
158 | // We don't continue the execution due to an error, calling the completion handler | |
159 | this->complete_now(result_continue_execution.error()); | |
160 | } else if( !*result_continue_execution ) { | |
161 | // We don't continue the execution due to the flag not being set, calling the completion handler | |
162 | this->complete_now(leaf::result<void>{}); | |
163 | } | |
164 | } | |
165 | ||
166 | void start_read_request() { | |
167 | m_data.parser.emplace(); | |
168 | m_data.parser->body_limit(1024); | |
169 | http::async_read(m_stream, m_data.buffer, *m_data.parser, std::move(*this)); | |
170 | } | |
171 | }; | |
172 | ||
173 | auto initiation = [](auto &&completion_handler, AsyncStream *stream, ErrorContext *error_context) { | |
174 | internal_op op{*stream, *error_context, std::forward<decltype(completion_handler)>(completion_handler)}; | |
175 | }; | |
176 | ||
177 | // We are in the "initiation" part of the async operation. | |
178 | [[maybe_unused]] auto load = leaf::on_error(e_last_operation{"async_demo_rpc::initiation"}); | |
179 | return net::async_initiate<CompletionToken, void(leaf::result<void>)>(initiation, token, &stream, &error_context); | |
180 | } | |
181 | ||
182 | // The location of a int64 parse error. | |
183 | // It refers the range of characters from which the parsing was done. | |
184 | struct e_parse_int64_error { | |
185 | using location_base = std::pair<std::string_view const, std::string_view::const_iterator>; | |
186 | struct location : public location_base { | |
187 | using location_base::location_base; | |
188 | ||
189 | friend std::ostream &operator<<(std::ostream &os, location const &value) { | |
190 | auto const &sv = value.first; | |
191 | std::size_t pos = std::distance(sv.begin(), value.second); | |
192 | if (pos == 0) { | |
193 | os << "->\"" << sv << "\""; | |
194 | } else if (pos < sv.size()) { | |
195 | os << "\"" << sv.substr(0, pos) << "\"->\"" << sv.substr(pos) << "\""; | |
196 | } else { | |
197 | os << "\"" << sv << "\"<-"; | |
198 | } | |
199 | return os; | |
200 | } | |
201 | }; | |
202 | ||
203 | location value; | |
204 | }; | |
205 | ||
206 | // Parses an integer from a string_view. | |
207 | leaf::result<std::int64_t> parse_int64(std::string_view word) { | |
208 | auto const begin = word.begin(); | |
209 | auto const end = word.end(); | |
210 | std::int64_t value = 0; | |
211 | auto i = begin; | |
212 | bool result = boost::spirit::qi::parse(i, end, boost::spirit::long_long, value); | |
213 | if (!result || i != end) { | |
214 | return leaf::new_error(e_parse_int64_error{std::make_pair(word, i)}); | |
215 | } | |
216 | return value; | |
217 | } | |
218 | ||
219 | // The command being executed while we get an error. | |
220 | // It refers the range of characters from which the command was extracted. | |
221 | struct e_command { | |
222 | std::string_view value; | |
223 | }; | |
224 | ||
225 | // The details about an incorrect number of arguments error | |
226 | // Some commands may accept a variable number of arguments (e.g. greater than 1 would mean [2, SIZE_MAX]). | |
227 | struct e_unexpected_arg_count { | |
228 | struct arg_info { | |
229 | std::size_t count; | |
230 | std::size_t min; | |
231 | std::size_t max; | |
232 | ||
233 | friend std::ostream &operator<<(std::ostream &os, arg_info const &value) { | |
234 | os << value.count << " (required: "; | |
235 | if (value.min == value.max) { | |
236 | os << value.min; | |
237 | } else if (value.max < SIZE_MAX) { | |
238 | os << "[" << value.min << ", " << value.max << "]"; | |
239 | } else { | |
240 | os << "[" << value.min << ", MAX]"; | |
241 | } | |
242 | os << ")"; | |
243 | return os; | |
244 | } | |
245 | }; | |
246 | ||
247 | arg_info value; | |
248 | }; | |
249 | ||
250 | // The HTTP status that should be returned in case we get into an error. | |
251 | struct e_http_status { | |
252 | http::status value; | |
253 | }; | |
254 | ||
255 | // Unexpected HTTP method. | |
256 | struct e_unexpected_http_method { | |
257 | http::verb value; | |
258 | }; | |
259 | ||
260 | // The E-type that describes the `error_quit` command as an error condition. | |
261 | struct e_error_quit { | |
262 | struct none_t {}; | |
263 | none_t value; | |
264 | }; | |
265 | ||
266 | // Processes a remote command. | |
267 | leaf::result<std::string> execute_command(std::string_view line) { | |
268 | // Split the command in words. | |
269 | std::list<std::string_view> words; // or std::deque<std::string_view> words; | |
270 | ||
271 | char const *const ws = "\t \r\n"; | |
272 | auto skip_ws = [&](std::string_view &line) { | |
273 | if (auto pos = line.find_first_not_of(ws); pos != std::string_view::npos) { | |
274 | line = line.substr(pos); | |
275 | } else { | |
276 | line = std::string_view{}; | |
277 | } | |
278 | }; | |
279 | ||
280 | skip_ws(line); | |
281 | while (!line.empty()) { | |
282 | std::string_view word; | |
283 | if (auto pos = line.find_first_of(ws); pos != std::string_view::npos) { | |
284 | word = line.substr(0, pos); | |
285 | line = line.substr(pos + 1); | |
286 | } else { | |
287 | word = line; | |
288 | line = std::string_view{}; | |
289 | } | |
290 | ||
291 | if (!word.empty()) { | |
292 | words.push_back(word); | |
293 | } | |
294 | skip_ws(line); | |
295 | } | |
296 | ||
297 | static char const *const help = "Help:\n" | |
298 | " error-quit Simulated error to end the session\n" | |
299 | " sum <int64>* Addition\n" | |
300 | " sub <int64>+ Substraction\n" | |
301 | " mul <int64>* Multiplication\n" | |
302 | " div <int64>+ Division\n" | |
303 | " mod <int64> <int64> Remainder\n" | |
304 | " <anything else> This message"; | |
305 | ||
306 | if (words.empty()) { | |
307 | return std::string(help); | |
308 | } | |
309 | ||
310 | auto command = words.front(); | |
311 | words.pop_front(); | |
312 | ||
313 | auto load_cmd = leaf::on_error(e_command{command}, e_http_status{http::status::bad_request}); | |
314 | std::string response; | |
315 | ||
316 | if (command == "error-quit") { | |
317 | return leaf::new_error(e_error_quit{}); | |
318 | } else if (command == "sum") { | |
319 | std::int64_t sum = 0; | |
320 | for (auto const &w : words) { | |
321 | BOOST_LEAF_AUTO(i, parse_int64(w)); | |
322 | sum += i; | |
323 | } | |
324 | response = std::to_string(sum); | |
325 | } else if (command == "sub") { | |
326 | if (words.size() < 2) { | |
327 | return leaf::new_error(e_unexpected_arg_count{words.size(), 2, SIZE_MAX}); | |
328 | } | |
329 | BOOST_LEAF_AUTO(sub, parse_int64(words.front())); | |
330 | words.pop_front(); | |
331 | for (auto const &w : words) { | |
332 | BOOST_LEAF_AUTO(i, parse_int64(w)); | |
333 | sub -= i; | |
334 | } | |
335 | response = std::to_string(sub); | |
336 | } else if (command == "mul") { | |
337 | std::int64_t mul = 1; | |
338 | for (auto const &w : words) { | |
339 | BOOST_LEAF_AUTO(i, parse_int64(w)); | |
340 | mul *= i; | |
341 | } | |
342 | response = std::to_string(mul); | |
343 | } else if (command == "div") { | |
344 | if (words.size() < 2) { | |
345 | return leaf::new_error(e_unexpected_arg_count{words.size(), 2, SIZE_MAX}); | |
346 | } | |
347 | BOOST_LEAF_AUTO(div, parse_int64(words.front())); | |
348 | words.pop_front(); | |
349 | for (auto const &w : words) { | |
350 | BOOST_LEAF_AUTO(i, parse_int64(w)); | |
351 | if (i == 0) { | |
352 | // In some cases this command execution function might throw, not just return an error. | |
353 | throw std::runtime_error{"division by zero"}; | |
354 | } | |
355 | div /= i; | |
356 | } | |
357 | response = std::to_string(div); | |
358 | } else if (command == "mod") { | |
359 | if (words.size() != 2) { | |
360 | return leaf::new_error(e_unexpected_arg_count{words.size(), 2, 2}); | |
361 | } | |
362 | BOOST_LEAF_AUTO(i1, parse_int64(words.front())); | |
363 | words.pop_front(); | |
364 | BOOST_LEAF_AUTO(i2, parse_int64(words.front())); | |
365 | words.pop_front(); | |
366 | if (i2 == 0) { | |
367 | // In some cases this command execution function might throw, not just return an error. | |
368 | throw leaf::exception(std::runtime_error{"division by zero"}); | |
369 | } | |
370 | response = std::to_string(i1 % i2); | |
371 | } else { | |
372 | response = help; | |
373 | } | |
374 | ||
375 | return response; | |
376 | } | |
377 | ||
378 | std::string diagnostic_to_str(leaf::verbose_diagnostic_info const &diag) { | |
379 | auto str = boost::str(boost::format("%1%") % diag); | |
380 | boost::algorithm::replace_all(str, "\n", "\n "); | |
381 | return "\nDetailed error diagnostic:\n----\n" + str + "\n----"; | |
382 | }; | |
383 | ||
384 | // Handles an HTTP request and returns the response to send back. | |
385 | response_t handle_request(request_t &&request) { | |
386 | ||
387 | auto msg_prefix = [](e_command const *cmd) { | |
388 | if (cmd != nullptr) { | |
389 | return boost::str(boost::format("Error (%1%):") % cmd->value); | |
390 | } | |
391 | return std::string("Error:"); | |
392 | }; | |
393 | ||
394 | auto make_sr = [](e_http_status const *status, std::string &&response) { | |
395 | return std::make_pair(status != nullptr ? status->value : http::status::internal_server_error, | |
396 | std::move(response)); | |
397 | }; | |
398 | ||
399 | // In this variant of the RPC example we execute the remote command and handle any errors coming from it | |
400 | // in one place (using `leaf::try_handle_all`). | |
401 | auto pair_status_response = leaf::try_handle_all( | |
402 | [&]() -> leaf::result<std::pair<http::status, std::string>> { | |
403 | if (request.method() != http::verb::post) { | |
404 | return leaf::new_error(e_unexpected_http_method{http::verb::post}, | |
405 | e_http_status{http::status::bad_request}); | |
406 | } | |
407 | BOOST_LEAF_AUTO(response, execute_command(request.body())); | |
408 | return std::make_pair(http::status::ok, std::move(response)); | |
409 | }, | |
410 | // For the `error_quit` command and associated error condition we have the error handler itself fail | |
411 | // (by throwing). This means that the server will not send any response to the client, it will just | |
412 | // shutdown the connection. | |
413 | // This implementation showcases two aspects: | |
414 | // - that the implementation of error handling can fail, too | |
415 | // - how the asynchronous operation calling this error handling function reacts to this failure. | |
416 | [](e_error_quit const &) -> std::pair<http::status, std::string> { throw std::runtime_error("error_quit"); }, | |
417 | // For the rest of error conditions we just build a message to be sent to the remote client. | |
418 | [&](e_parse_int64_error const &e, e_http_status const *status, e_command const *cmd, | |
419 | leaf::verbose_diagnostic_info const &diag) { | |
420 | return make_sr(status, boost::str(boost::format("%1% int64 parse error: %2%") % msg_prefix(cmd) % e.value) + | |
421 | diagnostic_to_str(diag)); | |
422 | }, | |
423 | [&](e_unexpected_arg_count const &e, e_http_status const *status, e_command const *cmd, | |
424 | leaf::verbose_diagnostic_info const &diag) { | |
425 | return make_sr(status, | |
426 | boost::str(boost::format("%1% wrong argument count: %2%") % msg_prefix(cmd) % e.value) + | |
427 | diagnostic_to_str(diag)); | |
428 | }, | |
429 | [&](e_unexpected_http_method const &e, e_http_status const *status, e_command const *cmd, | |
430 | leaf::verbose_diagnostic_info const &diag) { | |
431 | return make_sr(status, boost::str(boost::format("%1% unexpected HTTP method. Expected: %2%") % | |
432 | msg_prefix(cmd) % e.value) + | |
433 | diagnostic_to_str(diag)); | |
434 | }, | |
435 | [&](std::exception const & e, e_http_status const *status, e_command const *cmd, | |
436 | leaf::verbose_diagnostic_info const &diag) { | |
437 | return make_sr(status, boost::str(boost::format("%1% %2%") % msg_prefix(cmd) % e.what()) + | |
438 | diagnostic_to_str(diag)); | |
439 | }, | |
440 | [&](e_http_status const *status, e_command const *cmd, leaf::verbose_diagnostic_info const &diag) { | |
441 | return make_sr(status, boost::str(boost::format("%1% unknown failure") % msg_prefix(cmd)) + | |
442 | diagnostic_to_str(diag)); | |
443 | }); | |
444 | response_t response{pair_status_response.first, request.version()}; | |
445 | response.set(http::field::server, "Example-with-" BOOST_BEAST_VERSION_STRING); | |
446 | response.set(http::field::content_type, "text/plain"); | |
447 | response.keep_alive(request.keep_alive()); | |
448 | pair_status_response.second += "\n"; | |
449 | response.body() = std::move(pair_status_response.second); | |
450 | response.prepare_payload(); | |
451 | return response; | |
452 | } | |
453 | ||
454 | int main(int argc, char **argv) { | |
455 | auto msg_prefix = [](e_last_operation const *op) { | |
456 | if (op != nullptr) { | |
457 | return boost::str(boost::format("Error (%1%): ") % op->value); | |
458 | } | |
459 | return std::string("Error: "); | |
460 | }; | |
461 | ||
462 | // Error handler for internal server internal errors (not communicated to the remote client). | |
463 | auto error_handlers = std::make_tuple( | |
464 | [&](std::exception_ptr const &ep, e_last_operation const *op) { | |
465 | return leaf::try_handle_all( | |
466 | [&]() -> leaf::result<int> { std::rethrow_exception(ep); }, | |
467 | [&](std::exception const & e, leaf::verbose_diagnostic_info const &diag) { | |
468 | std::cerr << msg_prefix(op) << e.what() << " (captured)" << diagnostic_to_str(diag) | |
469 | << std::endl; | |
470 | return -11; | |
471 | }, | |
472 | [&](leaf::verbose_diagnostic_info const &diag) { | |
473 | std::cerr << msg_prefix(op) << "unknown (captured)" << diagnostic_to_str(diag) << std::endl; | |
474 | return -12; | |
475 | }); | |
476 | }, | |
477 | [&](std::exception const & e, e_last_operation const *op, leaf::verbose_diagnostic_info const &diag) { | |
478 | std::cerr << msg_prefix(op) << e.what() << diagnostic_to_str(diag) << std::endl; | |
479 | return -21; | |
480 | }, | |
481 | [&](error_code ec, leaf::verbose_diagnostic_info const &diag, e_last_operation const *op) { | |
482 | std::cerr << msg_prefix(op) << ec << ":" << ec.message() << diagnostic_to_str(diag) << std::endl; | |
483 | return -22; | |
484 | }, | |
485 | [&](leaf::verbose_diagnostic_info const &diag, e_last_operation const *op) { | |
486 | std::cerr << msg_prefix(op) << "unknown" << diagnostic_to_str(diag) << std::endl; | |
487 | return -23; | |
488 | }); | |
489 | ||
490 | // Top level try block and error handler. | |
491 | // It will handle errors from starting the server for example failure to bind to a given port | |
492 | // (e.g. ports less than 1024 if not running as root) | |
493 | return leaf::try_handle_all( | |
494 | [&]() -> leaf::result<int> { | |
495 | auto load = leaf::on_error(e_last_operation{"main"}); | |
496 | if (argc != 3) { | |
497 | std::cerr << "Usage: " << argv[0] << " <address> <port>" << std::endl; | |
498 | std::cerr << "Example:\n " << argv[0] << " 0.0.0.0 8080" << std::endl; | |
499 | return -1; | |
500 | } | |
501 | ||
502 | auto const address{net::ip::make_address(argv[1])}; | |
503 | auto const port{static_cast<std::uint16_t>(std::atoi(argv[2]))}; | |
504 | net::ip::tcp::endpoint const endpoint{address, port}; | |
505 | ||
506 | net::io_context io_context; | |
507 | ||
508 | // Start the server acceptor and wait for a client. | |
509 | net::ip::tcp::acceptor acceptor{io_context, endpoint}; | |
510 | ||
511 | auto local_endpoint = acceptor.local_endpoint(); | |
512 | auto address_try_msg = acceptor.local_endpoint().address().to_string(); | |
513 | if (address_try_msg == "0.0.0.0") { | |
514 | address_try_msg = "localhost"; | |
515 | } | |
516 | std::cout << "Server: Started on: " << local_endpoint << std::endl; | |
517 | std::cout << "Try in a different terminal:\n" | |
518 | << " curl " << address_try_msg << ":" << local_endpoint.port() << " -d \"\"\nor\n" | |
519 | << " curl " << address_try_msg << ":" << local_endpoint.port() << " -d \"sum 1 2 3\"" | |
520 | << std::endl; | |
521 | ||
522 | auto socket = acceptor.accept(); | |
523 | std::cout << "Server: Client connected: " << socket.remote_endpoint() << std::endl; | |
524 | ||
525 | // The error context for the async operation. | |
526 | auto error_context = leaf::make_context(error_handlers); | |
527 | int rv = 0; | |
528 | async_demo_rpc(socket, error_context, [&](leaf::result<void> result) { | |
529 | // Note: In case we wanted to add some additional information to the error associated with the result | |
530 | // we would need to activate the error-context | |
531 | auto active_context = activate_context(error_context); | |
532 | if (result) { | |
533 | std::cout << "Server: Client work completed successfully" << std::endl; | |
534 | rv = 0; | |
535 | } else { | |
536 | // Handle errors from running the server logic | |
537 | leaf::result<int> result_int{result.error()}; | |
538 | rv = error_context.handle_error<int>(result_int.error(), error_handlers); | |
539 | } | |
540 | }); | |
541 | io_context.run(); | |
542 | ||
543 | // Let the remote side know we are shutting down. | |
544 | error_code ignored; | |
545 | socket.shutdown(net::ip::tcp::socket::shutdown_both, ignored); | |
546 | return rv; | |
547 | }, | |
548 | error_handlers); | |
549 | } |