From 37df8437006b7f515484835044f470081c646413 Mon Sep 17 00:00:00 2001
From: Johannes Schmidt
Date: Wed, 2 Jul 2025 08:51:29 +0200
Subject: [PATCH 1/7] Add HttpRequest and HttpResponse classes
---
lib/remote/CMakeLists.txt | 1 +
lib/remote/httpmessage.cpp | 196 ++++++++++++++++++++++++++
lib/remote/httpmessage.hpp | 281 +++++++++++++++++++++++++++++++++++++
3 files changed, 478 insertions(+)
create mode 100644 lib/remote/httpmessage.cpp
create mode 100644 lib/remote/httpmessage.hpp
diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt
index 2271abff6ca..d8d3298c50a 100644
--- a/lib/remote/CMakeLists.txt
+++ b/lib/remote/CMakeLists.txt
@@ -27,6 +27,7 @@ set(remote_SOURCES
eventshandler.cpp eventshandler.hpp
filterutility.cpp filterutility.hpp
httphandler.cpp httphandler.hpp
+ httpmessage.cpp httpmessage.hpp
httpserverconnection.cpp httpserverconnection.hpp
httputility.cpp httputility.hpp
infohandler.cpp infohandler.hpp
diff --git a/lib/remote/httpmessage.cpp b/lib/remote/httpmessage.cpp
new file mode 100644
index 00000000000..18e5a301641
--- /dev/null
+++ b/lib/remote/httpmessage.cpp
@@ -0,0 +1,196 @@
+/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
+
+#include "remote/httpmessage.hpp"
+#include "base/io-engine.hpp"
+#include "base/json.hpp"
+#include "remote/httputility.hpp"
+#include "remote/url.hpp"
+#include
+#include
+#include
+
+using namespace icinga;
+
+/**
+ * This is the buffer size threshold above which to flush to the connection.
+ *
+ * This value was determined with a series of measurements in
+ * [PR #10516](https://github.com/Icinga/icinga2/pull/10516#issuecomment-3232642284).
+ */
+constexpr std::size_t l_FlushThreshold = 128UL * 1024UL;
+
+/**
+ * Adapter class for Boost Beast HTTP messages body to be used with the @c JsonEncoder.
+ *
+ * This class implements the @c nlohmann::detail::output_adapter_protocol<> interface and provides
+ * a way to write JSON data directly into the body of a @c HttpResponse.
+ *
+ * @ingroup base
+ */
+class HttpResponseJsonWriter : public AsyncJsonWriter
+{
+public:
+ explicit HttpResponseJsonWriter(HttpResponse& msg) : m_Message{msg}
+ {
+ m_Message.body().Start();
+#if BOOST_VERSION >= 107000
+ // We pre-allocate more than the threshold because we always go above the threshold
+ // at least once.
+ m_Message.body().Buffer().reserve(l_FlushThreshold + (l_FlushThreshold / 4));
+#endif /* BOOST_VERSION */
+ }
+
+ ~HttpResponseJsonWriter() override { m_Message.body().Finish(); }
+
+ void write_character(char c) override { write_characters(&c, 1); }
+
+ void write_characters(const char* s, std::size_t length) override
+ {
+ auto buf = m_Message.body().Buffer().prepare(length);
+ boost::asio::buffer_copy(buf, boost::asio::const_buffer{s, length});
+ m_Message.body().Buffer().commit(length);
+ }
+
+ void MayFlush(boost::asio::yield_context& yield) override
+ {
+ if (m_Message.body().Size() >= l_FlushThreshold) {
+ m_Message.Flush(yield);
+ }
+ }
+
+private:
+ HttpResponse& m_Message;
+};
+
+HttpRequest::HttpRequest(Shared::Ptr stream) : m_Stream(std::move(stream))
+{
+}
+
+void HttpRequest::ParseHeader(boost::beast::flat_buffer& buf, boost::asio::yield_context yc)
+{
+ boost::beast::http::async_read_header(*m_Stream, buf, m_Parser, yc);
+ base() = m_Parser.get().base();
+}
+
+void HttpRequest::ParseBody(boost::beast::flat_buffer& buf, boost::asio::yield_context yc)
+{
+ boost::beast::http::async_read(*m_Stream, buf, m_Parser, yc);
+ body() = std::move(m_Parser.release().body());
+}
+
+ApiUser::Ptr HttpRequest::User() const
+{
+ return m_User;
+}
+
+void HttpRequest::User(const ApiUser::Ptr& user)
+{
+ m_User = user;
+}
+
+Url::Ptr HttpRequest::Url() const
+{
+ return m_Url;
+}
+
+void HttpRequest::DecodeUrl()
+{
+ m_Url = new icinga::Url(std::string(target()));
+}
+
+Dictionary::Ptr HttpRequest::Params() const
+{
+ return m_Params;
+}
+
+void HttpRequest::DecodeParams()
+{
+ if (!m_Url) {
+ DecodeUrl();
+ }
+ m_Params = HttpUtility::FetchRequestParameters(m_Url, body());
+}
+
+HttpResponse::HttpResponse(Shared::Ptr stream, HttpServerConnection::Ptr server)
+ : m_Server(std::move(server)), m_Stream(std::move(stream))
+{
+}
+
+void HttpResponse::Clear()
+{
+ ASSERT(!m_SerializationStarted);
+ boost::beast::http::response::operator=({});
+}
+
+void HttpResponse::Flush(boost::asio::yield_context yc)
+{
+ if (!chunked() && !has_content_length()) {
+ ASSERT(!m_SerializationStarted);
+ prepare_payload();
+ }
+
+ m_SerializationStarted = true;
+
+ if (!m_Serializer.is_header_done()) {
+ boost::beast::http::write_header(*m_Stream, m_Serializer);
+ }
+
+ boost::system::error_code ec;
+ boost::beast::http::async_write(*m_Stream, m_Serializer, yc[ec]);
+ if (ec && ec != boost::beast::http::error::need_buffer) {
+ if (yc.ec_) {
+ *yc.ec_ = ec;
+ return;
+ }
+ BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
+ }
+ m_Stream->async_flush(yc);
+
+ ASSERT(m_Serializer.is_done() || !body().Finished());
+}
+
+void HttpResponse::StartStreaming(bool checkForDisconnect)
+{
+ ASSERT(body().Size() == 0 && !m_SerializationStarted);
+ body().Start();
+ chunked(true);
+
+ if (checkForDisconnect) {
+ ASSERT(m_Server);
+ m_Server->StartDetectClientSideShutdown();
+ }
+}
+
+bool HttpResponse::IsClientDisconnected() const
+{
+ ASSERT(m_Server);
+ return m_Server->Disconnected();
+}
+
+void HttpResponse::SendFile(const String& path, const boost::asio::yield_context& yc)
+{
+ std::ifstream fp(path.CStr(), std::ifstream::in | std::ifstream::binary | std::ifstream::ate);
+ fp.exceptions(std::ifstream::badbit | std::ifstream::eofbit);
+
+ std::uint64_t remaining = fp.tellg();
+ fp.seekg(0);
+
+ content_length(remaining);
+ body().Start();
+
+ while (remaining) {
+ auto maxTransfer = std::min(remaining, static_cast(l_FlushThreshold));
+
+ auto buf = *body().Buffer().prepare(maxTransfer).begin();
+ fp.read(static_cast(buf.data()), buf.size());
+ body().Buffer().commit(buf.size());
+
+ remaining -= buf.size();
+ Flush(yc);
+ }
+}
+
+JsonEncoder HttpResponse::GetJsonEncoder(bool pretty)
+{
+ return JsonEncoder{std::make_shared(*this), pretty};
+}
diff --git a/lib/remote/httpmessage.hpp b/lib/remote/httpmessage.hpp
new file mode 100644
index 00000000000..10d00fd4989
--- /dev/null
+++ b/lib/remote/httpmessage.hpp
@@ -0,0 +1,281 @@
+/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
+
+#pragma once
+
+#include "base/dictionary.hpp"
+#include "base/json.hpp"
+#include "base/tlsstream.hpp"
+#include "remote/apiuser.hpp"
+#include "remote/httpserverconnection.hpp"
+#include "remote/url.hpp"
+#include
+#include
+
+namespace icinga {
+
+/**
+ * A custom body_type for a @c boost::beast::http::message
+ *
+ * It combines the memory management of @c boost::beast::http::dynamic_body,
+ * which uses a multi_buffer, with the ability to continue serialization when
+ * new data arrives of the @c boost::beast::http::buffer_body.
+ *
+ * @tparam DynamicBuffer A buffer conforming to the boost::beast interface of the same name
+ *
+ * @ingroup remote
+ */
+template
+struct SerializableBody
+{
+ class writer;
+
+ class value_type
+ {
+ public:
+ template
+ value_type& operator<<(T&& right)
+ {
+ /* Preferably, we would return an ostream object here instead. However
+ * there seems to be a bug in boost::beast where if the ostream, or rather its
+ * streambuf object is moved into the return value, the chunked encoding gets
+ * mangled, leading to the client disconnecting.
+ *
+ * A workaround would have been to construct the boost::beast::detail::ostream_helper
+ * with the last parameter set to false, indicating that the streambuf object is not
+ * movable, but that is an implementation detail we'd rather not use directly in our
+ * code.
+ *
+ * This version has a certain overhead of the ostream being constructed on every call
+ * to the operator, which leads to an individual append for each time, whereas if the
+ * object could be kept until the entire chain of output operators is finished, only
+ * a single call to prepare()/commit() would have been needed.
+ *
+ * However, since this operator is mostly used for small error messages and the big
+ * responses are handled via a reader instance, this shouldn't be too much of a
+ * problem.
+ */
+ boost::beast::ostream(m_Buffer) << std::forward(right);
+ return *this;
+ }
+
+ [[nodiscard]] std::size_t Size() const { return m_Buffer.size(); }
+
+ void Finish() { m_More = false; }
+ bool Finished() { return !m_More; }
+ void Start() { m_More = true; }
+ DynamicBuffer& Buffer() { return m_Buffer; }
+
+ friend class writer;
+
+ private:
+ /* This defaults to false so the body does not require any special handling
+ * for simple messages and can still be written with http::async_write().
+ */
+ bool m_More = false;
+ DynamicBuffer m_Buffer;
+ };
+
+ static std::uint64_t size(const value_type& body) { return body.Size(); }
+
+ /**
+ * Implement the boost::beast BodyWriter interface for this body type
+ *
+ * This is used (for example) by the @c boost::beast::http::serializer to write out the
+ * message over the TLS stream. The logic is similar to the writer of the
+ * @c boost::beast::http::buffer_body.
+ *
+ * On the every call, it will free up the buffer range that has previously been written,
+ * then return a buffer containing data the has become available in the meantime. Otherwise,
+ * if there is more data expected in the future, for example because a corresponding reader
+ * has not yet finished filling the body, a `need_buffer` error is returned, to inform the
+ * serializer to abort writing for now, which in turn leads to the outer call to
+ * `http::async_write` to call their completion handlers with a `need_buffer` error, to
+ * notify that more data is required for another call to `http::async_write`.
+ */
+ class writer
+ {
+ public:
+ using const_buffers_type = typename DynamicBuffer::const_buffers_type;
+
+#if BOOST_VERSION > 106600
+ template
+ explicit writer(const boost::beast::http::header&, value_type& b) : m_Body(b)
+ {
+ }
+#else
+ /**
+ * This constructor is needed specifically for boost-1.66, which was the first version
+ * the beast library was introduced and is still used on older (supported) distros.
+ */
+ template
+ explicit writer(const boost::beast::http::message& msg)
+ : m_Body(const_cast(msg.body()))
+ {
+ }
+#endif
+ void init(boost::beast::error_code& ec) { ec = {}; }
+
+ boost::optional> get(boost::beast::error_code& ec)
+ {
+ using namespace boost::beast::http;
+
+ if (m_SizeWritten > 0) {
+ m_Body.m_Buffer.consume(std::exchange(m_SizeWritten, 0));
+ }
+
+ if (m_Body.m_Buffer.size()) {
+ ec = {};
+ m_SizeWritten = m_Body.m_Buffer.size();
+ return {{m_Body.m_Buffer.data(), m_Body.m_More}};
+ }
+
+ if (m_Body.m_More) {
+ ec = {make_error_code(error::need_buffer)};
+ } else {
+ ec = {};
+ }
+ return boost::none;
+ }
+
+ private:
+ value_type& m_Body;
+ std::size_t m_SizeWritten = 0;
+ };
+};
+
+/**
+ * A wrapper class for a boost::beast HTTP request
+ *
+ * @ingroup remote
+ */
+class HttpRequest : public boost::beast::http::request
+{
+public:
+ using ParserType = boost::beast::http::request_parser;
+
+ explicit HttpRequest(Shared::Ptr stream);
+
+ /**
+ * Parse the header of the response using the internal parser object.
+ *
+ * This first performs an @f async_read_header() into the parser, then copies
+ * the parsed header into this object.
+ */
+ void ParseHeader(boost::beast::flat_buffer& buf, boost::asio::yield_context yc);
+
+ /**
+ * Parse the body of the response using the internal parser object.
+ *
+ * This first performs an async_read() into the parser, then moves the parsed body
+ * into this object.
+ *
+ * @param buf The buffer used to track the state of the connection
+ * @param yc The yield_context for this operation
+ */
+ void ParseBody(boost::beast::flat_buffer& buf, boost::asio::yield_context yc);
+
+ ParserType& Parser() { return m_Parser; }
+
+ [[nodiscard]] ApiUser::Ptr User() const;
+ void User(const ApiUser::Ptr& user);
+
+ [[nodiscard]] icinga::Url::Ptr Url() const;
+ void DecodeUrl();
+
+ [[nodiscard]] Dictionary::Ptr Params() const;
+ void DecodeParams();
+
+private:
+ ApiUser::Ptr m_User;
+ Url::Ptr m_Url;
+ Dictionary::Ptr m_Params;
+
+ ParserType m_Parser;
+
+ Shared::Ptr m_Stream;
+};
+
+/**
+ * A wrapper class for a boost::beast HTTP response
+ *
+ * @ingroup remote
+ */
+class HttpResponse : public boost::beast::http::response>
+{
+public:
+ explicit HttpResponse(Shared::Ptr stream, HttpServerConnection::Ptr server = nullptr);
+
+ /* Delete the base class clear() which is inherited from the fields<> class and doesn't
+ * clear things like the body or obviously our own members.
+ */
+ void clear() = delete;
+
+ /**
+ * Clear the header and body of the message.
+ *
+ * @note This can only be used when nothing has been written to the stream yet.
+ */
+ void Clear();
+
+ /**
+ * Writes as much of the response as is currently available.
+ *
+ * Uses chunk-encoding if the content_length has not been set by the time this is called
+ * for the first time.
+ *
+ * The caller needs to ensure that the header is finished before calling this for the
+ * first time as changes to the header afterwards will not have any effect.
+ *
+ * @param yc The yield_context for this operation
+ */
+ void Flush(boost::asio::yield_context yc);
+
+ [[nodiscard]] bool HasSerializationStarted() const { return m_SerializationStarted; }
+
+ /**
+ * Enables chunked encoding.
+ *
+ * Optionally starts a coroutine that reads from the stream and checks for client-side
+ * disconnects. In this case, the stream can not be reused after the response has been
+ * sent and any further requests sent over the connections will be discarded, even if
+ * no client-side disconnect occurs. This requires that this object has been constructed
+ * with a valid HttpServerConnection::Ptr.
+ *
+ * @param checkForDisconnect Whether to start a coroutine to detect disconnects
+ */
+ void StartStreaming(bool checkForDisconnect = false);
+
+ /**
+ * Check if the server has initiated a disconnect.
+ *
+ * @note This requires that the message has been constructed with a pointer to the
+ * @c HttpServerConnection.
+ */
+ [[nodiscard]] bool IsClientDisconnected() const;
+
+ /**
+ * Sends the contents of a file.
+ *
+ * This does not use chunked encoding because the file size is expected to be fixed.
+ * The message will be flushed to the stream after a certain amount has been loaded into
+ * the buffer.
+ *
+ * @todo Switch the implementation to @c boost::asio::stream_file when we require >=boost-1.78.
+ *
+ * @param path A path to the file
+ * @param yc The yield context for flushing the message.
+ */
+ void SendFile(const String& path, const boost::asio::yield_context& yc);
+
+ JsonEncoder GetJsonEncoder(bool pretty = false);
+
+private:
+ using Serializer = boost::beast::http::response_serializer;
+ Serializer m_Serializer{*this};
+ bool m_SerializationStarted = false;
+
+ HttpServerConnection::Ptr m_Server;
+ Shared::Ptr m_Stream;
+};
+
+} // namespace icinga
From 3832bb4296626dfd31b31d3b56e0c41a0505d6d3 Mon Sep 17 00:00:00 2001
From: Johannes Schmidt
Date: Wed, 23 Jul 2025 09:37:05 +0200
Subject: [PATCH 2/7] Use new HTTP message classes in HttpServerConnection and
Handlers
---
lib/remote/actionshandler.cpp | 10 +-
lib/remote/actionshandler.hpp | 7 +-
lib/remote/configfileshandler.cpp | 18 +--
lib/remote/configfileshandler.hpp | 7 +-
lib/remote/configpackageshandler.cpp | 53 ++++---
lib/remote/configpackageshandler.hpp | 31 +---
lib/remote/configstageshandler.cpp | 53 ++++---
lib/remote/configstageshandler.hpp | 31 +---
lib/remote/consolehandler.cpp | 29 ++--
lib/remote/consolehandler.hpp | 17 +--
lib/remote/createobjecthandler.cpp | 11 +-
lib/remote/createobjecthandler.hpp | 7 +-
lib/remote/deleteobjecthandler.cpp | 11 +-
lib/remote/deleteobjecthandler.hpp | 7 +-
lib/remote/eventshandler.cpp | 13 +-
lib/remote/eventshandler.hpp | 7 +-
lib/remote/httphandler.cpp | 32 +++--
lib/remote/httphandler.hpp | 14 +-
lib/remote/httpserverconnection.cpp | 203 +++++++++++++--------------
lib/remote/httpserverconnection.hpp | 4 +-
lib/remote/httputility.cpp | 8 +-
lib/remote/httputility.hpp | 8 +-
lib/remote/infohandler.cpp | 29 ++--
lib/remote/infohandler.hpp | 7 +-
lib/remote/mallocinfohandler.cpp | 14 +-
lib/remote/mallocinfohandler.hpp | 7 +-
lib/remote/modifyobjecthandler.cpp | 11 +-
lib/remote/modifyobjecthandler.hpp | 7 +-
lib/remote/objectqueryhandler.cpp | 11 +-
lib/remote/objectqueryhandler.hpp | 7 +-
lib/remote/statushandler.cpp | 11 +-
lib/remote/statushandler.hpp | 7 +-
lib/remote/templatequeryhandler.cpp | 11 +-
lib/remote/templatequeryhandler.hpp | 7 +-
lib/remote/typequeryhandler.cpp | 11 +-
lib/remote/typequeryhandler.hpp | 7 +-
lib/remote/variablequeryhandler.cpp | 11 +-
lib/remote/variablequeryhandler.hpp | 7 +-
38 files changed, 325 insertions(+), 421 deletions(-)
diff --git a/lib/remote/actionshandler.cpp b/lib/remote/actionshandler.cpp
index 5ae5fdc8062..f0fd713b1e5 100644
--- a/lib/remote/actionshandler.cpp
+++ b/lib/remote/actionshandler.cpp
@@ -18,16 +18,16 @@ REGISTER_URLHANDLER("/v1/actions", ActionsHandler);
bool ActionsHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
if (url->GetPath().size() != 3)
return false;
diff --git a/lib/remote/actionshandler.hpp b/lib/remote/actionshandler.hpp
index fbf716797ec..83132eeecdf 100644
--- a/lib/remote/actionshandler.hpp
+++ b/lib/remote/actionshandler.hpp
@@ -18,11 +18,8 @@ class ActionsHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp
index 6c390e804b8..9a4da43ffe7 100644
--- a/lib/remote/configfileshandler.cpp
+++ b/lib/remote/configfileshandler.cpp
@@ -16,17 +16,18 @@ REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler);
bool ConfigFilesHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (request.method() != http::verb::get)
return false;
@@ -78,14 +79,9 @@ bool ConfigFilesHandler::HandleRequest(
}
try {
- std::ifstream fp(path.CStr(), std::ifstream::in | std::ifstream::binary);
- fp.exceptions(std::ifstream::badbit);
-
- String content((std::istreambuf_iterator(fp)), std::istreambuf_iterator());
response.result(http::status::ok);
response.set(http::field::content_type, "application/octet-stream");
- response.body() = content;
- response.content_length(response.body().size());
+ response.SendFile(path, yc);
} catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 500, "Could not read file.",
DiagnosticInformation(ex));
diff --git a/lib/remote/configfileshandler.hpp b/lib/remote/configfileshandler.hpp
index a8826d8c1f5..0bb12488d02 100644
--- a/lib/remote/configfileshandler.hpp
+++ b/lib/remote/configfileshandler.hpp
@@ -16,11 +16,8 @@ class ConfigFilesHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp
index f24f5b1d200..0f1009bfda2 100644
--- a/lib/remote/configpackageshandler.cpp
+++ b/lib/remote/configpackageshandler.cpp
@@ -14,42 +14,41 @@ REGISTER_URLHANDLER("/v1/config/packages", ConfigPackagesHandler);
bool ConfigPackagesHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() > 4)
return false;
if (request.method() == http::verb::get)
- HandleGet(user, request, url, response, params);
+ HandleGet(request, response);
else if (request.method() == http::verb::post)
- HandlePost(user, request, url, response, params);
+ HandlePost(request, response);
else if (request.method() == http::verb::delete_)
- HandleDelete(user, request, url, response, params);
+ HandleDelete(request, response);
else
return false;
return true;
}
-void ConfigPackagesHandler::HandleGet(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
-)
+void ConfigPackagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
FilterUtility::CheckPermission(user, "config/query");
std::vector packages;
@@ -90,16 +89,14 @@ void ConfigPackagesHandler::HandleGet(
HttpUtility::SendJsonBody(response, params, result);
}
-void ConfigPackagesHandler::HandlePost(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
-)
+void ConfigPackagesHandler::HandlePost(const HttpRequest& request, HttpResponse& response)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
FilterUtility::CheckPermission(user, "config/modify");
if (url->GetPath().size() >= 4)
@@ -142,16 +139,14 @@ void ConfigPackagesHandler::HandlePost(
HttpUtility::SendJsonBody(response, params, result);
}
-void ConfigPackagesHandler::HandleDelete(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
-)
+void ConfigPackagesHandler::HandleDelete(const HttpRequest& request, HttpResponse& response)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
FilterUtility::CheckPermission(user, "config/modify");
if (url->GetPath().size() >= 4)
diff --git a/lib/remote/configpackageshandler.hpp b/lib/remote/configpackageshandler.hpp
index 2bae0e26576..95bcfacbc64 100644
--- a/lib/remote/configpackageshandler.hpp
+++ b/lib/remote/configpackageshandler.hpp
@@ -16,37 +16,16 @@ class ConfigPackagesHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
private:
- void HandleGet(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
- );
- void HandlePost(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
- );
- void HandleDelete(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
- );
+ void HandleGet(const HttpRequest& request, HttpResponse& response);
+ void HandlePost(const HttpRequest& request, HttpResponse& response);
+ void HandleDelete(const HttpRequest& request, HttpResponse& response);
};
diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp
index 451ba1dbff9..8ee99fbddfb 100644
--- a/lib/remote/configstageshandler.cpp
+++ b/lib/remote/configstageshandler.cpp
@@ -21,42 +21,41 @@ static std::mutex l_RunningPackageUpdatesMutex; // Protects the above two variab
bool ConfigStagesHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() > 5)
return false;
if (request.method() == http::verb::get)
- HandleGet(user, request, url, response, params);
+ HandleGet(request, response);
else if (request.method() == http::verb::post)
- HandlePost(user, request, url, response, params);
+ HandlePost(request, response);
else if (request.method() == http::verb::delete_)
- HandleDelete(user, request, url, response, params);
+ HandleDelete(request, response);
else
return false;
return true;
}
-void ConfigStagesHandler::HandleGet(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
-)
+void ConfigStagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
FilterUtility::CheckPermission(user, "config/query");
if (url->GetPath().size() >= 4)
@@ -95,16 +94,14 @@ void ConfigStagesHandler::HandleGet(
HttpUtility::SendJsonBody(response, params, result);
}
-void ConfigStagesHandler::HandlePost(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
-)
+void ConfigStagesHandler::HandlePost(const HttpRequest& request, HttpResponse& response)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
FilterUtility::CheckPermission(user, "config/modify");
if (url->GetPath().size() >= 4)
@@ -208,16 +205,14 @@ void ConfigStagesHandler::HandlePost(
HttpUtility::SendJsonBody(response, params, result);
}
-void ConfigStagesHandler::HandleDelete(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
-)
+void ConfigStagesHandler::HandleDelete(const HttpRequest& request, HttpResponse& response)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
FilterUtility::CheckPermission(user, "config/modify");
if (url->GetPath().size() >= 4)
diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp
index a6abb726d8d..f49c2efb19e 100644
--- a/lib/remote/configstageshandler.hpp
+++ b/lib/remote/configstageshandler.hpp
@@ -16,37 +16,16 @@ class ConfigStagesHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
private:
- void HandleGet(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
- );
- void HandlePost(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
- );
- void HandleDelete(
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params
- );
+ void HandleGet(const HttpRequest& request, HttpResponse& response);
+ void HandlePost(const HttpRequest& request, HttpResponse& response);
+ void HandleDelete(const HttpRequest& request, HttpResponse& response);
};
}
diff --git a/lib/remote/consolehandler.cpp b/lib/remote/consolehandler.cpp
index c48821aaeff..c063e57814b 100644
--- a/lib/remote/consolehandler.cpp
+++ b/lib/remote/consolehandler.cpp
@@ -56,17 +56,18 @@ static void EnsureFrameCleanupTimer()
bool ConsoleHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() != 3)
return false;
@@ -96,17 +97,16 @@ bool ConsoleHandler::HandleRequest(
}
if (methodName == "execute-script")
- return ExecuteScriptHelper(request, response, params, command, session, sandboxed);
+ return ExecuteScriptHelper(request, response, command, session, sandboxed);
else if (methodName == "auto-complete-script")
- return AutocompleteScriptHelper(request, response, params, command, session, sandboxed);
+ return AutocompleteScriptHelper(request, response, command, session, sandboxed);
HttpUtility::SendJsonError(response, params, 400, "Invalid method specified: " + methodName);
return true;
}
-bool ConsoleHandler::ExecuteScriptHelper(boost::beast::http::request& request,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed)
+bool ConsoleHandler::ExecuteScriptHelper(const HttpRequest& request, HttpResponse& response,
+ const String& command, const String& session, bool sandboxed)
{
namespace http = boost::beast::http;
@@ -174,14 +174,13 @@ bool ConsoleHandler::ExecuteScriptHelper(boost::beast::http::request& request,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed)
+bool ConsoleHandler::AutocompleteScriptHelper(const HttpRequest& request, HttpResponse& response,
+ const String& command, const String& session, bool sandboxed)
{
namespace http = boost::beast::http;
@@ -213,7 +212,7 @@ bool ConsoleHandler::AutocompleteScriptHelper(boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
@@ -37,12 +34,10 @@ class ConsoleHandler final : public HttpHandler
static std::vector GetAutocompletionSuggestions(const String& word, ScriptFrame& frame);
private:
- static bool ExecuteScriptHelper(boost::beast::http::request& request,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed);
- static bool AutocompleteScriptHelper(boost::beast::http::request& request,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed);
+ static bool ExecuteScriptHelper(const HttpRequest& request, HttpResponse& response,
+ const String& command, const String& session, bool sandboxed);
+ static bool AutocompleteScriptHelper(const HttpRequest& request, HttpResponse& response,
+ const String& command, const String& session, bool sandboxed);
};
diff --git a/lib/remote/createobjecthandler.cpp b/lib/remote/createobjecthandler.cpp
index 119be1cd92f..447b74c6d23 100644
--- a/lib/remote/createobjecthandler.cpp
+++ b/lib/remote/createobjecthandler.cpp
@@ -18,17 +18,18 @@ REGISTER_URLHANDLER("/v1/objects", CreateObjectHandler);
bool CreateObjectHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() != 4)
return false;
diff --git a/lib/remote/createobjecthandler.hpp b/lib/remote/createobjecthandler.hpp
index 3f6a705c233..317cf023c4e 100644
--- a/lib/remote/createobjecthandler.hpp
+++ b/lib/remote/createobjecthandler.hpp
@@ -16,11 +16,8 @@ class CreateObjectHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp
index 54d31f13db5..d0f49f83c33 100644
--- a/lib/remote/deleteobjecthandler.cpp
+++ b/lib/remote/deleteobjecthandler.cpp
@@ -18,17 +18,18 @@ REGISTER_URLHANDLER("/v1/objects", DeleteObjectHandler);
bool DeleteObjectHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() < 3 || url->GetPath().size() > 4)
return false;
diff --git a/lib/remote/deleteobjecthandler.hpp b/lib/remote/deleteobjecthandler.hpp
index 0f9643277f4..076f7670499 100644
--- a/lib/remote/deleteobjecthandler.hpp
+++ b/lib/remote/deleteobjecthandler.hpp
@@ -16,11 +16,8 @@ class DeleteObjectHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp
index 2cbee92f39f..813d5f41e75 100644
--- a/lib/remote/eventshandler.cpp
+++ b/lib/remote/eventshandler.cpp
@@ -42,11 +42,8 @@ const String l_ApiQuery ("");
bool EventsHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
@@ -54,6 +51,10 @@ bool EventsHandler::HandleRequest(
namespace asio = boost::asio;
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() != 2)
return false;
@@ -101,7 +102,7 @@ bool EventsHandler::HandleRequest(
EventsSubscriber subscriber (std::move(eventTypes), HttpUtility::GetLastParameter(params, "filter"), l_ApiQuery);
- server.StartStreaming();
+ server.StartDetectClientSideShutdown();
response.result(http::status::ok);
response.set(http::field::content_type, "application/json");
diff --git a/lib/remote/eventshandler.hpp b/lib/remote/eventshandler.hpp
index 49229733a67..68a1f9844e9 100644
--- a/lib/remote/eventshandler.hpp
+++ b/lib/remote/eventshandler.hpp
@@ -17,11 +17,8 @@ class EventsHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp
index 79571d760d9..b6d8d0f4b3d 100644
--- a/lib/remote/httphandler.cpp
+++ b/lib/remote/httphandler.cpp
@@ -49,9 +49,8 @@ void HttpHandler::Register(const Url::Ptr& url, const HttpHandler::Ptr& handler)
void HttpHandler::ProcessRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- boost::beast::http::response& response,
+ HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
@@ -59,8 +58,8 @@ void HttpHandler::ProcessRequest(
Dictionary::Ptr node = m_UrlTree;
std::vector handlers;
- Url::Ptr url = new Url(std::string(request.target()));
- auto& path (url->GetPath());
+ request.DecodeUrl();
+ auto& path (request.Url()->GetPath());
for (std::vector::size_type i = 0; i <= path.size(); i++) {
Array::Ptr current_handlers = node->Get("handlers");
@@ -90,12 +89,10 @@ void HttpHandler::ProcessRequest(
std::reverse(handlers.begin(), handlers.end());
- Dictionary::Ptr params;
-
try {
- params = HttpUtility::FetchRequestParameters(url, request.body());
+ request.DecodeParams();
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid request body: " + DiagnosticInformation(ex, false));
+ HttpUtility::SendJsonError(response, request.Params(), 400, "Invalid request body: " + DiagnosticInformation(ex, false));
return;
}
@@ -109,12 +106,25 @@ void HttpHandler::ProcessRequest(
*/
try {
for (const HttpHandler::Ptr& handler : handlers) {
- if (handler->HandleRequest(waitGroup, stream, user, request, url, response, params, yc, server)) {
+ if (handler->HandleRequest(waitGroup, stream, request, response, yc, server)) {
processed = true;
break;
}
}
} catch (const std::exception& ex) {
+ // Errors related to writing the response should be handled in HttpServerConnection.
+ if (dynamic_cast(&ex)) {
+ throw;
+ }
+
+ /* This means we can't send an error response because the exception was thrown
+ * in the middle of a streaming response. We can't send any error response, so the
+ * only thing we can do is propagate it up.
+ */
+ if (response.HasSerializationStarted()) {
+ throw;
+ }
+
Log(LogWarning, "HttpServerConnection")
<< "Error while processing HTTP request: " << ex.what();
@@ -122,7 +132,7 @@ void HttpHandler::ProcessRequest(
}
if (!processed) {
- HttpUtility::SendJsonError(response, params, 404, "The requested path '" + boost::algorithm::join(path, "/") +
+ HttpUtility::SendJsonError(response, request.Params(), 404, "The requested path '" + boost::algorithm::join(path, "/") +
"' could not be found or the request method is not valid for this path.");
return;
}
diff --git a/lib/remote/httphandler.hpp b/lib/remote/httphandler.hpp
index ec67ae8a46f..0d6bd12b834 100644
--- a/lib/remote/httphandler.hpp
+++ b/lib/remote/httphandler.hpp
@@ -4,8 +4,10 @@
#define HTTPHANDLER_H
#include "remote/i2-remote.hpp"
+#include "base/io-engine.hpp"
#include "remote/url.hpp"
#include "remote/httpserverconnection.hpp"
+#include "remote/httpmessage.hpp"
#include "remote/apiuser.hpp"
#include "base/registry.hpp"
#include "base/tlsstream.hpp"
@@ -29,11 +31,8 @@ class HttpHandler : public Object
virtual bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) = 0;
@@ -42,9 +41,8 @@ class HttpHandler : public Object
static void ProcessRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- boost::beast::http::response& response,
+ HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
);
diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp
index 17e61f16037..cd4ca367b29 100644
--- a/lib/remote/httpserverconnection.cpp
+++ b/lib/remote/httpserverconnection.cpp
@@ -41,8 +41,7 @@ HttpServerConnection::HttpServerConnection(const WaitGroup::Ptr& waitGroup, cons
}
HttpServerConnection::HttpServerConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, const Shared::Ptr& stream, boost::asio::io_context& io)
- : m_WaitGroup(waitGroup), m_Stream(stream), m_Seen(Utility::GetTime()), m_IoStrand(io), m_ShuttingDown(false), m_HasStartedStreaming(false),
- m_CheckLivenessTimer(io)
+ : m_WaitGroup(waitGroup), m_Stream(stream), m_Seen(Utility::GetTime()), m_IoStrand(io), m_ShuttingDown(false), m_ConnectionReusable(true), m_CheckLivenessTimer(io)
{
if (authenticated) {
m_ApiUser = ApiUser::GetByClientCN(identity);
@@ -99,14 +98,40 @@ void HttpServerConnection::Disconnect(boost::asio::yield_context yc)
}
}
-void HttpServerConnection::StartStreaming()
+/**
+ * Starts a coroutine that continually reads from the stream to detect a disconnect from the client.
+ *
+ * This can be accessed inside an @c HttpHandler via the HttpResponse::StartStreaming() method by
+ * passing true as the argument, expressing that disconnect detection is desired.
+ */
+void HttpServerConnection::StartDetectClientSideShutdown()
{
namespace asio = boost::asio;
- m_HasStartedStreaming = true;
+ m_ConnectionReusable = false;
HttpServerConnection::Ptr keepAlive (this);
+ /* Technically it would be possible to detect disconnects on the TCP-side by setting the
+ * socket to non-blocking and then performing a read directly on the socket with the message_peek
+ * flag. As the TCP FIN message will put the connection into a CLOSE_WAIT even if the kernel
+ * buffer is full, this would technically be reliable way of detecting a shutdown and free
+ * of side-effects.
+ *
+ * However, for detecting the close_notify on the SSL/TLS-side, an async_fill() would be necessary
+ * when the check on the TCP level above returns that there are readable bytes (and no FIN/eof).
+ * If this async_fill() then buffers more application data and not an immediate eof, we could
+ * attempt to read another message before disconnecting.
+ *
+ * This could either be done at the level of the handlers, via the @c HttpResponse class, or
+ * generally as a separate coroutine here in @c HttpServerConnection, both (mostly) side-effect
+ * free and without affecting the state of the connection.
+ *
+ * However, due to the complexity of this approach, involving several asio operations, message
+ * flags, synchronous and asynchronous operations in blocking and non-blocking mode, ioctl cmds,
+ * etc., it was decided to stick with a simple reading loop, started conditionally on request by
+ * the handler.
+ */
IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) {
if (!m_ShuttingDown) {
char buf[128];
@@ -129,10 +154,9 @@ bool HttpServerConnection::Disconnected()
static inline
bool EnsureValidHeaders(
- AsioTlsStream& stream,
boost::beast::flat_buffer& buf,
- boost::beast::http::parser& parser,
- boost::beast::http::response& response,
+ HttpRequest& request,
+ HttpResponse& response,
bool& shuttingDown,
boost::asio::yield_context& yc
)
@@ -147,7 +171,7 @@ bool EnsureValidHeaders(
boost::system::error_code ec;
- http::async_read_header(stream, buf, parser, yc[ec]);
+ request.ParseHeader(buf, yc[ec]);
if (ec) {
if (ec == boost::asio::error::operation_aborted)
@@ -156,7 +180,7 @@ bool EnsureValidHeaders(
errorMsg = ec.message();
httpError = true;
} else {
- switch (parser.get().version()) {
+ switch (request.version()) {
case 10:
case 11:
break;
@@ -168,21 +192,16 @@ bool EnsureValidHeaders(
if (!errorMsg.IsEmpty() || httpError) {
response.result(http::status::bad_request);
- if (!httpError && parser.get()[http::field::accept] == "application/json") {
- HttpUtility::SendJsonBody(response, nullptr, new Dictionary({
- { "error", 400 },
- { "status", String("Bad Request: ") + errorMsg }
- }));
+ if (!httpError && request[http::field::accept] == "application/json") {
+ HttpUtility::SendJsonError(response, nullptr, 400, "Bad Request: " + errorMsg);
} else {
response.set(http::field::content_type, "text/html");
- response.body() = String("Bad Request
") + errorMsg + "
";
- response.content_length(response.body().size());
+ response.body() << "Bad Request
" << errorMsg << "
";
}
response.set(http::field::connection, "close");
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
+ response.Flush(yc);
return false;
}
@@ -192,28 +211,24 @@ bool EnsureValidHeaders(
static inline
void HandleExpect100(
- AsioTlsStream& stream,
- boost::beast::http::request& request,
+ const Shared::Ptr& stream,
+ const HttpRequest& request,
boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
if (request[http::field::expect] == "100-continue") {
- http::response response;
-
+ HttpResponse response{stream};
response.result(http::status::continue_);
-
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
+ response.Flush(yc);
}
}
static inline
bool HandleAccessControl(
- AsioTlsStream& stream,
- boost::beast::http::request& request,
- boost::beast::http::response& response,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc
)
{
@@ -240,12 +255,10 @@ bool HandleAccessControl(
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, Content-Type, X-HTTP-Method-Override");
- response.body() = "Preflight OK";
- response.content_length(response.body().size());
+ response.body() << "Preflight OK";
response.set(http::field::connection, "close");
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
+ response.Flush(yc);
return false;
}
@@ -258,9 +271,8 @@ bool HandleAccessControl(
static inline
bool EnsureAcceptHeader(
- AsioTlsStream& stream,
- boost::beast::http::request& request,
- boost::beast::http::response& response,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc
)
{
@@ -269,12 +281,10 @@ bool EnsureAcceptHeader(
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.content_length(response.body().size());
+ response.body() << "Accept header is missing or not set to 'application/json'.
";
response.set(http::field::connection, "close");
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
+ response.Flush(yc);
return false;
}
@@ -284,16 +294,14 @@ bool EnsureAcceptHeader(
static inline
bool EnsureAuthenticatedUser(
- AsioTlsStream& stream,
- boost::beast::http::request& request,
- ApiUser::Ptr& authenticatedUser,
- boost::beast::http::response& response,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
- if (!authenticatedUser) {
+ if (!request.User()) {
Log(LogWarning, "HttpServerConnection")
<< "Unauthorized request: " << request.method_string() << ' ' << request.target();
@@ -302,18 +310,13 @@ bool EnsureAuthenticatedUser(
response.set(http::field::connection, "close");
if (request[http::field::accept] == "application/json") {
- HttpUtility::SendJsonBody(response, nullptr, new Dictionary({
- { "error", 401 },
- { "status", "Unauthorized. Please check your user credentials." }
- }));
+ HttpUtility::SendJsonError(response, nullptr, 401, "Unauthorized. Please check your user credentials.");
} else {
response.set(http::field::content_type, "text/html");
- response.body() = "Unauthorized. Please check your user credentials.
";
- response.content_length(response.body().size());
+ response.body() << "Unauthorized. Please check your user credentials.
";
}
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
+ response.Flush(yc);
return false;
}
@@ -323,11 +326,9 @@ bool EnsureAuthenticatedUser(
static inline
bool EnsureValidBody(
- AsioTlsStream& stream,
boost::beast::flat_buffer& buf,
- boost::beast::http::parser& parser,
- ApiUser::Ptr& authenticatedUser,
- boost::beast::http::response& response,
+ HttpRequest& request,
+ HttpResponse& response,
bool& shuttingDown,
boost::asio::yield_context& yc
)
@@ -336,7 +337,7 @@ bool EnsureValidBody(
{
size_t maxSize = 1024 * 1024;
- Array::Ptr permissions = authenticatedUser->GetPermissions();
+ Array::Ptr permissions = request.User()->GetPermissions();
if (permissions) {
ObjectLock olock(permissions);
@@ -366,7 +367,7 @@ bool EnsureValidBody(
}
}
- parser.body_limit(maxSize);
+ request.Parser().body_limit(maxSize);
}
if (shuttingDown)
@@ -374,7 +375,7 @@ bool EnsureValidBody(
boost::system::error_code ec;
- http::async_read(stream, buf, parser, yc[ec]);
+ request.ParseBody(buf, yc[ec]);
if (ec) {
if (ec == boost::asio::error::operation_aborted)
@@ -389,21 +390,16 @@ bool EnsureValidBody(
response.result(http::status::bad_request);
- if (parser.get()[http::field::accept] == "application/json") {
- HttpUtility::SendJsonBody(response, nullptr, new Dictionary({
- { "error", 400 },
- { "status", String("Bad Request: ") + ec.message() }
- }));
+ if (request[http::field::accept] == "application/json") {
+ HttpUtility::SendJsonError(response, nullptr, 400, "Bad Request: " + ec.message());
} else {
response.set(http::field::content_type, "text/html");
- response.body() = String("Bad Request
") + ec.message() + "
";
- response.content_length(response.body().size());
+ response.body() << "Bad Request
" << ec.message() << "
";
}
response.set(http::field::connection, "close");
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
+ response.Flush(yc);
return false;
}
@@ -414,52 +410,45 @@ bool EnsureValidBody(
static inline
bool ProcessRequest(
AsioTlsStream& stream,
- boost::beast::http::request& request,
- ApiUser::Ptr& authenticatedUser,
- boost::beast::http::response& response,
+ HttpRequest& request,
+ HttpResponse& response,
HttpServerConnection& server,
- bool& hasStartedStreaming,
+ bool& connectionReusable,
const WaitGroup::Ptr& waitGroup,
std::chrono::steady_clock::duration& cpuBoundWorkTime,
boost::asio::yield_context& yc
)
{
- namespace http = boost::beast::http;
-
try {
// Cache the elapsed time to acquire a CPU semaphore used to detect extremely heavy workloads.
auto start (std::chrono::steady_clock::now());
CpuBoundWork handlingRequest (yc);
cpuBoundWorkTime = std::chrono::steady_clock::now() - start;
- HttpHandler::ProcessRequest(waitGroup, stream, authenticatedUser, request, response, yc, server);
+ HttpHandler::ProcessRequest(waitGroup, stream, request, response, yc, server);
} catch (const std::exception& ex) {
- if (hasStartedStreaming) {
+ if (!connectionReusable) {
return false;
}
- auto sysErr (dynamic_cast(&ex));
-
- if (sysErr && sysErr->code() == boost::asio::error::operation_aborted) {
+ /* Since we don't know the state the stream is in, we can't send an error response and
+ * have to just cause a disconnect here.
+ */
+ if (response.HasSerializationStarted()) {
throw;
}
- http::response response;
-
- HttpUtility::SendJsonError(response, nullptr, 500, "Unhandled exception" , DiagnosticInformation(ex));
-
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
-
+ HttpUtility::SendJsonError(response, request.Params(), 500, "Unhandled exception", DiagnosticInformation(ex));
+ response.Flush(yc);
return true;
}
- if (hasStartedStreaming) {
+ if (!connectionReusable) {
return false;
}
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
+ response.body().Finish();
+ response.Flush(yc);
return true;
}
@@ -481,23 +470,21 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc)
while (m_WaitGroup->IsLockable()) {
m_Seen = Utility::GetTime();
- http::parser parser;
- http::response response;
+ HttpRequest request(m_Stream);
+ HttpResponse response(m_Stream, this);
- parser.header_limit(1024 * 1024);
- parser.body_limit(-1);
+ request.Parser().header_limit(1024 * 1024);
+ request.Parser().body_limit(-1);
response.set(http::field::server, l_ServerHeader);
- if (!EnsureValidHeaders(*m_Stream, buf, parser, response, m_ShuttingDown, yc)) {
+ if (!EnsureValidHeaders(buf, request, response, m_ShuttingDown, yc)) {
break;
}
m_Seen = Utility::GetTime();
auto start (ch::steady_clock::now());
- auto& request (parser.get());
-
{
auto method (http::string_to_verb(request["X-Http-Method-Override"]));
@@ -506,19 +493,19 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc)
}
}
- HandleExpect100(*m_Stream, request, yc);
-
- auto authenticatedUser (m_ApiUser);
+ HandleExpect100(m_Stream, request, yc);
- if (!authenticatedUser) {
- authenticatedUser = ApiUser::GetByAuthHeader(std::string(request[http::field::authorization]));
+ if (m_ApiUser) {
+ request.User(m_ApiUser);
+ } else {
+ request.User(ApiUser::GetByAuthHeader(std::string(request[http::field::authorization])));
}
Log logMsg (LogInformation, "HttpServerConnection");
logMsg << "Request " << request.method_string() << ' ' << request.target()
<< " (from " << m_PeerAddress
- << ", user: " << (authenticatedUser ? authenticatedUser->GetName() : "")
+ << ", user: " << (request.User() ? request.User()->GetName() : "")
<< ", agent: " << request[http::field::user_agent]; //operator[] - Returns the value for a field, or "" if it does not exist.
ch::steady_clock::duration cpuBoundWorkTime(0);
@@ -531,29 +518,29 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc)
logMsg << " took total " << ch::duration_cast(ch::steady_clock::now() - start).count() << "ms.";
});
- if (!HandleAccessControl(*m_Stream, request, response, yc)) {
+ if (!HandleAccessControl(request, response, yc)) {
break;
}
- if (!EnsureAcceptHeader(*m_Stream, request, response, yc)) {
+ if (!EnsureAcceptHeader(request, response, yc)) {
break;
}
- if (!EnsureAuthenticatedUser(*m_Stream, request, authenticatedUser, response, yc)) {
+ if (!EnsureAuthenticatedUser(request, response, yc)) {
break;
}
- if (!EnsureValidBody(*m_Stream, buf, parser, authenticatedUser, response, m_ShuttingDown, yc)) {
+ if (!EnsureValidBody(buf, request, response, m_ShuttingDown, yc)) {
break;
}
m_Seen = std::numeric_limits::max();
- if (!ProcessRequest(*m_Stream, request, authenticatedUser, response, *this, m_HasStartedStreaming, m_WaitGroup, cpuBoundWorkTime, yc)) {
+ if (!ProcessRequest(*m_Stream, request, response, *this, m_ConnectionReusable, m_WaitGroup, cpuBoundWorkTime, yc)) {
break;
}
- if (request.version() != 11 || request[http::field::connection] == "close") {
+ if (!request.keep_alive() || !m_ConnectionReusable) {
break;
}
}
diff --git a/lib/remote/httpserverconnection.hpp b/lib/remote/httpserverconnection.hpp
index e4f7d257ee6..1f3d5d7f959 100644
--- a/lib/remote/httpserverconnection.hpp
+++ b/lib/remote/httpserverconnection.hpp
@@ -30,7 +30,7 @@ class HttpServerConnection final : public Object
const Shared::Ptr& stream);
void Start();
- void StartStreaming();
+ void StartDetectClientSideShutdown();
bool Disconnected();
private:
@@ -41,7 +41,7 @@ class HttpServerConnection final : public Object
String m_PeerAddress;
boost::asio::io_context::strand m_IoStrand;
bool m_ShuttingDown;
- bool m_HasStartedStreaming;
+ bool m_ConnectionReusable;
boost::asio::deadline_timer m_CheckLivenessTimer;
HttpServerConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated,
diff --git a/lib/remote/httputility.cpp b/lib/remote/httputility.cpp
index a2142e5d86f..b53a8721b7a 100644
--- a/lib/remote/httputility.cpp
+++ b/lib/remote/httputility.cpp
@@ -52,16 +52,15 @@ Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String&
return arr->Get(arr->GetLength() - 1);
}
-void HttpUtility::SendJsonBody(boost::beast::http::response& response, const Dictionary::Ptr& params, const Value& val)
+void HttpUtility::SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val)
{
namespace http = boost::beast::http;
response.set(http::field::content_type, "application/json");
- response.body() = JsonEncode(val, params && GetLastParameter(params, "pretty"));
- response.content_length(response.body().size());
+ response.GetJsonEncoder(params && GetLastParameter(params, "pretty")).Encode(val);
}
-void HttpUtility::SendJsonError(boost::beast::http::response& response,
+void HttpUtility::SendJsonError(HttpResponse& response,
const Dictionary::Ptr& params, int code, const String& info, const String& diagnosticInformation)
{
Dictionary::Ptr result = new Dictionary({ { "error", code } });
@@ -74,6 +73,7 @@ void HttpUtility::SendJsonError(boost::beast::http::responseSet("diagnostic_information", diagnosticInformation);
}
+ response.Clear();
response.result(code);
HttpUtility::SendJsonBody(response, params, result);
diff --git a/lib/remote/httputility.hpp b/lib/remote/httputility.hpp
index 6465b4af925..6f64277136e 100644
--- a/lib/remote/httputility.hpp
+++ b/lib/remote/httputility.hpp
@@ -5,7 +5,7 @@
#include "remote/url.hpp"
#include "base/dictionary.hpp"
-#include
+#include "remote/httpmessage.hpp"
#include
namespace icinga
@@ -23,9 +23,9 @@ class HttpUtility
static Dictionary::Ptr FetchRequestParameters(const Url::Ptr& url, const std::string& body);
static Value GetLastParameter(const Dictionary::Ptr& params, const String& key);
- static void SendJsonBody(boost::beast::http::response& response, const Dictionary::Ptr& params, const Value& val);
- static void SendJsonError(boost::beast::http::response& response, const Dictionary::Ptr& params, const int code,
- const String& verbose = String(), const String& diagnosticInformation = String());
+ static void SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val);
+ static void SendJsonError(HttpResponse& response, const Dictionary::Ptr& params, const int code,
+ const String& info = {}, const String& diagnosticInformation = {});
};
}
diff --git a/lib/remote/infohandler.cpp b/lib/remote/infohandler.cpp
index 5fc621cd870..9363f7ca0b9 100644
--- a/lib/remote/infohandler.cpp
+++ b/lib/remote/infohandler.cpp
@@ -11,17 +11,18 @@ REGISTER_URLHANDLER("/", InfoHandler);
bool InfoHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() > 2)
return false;
@@ -77,23 +78,23 @@ bool InfoHandler::HandleRequest(
} else {
response.set(http::field::content_type, "text/html");
- String body = "Icinga 2Hello from Icinga 2 (Version: " + Application::GetAppVersion() + ")!
";
- body += "You are authenticated as " + user->GetName() + ". ";
+ auto& body = response.body();
+ body << "
Icinga 2Hello from Icinga 2 (Version: "
+ << Application::GetAppVersion() << ")!
"
+ << "You are authenticated as " << user->GetName() << ". ";
if (!permInfo.empty()) {
- body += "Your user has the following permissions:
";
+ body << "Your user has the following permissions: ";
for (const String& perm : permInfo) {
- body += "- " + perm + "
";
+ body << "- " << perm << "
";
}
- body += "
";
+ body << "
";
} else
- body += "Your user does not have any permissions.";
+ body << "Your user does not have any permissions.";
- body += R"(More information about API requests is available in the documentation.
)";
- response.body() = body;
- response.content_length(response.body().size());
+ body << R"(More information about API requests is available in the documentation.
)";
}
return true;
diff --git a/lib/remote/infohandler.hpp b/lib/remote/infohandler.hpp
index 7396f5ac9de..f0f6499a305 100644
--- a/lib/remote/infohandler.hpp
+++ b/lib/remote/infohandler.hpp
@@ -16,11 +16,8 @@ class InfoHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp
index f4c27cac48b..465b47b86aa 100644
--- a/lib/remote/mallocinfohandler.cpp
+++ b/lib/remote/mallocinfohandler.cpp
@@ -20,17 +20,18 @@ REGISTER_URLHANDLER("/v1/debug/malloc_info", MallocInfoHandler);
bool MallocInfoHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream&,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context&,
HttpServerConnection&
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() != 3) {
return false;
}
@@ -87,8 +88,7 @@ bool MallocInfoHandler::HandleRequest(
response.result(200);
response.set(http::field::content_type, "application/xml");
- response.body() = std::string(buf, bufSize);
- response.content_length(response.body().size());
+ response.body() << std::string_view(buf, bufSize);
#endif /* HAVE_MALLOC_INFO */
return true;
diff --git a/lib/remote/mallocinfohandler.hpp b/lib/remote/mallocinfohandler.hpp
index 9648fac9f08..fc32341fa18 100644
--- a/lib/remote/mallocinfohandler.hpp
+++ b/lib/remote/mallocinfohandler.hpp
@@ -15,11 +15,8 @@ class MallocInfoHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp
index c71be6a9afd..4b9157af899 100644
--- a/lib/remote/modifyobjecthandler.cpp
+++ b/lib/remote/modifyobjecthandler.cpp
@@ -16,17 +16,18 @@ REGISTER_URLHANDLER("/v1/objects", ModifyObjectHandler);
bool ModifyObjectHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() < 3 || url->GetPath().size() > 4)
return false;
diff --git a/lib/remote/modifyobjecthandler.hpp b/lib/remote/modifyobjecthandler.hpp
index f299acd6e38..32ddf176c39 100644
--- a/lib/remote/modifyobjecthandler.hpp
+++ b/lib/remote/modifyobjecthandler.hpp
@@ -16,11 +16,8 @@ class ModifyObjectHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp
index f6f049e4e2a..edc2824531f 100644
--- a/lib/remote/objectqueryhandler.cpp
+++ b/lib/remote/objectqueryhandler.cpp
@@ -91,17 +91,18 @@ Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& obje
bool ObjectQueryHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() < 3 || url->GetPath().size() > 4)
return false;
diff --git a/lib/remote/objectqueryhandler.hpp b/lib/remote/objectqueryhandler.hpp
index 376eb661e7e..d26a9e1ca73 100644
--- a/lib/remote/objectqueryhandler.hpp
+++ b/lib/remote/objectqueryhandler.hpp
@@ -16,11 +16,8 @@ class ObjectQueryHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/statushandler.cpp b/lib/remote/statushandler.cpp
index bf14152f870..9c597dd9802 100644
--- a/lib/remote/statushandler.cpp
+++ b/lib/remote/statushandler.cpp
@@ -71,17 +71,18 @@ class StatusTargetProvider final : public TargetProvider
bool StatusHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() > 3)
return false;
diff --git a/lib/remote/statushandler.hpp b/lib/remote/statushandler.hpp
index 109fd488191..1d05347d1a1 100644
--- a/lib/remote/statushandler.hpp
+++ b/lib/remote/statushandler.hpp
@@ -16,11 +16,8 @@ class StatusHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/templatequeryhandler.cpp b/lib/remote/templatequeryhandler.cpp
index a68ad6dad57..9dceabb7b62 100644
--- a/lib/remote/templatequeryhandler.cpp
+++ b/lib/remote/templatequeryhandler.cpp
@@ -78,17 +78,18 @@ class TemplateTargetProvider final : public TargetProvider
bool TemplateQueryHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() < 3 || url->GetPath().size() > 4)
return false;
diff --git a/lib/remote/templatequeryhandler.hpp b/lib/remote/templatequeryhandler.hpp
index 312cf4221e2..c62670610bc 100644
--- a/lib/remote/templatequeryhandler.hpp
+++ b/lib/remote/templatequeryhandler.hpp
@@ -16,11 +16,8 @@ class TemplateQueryHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/typequeryhandler.cpp b/lib/remote/typequeryhandler.cpp
index b2184344d73..ce09293e071 100644
--- a/lib/remote/typequeryhandler.cpp
+++ b/lib/remote/typequeryhandler.cpp
@@ -49,17 +49,18 @@ class TypeTargetProvider final : public TargetProvider
bool TypeQueryHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() > 3)
return false;
diff --git a/lib/remote/typequeryhandler.hpp b/lib/remote/typequeryhandler.hpp
index 45cbc38ec80..e0567249c87 100644
--- a/lib/remote/typequeryhandler.hpp
+++ b/lib/remote/typequeryhandler.hpp
@@ -16,11 +16,8 @@ class TypeQueryHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
diff --git a/lib/remote/variablequeryhandler.cpp b/lib/remote/variablequeryhandler.cpp
index 40552dd7d2b..b8b62bec17e 100644
--- a/lib/remote/variablequeryhandler.cpp
+++ b/lib/remote/variablequeryhandler.cpp
@@ -59,17 +59,18 @@ class VariableTargetProvider final : public TargetProvider
bool VariableQueryHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
+ auto url = request.Url();
+ auto user = request.User();
+ auto params = request.Params();
+
if (url->GetPath().size() > 3)
return false;
diff --git a/lib/remote/variablequeryhandler.hpp b/lib/remote/variablequeryhandler.hpp
index d145f5b59b7..3b7a522ae95 100644
--- a/lib/remote/variablequeryhandler.hpp
+++ b/lib/remote/variablequeryhandler.hpp
@@ -16,11 +16,8 @@ class VariableQueryHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
AsioTlsStream& stream,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
+ const HttpRequest& request,
+ HttpResponse& response,
boost::asio::yield_context& yc,
HttpServerConnection& server
) override;
From d32f04a86356b8690f391755fa84b8fe72ea9334 Mon Sep 17 00:00:00 2001
From: Johannes Schmidt
Date: Wed, 23 Jul 2025 09:44:13 +0200
Subject: [PATCH 3/7] Refactor EventsHandler to stream responses via chunked
encoding
---
lib/remote/eventshandler.cpp | 30 ++++++++++++------------------
1 file changed, 12 insertions(+), 18 deletions(-)
diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp
index 813d5f41e75..ac1fecb748c 100644
--- a/lib/remote/eventshandler.cpp
+++ b/lib/remote/eventshandler.cpp
@@ -102,33 +102,27 @@ bool EventsHandler::HandleRequest(
EventsSubscriber subscriber (std::move(eventTypes), HttpUtility::GetLastParameter(params, "filter"), l_ApiQuery);
- server.StartDetectClientSideShutdown();
+ IoBoundWorkSlot dontLockTheIoThread (yc);
response.result(http::status::ok);
response.set(http::field::content_type, "application/json");
+ response.StartStreaming(true);
+ // Send response headers before waiting for the first event.
+ response.Flush(yc);
- IoBoundWorkSlot dontLockTheIoThread (yc);
-
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
-
- asio::const_buffer newLine ("\n", 1);
+ auto encoder = response.GetJsonEncoder();
for (;;) {
auto event (subscriber.GetInbox()->Shift(yc));
- if (event) {
- String body = JsonEncode(event);
-
- boost::algorithm::replace_all(body, "\n", "");
-
- asio::const_buffer payload (body.CStr(), body.GetLength());
-
- asio::async_write(stream, payload, yc);
- asio::async_write(stream, newLine, yc);
- stream.async_flush(yc);
- } else if (server.Disconnected()) {
+ if (response.IsClientDisconnected()) {
return true;
}
+
+ if (event) {
+ encoder.Encode(event);
+ response.body() << '\n';
+ response.Flush(yc);
+ }
}
}
From 62b2dadbac828cd7f3da6c8bbe7f7f95a80e716c Mon Sep 17 00:00:00 2001
From: Johannes Schmidt
Date: Wed, 23 Jul 2025 09:44:26 +0200
Subject: [PATCH 4/7] Remove extra parameters from HTTP handler signature
These parameters are no longer needed since they were only used
by EventsHandler which was refactored in an earlier commit.
---
lib/remote/actionshandler.cpp | 4 +---
lib/remote/actionshandler.hpp | 4 +---
lib/remote/configfileshandler.cpp | 4 +---
lib/remote/configfileshandler.hpp | 4 +---
lib/remote/configpackageshandler.cpp | 4 +---
lib/remote/configpackageshandler.hpp | 4 +---
lib/remote/configstageshandler.cpp | 4 +---
lib/remote/configstageshandler.hpp | 4 +---
lib/remote/consolehandler.cpp | 4 +---
lib/remote/consolehandler.hpp | 4 +---
lib/remote/createobjecthandler.cpp | 4 +---
lib/remote/createobjecthandler.hpp | 4 +---
lib/remote/deleteobjecthandler.cpp | 4 +---
lib/remote/deleteobjecthandler.hpp | 4 +---
lib/remote/eventshandler.cpp | 4 +---
lib/remote/eventshandler.hpp | 4 +---
lib/remote/httphandler.cpp | 6 ++----
lib/remote/httphandler.hpp | 8 ++------
lib/remote/httpserverconnection.cpp | 25 ++++---------------------
lib/remote/infohandler.cpp | 4 +---
lib/remote/infohandler.hpp | 4 +---
lib/remote/mallocinfohandler.cpp | 4 +---
lib/remote/mallocinfohandler.hpp | 4 +---
lib/remote/modifyobjecthandler.cpp | 4 +---
lib/remote/modifyobjecthandler.hpp | 4 +---
lib/remote/objectqueryhandler.cpp | 4 +---
lib/remote/objectqueryhandler.hpp | 4 +---
lib/remote/statushandler.cpp | 4 +---
lib/remote/statushandler.hpp | 4 +---
lib/remote/templatequeryhandler.cpp | 4 +---
lib/remote/templatequeryhandler.hpp | 4 +---
lib/remote/typequeryhandler.cpp | 4 +---
lib/remote/typequeryhandler.hpp | 4 +---
lib/remote/variablequeryhandler.cpp | 4 +---
lib/remote/variablequeryhandler.hpp | 4 +---
35 files changed, 40 insertions(+), 127 deletions(-)
diff --git a/lib/remote/actionshandler.cpp b/lib/remote/actionshandler.cpp
index f0fd713b1e5..b9853945be2 100644
--- a/lib/remote/actionshandler.cpp
+++ b/lib/remote/actionshandler.cpp
@@ -17,11 +17,9 @@ REGISTER_URLHANDLER("/v1/actions", ActionsHandler);
bool ActionsHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/actionshandler.hpp b/lib/remote/actionshandler.hpp
index 83132eeecdf..3ba856f6979 100644
--- a/lib/remote/actionshandler.hpp
+++ b/lib/remote/actionshandler.hpp
@@ -17,11 +17,9 @@ class ActionsHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp
index 9a4da43ffe7..2bd54038647 100644
--- a/lib/remote/configfileshandler.cpp
+++ b/lib/remote/configfileshandler.cpp
@@ -15,11 +15,9 @@ REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler);
bool ConfigFilesHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/configfileshandler.hpp b/lib/remote/configfileshandler.hpp
index 0bb12488d02..3294811c0b0 100644
--- a/lib/remote/configfileshandler.hpp
+++ b/lib/remote/configfileshandler.hpp
@@ -15,11 +15,9 @@ class ConfigFilesHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp
index 0f1009bfda2..7e0c7b02c5a 100644
--- a/lib/remote/configpackageshandler.cpp
+++ b/lib/remote/configpackageshandler.cpp
@@ -13,11 +13,9 @@ REGISTER_URLHANDLER("/v1/config/packages", ConfigPackagesHandler);
bool ConfigPackagesHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/configpackageshandler.hpp b/lib/remote/configpackageshandler.hpp
index 95bcfacbc64..172690f6399 100644
--- a/lib/remote/configpackageshandler.hpp
+++ b/lib/remote/configpackageshandler.hpp
@@ -15,11 +15,9 @@ class ConfigPackagesHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
private:
diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp
index 8ee99fbddfb..b08270e56ea 100644
--- a/lib/remote/configstageshandler.cpp
+++ b/lib/remote/configstageshandler.cpp
@@ -20,11 +20,9 @@ static std::mutex l_RunningPackageUpdatesMutex; // Protects the above two variab
bool ConfigStagesHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp
index f49c2efb19e..ec333cc50c8 100644
--- a/lib/remote/configstageshandler.hpp
+++ b/lib/remote/configstageshandler.hpp
@@ -15,11 +15,9 @@ class ConfigStagesHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
private:
diff --git a/lib/remote/consolehandler.cpp b/lib/remote/consolehandler.cpp
index c063e57814b..e17d7e3c14d 100644
--- a/lib/remote/consolehandler.cpp
+++ b/lib/remote/consolehandler.cpp
@@ -55,11 +55,9 @@ static void EnsureFrameCleanupTimer()
bool ConsoleHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/consolehandler.hpp b/lib/remote/consolehandler.hpp
index c2e302ed3a8..30fb98f2eeb 100644
--- a/lib/remote/consolehandler.hpp
+++ b/lib/remote/consolehandler.hpp
@@ -24,11 +24,9 @@ class ConsoleHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
static std::vector GetAutocompletionSuggestions(const String& word, ScriptFrame& frame);
diff --git a/lib/remote/createobjecthandler.cpp b/lib/remote/createobjecthandler.cpp
index 447b74c6d23..beff9c98707 100644
--- a/lib/remote/createobjecthandler.cpp
+++ b/lib/remote/createobjecthandler.cpp
@@ -17,11 +17,9 @@ REGISTER_URLHANDLER("/v1/objects", CreateObjectHandler);
bool CreateObjectHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/createobjecthandler.hpp b/lib/remote/createobjecthandler.hpp
index 317cf023c4e..972d7b3bdc9 100644
--- a/lib/remote/createobjecthandler.hpp
+++ b/lib/remote/createobjecthandler.hpp
@@ -15,11 +15,9 @@ class CreateObjectHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp
index d0f49f83c33..cd99f7b282b 100644
--- a/lib/remote/deleteobjecthandler.cpp
+++ b/lib/remote/deleteobjecthandler.cpp
@@ -17,11 +17,9 @@ REGISTER_URLHANDLER("/v1/objects", DeleteObjectHandler);
bool DeleteObjectHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/deleteobjecthandler.hpp b/lib/remote/deleteobjecthandler.hpp
index 076f7670499..f969facda01 100644
--- a/lib/remote/deleteobjecthandler.hpp
+++ b/lib/remote/deleteobjecthandler.hpp
@@ -15,11 +15,9 @@ class DeleteObjectHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp
index ac1fecb748c..1b7798c04ac 100644
--- a/lib/remote/eventshandler.cpp
+++ b/lib/remote/eventshandler.cpp
@@ -41,11 +41,9 @@ const String l_ApiQuery ("");
bool EventsHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace asio = boost::asio;
diff --git a/lib/remote/eventshandler.hpp b/lib/remote/eventshandler.hpp
index 68a1f9844e9..91d5ffe3f0c 100644
--- a/lib/remote/eventshandler.hpp
+++ b/lib/remote/eventshandler.hpp
@@ -16,11 +16,9 @@ class EventsHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp
index b6d8d0f4b3d..db27da31a77 100644
--- a/lib/remote/httphandler.cpp
+++ b/lib/remote/httphandler.cpp
@@ -48,11 +48,9 @@ void HttpHandler::Register(const Url::Ptr& url, const HttpHandler::Ptr& handler)
void HttpHandler::ProcessRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
Dictionary::Ptr node = m_UrlTree;
@@ -106,7 +104,7 @@ void HttpHandler::ProcessRequest(
*/
try {
for (const HttpHandler::Ptr& handler : handlers) {
- if (handler->HandleRequest(waitGroup, stream, request, response, yc, server)) {
+ if (handler->HandleRequest(waitGroup, request, response, yc)) {
processed = true;
break;
}
diff --git a/lib/remote/httphandler.hpp b/lib/remote/httphandler.hpp
index 0d6bd12b834..77f7d433717 100644
--- a/lib/remote/httphandler.hpp
+++ b/lib/remote/httphandler.hpp
@@ -30,21 +30,17 @@ class HttpHandler : public Object
virtual bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) = 0;
static void Register(const Url::Ptr& url, const HttpHandler::Ptr& handler);
static void ProcessRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
);
private:
diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp
index cd4ca367b29..d8befd21144 100644
--- a/lib/remote/httpserverconnection.cpp
+++ b/lib/remote/httpserverconnection.cpp
@@ -408,12 +408,9 @@ bool EnsureValidBody(
}
static inline
-bool ProcessRequest(
- AsioTlsStream& stream,
+void ProcessRequest(
HttpRequest& request,
HttpResponse& response,
- HttpServerConnection& server,
- bool& connectionReusable,
const WaitGroup::Ptr& waitGroup,
std::chrono::steady_clock::duration& cpuBoundWorkTime,
boost::asio::yield_context& yc
@@ -425,12 +422,9 @@ bool ProcessRequest(
CpuBoundWork handlingRequest (yc);
cpuBoundWorkTime = std::chrono::steady_clock::now() - start;
- HttpHandler::ProcessRequest(waitGroup, stream, request, response, yc, server);
+ HttpHandler::ProcessRequest(waitGroup, request, response, yc);
+ response.body().Finish();
} catch (const std::exception& ex) {
- if (!connectionReusable) {
- return false;
- }
-
/* Since we don't know the state the stream is in, we can't send an error response and
* have to just cause a disconnect here.
*/
@@ -439,18 +433,9 @@ bool ProcessRequest(
}
HttpUtility::SendJsonError(response, request.Params(), 500, "Unhandled exception", DiagnosticInformation(ex));
- response.Flush(yc);
- return true;
}
- if (!connectionReusable) {
- return false;
- }
-
- response.body().Finish();
response.Flush(yc);
-
- return true;
}
void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc)
@@ -536,9 +521,7 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc)
m_Seen = std::numeric_limits::max();
- if (!ProcessRequest(*m_Stream, request, response, *this, m_ConnectionReusable, m_WaitGroup, cpuBoundWorkTime, yc)) {
- break;
- }
+ ProcessRequest(request, response, m_WaitGroup, cpuBoundWorkTime, yc);
if (!request.keep_alive() || !m_ConnectionReusable) {
break;
diff --git a/lib/remote/infohandler.cpp b/lib/remote/infohandler.cpp
index 9363f7ca0b9..52d7c4b2669 100644
--- a/lib/remote/infohandler.cpp
+++ b/lib/remote/infohandler.cpp
@@ -10,11 +10,9 @@ REGISTER_URLHANDLER("/", InfoHandler);
bool InfoHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/infohandler.hpp b/lib/remote/infohandler.hpp
index f0f6499a305..e62a497ff86 100644
--- a/lib/remote/infohandler.hpp
+++ b/lib/remote/infohandler.hpp
@@ -15,11 +15,9 @@ class InfoHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp
index 465b47b86aa..4ca37d555b0 100644
--- a/lib/remote/mallocinfohandler.cpp
+++ b/lib/remote/mallocinfohandler.cpp
@@ -19,11 +19,9 @@ REGISTER_URLHANDLER("/v1/debug/malloc_info", MallocInfoHandler);
bool MallocInfoHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream&,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context&,
- HttpServerConnection&
+ boost::asio::yield_context&
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/mallocinfohandler.hpp b/lib/remote/mallocinfohandler.hpp
index fc32341fa18..10d8b162f42 100644
--- a/lib/remote/mallocinfohandler.hpp
+++ b/lib/remote/mallocinfohandler.hpp
@@ -14,11 +14,9 @@ class MallocInfoHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp
index 4b9157af899..9264e3c64d4 100644
--- a/lib/remote/modifyobjecthandler.cpp
+++ b/lib/remote/modifyobjecthandler.cpp
@@ -15,11 +15,9 @@ REGISTER_URLHANDLER("/v1/objects", ModifyObjectHandler);
bool ModifyObjectHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/modifyobjecthandler.hpp b/lib/remote/modifyobjecthandler.hpp
index 32ddf176c39..abc7f973537 100644
--- a/lib/remote/modifyobjecthandler.hpp
+++ b/lib/remote/modifyobjecthandler.hpp
@@ -15,11 +15,9 @@ class ModifyObjectHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp
index edc2824531f..c910a653d8b 100644
--- a/lib/remote/objectqueryhandler.cpp
+++ b/lib/remote/objectqueryhandler.cpp
@@ -90,11 +90,9 @@ Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& obje
bool ObjectQueryHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/objectqueryhandler.hpp b/lib/remote/objectqueryhandler.hpp
index d26a9e1ca73..1c7d25afd10 100644
--- a/lib/remote/objectqueryhandler.hpp
+++ b/lib/remote/objectqueryhandler.hpp
@@ -15,11 +15,9 @@ class ObjectQueryHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
private:
diff --git a/lib/remote/statushandler.cpp b/lib/remote/statushandler.cpp
index 9c597dd9802..8a16ad81e49 100644
--- a/lib/remote/statushandler.cpp
+++ b/lib/remote/statushandler.cpp
@@ -70,11 +70,9 @@ class StatusTargetProvider final : public TargetProvider
bool StatusHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/statushandler.hpp b/lib/remote/statushandler.hpp
index 1d05347d1a1..dceb58ac2a9 100644
--- a/lib/remote/statushandler.hpp
+++ b/lib/remote/statushandler.hpp
@@ -15,11 +15,9 @@ class StatusHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/templatequeryhandler.cpp b/lib/remote/templatequeryhandler.cpp
index 9dceabb7b62..81261f02d0e 100644
--- a/lib/remote/templatequeryhandler.cpp
+++ b/lib/remote/templatequeryhandler.cpp
@@ -77,11 +77,9 @@ class TemplateTargetProvider final : public TargetProvider
bool TemplateQueryHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/templatequeryhandler.hpp b/lib/remote/templatequeryhandler.hpp
index c62670610bc..3b3b58cc41c 100644
--- a/lib/remote/templatequeryhandler.hpp
+++ b/lib/remote/templatequeryhandler.hpp
@@ -15,11 +15,9 @@ class TemplateQueryHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/typequeryhandler.cpp b/lib/remote/typequeryhandler.cpp
index ce09293e071..dda19cd120b 100644
--- a/lib/remote/typequeryhandler.cpp
+++ b/lib/remote/typequeryhandler.cpp
@@ -48,11 +48,9 @@ class TypeTargetProvider final : public TargetProvider
bool TypeQueryHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/typequeryhandler.hpp b/lib/remote/typequeryhandler.hpp
index e0567249c87..f065d2471a1 100644
--- a/lib/remote/typequeryhandler.hpp
+++ b/lib/remote/typequeryhandler.hpp
@@ -15,11 +15,9 @@ class TypeQueryHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/variablequeryhandler.cpp b/lib/remote/variablequeryhandler.cpp
index b8b62bec17e..e96f6abf817 100644
--- a/lib/remote/variablequeryhandler.cpp
+++ b/lib/remote/variablequeryhandler.cpp
@@ -58,11 +58,9 @@ class VariableTargetProvider final : public TargetProvider
bool VariableQueryHandler::HandleRequest(
const WaitGroup::Ptr&,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/variablequeryhandler.hpp b/lib/remote/variablequeryhandler.hpp
index 3b7a522ae95..b6706037e11 100644
--- a/lib/remote/variablequeryhandler.hpp
+++ b/lib/remote/variablequeryhandler.hpp
@@ -15,11 +15,9 @@ class VariableQueryHandler final : public HttpHandler
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
- AsioTlsStream& stream,
const HttpRequest& request,
HttpResponse& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ boost::asio::yield_context& yc
) override;
};
From bb75d7301282e7c034abbef78cdacc87234915a7 Mon Sep 17 00:00:00 2001
From: Johannes Schmidt
Date: Fri, 27 Jun 2025 12:46:15 +0200
Subject: [PATCH 5/7] Refactor ObjectQueryHandler to use new JSON stream
encoder
---
lib/remote/objectqueryhandler.cpp | 120 +++++++++++++++++-------------
1 file changed, 68 insertions(+), 52 deletions(-)
diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp
index c910a653d8b..4384abb5576 100644
--- a/lib/remote/objectqueryhandler.cpp
+++ b/lib/remote/objectqueryhandler.cpp
@@ -1,6 +1,8 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "remote/objectqueryhandler.hpp"
+#include "base/generator.hpp"
+#include "base/json.hpp"
#include "remote/httputility.hpp"
#include "remote/filterutility.hpp"
#include "base/serializer.hpp"
@@ -9,6 +11,7 @@
#include
#include
#include
+#include
using namespace icinga;
@@ -144,6 +147,22 @@ bool ObjectQueryHandler::HandleRequest(
return true;
}
+ bool includeUsedBy = false;
+ bool includeLocation = false;
+ if (umetas) {
+ ObjectLock olock(umetas);
+ for (String meta : umetas) {
+ if (meta == "used_by") {
+ includeUsedBy = true;
+ } else if (meta == "location") {
+ includeLocation = true;
+ } else {
+ HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for meta: " + meta);
+ return true;
+ }
+ }
+ }
+
bool allJoins = HttpUtility::GetLastParameter(params, "all_joins");
params->Set("type", type->GetName());
@@ -165,10 +184,7 @@ bool ObjectQueryHandler::HandleRequest(
return true;
}
- ArrayData results;
- results.reserve(objs.size());
-
- std::set joinAttrs;
+ std::set joinAttrs;
std::set userJoinAttrs;
if (ujoins) {
@@ -187,70 +203,63 @@ bool ObjectQueryHandler::HandleRequest(
if (!allJoins && userJoinAttrs.find(field.NavigationName) == userJoinAttrs.end())
continue;
- joinAttrs.insert(field.Name);
+ joinAttrs.insert(fid);
}
std::unordered_map>> typePermissions;
std::unordered_map