From 7fe0431ada4c0cd26cde735a3da592d05070c4de Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 14 Feb 2019 16:10:41 +0100 Subject: [PATCH] HttpServerConnection: verify requests via Boost ASIO + Beast --- lib/remote/apilistener.cpp | 14 +- lib/remote/httpserverconnection.cpp | 561 +++++++++++++++------------- lib/remote/httpserverconnection.hpp | 36 +- 3 files changed, 316 insertions(+), 295 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 3f882a3d1..339ab83bf 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -619,12 +619,6 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri aclient->Disconnect(); } } - } else { - Log(LogNotice, "ApiListener", "New HTTP client"); - - HttpServerConnection::Ptr aclient = new HttpServerConnection(identity, verify_ok, tlsStream); - aclient->Start(); - AddHttpClient(aclient); } } @@ -790,6 +784,14 @@ void ApiListener::NewClientHandlerInternal(boost::asio::yield_context yc, const ctype = ClientHttp; } } + + if (ctype != ClientJsonRpc) { + Log(LogNotice, "ApiListener", "New HTTP client"); + + HttpServerConnection::Ptr aclient = new HttpServerConnection(identity, verify_ok, client); + AddHttpClient(aclient); + aclient->Start(); + } } void ApiListener::SyncClient(const JsonRpcConnection::Ptr& aclient, const Endpoint::Ptr& endpoint, bool needSync) diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index 8192dfaca..707c1a635 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -6,306 +6,303 @@ #include "remote/apilistener.hpp" #include "remote/apifunction.hpp" #include "remote/jsonrpc.hpp" +#include "base/application.hpp" #include "base/base64.hpp" #include "base/convert.hpp" #include "base/configtype.hpp" +#include "base/defer.hpp" #include "base/exception.hpp" +#include "base/io-engine.hpp" #include "base/logger.hpp" #include "base/objectlock.hpp" #include "base/timer.hpp" +#include "base/tlsstream.hpp" #include "base/utility.hpp" +#include +#include +#include +#include +#include +#include #include using namespace icinga; -static boost::once_flag l_HttpServerConnectionOnceFlag = BOOST_ONCE_INIT; -static Timer::Ptr l_HttpServerConnectionTimeoutTimer; +auto const l_ServerHeader ("Icinga/" + Application::GetAppVersion()); -HttpServerConnection::HttpServerConnection(const String& identity, bool authenticated, const TlsStream::Ptr& stream) - : m_Stream(stream), m_Seen(Utility::GetTime()), m_CurrentRequest(stream), m_PendingRequests(0) +HttpServerConnection::HttpServerConnection(const String& identity, bool authenticated, const std::shared_ptr& stream) + : m_Stream(stream) { - boost::call_once(l_HttpServerConnectionOnceFlag, &HttpServerConnection::StaticInitialize); - - m_RequestQueue.SetName("HttpServerConnection"); - - if (authenticated) + if (authenticated) { m_ApiUser = ApiUser::GetByClientCN(identity); + } - /* Cache the peer address. */ - m_PeerAddress = ""; + { + std::ostringstream address; + auto endpoint (stream->lowest_layer().remote_endpoint()); - if (stream) { - Socket::Ptr socket = m_Stream->GetSocket(); + address << '[' << endpoint.address() << "]:" << endpoint.port(); - if (socket) { - m_PeerAddress = socket->GetPeerAddress(); - } + m_PeerAddress = address.str(); } } -void HttpServerConnection::StaticInitialize() -{ - l_HttpServerConnectionTimeoutTimer = new Timer(); - l_HttpServerConnectionTimeoutTimer->OnTimerExpired.connect(std::bind(&HttpServerConnection::TimeoutTimerHandler)); - l_HttpServerConnectionTimeoutTimer->SetInterval(5); - l_HttpServerConnectionTimeoutTimer->Start(); -} - void HttpServerConnection::Start() { - /* the stream holds an owning reference to this object through the callback we're registering here */ - m_Stream->RegisterDataHandler(std::bind(&HttpServerConnection::DataAvailableHandler, HttpServerConnection::Ptr(this))); - if (m_Stream->IsDataAvailable()) - DataAvailableHandler(); -} + namespace asio = boost::asio; -ApiUser::Ptr HttpServerConnection::GetApiUser() const -{ - return m_ApiUser; + asio::spawn(IoEngine::Get().GetIoService(), [this](asio::yield_context yc) { ProcessMessages(yc); }); } -TlsStream::Ptr HttpServerConnection::GetStream() const +static inline +bool EnsureValidHeaders( + AsioTlsStream& stream, + boost::beast::flat_buffer& buf, + boost::beast::http::parser& parser, + boost::beast::http::response& response, + boost::asio::yield_context& yc +) { - return m_Stream; -} + namespace http = boost::beast::http; -void HttpServerConnection::Disconnect() -{ - boost::recursive_mutex::scoped_try_lock lock(m_DataHandlerMutex); - if (!lock.owns_lock()) { - Log(LogInformation, "HttpServerConnection", "Unable to disconnect Http client, I/O thread busy"); - return; - } + try { + try { + http::async_read_header(stream, buf, parser, yc); + } catch (const boost::system::system_error& ex) { + /** + * Unfortunately there's no way to tell an HTTP protocol error + * from an error on a lower layer: + * + * + */ + throw std::invalid_argument(ex.what()); + } - Log(LogInformation, "HttpServerConnection") - << "HTTP client disconnected (from " << m_PeerAddress << ")"; + switch (parser.get().version()) { + case 10: + case 11: + break; + default: + throw std::invalid_argument("Unsupported HTTP version"); + } + } catch (const std::invalid_argument& ex) { + response.result(http::status::bad_request); + response.set(http::field::content_type, "text/html"); + response.body() = String("

