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