RESTinio
response_coordinator.hpp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  Coordinator for process od sending responses with
7  respect to http pipeline technique and chunk transfer.
8 */
9 
10 #pragma once
11 
12 #include <restinio/impl/include_fmtlib.hpp>
13 
14 #include <restinio/utils/suppress_exceptions.hpp>
15 
16 #include <restinio/exception.hpp>
17 #include <restinio/request_handler.hpp>
18 #include <restinio/buffers.hpp>
19 
20 #include <string>
21 #include <deque>
22 #include <optional>
23 
24 
25 namespace restinio
26 {
27 
28 namespace impl
29 {
30 
32 
33 //
34 // response_context_t
35 //
36 
37 //! A context for a single response.
39 {
40  public:
41  //! Access write-groups container (used in unit tests)
44  {
45  return ctx.m_write_groups;
46  }
47 
48  //! Reinitialize context.
49  void
51  //! New request id.
52  request_id_t request_id ) noexcept
53  {
54  m_request_id = request_id;
55  m_response_output_flags =
56  response_output_flags_t{
57  response_parts_attr_t::not_final_parts,
58  response_connection_attr_t::connection_keepalive };
59  }
60 
61  //! Put write group to data queue.
62  void
63  enqueue_group( write_group_t wg )
64  {
65  // There is at least one group.
66  // So we check if this group can be merged with existing (the last one).
67  if( !m_write_groups.empty() &&
68  !m_write_groups.back().has_after_write_notificator() &&
69  std::size_t{ 0 } == wg.status_line_size() )
70  {
71  m_write_groups.back().merge( std::move( wg ) );
72  }
73  else
74  {
75  m_write_groups.emplace_back( std::move( wg ) );
76  }
77 
78  }
79 
80  //! Is context empty.
81  bool empty() const noexcept { return m_write_groups.empty(); }
82 
83  //! Extract write group from data queue.
85  dequeue_group() noexcept
86  {
87  assert( !m_write_groups.empty() );
88 
89  // Move constructor for write_group_t shouldn't throw.
90  RESTINIO_STATIC_ASSERT_NOEXCEPT(
91  write_group_t{ std::declval<write_group_t>() } );
92 
93  write_group_t result{ std::move( m_write_groups.front() ) };
94 
95  // Some STL implementation can have std::vector::erase that
96  // doesn't throw. So we use a kind of static if to select
97  // an appropriate behaviour.
98  if constexpr( noexcept(m_write_groups.erase(m_write_groups.begin())) )
99  {
100  // This is for the case when std::vector::erase doesn't throw.
101  m_write_groups.erase( m_write_groups.begin() );
102  }
103  else
104  {
105  // This is for the case when std::vector::erase does throw.
106  restinio::utils::suppress_exceptions_quietly( [this] {
107  m_write_groups.erase( m_write_groups.begin() );
108  } );
109  }
110 
111  return result;
112  }
113 
114  //! Get id of associated request.
115  auto request_id() const noexcept { return m_request_id; }
116 
117  //! Get flags of corrent response data flow.
118  void
119  response_output_flags( response_output_flags_t flags ) noexcept
120  {
121  m_response_output_flags = flags;
122  }
123 
124  //! Get flags of corrent response data flow.
125  auto
126  response_output_flags() const noexcept
127  {
128  return m_response_output_flags;
129  }
130 
131  //! Is response data of a given request is complete.
132  bool
133  is_complete() const noexcept
134  {
135  return m_write_groups.empty() &&
136  response_parts_attr_t::final_parts ==
137  m_response_output_flags.m_response_parts;
138  }
139 
140  private:
142 
143  //! Unsent responses parts.
145 
146  //! Response flags
151 };
152 
153 //
154 // response_context_table_t
155 //
156 
157 //! Helper storage for responses' contexts.
159 {
160  public:
161  response_context_table_t( std::size_t max_elements_count )
162  {
163  m_contexts.resize( max_elements_count );
164  }
165 
166  //! If table is empty.
167  bool
168  empty() const noexcept
169  {
170  return !m_elements_exists;
171  }
172 
173  //! If table is full.
174  bool
175  is_full() const noexcept
176  {
177  return m_contexts.size() == m_elements_exists;
178  }
179 
180  //! Get first context.
182  front() noexcept
183  {
184  return m_contexts[ m_first_element_index ];
185  }
186 
187  //! Get last context.
189  back() noexcept
190  {
191  return m_contexts[
192  (m_first_element_index + (m_elements_exists - 1) ) %
193  m_contexts.size() ];
194  }
195 
196  //! Get context of specified request.
198  get_by_req_id( request_id_t req_id ) noexcept
199  {
200  if( empty() ||
201  req_id < front().request_id() ||
202  req_id > back().request_id() )
203  {
204  return nullptr;
205  }
206 
207  return &m_contexts[ get_real_index( req_id ) ];
208  }
209 
210  //! Insert new context into queue.
211  void
212  push_response_context( request_id_t req_id )
213  {
214  if( is_full() )
215  throw exception_t{
216  "unable to insert context because "
217  "response_context_table is full" };
218 
219  auto & ctx =
220  m_contexts[
221  // Current next.
222  ( m_first_element_index + m_elements_exists ) % m_contexts.size()
223  ];
224 
225  ctx.reinit( req_id );
226 
227  // 1 more element added.
228  ++m_elements_exists;
229  }
230 
231  //! Remove the first context from queue.
232  void
234  {
235  if( empty() )
236  throw exception_t{
237  "unable to pop context because "
238  "response_context_table is empty" };
239 
241  }
242 
243  //! Remove the first context from queue with the check for
244  //! emptiness of the queue.
245  /*!
246  * @note
247  * This method is noexcept and indended to be used in noexcept
248  * context. But the emptiness of the queue should be checked
249  * before the call of this method.
250  *
251  * @since v.0.6.0
252  */
253  void
255  {
256  --m_elements_exists;
257  ++m_first_element_index;
258  if( m_contexts.size() == m_first_element_index )
259  {
260  m_first_element_index = std::size_t{0};
261  }
262  }
263 
264  private:
265  std::size_t
267  {
268  const auto distance_from_first =
269  req_id - front().request_id();
270 
271  return ( m_first_element_index + distance_from_first ) % m_contexts.size();
272  }
273 
277 };
278 
279 //
280 // response_coordinator_t
281 //
282 
283 //! Coordinator for process of sending responses with
284 //! respect to http pipeline technique and chunk transfer.
285 /*
286  Keeps track of maximum N (max_req_count) pipelined requests,
287  gathers pieces (write groups) of responses and provides access to
288  ready-to-send buffers on demand.
289 */
291 {
292  public:
294  //! Maximum count of requests to keep track of.
295  std::size_t max_req_count )
297  {}
298 
299  /** @name Response coordinator state.
300  * @brief Various state flags.
301  */
302  ///@{
303  bool closed() const noexcept { return m_connection_closed_response_occured; }
304  bool empty() const noexcept { return m_context_table.empty(); }
305  bool is_full() const noexcept { return m_context_table.is_full(); }
306  ///@}
307 
308  //! Check if it is possible to accept more requests.
309  bool
311  {
312  return !closed() && !is_full();
313  }
314 
315  //! Create a new request and reserve context for its response.
318  {
319  m_context_table.push_response_context( m_request_id_counter );
320 
321  return m_request_id_counter++;
322  }
323 
324  //! Add outgoing data for specified request.
325  void
327  //! Request id the responses parts are for.
328  request_id_t req_id,
329  //! Resp output flag.
330  response_output_flags_t response_output_flags,
331  //! The parts of response.
332  write_group_t wg )
333  {
334  // Nothing to do if already closed response emitted.
335  if( closed() )
336  throw exception_t{
337  "unable to append response parts, "
338  "response coordinator is closed" };
339 
340  auto * ctx = m_context_table.get_by_req_id( req_id );
341 
342  if( nullptr == ctx )
343  {
344  // Request is unknown...
345  throw exception_t{
346  fmt::format(
347  RESTINIO_FMT_FORMAT_STRING(
348  "no context associated with request {}" ),
349  req_id ) };
350  }
351 
352  if( response_parts_attr_t::final_parts ==
353  ctx->response_output_flags().m_response_parts )
354  {
355  // Request is already completed...
356  throw exception_t{
357  "unable to append response, "
358  "it marked as complete" };
359  }
360 
361  ctx->response_output_flags( response_output_flags );
362 
363  ctx->enqueue_group( std::move( wg ) );
364  }
365 
366  //! Extract a portion of data available for write.
367  /*!
368  Data (if available) is wrapped in an instance of write_group_t.
369  It can have a stats line mark (that is necessary for logging)
370  and a notificator that must be invoked after the write operation
371  of a given group completes.
372  */
375  {
376  if( closed() )
377  throw exception_t{
378  "unable to prepare output buffers, "
379  "response coordinator is closed" };
380 
382 
383  // Check for custom write operation.
384  if( !m_context_table.empty() )
385  {
386  auto & current_ctx = m_context_table.front();
387 
388  if( !current_ctx.empty() )
389  {
390  result =
391  std::make_pair(
394 
395  if( current_ctx.is_complete() )
396  {
402 
404  }
405  }
406  }
407 
408  return result;
409  }
410 
411  //! Remove all contexts.
412  /*!
413  Invoke write groups after-write callbacks with error status.
414 
415  @note
416  Since v.0.6.0 this method is noexcept
417  */
418  void
419  reset() noexcept
420  {
426 
429 
430  for(; !m_context_table.empty();
432  {
433  const auto ec =
436 
437  auto & current_ctx = m_context_table.front();
438  while( !current_ctx.empty() )
439  {
440  auto wg = current_ctx.dequeue_group();
441 
444  } );
445  }
446  }
447  }
448 
449  private:
450  //! Counter for asigining id to new requests.
452 
453  //! Indicate whether a response with connection close flag was emitted.
455 
456  //! A storage for resp-context items.
458 };
459 
460 } /* namespace impl */
461 
462 } /* namespace restinio */
response_context_t & back() noexcept
Get last context.
std::size_t get_real_index(request_id_t req_id) noexcept
A context for a single response.
response_output_flags_t m_response_output_flags
Response flags.
void reset() noexcept
Remove all contexts.
bool m_connection_closed_response_occured
Indicate whether a response with connection close flag was emitted.
void pop_response_context_nonchecked() noexcept
Remove the first context from queue with the check for emptiness of the queue.
bool is_complete() const noexcept
Is response data of a given request is complete.
response_context_t * get_by_req_id(request_id_t req_id) noexcept
Get context of specified request.
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, std::optional< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in std::optional<Value_Type> if parameter exis...
Definition: value_or.hpp:64
bool empty() const noexcept
If table is empty.
void append_response(request_id_t req_id, response_output_flags_t response_output_flags, write_group_t wg)
Add outgoing data for specified request.
bool empty() const noexcept
Is context empty.
auto request_id() const noexcept
Get id of associated request.
auto response_output_flags() const noexcept
Get flags of corrent response data flow.
response_coordinator_t(std::size_t max_req_count)
std::vector< response_context_t > m_contexts
void reinit(request_id_t request_id) noexcept
Reinitialize context.
void response_output_flags(response_output_flags_t flags) noexcept
Get flags of corrent response data flow.
friend write_groups_container_t & utest_access(response_context_t &ctx)
Access write-groups container (used in unit tests)
void push_response_context(request_id_t req_id)
Insert new context into queue.
response_context_t & front() noexcept
Get first context.
Helper storage for responses&#39; contexts.
response_context_table_t(std::size_t max_elements_count)
request_id_t register_new_request()
Create a new request and reserve context for its response.
write_groups_container_t m_write_groups
Unsent responses parts.
void enqueue_group(write_group_t wg)
Put write group to data queue.
response_context_table_t m_context_table
A storage for resp-context items.
void pop_response_context()
Remove the first context from queue.
write_group_t dequeue_group() noexcept
Extract write group from data queue.
bool is_able_to_get_more_messages() const noexcept
Check if it is possible to accept more requests.
bool is_full() const noexcept
If table is full.