RESTinio
Loading...
Searching...
No Matches
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
25namespace restinio
26{
27
28namespace 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
60
61 //! Put write group to data queue.
62 void
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.
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
123
124 //! Get flags of corrent response data flow.
125 auto
126 response_output_flags() const noexcept
127 {
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
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.
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 {
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
266 get_real_index( request_id_t req_id ) noexcept
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
275 std::size_t m_first_element_index{0};
276 std::size_t m_elements_exists{0};
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(
348 "no context associated with request {}" ),
349 req_id ) };
350 }
351
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 */
373 std::optional< std::pair< write_group_t, request_id_t > >
375 {
376 if( closed() )
377 throw exception_t{
378 "unable to prepare output buffers, "
379 "response coordinator is closed" };
380
381 std::optional< std::pair< write_group_t, request_id_t > > result;
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(
392 current_ctx.dequeue_group(),
393 current_ctx.request_id() );
394
395 if( current_ctx.is_complete() )
396 {
399 current_ctx.response_output_flags().m_response_parts )
401 current_ctx.response_output_flags().m_response_connection );
402
403 m_context_table.pop_response_context();
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 {
421 RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.empty());
423 m_context_table.pop_response_context_nonchecked());
424 RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.front());
425 RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.front().dequeue_group());
426
429
430 for(; !m_context_table.empty();
431 m_context_table.pop_response_context_nonchecked() )
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
442 restinio::utils::suppress_exceptions_quietly( [&] {
443 wg.invoke_after_write_notificator_if_exists( ec );
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 */
Exception class for all exceptions thrown by RESTinio.
Definition exception.hpp:26
exception_t(const char *err)
Definition exception.hpp:29
A context for a single response.
write_group_t dequeue_group() noexcept
Extract write group from data queue.
friend write_groups_container_t & utest_access(response_context_t &ctx)
Access write-groups container (used in unit tests)
void reinit(request_id_t request_id) noexcept
Reinitialize context.
response_output_flags_t m_response_output_flags
Response flags.
auto response_output_flags() const noexcept
Get flags of corrent response data flow.
write_groups_container_t m_write_groups
Unsent responses parts.
auto request_id() const noexcept
Get id of associated request.
bool is_complete() const noexcept
Is response data of a given request is complete.
bool empty() const noexcept
Is context empty.
void enqueue_group(write_group_t wg)
Put write group to data queue.
void response_output_flags(response_output_flags_t flags) noexcept
Get flags of corrent response data flow.
Helper storage for responses' contexts.
response_context_t & back() noexcept
Get last context.
response_context_table_t(std::size_t max_elements_count)
response_context_t & front() noexcept
Get first context.
void pop_response_context()
Remove the first context from queue.
bool empty() const noexcept
If table is empty.
void pop_response_context_nonchecked() noexcept
Remove the first context from queue with the check for emptiness of the queue.
std::size_t get_real_index(request_id_t req_id) noexcept
response_context_t * get_by_req_id(request_id_t req_id) noexcept
Get context of specified request.
std::vector< response_context_t > m_contexts
void push_response_context(request_id_t req_id)
Insert new context into queue.
bool is_full() const noexcept
If table is full.
Coordinator for process of sending responses with respect to http pipeline technique and chunk transf...
void reset() noexcept
Remove all contexts.
request_id_t register_new_request()
Create a new request and reserve context for its response.
bool is_able_to_get_more_messages() const noexcept
Check if it is possible to accept more requests.
request_id_t m_request_id_counter
Counter for asigining id to new requests.
response_context_table_t m_context_table
A storage for resp-context items.
bool m_connection_closed_response_occured
Indicate whether a response with connection close flag was emitted.
std::optional< std::pair< write_group_t, request_id_t > > pop_ready_buffers()
Extract a portion of data available for write.
response_coordinator_t(std::size_t max_req_count)
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.
Group of writable items transported to the context of underlying connection as one solid piece.
Definition buffers.hpp:727
#define RESTINIO_STATIC_ASSERT_NOEXCEPT(expr)
A wrapper around static_assert for checking that an expression is noexcept.
#define RESTINIO_FMT_FORMAT_STRING(s)
unsigned int request_id_t
Request id in scope of single connection.
asio_convertible_error_t
Enum for restinio errors that must presented as asio_ns::error_code value.
@ write_was_not_executed
After write notificator error: data was not sent, connection closed (or aborted) before a given piece...
response_connection_attr_t
Attribute for parts.
@ connection_close
This response says to close connection.
@ connection_keepalive
This response says to keep connection.
asio_ns::error_code make_asio_compaible_error(asio_convertible_error_t err) noexcept
Make restinio error_code compatible with asio_ns::error_code.
response_parts_attr_t
Attribute for parts.
@ final_parts
Final parts (response ands with these parts).
@ not_final_parts
Intermediate parts (more parts of response to follow).
Response output flags for buffers commited to response-coordinator.
response_output_flags_t(response_parts_attr_t response_parts, response_connection_attr_t response_connection) noexcept