Bad Request

") + ex.what() + "

"; + response.set(http::field::content_length, response.body().size()); + response.set(http::field::connection, "close"); - ApiListener::Ptr listener = ApiListener::GetInstance(); - listener->RemoveHttpClient(this); + http::async_write(stream, response, yc); + stream.async_flush(yc); - m_CurrentRequest.~HttpRequest(); - new (&m_CurrentRequest) HttpRequest(nullptr); + return false; + } - m_Stream->Close(); + return true; } -bool HttpServerConnection::ProcessMessage() +static inline +void HandleExpect100( + AsioTlsStream& stream, + boost::beast::http::request& request, + boost::asio::yield_context& yc +) { - bool res; - HttpResponse response(m_Stream, m_CurrentRequest); + namespace http = boost::beast::http; - if (!m_CurrentRequest.CompleteHeaders) { - try { - res = m_CurrentRequest.ParseHeaders(m_Context, false); - } catch (const std::invalid_argument& ex) { - response.SetStatus(400, "Bad Request"); - String msg = String("

Bad Request

") + ex.what() + "

"; - response.WriteBody(msg.CStr(), msg.GetLength()); - response.Finish(); + if (request[http::field::expect] == "100-continue") { + http::response response; - m_CurrentRequest.~HttpRequest(); - new (&m_CurrentRequest) HttpRequest(m_Stream); + response.result(http::status::continue_); - m_Stream->Shutdown(); + http::async_write(stream, response, yc); + stream.async_flush(yc); + } +} - return false; - } catch (const std::exception& ex) { - response.SetStatus(500, "Internal Server Error"); - String msg = "

Internal Server Error

" + DiagnosticInformation(ex) + "

"; - response.WriteBody(msg.CStr(), msg.GetLength()); - response.Finish(); +static inline +bool HandleAccessControl( + AsioTlsStream& stream, + boost::beast::http::request& request, + boost::beast::http::response& response, + boost::asio::yield_context& yc +) +{ + namespace http = boost::beast::http; - m_CurrentRequest.~HttpRequest(); - new (&m_CurrentRequest) HttpRequest(m_Stream); + auto listener (ApiListener::GetInstance()); - m_Stream->Shutdown(); + if (listener) { + auto headerAllowOrigin (listener->GetAccessControlAllowOrigin()); - return false; - } - return res; - } + if (headerAllowOrigin) { + CpuBoundWork allowOriginHeader (yc); - if (!m_CurrentRequest.CompleteHeaderCheck) { - m_CurrentRequest.CompleteHeaderCheck = true; - if (!ManageHeaders(response)) { - m_CurrentRequest.~HttpRequest(); - new (&m_CurrentRequest) HttpRequest(m_Stream); + auto allowedOrigins (headerAllowOrigin->ToSet()); - m_Stream->Shutdown(); + if (!allowedOrigins.empty()) { + auto& origin (request[http::field::origin]); - return false; - } - } + if (allowedOrigins.find(origin.to_string()) != allowedOrigins.end()) { + response.set(http::field::access_control_allow_origin, origin); + } - if (!m_CurrentRequest.CompleteBody) { - try { - res = m_CurrentRequest.ParseBody(m_Context, false); - } catch (const std::invalid_argument& ex) { - response.SetStatus(400, "Bad Request"); - String msg = String("

