RESTinio

response_coordinator.hpp
Go to the documentation of this file.
1/*
2 restinio
3*/
4
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
30 using write_groups_container_t = std::vector< write_group_t >;
31
32//
33// response_context_t
34//
35
37 class response_context_t
38{
39 public:
41 friend write_groups_container_t &
42 utest_access( response_context_t & ctx )
43 {
44 return ctx.m_write_groups;
45 }
46
48 void
49 reinit(
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
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
80 bool empty() const noexcept { return m_write_groups.empty(); }
81
83 write_group_t
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
113 auto request_id() const noexcept { return m_request_id; }
114
116 void
117 response_output_flags( response_output_flags_t flags ) noexcept
118 {
119 m_response_output_flags = flags;
120 }
121
123 auto
124 response_output_flags() const noexcept
125 {
126 return m_response_output_flags;
127 }
128
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:
139 request_id_t m_request_id{ 0 };
140
142 write_groups_container_t m_write_groups;
143
145 response_output_flags_t
146 m_response_output_flags{
147 response_parts_attr_t::not_final_parts,
148 response_connection_attr_t::connection_keepalive };
149};
150
151//
152// response_context_table_t
153//
154
156 class response_context_table_t
157{
158 public:
159 response_context_table_t( std::size_t max_elements_count )
160 {
161 m_contexts.resize( max_elements_count );
162 }
163
165 bool
166 empty() const noexcept
167 {
168 return !m_elements_exists;
169 }
170
172 bool
173 is_full() const noexcept
174 {
175 return m_contexts.size() == m_elements_exists;
176 }
177
179 response_context_t &
180 front() noexcept
181 {
182 return m_contexts[ m_first_element_index ];
183 }
184
186 response_context_t &
187 back() noexcept
188 {
189 return m_contexts[
190 (m_first_element_index + (m_elements_exists - 1) ) %
191 m_contexts.size() ];
192 }
193
195 response_context_t *
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
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
230 void
231 pop_response_context()
232 {
233 if( empty() )
234 throw exception_t{
235 "unable to pop context because "
236 "response_context_table is empty" };
237
238 pop_response_context_nonchecked();
239 }
240
243
251 void
252 pop_response_context_nonchecked() noexcept
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
264 get_real_index( request_id_t req_id ) noexcept
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
272 std::vector< response_context_t > m_contexts;
273 std::size_t m_first_element_index{0};
274 std::size_t m_elements_exists{0};
275};
276
277//
278// response_coordinator_t
279//
280
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*/
288 class response_coordinator_t
289{
290 public:
291 response_coordinator_t(
293 std::size_t max_req_count )
294 : m_context_table{ max_req_count }
295 {}
296
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(); }
305
307 bool
308 is_able_to_get_more_messages() const noexcept
309 {
310 return !closed() && !is_full();
311 }
312
314 request_id_t
315 register_new_request()
316 {
317 m_context_table.push_response_context( m_request_id_counter );
318
319 return m_request_id_counter++;
320 }
321
323 void
324 append_response(
326 request_id_t req_id,
328 response_output_flags_t response_output_flags,
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
364
370 optional_t< std::pair< write_group_t, request_id_t > >
371 pop_ready_buffers()
372 {
373 if( closed() )
374 throw exception_t{
375 "unable to prepare output buffers, "
376 "response coordinator is closed" };
377
378 optional_t< std::pair< write_group_t, request_id_t > > result;
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(
389 current_ctx.dequeue_group(),
390 current_ctx.request_id() );
391
392 if( current_ctx.is_complete() )
393 {
394 m_connection_closed_response_occured =
395 ( response_parts_attr_t::final_parts ==
396 current_ctx.response_output_flags().m_response_parts )
397 &&( response_connection_attr_t::connection_close ==
398 current_ctx.response_output_flags().m_response_connection );
399
400 m_context_table.pop_response_context();
401 }
402 }
403 }
404
405 return result;
406 }
407
409
415 void
416 reset() noexcept
417 {
418 RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.empty());
419 RESTINIO_STATIC_ASSERT_NOEXCEPT(
420 m_context_table.pop_response_context_nonchecked());
421 RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.front());
422 RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.front().dequeue_group());
423
424 RESTINIO_STATIC_ASSERT_NOEXCEPT(make_asio_compaible_error(
425 asio_convertible_error_t::write_was_not_executed));
426
427 for(; !m_context_table.empty();
428 m_context_table.pop_response_context_nonchecked() )
429 {
430 const auto ec =
431 make_asio_compaible_error(
432 asio_convertible_error_t::write_was_not_executed );
433
434 auto & current_ctx = m_context_table.front();
435 while( !current_ctx.empty() )
436 {
437 auto wg = current_ctx.dequeue_group();
438
439 restinio::utils::suppress_exceptions_quietly( [&] {
440 wg.invoke_after_write_notificator_if_exists( ec );
441 } );
442 }
443 }
444 }
445
446 private:
448 request_id_t m_request_id_counter{ 0 };
449
451 bool m_connection_closed_response_occured{ false };
452
454 response_context_table_t m_context_table;
455};
456
457} /* namespace impl */
458
459} /* namespace restinio */
buffers.hpp
nonstd::optional_lite::optional
class optional
Definition: optional.hpp:919
restinio::exception_t
Exception class for all exceptions thrown by RESTinio.
Definition: exception.hpp:26
restinio::impl::response_context_t
A context for a single response.
Definition: response_coordinator.hpp:38
restinio::impl::response_context_t::dequeue_group
write_group_t dequeue_group() noexcept
Extract write group from data queue.
Definition: response_coordinator.hpp:84
restinio::impl::response_context_t::utest_access
friend write_groups_container_t & utest_access(response_context_t &ctx)
Access write-groups container (used in unit tests)
Definition: response_coordinator.hpp:42
restinio::impl::response_context_t::m_request_id
request_id_t m_request_id
Definition: response_coordinator.hpp:139
restinio::impl::response_context_t::reinit
void reinit(request_id_t request_id) noexcept
Reinitialize context.
Definition: response_coordinator.hpp:49
restinio::impl::response_context_t::m_response_output_flags
response_output_flags_t m_response_output_flags
Response flags.
Definition: response_coordinator.hpp:146
restinio::impl::response_context_t::response_output_flags
auto response_output_flags() const noexcept
Get flags of corrent response data flow.
Definition: response_coordinator.hpp:124
restinio::impl::response_context_t::m_write_groups
write_groups_container_t m_write_groups
Unsent responses parts.
Definition: response_coordinator.hpp:142
restinio::impl::response_context_t::request_id
auto request_id() const noexcept
Get id of associated request.
Definition: response_coordinator.hpp:113
restinio::impl::response_context_t::is_complete
bool is_complete() const noexcept
Is response data of a given request is complete.
Definition: response_coordinator.hpp:131
restinio::impl::response_context_t::empty
bool empty() const noexcept
Is context empty.
Definition: response_coordinator.hpp:80
restinio::impl::response_context_t::enqueue_group
void enqueue_group(write_group_t wg)
Put write group to data queue.
Definition: response_coordinator.hpp:62
restinio::impl::response_context_t::response_output_flags
void response_output_flags(response_output_flags_t flags) noexcept
Get flags of corrent response data flow.
Definition: response_coordinator.hpp:117
restinio::impl::response_context_table_t
Helper storage for responses' contexts.
Definition: response_coordinator.hpp:157
restinio::impl::response_context_table_t::back
response_context_t & back() noexcept
Get last context.
Definition: response_coordinator.hpp:187
restinio::impl::response_context_table_t::response_context_table_t
response_context_table_t(std::size_t max_elements_count)
Definition: response_coordinator.hpp:159
restinio::impl::response_context_table_t::front
response_context_t & front() noexcept
Get first context.
Definition: response_coordinator.hpp:180
restinio::impl::response_context_table_t::pop_response_context
void pop_response_context()
Remove the first context from queue.
Definition: response_coordinator.hpp:231
restinio::impl::response_context_table_t::empty
bool empty() const noexcept
If table is empty.
Definition: response_coordinator.hpp:166
restinio::impl::response_context_table_t::pop_response_context_nonchecked
void pop_response_context_nonchecked() noexcept
Remove the first context from queue with the check for emptiness of the queue.
Definition: response_coordinator.hpp:252
restinio::impl::response_context_table_t::get_real_index
std::size_t get_real_index(request_id_t req_id) noexcept
Definition: response_coordinator.hpp:264
restinio::impl::response_context_table_t::m_first_element_index
std::size_t m_first_element_index
Definition: response_coordinator.hpp:273
restinio::impl::response_context_table_t::get_by_req_id
response_context_t * get_by_req_id(request_id_t req_id) noexcept
Get context of specified request.
Definition: response_coordinator.hpp:196
restinio::impl::response_context_table_t::m_elements_exists
std::size_t m_elements_exists
Definition: response_coordinator.hpp:274
restinio::impl::response_context_table_t::m_contexts
std::vector< response_context_t > m_contexts
Definition: response_coordinator.hpp:272
restinio::impl::response_context_table_t::push_response_context
void push_response_context(request_id_t req_id)
Insert new context into queue.
Definition: response_coordinator.hpp:210
restinio::impl::response_context_table_t::is_full
bool is_full() const noexcept
If table is full.
Definition: response_coordinator.hpp:173
restinio::impl::response_coordinator_t
Coordinator for process of sending responses with respect to http pipeline technique and chunk transf...
Definition: response_coordinator.hpp:289
restinio::impl::response_coordinator_t::reset
void reset() noexcept
Remove all contexts.
Definition: response_coordinator.hpp:416
restinio::impl::response_coordinator_t::register_new_request
request_id_t register_new_request()
Create a new request and reserve context for its response.
Definition: response_coordinator.hpp:315
restinio::impl::response_coordinator_t::is_able_to_get_more_messages
bool is_able_to_get_more_messages() const noexcept
Check if it is possible to accept more requests.
Definition: response_coordinator.hpp:308
restinio::impl::response_coordinator_t::m_request_id_counter
request_id_t m_request_id_counter
Counter for asigining id to new requests.
Definition: response_coordinator.hpp:448
restinio::impl::response_coordinator_t::is_full
bool is_full() const noexcept
Definition: response_coordinator.hpp:303
restinio::impl::response_coordinator_t::m_context_table
response_context_table_t m_context_table
A storage for resp-context items.
Definition: response_coordinator.hpp:454
restinio::impl::response_coordinator_t::empty
bool empty() const noexcept
Definition: response_coordinator.hpp:302
restinio::impl::response_coordinator_t::m_connection_closed_response_occured
bool m_connection_closed_response_occured
Indicate whether a response with connection close flag was emitted.
Definition: response_coordinator.hpp:451
restinio::impl::response_coordinator_t::pop_ready_buffers
optional_t< std::pair< write_group_t, request_id_t > > pop_ready_buffers()
Extract a portion of data available for write.
Definition: response_coordinator.hpp:371
restinio::impl::response_coordinator_t::closed
bool closed() const noexcept
Definition: response_coordinator.hpp:301
restinio::impl::response_coordinator_t::response_coordinator_t
response_coordinator_t(std::size_t max_req_count)
Definition: response_coordinator.hpp:291
restinio::impl::response_coordinator_t::append_response
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.
Definition: response_coordinator.hpp:324
restinio::write_group_t
Group of writable items transported to the context of underlying connection as one solid piece.
Definition: buffers.hpp:692
restinio::write_group_t::status_line_size
void status_line_size(std::size_t n)
Definition: buffers.hpp:763
RESTINIO_STATIC_ASSERT_NOEXCEPT
#define RESTINIO_STATIC_ASSERT_NOEXCEPT(expr)
A wrapper around static_assert for checking that an expression is noexcept.
Definition: compiler_features.hpp:89
exception.hpp
include_fmtlib.hpp
A special wrapper around fmtlib include files.
nonstd::optional_lite::std11::move
T & move(T &t)
Definition: optional.hpp:465
restinio::impl::write_groups_container_t
std::vector< write_group_t > write_groups_container_t
Definition: response_coordinator.hpp:30
restinio::utils::suppress_exceptions_quietly
void suppress_exceptions_quietly(Lambda &&lambda) noexcept
Helper function for execution a block of code with suppression of any exceptions raised inside that b...
Definition: suppress_exceptions.hpp:133
restinio
Definition: asio_include.hpp:21
restinio::request_id_t
unsigned int request_id_t
Request id in scope of single connection.
Definition: common_types.hpp:70
restinio::asio_convertible_error_t::write_was_not_executed
@ write_was_not_executed
After write notificator error: data was not sent, connection closed (or aborted) before a given piece...
restinio::static_if_else
decltype(auto) static_if_else(If_Part &&if_part, Else_Part &&else_part)
An emulation of if constexpr for C++14.
Definition: compiler_features.hpp:176
restinio::response_connection_attr_t::connection_close
@ connection_close
This response says to close connection.
restinio::response_connection_attr_t::connection_keepalive
@ connection_keepalive
This response says to keep connection.
restinio::make_asio_compaible_error
asio_ns::error_code make_asio_compaible_error(asio_convertible_error_t err) noexcept
Make restinio error_code compatible with asio_ns::error_code.
Definition: asio_include.hpp:210
restinio::response_parts_attr_t::final_parts
@ final_parts
Final parts (response ands with these parts).
restinio::response_parts_attr_t::not_final_parts
@ not_final_parts
Intermediate parts (more parts of response to follow).
optional.hpp
request_handler.hpp
restinio::response_output_flags_t
Response output flags for buffers commited to response-coordinator.
Definition: common_types.hpp:123
restinio::response_output_flags_t::m_response_parts
response_parts_attr_t m_response_parts
Definition: common_types.hpp:131
suppress_exceptions.hpp
Utilities for suppressing exceptions from some code block.
const
#define const
Definition: zconf.h:230