]> git.proxmox.com Git - ceph.git/blame - ceph/src/boost/libs/beast/example/echo-op/echo_op.cpp
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / boost / libs / beast / example / echo-op / echo_op.cpp
CommitLineData
b32b8144 1//
92f5a8d4 2// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
b32b8144
FG
3//
4// Distributed under the Boost Software License, Version 1.0. (See accompanying
5// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6//
7// Official repository: https://github.com/boostorg/beast
8//
9
10#include <boost/beast/core.hpp>
11#include <boost/asio.hpp>
12#include <cstddef>
13#include <iostream>
14#include <memory>
15#include <utility>
16
92f5a8d4
TL
17namespace net = boost::asio;
18namespace beast = boost::beast;
19
b32b8144
FG
20//[example_core_echo_op_1
21
22template<
23 class AsyncStream,
92f5a8d4 24 class DynamicBuffer,
b32b8144
FG
25 class CompletionToken>
26auto
92f5a8d4 27async_echo (AsyncStream& stream, DynamicBuffer& buffer, CompletionToken&& token)
b32b8144
FG
28
29//]
92f5a8d4
TL
30 ->
31 typename net::async_result<
32 typename std::decay<CompletionToken>::type,
33 void(beast::error_code)>::return_type;
34
35//------------------------------------------------------------------------------
b32b8144
FG
36
37//[example_core_echo_op_2
38
39/** Asynchronously read a line and echo it back.
40
41 This function is used to asynchronously read a line ending
92f5a8d4
TL
42 in a newline (`"\n"`) from the stream, and then write
43 it back.
44
45 This call always returns immediately. The asynchronous operation
46 will continue until one of the following conditions is true:
b32b8144 47
92f5a8d4 48 @li A line was read in and written back on the stream
b32b8144
FG
49
50 @li An error occurs.
51
92f5a8d4
TL
52 The algorithm, known as a <em>composed asynchronous operation</em>,
53 is implemented in terms of calls to the stream's `async_read_some`
54 and `async_write_some` function. The program must ensure that no
55 other reads or writes are performed until this operation completes.
56
57 Since the length of the line is not known ahead of time, the
58 implementation may read additional characters that lie past the
59 first line. These characters are stored in the dynamic buffer_.
60 The same dynamic buffer must be presented again in each call,
61 to provide the implementation with any leftover bytes.
62
63 @param stream The stream to operate on. The type must meet the
64 requirements of <em>AsyncReadStream</em> and @AsyncWriteStream
b32b8144 65
92f5a8d4
TL
66 @param buffer A dynamic buffer to hold implementation-defined
67 temporary data. Ownership is not transferred; the caller is
68 responsible for ensuring that the lifetime of this object is
69 extended least until the completion handler is invoked.
b32b8144 70
92f5a8d4
TL
71 @param token The handler to be called when the operation completes.
72 The implementation takes ownership of the handler by performing a decay-copy.
73 The handler must be invocable with this signature:
b32b8144
FG
74 @code
75 void handler(
92f5a8d4 76 beast::error_code error // Result of operation.
b32b8144
FG
77 );
78 @endcode
92f5a8d4
TL
79
80 Regardless of whether the asynchronous operation completes immediately or
81 not, the handler will not be invoked from within this function. Invocation
82 of the handler will be performed in a manner equivalent to using
83 `net::post`.
b32b8144
FG
84*/
85template<
86 class AsyncStream,
92f5a8d4 87 class DynamicBuffer,
b32b8144 88 class CompletionToken>
92f5a8d4
TL
89auto
90async_echo (
b32b8144 91 AsyncStream& stream,
92f5a8d4
TL
92 DynamicBuffer& buffer, /*< Unlike Asio, we pass by non-const reference instead of rvalue-ref >*/
93 CompletionToken&& token) ->
94 typename net::async_result< /*< `async_result` deduces the return type from the completion handler >*/
95 typename std::decay<CompletionToken>::type,
96 void(beast::error_code) /*< The completion handler signature goes here >*/
97 >::return_type;
b32b8144
FG
98//]
99
92f5a8d4 100//[example_core_echo_op_3
b32b8144 101
b32b8144 102template<class AsyncStream, class Handler>
92f5a8d4
TL
103class echo_op;
104
105// This example uses the Asio's stackless "fauxroutines", implemented
106// using a macro-based solution. It makes the code easier to write and
107// easier to read. This include file defines the necessary macros and types.
108#include <boost/asio/yield.hpp>
109
110// Read a line and echo it back
111//
112template<
113 class AsyncStream,
114 class DynamicBuffer,
115 class CompletionToken>
116auto
117async_echo(
118 AsyncStream& stream,
119 DynamicBuffer& buffer,
120 CompletionToken&& token) ->
121 typename net::async_result<
122 typename std::decay<CompletionToken>::type,
123 void(beast::error_code)>::return_type /*< The completion handler signature goes here >*/
b32b8144 124{
92f5a8d4
TL
125 // Perform some type checks using static assert, this helps
126 // with more friendly error messages when passing the wrong types.
127 static_assert(
128 beast::is_async_stream<AsyncStream>::value,
129 "AsyncStream type requirements not met");
130 static_assert(
131 net::is_dynamic_buffer<DynamicBuffer>::value,
132 "DynamicBuffer type requirements not met");
133
134 // This class template deduces the actual handler type from a
135 // CompletionToken, captures a local reference to the handler,
136 // and creates the `async_result` object which becomes the
137 // return value of this initiating function.
138
139 net::async_completion<CompletionToken, void(beast::error_code)> init(token);
140
141 // The helper macro BOOST_ASIO_HANDLER_TYPE converts the completion
142 // token type into a concrete handler type of the correct signature.
143
144 using handler_type = BOOST_ASIO_HANDLER_TYPE(CompletionToken, void(beast::error_code));
145
146 // The class template `async_base` holds the caller's completion
147 // handler for us, and provides all of the boilerplate for forwarding
148 // the associated allocator and associated executor from the caller's
149 // handler to our operation. It also maintains a `net::executor_work_guard`
150 // for the executor associated with the stream. This work guard is
151 // inexpensive, and prevents the execution context from running out
152 // of work. It is usually necessary although rarely it can be skipped
153 // depending on the operation (this echo example needs it because it
154 // performs more than one asynchronous operation in a row).
155 // We declare this type alias to make the code easier to read.
156
157 using base_type = beast::async_base<
158 handler_type, /*< The type of the completion handler obtained from the token >*/
159 beast::executor_type<AsyncStream> /*< The type of executor used by the stream to dispatch asynchronous operations >*/
160 >;
161
162 // This nested class implements the echo composed operation as a
163 // stateful completion handler. We derive from `async_base` to
164 // take care of boilerplate and we derived from asio::coroutine to
165 // allow the reenter and yield keywords to work.
166
167 struct echo_op : base_type, boost::asio::coroutine
b32b8144 168 {
92f5a8d4
TL
169 AsyncStream& stream_;
170 DynamicBuffer& buffer_;
171
172 echo_op(
173 AsyncStream& stream,
174 DynamicBuffer& buffer,
175 handler_type&& handler)
176 : base_type(
177 std::move(handler), /*< The `async_base` helper takes ownership of the handler, >*/
178 stream.get_executor()) /*< and also needs to know which executor to use. >*/
179 , stream_(stream)
180 , buffer_(buffer)
b32b8144 181 {
92f5a8d4
TL
182 // Launch the operation directly from the constructor. We
183 // pass `false` for `cont` to indicate that the calling
184 // thread does not represent a continuation of our
185 // asynchronous control flow.
186 (*this)({}, 0, false);
b32b8144 187 }
b32b8144 188
92f5a8d4
TL
189 // If a newline is present in the buffer sequence, this function returns
190 // the number of characters from the beginning of the buffer up to the
191 // newline, including the newline character. Otherwise it returns zero.
b32b8144 192
92f5a8d4
TL
193 std::size_t
194 find_newline(typename DynamicBuffer::const_buffers_type const& buffers)
195 {
196 // The `buffers_iterator` class template provides random-access
197 // iterators into a buffer sequence. Use the standard algorithm
198 // to look for the new line if it exists.
b32b8144 199
92f5a8d4
TL
200 auto begin = net::buffers_iterator<
201 typename DynamicBuffer::const_buffers_type>::begin(buffers);
202 auto end = net::buffers_iterator<
203 typename DynamicBuffer::const_buffers_type>::end(buffers);
204 auto result = std::find(begin, end, '\n');
b32b8144 205
92f5a8d4
TL
206 if(result == end)
207 return 0; // not found
b32b8144 208
92f5a8d4
TL
209 return result + 1 - begin;
210 }
b32b8144 211
92f5a8d4
TL
212 // This is the entry point of our completion handler. Every time an
213 // asynchronous operation completes, this function will be invoked.
b32b8144 214
92f5a8d4
TL
215 void
216 operator()(
217 beast::error_code ec,
218 std::size_t bytes_transferred = 0,
219 bool cont = true) /*< Second and subsequent invocations will seee `cont=true`. */
220 {
221 // The `reenter` keyword transfers control to the last
222 // yield point, or to the beginning of the scope if
223 // this is the first time.
224
225 reenter(*this)
226 {
227 for(;;)
228 {
229 std::size_t pos;
230
231 // Search for a newline in the readable bytes of the buffer
232 pos = find_newline(buffer_.data());
233
234 // If we don't have the newline, then read more
235 if(pos == 0)
236 {
237 std::size_t bytes_to_read;
238
239 // Determine the number of bytes to read,
240 // using available capacity in the buffer first.
241
242 bytes_to_read = std::min<std::size_t>(
243 std::max<std::size_t>(512, // under 512 is too little,
244 buffer_.capacity() - buffer_.size()),
245 std::min<std::size_t>(65536, // and over 65536 is too much.
246 buffer_.max_size() - buffer_.size()));
247
248 // Read some data into our dynamic buffer_. We transfer
249 // ownership of the composed operation by using the
250 // `std::move(*this)` idiom. The `yield` keyword causes
251 // the function to return immediately after the initiating
252 // function returns.
253
254 yield stream_.async_read_some(
255 buffer_.prepare(bytes_to_read), std::move(*this));
256
257 // After the `async_read_some` completes, control is
258 // transferred to this line by the `reenter` keyword.
259
260 // Move the bytes read from the writable area to the
261 // readable area.
262
263 buffer_.commit(bytes_transferred);
264
265 // If an error occurs, deliver it to the caller's completion handler.
266 if(ec)
267 break;
268
269 // Keep looping until we get the newline
270 continue;
271 }
272
273 // We have our newline, so send the first `pos` bytes of the
274 // buffers. The function `buffers_prefix` returns the front part
275 // of the buffers we want.
276
277 yield net::async_write(stream_,
278 beast::buffers_prefix(pos, buffer_.data()), std::move(*this));
279
280 // After the `async_write` completes, our completion handler will
281 // be invoked with the error and the number of bytes transferred,
282 // and the `reenter` statement above will cause control to jump
283 // to the following line. The variable `pos` is no longer valid
284 // (remember that we returned from the function using `yield` above)
285 // but we can use `bytes_transferred` to know how much of the buffer
286 // to consume. With "real" coroutines this will be easier and more
287 // natural.
288
289 buffer_.consume(bytes_transferred);
290
291 // The loop terminates here, and we will either deliver a
292 // successful result or an error to the caller's completion handler.
293
294 break;
295 }
296
297 // When a composed operation completes immediately, it must not
298 // directly invoke the completion handler otherwise it could
299 // lead to unfairness, starvation, or stack overflow. Therefore,
300 // if cont == false (meaning, that the call stack still includes
301 // the frame of the initiating function) then we need to use
302 // `net::post` to cause us to be called again after the initiating
303 // function. The function `async_base::invoke` takes care of
304 // calling the final completion handler, using post if the
305 // first argument is false, otherwise invoking it directly.
306
307 this->complete(cont, ec);
308 }
309 }
310 };
b32b8144
FG
311
312 // Create the composed operation and launch it. This is a constructor
313 // call followed by invocation of operator(). We use BOOST_ASIO_HANDLER_TYPE
314 // to convert the completion token into the correct handler type,
315 // allowing user-defined specializations of the async_result template
316 // to be used.
92f5a8d4
TL
317
318 echo_op(stream, buffer, std::move(init.completion_handler));
b32b8144
FG
319
320 // This hook lets the caller see a return value when appropriate.
321 // For example this might return std::future<error_code> if
92f5a8d4 322 // CompletionToken is net::use_future, or this might
b32b8144 323 // return an error code if CompletionToken specifies a coroutine.
92f5a8d4 324
b32b8144
FG
325 return init.result.get();
326}
327
92f5a8d4
TL
328// Including this file undefines the macros used by the stackless fauxroutines.
329#include <boost/asio/unyield.hpp>
330
b32b8144
FG
331//]
332
92f5a8d4
TL
333struct move_only_handler
334{
335 move_only_handler(move_only_handler&&) = default;
336 move_only_handler(move_only_handler const&) = delete;
337
338 void operator()(beast::error_code ec)
339 {
340 if(ec)
341 std::cerr << ": " << ec.message() << std::endl;
342 }
343};
344
345int main(int argc, char** argv)
b32b8144 346{
92f5a8d4
TL
347 if(argc != 3)
348 {
349 std::cerr
350 << "Usage: echo-op <address> <port>\n"
351 << "Example:\n"
352 << " echo-op 0.0.0.0 8080\n";
353 return EXIT_FAILURE;
354 }
355
356 namespace net = boost::asio;
357 auto const address{net::ip::make_address(argv[1])};
358 auto const port{static_cast<unsigned short>(std::atoi(argv[2]))};
359
360 using endpoint_type = net::ip::tcp::endpoint;
b32b8144
FG
361
362 // Create a listening socket, accept a connection, perform
363 // the echo, and then shut everything down and exit.
92f5a8d4
TL
364 net::io_context ioc;
365 net::ip::tcp::acceptor acceptor{ioc};
366 endpoint_type ep{address, port};
b32b8144 367 acceptor.open(ep.protocol());
92f5a8d4 368 acceptor.set_option(net::socket_base::reuse_address(true));
b32b8144
FG
369 acceptor.bind(ep);
370 acceptor.listen();
92f5a8d4
TL
371 auto sock = acceptor.accept();
372 beast::flat_buffer buffer;
373 async_echo(sock, buffer, move_only_handler{});
b32b8144 374 ioc.run();
92f5a8d4 375 return EXIT_SUCCESS;
b32b8144 376}