Bad Request

") + ex.what() + "

"; - response.WriteBody(msg.CStr(), msg.GetLength()); - response.Finish(); - - m_CurrentRequest.~HttpRequest(); - new (&m_CurrentRequest) HttpRequest(m_Stream); + allowOriginHeader.Done(); - m_Stream->Shutdown(); + response.set(http::field::access_control_allow_credentials, "true"); - return false; - } catch (const std::exception& ex) { - response.SetStatus(500, "Internal Server Error"); - String msg = "

Internal Server Error

" + DiagnosticInformation(ex) + "

"; - response.WriteBody(msg.CStr(), msg.GetLength()); - response.Finish(); + if (request.method() == http::verb::options && !request[http::field::access_control_request_method].empty()) { + response.result(http::status::ok); + response.set(http::field::access_control_allow_methods, "GET, POST, PUT, DELETE"); + response.set(http::field::access_control_allow_headers, "Authorization, X-HTTP-Method-Override"); + response.body() = "Preflight OK"; + response.set(http::field::content_length, response.body().size()); + response.set(http::field::connection, "close"); - m_CurrentRequest.~HttpRequest(); - new (&m_CurrentRequest) HttpRequest(m_Stream); + http::async_write(stream, response, yc); + stream.async_flush(yc); - m_Stream->Shutdown(); - - return false; + return false; + } + } } - return res; } - m_RequestQueue.Enqueue(std::bind(&HttpServerConnection::ProcessMessageAsync, - HttpServerConnection::Ptr(this), m_CurrentRequest, response, m_AuthenticatedUser)); - - m_Seen = Utility::GetTime(); - m_PendingRequests++; - - m_CurrentRequest.~HttpRequest(); - new (&m_CurrentRequest) HttpRequest(m_Stream); - - return false; + return true; } -bool HttpServerConnection::ManageHeaders(HttpResponse& response) +static inline +bool EnsureAcceptHeader( + AsioTlsStream& stream, + boost::beast::http::request& request, + boost::beast::http::response& response, + boost::asio::yield_context& yc +) { - if (m_CurrentRequest.Headers->Get("expect") == "100-continue") { - String continueResponse = "HTTP/1.1 100 Continue\r\n\r\n"; - m_Stream->Write(continueResponse.CStr(), continueResponse.GetLength()); - } - - /* client_cn matched. */ - if (m_ApiUser) - m_AuthenticatedUser = m_ApiUser; - else - m_AuthenticatedUser = ApiUser::GetByAuthHeader(m_CurrentRequest.Headers->Get("authorization")); + namespace http = boost::beast::http; - String requestUrl = m_CurrentRequest.RequestUrl->Format(); + if (request.method() == http::verb::get && request[http::field::accept] != "application/json") { + response.result(http::status::bad_request); + response.set(http::field::content_type, "text/html"); + response.body() = "

Accept header is missing or not set to 'application/json'.

