response_coordinator.hpp
Go to the documentation of this file.
/*
restinio
*/
#pragma once
#include <string>
#include <deque>
#include <restinio/impl/include_fmtlib.hpp>
#include <restinio/utils/suppress_exceptions.hpp>
#include <restinio/exception.hpp>
#include <restinio/request_handler.hpp>
#include <restinio/buffers.hpp>
#include <restinio/optional.hpp>
namespace restinio
{
namespace impl
{
using write_groups_container_t = std::vector< write_group_t >;
//
// response_context_t
//
class response_context_t
{
public:
friend write_groups_container_t &
utest_access( response_context_t & ctx )
{
return ctx.m_write_groups;
}
void
reinit(
request_id_t request_id ) noexcept
{
m_request_id = request_id;
m_response_output_flags =
response_output_flags_t{
response_parts_attr_t::not_final_parts,
response_connection_attr_t::connection_keepalive };
}
void
enqueue_group( write_group_t wg )
{
// There is at least one group.
// So we check if this group can be merged with existing (the last one).
if( !m_write_groups.empty() &&
!m_write_groups.back().has_after_write_notificator() &&
std::size_t{ 0 } == wg.status_line_size() )
{
m_write_groups.back().merge( std::move( wg ) );
}
else
{
m_write_groups.emplace_back( std::move( wg ) );
}
}
bool empty() const noexcept { return m_write_groups.empty(); }
write_group_t
dequeue_group() noexcept
{
assert( !m_write_groups.empty() );
// Move constructor for write_group_t shouldn't throw.
RESTINIO_STATIC_ASSERT_NOEXCEPT(
write_group_t{ std::declval<write_group_t>() } );
write_group_t result{ std::move( m_write_groups.front() ) };
// Some STL implementation can have std::vector::erase that
// doesn't throw. So we use a kind of static if to select
// an appropriate behaviour.
static_if_else< noexcept(m_write_groups.erase(m_write_groups.begin())) >(
// This is for the case when std::vector::erase doesn't throw.
[this]() noexcept {
m_write_groups.erase( m_write_groups.begin() );
},
// This is for the case when std::vector::erase does throw.
[this]() {
restinio::utils::suppress_exceptions_quietly( [this] {
m_write_groups.erase( m_write_groups.begin() );
} );
} );
return result;
}
auto request_id() const noexcept { return m_request_id; }
void
response_output_flags( response_output_flags_t flags ) noexcept
{
m_response_output_flags = flags;
}
auto
response_output_flags() const noexcept
{
return m_response_output_flags;
}
bool
is_complete() const noexcept
{
return m_write_groups.empty() &&
response_parts_attr_t::final_parts ==
m_response_output_flags.m_response_parts;
}
private:
request_id_t m_request_id{ 0 };
write_groups_container_t m_write_groups;
response_output_flags_t
m_response_output_flags{
response_parts_attr_t::not_final_parts,
response_connection_attr_t::connection_keepalive };
};
//
// response_context_table_t
//
class response_context_table_t
{
public:
response_context_table_t( std::size_t max_elements_count )
{
m_contexts.resize( max_elements_count );
}
bool
empty() const noexcept
{
return !m_elements_exists;
}
bool
is_full() const noexcept
{
return m_contexts.size() == m_elements_exists;
}
response_context_t &
front() noexcept
{
return m_contexts[ m_first_element_index ];
}
response_context_t &
back() noexcept
{
return m_contexts[
(m_first_element_index + (m_elements_exists - 1) ) %
m_contexts.size() ];
}
response_context_t *
get_by_req_id( request_id_t req_id ) noexcept
{
if( empty() ||
req_id < front().request_id() ||
req_id > back().request_id() )
{
return nullptr;
}
return &m_contexts[ get_real_index( req_id ) ];
}
void
push_response_context( request_id_t req_id )
{
if( is_full() )
throw exception_t{
"unable to insert context because "
"response_context_table is full" };
auto & ctx =
m_contexts[
// Current next.
( m_first_element_index + m_elements_exists ) % m_contexts.size()
];
ctx.reinit( req_id );
// 1 more element added.
++m_elements_exists;
}
void
pop_response_context()
{
if( empty() )
throw exception_t{
"unable to pop context because "
"response_context_table is empty" };
pop_response_context_nonchecked();
}
void
pop_response_context_nonchecked() noexcept
{
--m_elements_exists;
++m_first_element_index;
if( m_contexts.size() == m_first_element_index )
{
m_first_element_index = std::size_t{0};
}
}
private:
std::size_t
get_real_index( request_id_t req_id ) noexcept
{
const auto distance_from_first =
req_id - front().request_id();
return ( m_first_element_index + distance_from_first ) % m_contexts.size();
}
std::vector< response_context_t > m_contexts;
std::size_t m_first_element_index{0};
std::size_t m_elements_exists{0};
};
//
// response_coordinator_t
//
/*
Keeps track of maximum N (max_req_count) pipelined requests,
gathers pieces (write groups) of responses and provides access to
ready-to-send buffers on demand.
*/
class response_coordinator_t
{
public:
response_coordinator_t(
std::size_t max_req_count )
: m_context_table{ max_req_count }
{}
bool closed() const noexcept { return m_connection_closed_response_occured; }
bool empty() const noexcept { return m_context_table.empty(); }
bool is_full() const noexcept { return m_context_table.is_full(); }
bool
is_able_to_get_more_messages() const noexcept
{
return !closed() && !is_full();
}
request_id_t
register_new_request()
{
m_context_table.push_response_context( m_request_id_counter );
return m_request_id_counter++;
}
void
append_response(
request_id_t req_id,
response_output_flags_t response_output_flags,
write_group_t wg )
{
// Nothing to do if already closed response emitted.
if( closed() )
throw exception_t{
"unable to append response parts, "
"response coordinator is closed" };
auto * ctx = m_context_table.get_by_req_id( req_id );
if( nullptr == ctx )
{
// Request is unknown...
throw exception_t{
fmt::format(
"no context associated with request {}",
req_id ) };
}
if( response_parts_attr_t::final_parts ==
ctx->response_output_flags().m_response_parts )
{
// Request is already completed...
throw exception_t{
"unable to append response, "
"it marked as complete" };
}
ctx->response_output_flags( response_output_flags );
ctx->enqueue_group( std::move( wg ) );
}
optional_t< std::pair< write_group_t, request_id_t > >
pop_ready_buffers()
{
if( closed() )
throw exception_t{
"unable to prepare output buffers, "
"response coordinator is closed" };
optional_t< std::pair< write_group_t, request_id_t > > result;
// Check for custom write operation.
if( !m_context_table.empty() )
{
auto & current_ctx = m_context_table.front();
if( !current_ctx.empty() )
{
result =
std::make_pair(
current_ctx.dequeue_group(),
current_ctx.request_id() );
if( current_ctx.is_complete() )
{
m_connection_closed_response_occured =
( response_parts_attr_t::final_parts ==
current_ctx.response_output_flags().m_response_parts )
&&( response_connection_attr_t::connection_close ==
current_ctx.response_output_flags().m_response_connection );
m_context_table.pop_response_context();
}
}
}
return result;
}
void
reset() noexcept
{
RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.empty());
RESTINIO_STATIC_ASSERT_NOEXCEPT(
m_context_table.pop_response_context_nonchecked());
RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.front());
RESTINIO_STATIC_ASSERT_NOEXCEPT(m_context_table.front().dequeue_group());
RESTINIO_STATIC_ASSERT_NOEXCEPT(make_asio_compaible_error(
asio_convertible_error_t::write_was_not_executed));
for(; !m_context_table.empty();
m_context_table.pop_response_context_nonchecked() )
{
const auto ec =
make_asio_compaible_error(
asio_convertible_error_t::write_was_not_executed );
auto & current_ctx = m_context_table.front();
while( !current_ctx.empty() )
{
auto wg = current_ctx.dequeue_group();
restinio::utils::suppress_exceptions_quietly( [&] {
wg.invoke_after_write_notificator_if_exists( ec );
} );
}
}
}
private:
request_id_t m_request_id_counter{ 0 };
bool m_connection_closed_response_occured{ false };
response_context_table_t m_context_table;
};
} /* namespace impl */
} /* 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