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