"; + response.set(http::field::content_length, response.body().size()); + response.set(http::field::connection, "close"); - Log(LogInformation, "HttpServerConnection") - << "Request: " << m_CurrentRequest.RequestMethod << " " << requestUrl - << " (from " << m_PeerAddress << ")" - << ", user: " << (m_AuthenticatedUser ? m_AuthenticatedUser->GetName() : "") << ")"; + http::async_write(stream, response, yc); + stream.async_flush(yc); - ApiListener::Ptr listener = ApiListener::GetInstance(); - - if (!listener) return false; - - Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin(); - - if (headerAllowOrigin && headerAllowOrigin->GetLength() != 0) { - String origin = m_CurrentRequest.Headers->Get("origin"); - { - ObjectLock olock(headerAllowOrigin); - - for (const String& allowedOrigin : headerAllowOrigin) { - if (allowedOrigin == origin) - response.AddHeader("Access-Control-Allow-Origin", origin); - } - } - - response.AddHeader("Access-Control-Allow-Credentials", "true"); - - String accessControlRequestMethodHeader = m_CurrentRequest.Headers->Get("access-control-request-method"); - - if (m_CurrentRequest.RequestMethod == "OPTIONS" && !accessControlRequestMethodHeader.IsEmpty()) { - response.SetStatus(200, "OK"); - - response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); - response.AddHeader("Access-Control-Allow-Headers", "Authorization, X-HTTP-Method-Override"); - - String msg = "Preflight OK"; - response.WriteBody(msg.CStr(), msg.GetLength()); - - response.Finish(); - return false; - } } - if (m_CurrentRequest.RequestMethod != "GET" && m_CurrentRequest.Headers->Get("accept") != "application/json") { - response.SetStatus(400, "Wrong Accept header"); - response.AddHeader("Content-Type", "text/html"); - String msg = "

Accept header is missing or not set to 'application/json'.

