]> granicus.if.org Git - icinga2/commitdiff
HttpServerConnection: verify requests via Boost ASIO + Beast
authorAlexander A. Klimov <alexander.klimov@icinga.com>
Thu, 14 Feb 2019 15:10:41 +0000 (16:10 +0100)
committerAlexander A. Klimov <alexander.klimov@icinga.com>
Mon, 1 Apr 2019 09:40:14 +0000 (11:40 +0200)
lib/remote/apilistener.cpp
lib/remote/httpserverconnection.cpp
lib/remote/httpserverconnection.hpp

index 3f882a3d1039f93e84015167348024e39c0e0eec..339ab83bf3ab44151f57007d7a807a5815fe9073 100644 (file)
@@ -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)
index 8192dfaca8b3ca1b6a7f134e4e0e2ca8b43e6f9a..707c1a6351d3791b41290073251e9800f77e0f0e 100644 (file)
 #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 <memory>
+#include <stdexcept>
+#include <boost/asio/spawn.hpp>
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/system/system_error.hpp>
 #include <boost/thread/once.hpp>
 
 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<AsioTlsStream>& 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 = "<unknown>";
+       {
+               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<true, boost::beast::http::string_body>& parser,
+       boost::beast::http::response<boost::beast::http::string_body>& 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:
+                        *
+                        * <https://github.com/boostorg/beast/issues/643>
+                        */
+                       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("<h1>Bad Request</h1><p><pre>") + ex.what() + "</pre></p>";
+               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<boost::beast::http::string_body>& 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("<h1>Bad Request</h1><p><pre>") + ex.what() + "</pre></p>";
-                       response.WriteBody(msg.CStr(), msg.GetLength());
-                       response.Finish();
+       if (request[http::field::expect] == "100-continue") {
+               http::response<http::string_body> 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 = "<h1>Internal Server Error</h1><p><pre>" + DiagnosticInformation(ex) + "</pre></p>";
-                       response.WriteBody(msg.CStr(), msg.GetLength());
-                       response.Finish();
+static inline
+bool HandleAccessControl(
+       AsioTlsStream& stream,
+       boost::beast::http::request<boost::beast::http::string_body>& request,
+       boost::beast::http::response<boost::beast::http::string_body>& 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<String>());
 
-                       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("<h1>Bad Request</h1><p><pre>") + ex.what() + "</pre></p>";
-                       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 = "<h1>Internal Server Error</h1><p><pre>" + DiagnosticInformation(ex) + "</pre></p>";
-                       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<boost::beast::http::string_body>& request,
+       boost::beast::http::response<boost::beast::http::string_body>& 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() = "<h1>Accept header is missing or not set to 'application/json'.</h1>";
+               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() : "<unauthenticated>") << ")";
+               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 = "<h1>Accept header is missing or not set to 'application/json'.</h1>";
-               response.WriteBody(msg.CStr(), msg.GetLength());
-               response.Finish();
-               return false;
-       }
+       return true;
+}
+
+static inline
+bool EnsureAuthenticatedUser(
+       AsioTlsStream& stream,
+       boost::beast::http::request<boost::beast::http::string_body>& request,
+       ApiUser::Ptr& authenticatedUser,
+       boost::beast::http::response<boost::beast::http::string_body>& 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 = "<h1>Unauthorized. Please check your user credentials.</h1>";
-                       response.WriteBody(msg.CStr(), msg.GetLength());
+                       response.set(http::field::content_type, "text/html");
+                       response.body() = "<h1>Unauthorized. Please check your user credentials.</h1>";
+                       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<true, boost::beast::http::string_body>& parser,
+       ApiUser::Ptr& authenticatedUser,
+       boost::beast::http::response<boost::beast::http::string_body>& 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<Dictionary>())
-                               permission = static_cast<Dictionary::Ptr>(permissionInfo)->Get("permission");
-                       else
-                               permission = permissionInfo;
+                               if (permissionInfo.IsObjectType<Dictionary>()) {
+                                       permission = static_cast<Dictionary::Ptr>(permissionInfo)->Get("permission");
+                               } else {
+                                       permission = permissionInfo;
+                               }
 
-                       static std::vector<std::pair<String, size_t>> specialContentLengthLimits {
-                                 { "config/modify", 512 * 1024 * 1024 }
-                       };
+                               static std::vector<std::pair<String, size_t>> 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("<h1>Content length exceeded maximum</h1>");
-               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:
+                *
+                * <https://github.com/boostorg/beast/issues/643>
+                */
+
+               response.result(http::status::bad_request);
+               response.set(http::field::content_type, "text/html");
+               response.body() = String("<h1>Bad Request</h1><p><pre>") + ex.what() + "</pre></p>";
+               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<boost::beast::http::string_body>& request,
+       ApiUser::Ptr& authenticatedUser,
+       boost::beast::http::response<boost::beast::http::string_body>& 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<true, http::string_body> parser;
+                       http::response<http::string_body> 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() : "<unauthenticated>") << ')';
+
+                       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);
        }
 }
-
index e27ba839c9e1c93ad7acd282be7a67ba94591b16..3fdbeef508879ecc4a261af95adf2cea3fe844d1 100644 (file)
@@ -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 <boost/thread/recursive_mutex.hpp>
+#include <memory>
+#include <boost/asio/spawn.hpp>
 
 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<AsioTlsStream>& 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<AsioTlsStream> 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);
 };
 
 }