RESTinio
write_group_output_ctx.hpp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  Helper output context for writing buffers to output stream (socket).
7 */
8 
9 #pragma once
10 
11 #include <vector>
12 
13 #include <restinio/asio_include.hpp>
14 
15 #include <restinio/buffers.hpp>
16 #include <restinio/optional.hpp>
17 #include <restinio/variant.hpp>
18 #include <restinio/impl/sendfile_operation.hpp>
19 
20 namespace restinio
21 {
22 
23 namespace impl
24 {
25 
27 
28 //
29 // write_group_output_ctx_t
30 //
31 
32 //! Helper class for writting response data.
33 /*!
34  The usage scenario is some kind of the following:
35  \code{.cpp}
36  write_group_output_ctx_t output_ctx;
37  wtite_group_t wg = ...
38 
39  // Feed write group to context:
40  output_ctx.start_next_write_group( std::move( wg ) );
41 
42  try
43  {
44  // start handling loop:
45  for(
46  // Extract next solid output piece.
47  auto wo = output_ctx.extract_next_write_operation();
48  // Are we done with consuming a given write_group_t instance?
49  !holds_alternative< none_write_operation_t >( wo );
50  // Get next output piece.
51  wo = output_ctx.extract_next_write_operation() )
52  {
53  if( holds_alternative< trivial_write_operation_t >( wo ) )
54  {
55  handle_trivial_bufs( get< trivial_write_operation_t >( wo ) );
56  }
57  else
58  {
59  handle_sendfile( get< file_write_operation_t >( wo ) );
60  }
61  }
62 
63  // Finalize.
64  output_ctx.finish_write_group();
65  }
66  catch( ec ) // asio error code
67  {
68  // Loop failed, so we finish write group abnormally.
69  output_ctx.fail_write_group( ec )
70  }
71  \endcode
72 
73  Of course, the real usage is complicated by spreading in time and
74  running plenty of other logic cooperatively.
75 
76 */
78 {
79  //! Get the maximum number of buffers that can be written with
80  //! gather write operation.
81  static constexpr auto
82  max_iov_len() noexcept
83  {
84  using len_t = decltype( asio_ns::detail::max_iov_len );
85  return std::min< len_t >( asio_ns::detail::max_iov_len, 64 );
86  }
87 
88  public:
89  //! Contruct an object.
90  /*
91  Space for m_asio_bufs is reserved to be ready to store max_iov_len() asio bufs.
92  */
94  {
95  m_asio_bufs.reserve( max_iov_len() );
96  }
97 
98  //! Trivial write operaton.
99  /*!
100  Presented with a vector of ordinary buffers (data-size objects).
101  */
103  {
105 
107  //! Container of asio buf objects.
108  const asio_bufs_container_t & asio_bufs,
109  //! Total size of data represented by buffers.
110  std::size_t total_size ) noexcept
111  : m_asio_bufs{ &asio_bufs }
113  {}
114 
115  public:
118 
121 
122  //! Get buffer "iovec" for performing gather write.
123  const std::vector< asio_ns::const_buffer > &
124  get_trivial_bufs() const noexcept
125  {
126  return *m_asio_bufs;
127  }
128 
129  //! The size of data within this operation.
130  auto size() const noexcept { return m_total_size; }
131 
132  private:
134  size_t m_total_size;
135  };
136 
137  //! Write operaton using sendfile.
139  {
141 
143  sendfile_t & sendfile,
144  sendfile_operation_shared_ptr_t & sendfile_operation ) noexcept
145  : m_sendfile{ &sendfile }
147  {}
148 
149  public:
150  file_write_operation_t( const file_write_operation_t & ) = default;
151  file_write_operation_t & operator = ( const file_write_operation_t & ) = default;
152 
155 
156  //! Start a sendfile operation.
157  /*!
158  @note
159  Since v.0.4.9 it is non-const method. This is necessary
160  to get a non-const reference to sendfile operation.
161  */
162  template< typename Socket, typename After_Write_CB >
163  void
165  asio_ns::executor executor,
166  Socket & socket,
167  After_Write_CB after_sendfile_cb )
168  {
169  assert( m_sendfile->is_valid() );
170 
171  if( !m_sendfile->is_valid() )
172  {
173  // This must never happen.
174  throw exception_t{ "invalid file descriptor in sendfile operation." };
175  }
176 
177  auto sendfile_operation =
179  *m_sendfile,
180  std::move( executor ),
181  socket,
182  std::move( after_sendfile_cb ) );
183 
186  }
187 
188  //! Get the timelimit on this sendfile operation.
189  auto
190  timelimit() const noexcept
191  {
192  assert( m_sendfile->is_valid() );
193 
194  return m_sendfile->timelimit();
195  }
196 
197  //! Reset write operation context.
198  void
200  {
201  m_sendfile_operation->reset();
202  }
203 
204  //! Get the size of sendfile operation.
205  auto size() const noexcept { return m_sendfile->size(); }
206 
207  private:
208  //! A pointer to sendfile.
209  sendfile_t * m_sendfile; // Pointer is used to be able to copy/assign.
210 
211  //! A curernt sendfile operation.
212  /*!
213  This context must be external to
214  file_write_operation_t instance (in order to avoid circle links).
215  */
217  };
218 
219  //! None write operation.
220  /*!
221  When extract_next_write_operation() returns a variant with
222  none_write_operation_t instance it means that current write group
223  was handled to the end of its buffer sequence.
224  */
226 
227  //! Check if data is trunsmitting now
228  bool transmitting() const noexcept { return static_cast< bool >( m_current_wg ); }
229 
230  //! Start handlong next write group.
231  void
232  start_next_write_group( optional_t< write_group_t > next_wg ) noexcept
233  {
234  m_current_wg = std::move( next_wg );
235  }
236 
237  //! An alias for variant holding write operation specifics.
239  variant_t<
243 
244  //! et an object with next write operation to perform.
247  {
248  assert( m_current_wg );
249 
250  solid_write_operation_variant_t result{ none_write_operation_t{} };
251 
252  if( m_next_writable_item_index < m_current_wg->items_count() )
253  {
254  // Has writable items.
255  const auto next_wi_type =
256  m_current_wg->items()[ m_next_writable_item_index ].write_type();
257 
258  if( writable_item_type_t::trivial_write_operation == next_wi_type )
259  {
260  // Trivial buffers.
261  result = prepare_trivial_buffers_wo();
262  }
263  else
264  {
265  // Sendfile.
266  assert( writable_item_type_t::file_write_operation == next_wi_type );
267  result = prepare_sendfile_wo();
268  }
269  }
270 
271  return result;
272  }
273 
274  //! Handle current group write process failed.
275  void
276  fail_write_group( const asio_ns::error_code & ec )
277  {
278  assert( m_current_wg );
279 
280  invoke_after_write_notificator_if_necessary( ec );
281  m_current_wg.reset();
282  m_sendfile_operation.reset();
283  }
284 
285  //! Finish writing group normally.
286  void
288  {
289  assert( m_current_wg );
290 
291  invoke_after_write_notificator_if_necessary( asio_ns::error_code{} );
293  }
294 
295  private:
296  //! Reset the write group and associated context.
297  void
299  {
300  m_current_wg.reset();
301  m_next_writable_item_index = 0;
302  }
303 
304  //! Execute notification callback if necessary.
305  void
306  invoke_after_write_notificator_if_necessary( const asio_ns::error_code & ec )
307  {
308  try
309  {
310  m_current_wg->invoke_after_write_notificator_if_exists( ec );
311  }
312  catch( const std::exception & ex )
313  {
314  // Actualy no need to reset m_current_wg as a thrown exception
315  // will break working circle of connection.
316  // But as it is used as flag for transmitting()
317  // we reset the object.
319 
320  throw exception_t{
321  fmt::format( "after write callback failed: {}", ex.what() ) };
322  }
323  }
324 
325  //! Prepare write operation for trivial buffers.
328  {
329  m_asio_bufs.clear();
330 
331  const auto & items = m_current_wg->items();
332  std::size_t total_size{ 0 };
333 
334  for( ;m_next_writable_item_index < items.size() &&
335  writable_item_type_t::trivial_write_operation ==
336  items[ m_next_writable_item_index ].write_type() &&
337  max_iov_len() > m_asio_bufs.size();
338  ++m_next_writable_item_index )
339  {
340  const auto & item = items[ m_next_writable_item_index ];
341  m_asio_bufs.emplace_back( item.buf() );
342  total_size += item.size();
343  }
344 
345  assert( !m_asio_bufs.empty() );
346  return trivial_write_operation_t{ m_asio_bufs, total_size };
347  }
348 
349  //! Prepare write operation for sendfile.
352  {
353  auto & sf =
354  m_current_wg->items()[ m_next_writable_item_index++ ].sendfile_operation();
355 
356  return file_write_operation_t{ sf, m_sendfile_operation };
357  }
358 
359  //! Real buffers with data.
361 
362  //! Keeps track of the next writable item stored in m_current_wg.
363  /*!
364  When emitting next solid write operation
365  we need to know where the next starting item is.
366  */
368 
369  //! Asio buffers storage.
371 
372  //! Sendfile operation storage context.
374 };
375 
376 } /* namespace impl */
377 
378 } /* namespace restinio */
file_write_operation_t & operator=(const file_write_operation_t &)=default
auto size() const noexcept
The size of data within this operation.
Helper class for writting response data.
bool transmitting() const noexcept
Check if data is trunsmitting now.
file_write_operation_t(sendfile_t &sendfile, sendfile_operation_shared_ptr_t &sendfile_operation) noexcept
void start_next_write_group(optional_t< write_group_t > next_wg) noexcept
Start handlong next write group.
trivial_write_operation_t & operator=(trivial_write_operation_t &&)=default
file_write_operation_t(const file_write_operation_t &)=default
const std::vector< asio_ns::const_buffer > & get_trivial_bufs() const noexcept
Get buffer "iovec" for performing gather write.
void fail_write_group(const asio_ns::error_code &ec)
Handle current group write process failed.
trivial_write_operation_t prepare_trivial_buffers_wo()
Prepare write operation for trivial buffers.
static constexpr auto max_iov_len() noexcept
Get the maximum number of buffers that can be written with gather write operation.
asio_bufs_container_t m_asio_bufs
Asio buffers storage.
auto size() const noexcept
Get the size of sendfile operation.
void finish_write_group()
Finish writing group normally.
file_write_operation_t prepare_sendfile_wo()
Prepare write operation for sendfile.
trivial_write_operation_t(trivial_write_operation_t &&)=default
void invoke_after_write_notificator_if_necessary(const asio_ns::error_code &ec)
Execute notification callback if necessary.
trivial_write_operation_t(const trivial_write_operation_t &)=default
trivial_write_operation_t(const asio_bufs_container_t &asio_bufs, std::size_t total_size) noexcept
sendfile_operation_shared_ptr_t m_sendfile_operation
Sendfile operation storage context.
optional_t< write_group_t > m_current_wg
Real buffers with data.
file_write_operation_t & operator=(file_write_operation_t &&)=default
std::size_t m_next_writable_item_index
Keeps track of the next writable item stored in m_current_wg.
sendfile_operation_shared_ptr_t * m_sendfile_operation
A curernt sendfile operation.
auto timelimit() const noexcept
Get the timelimit on this sendfile operation.
solid_write_operation_variant_t extract_next_write_operation()
et an object with next write operation to perform.
void start_sendfile_operation(asio_ns::executor executor, Socket &socket, After_Write_CB after_sendfile_cb)
Start a sendfile operation.
void reset_write_group()
Reset the write group and associated context.
trivial_write_operation_t & operator=(const trivial_write_operation_t &)=default
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, optional_t< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in optional_t<Value_Type> if parameter exists ...
Definition: value_or.hpp:64