"; - response.WriteBody(msg.CStr(), msg.GetLength()); - response.Finish(); - return false; - } + return true; +} + +static inline +bool EnsureAuthenticatedUser( + AsioTlsStream& stream, + boost::beast::http::request& request, + ApiUser::Ptr& authenticatedUser, + boost::beast::http::response& response, + boost::asio::yield_context& yc +) +{ + namespace http = boost::beast::http; - if (!m_AuthenticatedUser) { + if (!authenticatedUser) { Log(LogWarning, "HttpServerConnection") - << "Unauthorized request: " << m_CurrentRequest.RequestMethod << " " << requestUrl; + << "Unauthorized request: " << request.method_string() << ' ' << request.target(); - response.SetStatus(401, "Unauthorized"); - response.AddHeader("WWW-Authenticate", "Basic realm=\"Icinga 2\""); + response.result(http::status::unauthorized); + response.set(http::field::www_authenticate, "Basic realm=\"Icinga 2\""); + response.set(http::field::connection, "close"); - if (m_CurrentRequest.Headers->Get("accept") == "application/json") { - Dictionary::Ptr result = new Dictionary({ + if (request[http::field::accept] == "application/json") { + HttpUtility::SendJsonBody(response, nullptr, new Dictionary({ { "error", 401 }, { "status", "Unauthorized. Please check your user credentials." } - }); - - HttpUtility::SendJsonBody(response, nullptr, result); + })); } else { - response.AddHeader("Content-Type", "text/html"); - String msg = "

Unauthorized. Please check your user credentials.

"; - response.WriteBody(msg.CStr(), msg.GetLength()); + response.set(http::field::content_type, "text/html"); + response.body() = "

Unauthorized. Please check your user credentials.

"; + response.set(http::field::content_length, response.body().size()); } - response.Finish(); + http::async_write(stream, response, yc); + stream.async_flush(yc); + return false; } - static const size_t defaultContentLengthLimit = 1 * 1024 * 1024; - size_t maxSize = defaultContentLengthLimit; + return true; +} + +static inline +bool EnsureValidBody( + AsioTlsStream& stream, + boost::beast::flat_buffer& buf, + boost::beast::http::parser& parser, + ApiUser::Ptr& authenticatedUser, + boost::beast::http::response& response, + boost::asio::yield_context& yc +) +{ + namespace http = boost::beast::http; + + { + size_t maxSize = 1024 * 1024; + Array::Ptr permissions = authenticatedUser->GetPermissions(); - Array::Ptr permissions = m_AuthenticatedUser->GetPermissions(); + if (permissions) { + CpuBoundWork evalPermissions (yc); - if (permissions) { - ObjectLock olock(permissions); + ObjectLock olock(permissions); - for (const Value& permissionInfo : permissions) { - String permission; + for (const Value& permissionInfo : permissions) { + String permission; - if (permissionInfo.IsObjectType()) - permission = static_cast(permissionInfo)->Get("permission"); - else - permission = permissionInfo; + if (permissionInfo.IsObjectType()) { + permission = static_cast(permissionInfo)->Get("permission"); + } else { + permission = permissionInfo; + } - static std::vector> specialContentLengthLimits { - { "config/modify", 512 * 1024 * 1024 } - }; + static std::vector> specialContentLengthLimits { + { "config/modify", 512 * 1024 * 1024 } + }; - for (const auto& limitInfo : specialContentLengthLimits) { - if (limitInfo.second <= maxSize) - continue; + for (const auto& limitInfo : specialContentLengthLimits) { + if (limitInfo.second <= maxSize) { + continue; + } - if (Utility::Match(permission, limitInfo.first)) - maxSize = limitInfo.second; + if (Utility::Match(permission, limitInfo.first)) { + maxSize = limitInfo.second; + } + } } } - } - size_t contentLength = m_CurrentRequest.Headers->Get("content-length"); + parser.body_limit(maxSize); + } - if (contentLength > maxSize) { - response.SetStatus(400, "Bad Request"); - String msg = String("

Content length exceeded maximum

"); - response.WriteBody(msg.CStr(), msg.GetLength()); - response.Finish(); + try { + http::async_read(stream, buf, parser, yc); + } catch (const boost::system::system_error& ex) { + /** + * Unfortunately there's no way to tell an HTTP protocol error + * from an error on a lower layer: + * + * + */ + + response.result(http::status::bad_request); + response.set(http::field::content_type, "text/html"); + response.body() = String("

Bad Request

") + ex.what() + "

"; + response.set(http::field::content_length, response.body().size()); + response.set(http::field::connection, "close"); + + http::async_write(stream, response, yc); + stream.async_flush(yc); return false; } @@ -313,64 +310,110 @@ bool HttpServerConnection::ManageHeaders(HttpResponse& response) return true; } -void HttpServerConnection::ProcessMessageAsync(HttpRequest& request, HttpResponse& response, const ApiUser::Ptr& user) +static inline +void ProcessRequest( + AsioTlsStream& stream, + boost::beast::http::request& request, + ApiUser::Ptr& authenticatedUser, + boost::beast::http::response& response, + boost::asio::yield_context& yc +) { - response.RebindRequest(request); + namespace http = boost::beast::http; - try { - HttpHandler::ProcessRequest(user, request, response); - } catch (const std::exception& ex) { - Log(LogCritical, "HttpServerConnection") - << "Unhandled exception while processing Http request: " << DiagnosticInformation(ex); - HttpUtility::SendJsonError(response, nullptr, 503, "Unhandled exception" , DiagnosticInformation(ex)); - } + HttpUtility::SendJsonError(response, nullptr, 503, "Unhandled exception" , ""); - response.Finish(); - m_PendingRequests--; + http::async_write(stream, response, yc); + stream.async_flush(yc); } -void HttpServerConnection::DataAvailableHandler() +void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) { - bool close = false; + namespace beast = boost::beast; + namespace http = beast::http; + + Defer removeHttpClient ([this, &yc]() { + auto listener (ApiListener::GetInstance()); - if (!m_Stream->IsEof()) { - boost::recursive_mutex::scoped_try_lock lock(m_DataHandlerMutex); - if (!lock.owns_lock()) { - Log(LogNotice, "HttpServerConnection", "Unable to process available data, they're already being processed in another thread"); - return; + if (listener) { + CpuBoundWork removeHttpClient (yc); + + listener->RemoveHttpClient(this); } + }); + Defer shutdown ([this, &yc]() { try { - while (ProcessMessage()) - ; /* empty loop body */ - } catch (const std::exception& ex) { - Log(LogWarning, "HttpServerConnection") - << "Error while reading Http request: " << DiagnosticInformation(ex); - - close = true; + m_Stream->next_layer().async_shutdown(yc); + } catch (...) { + // https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor } - } else - close = true; + }); - if (close) - Disconnect(); -} + try { + beast::flat_buffer buf; -void HttpServerConnection::CheckLiveness() -{ - if (m_Seen < Utility::GetTime() - 10 && m_PendingRequests == 0 && m_Stream->IsEof()) { - Log(LogInformation, "HttpServerConnection") - << "No messages for Http connection have been received in the last 10 seconds."; - Disconnect(); - } -} + for (;;) { + http::parser parser; + http::response response; -void HttpServerConnection::TimeoutTimerHandler() -{ - ApiListener::Ptr listener = ApiListener::GetInstance(); + parser.header_limit(1024 * 1024); + + response.set(http::field::server, l_ServerHeader); - for (const HttpServerConnection::Ptr& client : listener->GetHttpClients()) { - client->CheckLiveness(); + if (!EnsureValidHeaders(*m_Stream, buf, parser, response, yc)) { + break; + } + + auto& request (parser.get()); + + { + auto method (http::string_to_verb(request["X-Http-Method-Override"])); + + if (method != http::verb::unknown) { + request.method(method); + } + } + + HandleExpect100(*m_Stream, request, yc); + + auto authenticatedUser (m_ApiUser); + + if (!authenticatedUser) { + CpuBoundWork fetchingAuthenticatedUser (yc); + + authenticatedUser = ApiUser::GetByAuthHeader(request[http::field::authorization].to_string()); + } + + Log(LogInformation, "HttpServerConnection") + << "Request: " << request.method_string() << ' ' << request.target() + << " (from " << m_PeerAddress + << "), user: " << (authenticatedUser ? authenticatedUser->GetName() : "") << ')'; + + if (!HandleAccessControl(*m_Stream, request, response, yc)) { + break; + } + + if (!EnsureAcceptHeader(*m_Stream, request, response, yc)) { + break; + } + + if (!EnsureAuthenticatedUser(*m_Stream, request, authenticatedUser, response, yc)) { + break; + } + + if (!EnsureValidBody(*m_Stream, buf, parser, authenticatedUser, response, yc)) { + break; + } + + ProcessRequest(*m_Stream, request, authenticatedUser, response, yc); + + if (request.version() != 11 || request[http::field::connection] == "close") { + break; + } + } + } catch (const std::exception& ex) { + Log(LogCritical, "HttpServerConnection") + << "Unhandled exception while processing HTTP request: " << DiagnosticInformation(ex); } } - diff --git a/lib/remote/httpserverconnection.hpp b/lib/remote/httpserverconnection.hpp index e27ba839c..3fdbeef50 100644 --- a/lib/remote/httpserverconnection.hpp +++ b/lib/remote/httpserverconnection.hpp @@ -3,12 +3,11 @@ #ifndef HTTPSERVERCONNECTION_H #define HTTPSERVERCONNECTION_H -#include "remote/httprequest.hpp" -#include "remote/httpresponse.hpp" #include "remote/apiuser.hpp" +#include "base/string.hpp" #include "base/tlsstream.hpp" -#include "base/workqueue.hpp" -#include +#include +#include namespace icinga { @@ -23,39 +22,16 @@ class HttpServerConnection final : public Object public: DECLARE_PTR_TYPEDEFS(HttpServerConnection); - HttpServerConnection(const String& identity, bool authenticated, const TlsStream::Ptr& stream); + HttpServerConnection(const String& identity, bool authenticated, const std::shared_ptr& stream); void Start(); - ApiUser::Ptr GetApiUser() const; - bool IsAuthenticated() const; - TlsStream::Ptr GetStream() const; - - void Disconnect(); - private: ApiUser::Ptr m_ApiUser; - ApiUser::Ptr m_AuthenticatedUser; - TlsStream::Ptr m_Stream; - double m_Seen; - HttpRequest m_CurrentRequest; - boost::recursive_mutex m_DataHandlerMutex; - WorkQueue m_RequestQueue; - int m_PendingRequests; + std::shared_ptr m_Stream; String m_PeerAddress; - StreamReadContext m_Context; - - bool ProcessMessage(); - void DataAvailableHandler(); - - static void StaticInitialize(); - static void TimeoutTimerHandler(); - void CheckLiveness(); - - bool ManageHeaders(HttpResponse& response); - - void ProcessMessageAsync(HttpRequest& request, HttpResponse& response, const ApiUser::Ptr&); + void ProcessMessages(boost::asio::yield_context yc); }; } -- 2.40.0