diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt
index 2271abff6c..d8d3298c50 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/actionshandler.cpp b/lib/remote/actionshandler.cpp
index 5ae5fdc806..b9853945be 100644
--- a/lib/remote/actionshandler.cpp
+++ b/lib/remote/actionshandler.cpp
@@ -17,17 +17,15 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 fbf716797e..3ba856f697 100644
--- a/lib/remote/actionshandler.hpp
+++ b/lib/remote/actionshandler.hpp
@@ -17,14 +17,9 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp
index f278c2e9b5..0b0e29f331 100644
--- a/lib/remote/apilistener.hpp
+++ b/lib/remote/apilistener.hpp
@@ -161,12 +161,12 @@ class ApiListener final : public ObjectImpl
return m_WaitGroup;
}
-protected:
void OnConfigLoaded() override;
void OnAllConfigLoaded() override;
void Start(bool runtimeCreated) override;
void Stop(bool runtimeDeleted) override;
+protected:
void ValidateTlsProtocolmin(const Lazy& lvalue, const ValidationUtils& utils) override;
void ValidateTlsHandshakeTimeout(const Lazy& lvalue, const ValidationUtils& utils) override;
diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp
index 6c390e804b..2bd5403864 100644
--- a/lib/remote/configfileshandler.cpp
+++ b/lib/remote/configfileshandler.cpp
@@ -15,18 +15,17 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 +77,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 a8826d8c1f..3294811c0b 100644
--- a/lib/remote/configfileshandler.hpp
+++ b/lib/remote/configfileshandler.hpp
@@ -15,14 +15,9 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp
index f24f5b1d20..7e0c7b02c5 100644
--- a/lib/remote/configpackageshandler.cpp
+++ b/lib/remote/configpackageshandler.cpp
@@ -13,43 +13,40 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 +87,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 +137,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 2bae0e2657..172690f639 100644
--- a/lib/remote/configpackageshandler.hpp
+++ b/lib/remote/configpackageshandler.hpp
@@ -15,38 +15,15 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) 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 451ba1dbff..b08270e56e 100644
--- a/lib/remote/configstageshandler.cpp
+++ b/lib/remote/configstageshandler.cpp
@@ -20,43 +20,40 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 +92,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 +203,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 a6abb726d8..ec333cc50c 100644
--- a/lib/remote/configstageshandler.hpp
+++ b/lib/remote/configstageshandler.hpp
@@ -15,38 +15,15 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) 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 c48821aaef..e17d7e3c14 100644
--- a/lib/remote/consolehandler.cpp
+++ b/lib/remote/consolehandler.cpp
@@ -55,18 +55,17 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 +95,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 +172,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 +210,7 @@ bool ConsoleHandler::AutocompleteScriptHelper(boost::beast::http::request& request,
- const Url::Ptr& url,
- boost::beast::http::response& response,
- const Dictionary::Ptr& params,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
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 119be1cd92..beff9c9870 100644
--- a/lib/remote/createobjecthandler.cpp
+++ b/lib/remote/createobjecthandler.cpp
@@ -17,18 +17,17 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 3f6a705c23..972d7b3bdc 100644
--- a/lib/remote/createobjecthandler.hpp
+++ b/lib/remote/createobjecthandler.hpp
@@ -15,14 +15,9 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp
index 54d31f13db..cd99f7b282 100644
--- a/lib/remote/deleteobjecthandler.cpp
+++ b/lib/remote/deleteobjecthandler.cpp
@@ -17,18 +17,17 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 0f9643277f..f969facda0 100644
--- a/lib/remote/deleteobjecthandler.hpp
+++ b/lib/remote/deleteobjecthandler.hpp
@@ -15,14 +15,9 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp
index 2cbee92f39..1b7798c04a 100644
--- a/lib/remote/eventshandler.cpp
+++ b/lib/remote/eventshandler.cpp
@@ -41,19 +41,18 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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,33 +100,27 @@ bool EventsHandler::HandleRequest(
EventsSubscriber subscriber (std::move(eventTypes), HttpUtility::GetLastParameter(params, "filter"), l_ApiQuery);
- server.StartStreaming();
+ 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);
+ }
}
}
diff --git a/lib/remote/eventshandler.hpp b/lib/remote/eventshandler.hpp
index 49229733a6..91d5ffe3f0 100644
--- a/lib/remote/eventshandler.hpp
+++ b/lib/remote/eventshandler.hpp
@@ -16,14 +16,9 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp
index 79571d760d..db27da31a7 100644
--- a/lib/remote/httphandler.cpp
+++ b/lib/remote/httphandler.cpp
@@ -48,19 +48,16 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 +87,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 +104,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, request, response, yc)) {
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 +130,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 ec67ae8a46..77f7d43371 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"
@@ -28,25 +30,17 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ 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,
- const ApiUser::Ptr& user,
- boost::beast::http::request& request,
- boost::beast::http::response& response,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
);
private:
diff --git a/lib/remote/httpmessage.cpp b/lib/remote/httpmessage.cpp
new file mode 100644
index 0000000000..18e5a30164
--- /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 0000000000..10d00fd498
--- /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
diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp
index 17e61f1603..d8befd2114 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;
}
@@ -412,56 +408,34 @@ bool EnsureValidBody(
}
static inline
-bool ProcessRequest(
- AsioTlsStream& stream,
- boost::beast::http::request& request,
- ApiUser::Ptr& authenticatedUser,
- boost::beast::http::response& response,
- HttpServerConnection& server,
- bool& hasStartedStreaming,
+void ProcessRequest(
+ HttpRequest& request,
+ HttpResponse& response,
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, request, response, yc);
+ response.body().Finish();
} catch (const std::exception& ex) {
- if (hasStartedStreaming) {
- 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);
-
- return true;
- }
-
- if (hasStartedStreaming) {
- return false;
+ HttpUtility::SendJsonError(response, request.Params(), 500, "Unhandled exception", DiagnosticInformation(ex));
}
- http::async_write(stream, response, yc);
- stream.async_flush(yc);
-
- return true;
+ response.Flush(yc);
}
void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc)
@@ -481,23 +455,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 +478,19 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc)
}
}
- HandleExpect100(*m_Stream, request, yc);
+ HandleExpect100(m_Stream, request, yc);
- auto authenticatedUser (m_ApiUser);
-
- 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 +503,27 @@ 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)) {
- break;
- }
+ ProcessRequest(request, response, m_WaitGroup, cpuBoundWorkTime, yc);
- 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 e4f7d257ee..1f3d5d7f95 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 a2142e5d86..b53a8721b7 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 6465b4af92..6f64277136 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 5fc621cd87..52d7c4b266 100644
--- a/lib/remote/infohandler.cpp
+++ b/lib/remote/infohandler.cpp
@@ -10,18 +10,17 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 +76,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 7396f5ac9d..e62a497ff8 100644
--- a/lib/remote/infohandler.hpp
+++ b/lib/remote/infohandler.hpp
@@ -15,14 +15,9 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp
index f4c27cac48..4ca37d555b 100644
--- a/lib/remote/mallocinfohandler.cpp
+++ b/lib/remote/mallocinfohandler.cpp
@@ -19,18 +19,17 @@ 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,
- boost::asio::yield_context&,
- HttpServerConnection&
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context&
)
{
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 +86,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 9648fac9f0..10d8b162f4 100644
--- a/lib/remote/mallocinfohandler.hpp
+++ b/lib/remote/mallocinfohandler.hpp
@@ -14,14 +14,9 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp
index c71be6a9af..9264e3c64d 100644
--- a/lib/remote/modifyobjecthandler.cpp
+++ b/lib/remote/modifyobjecthandler.cpp
@@ -15,18 +15,17 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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 f299acd6e3..abc7f97353 100644
--- a/lib/remote/modifyobjecthandler.hpp
+++ b/lib/remote/modifyobjecthandler.hpp
@@ -15,14 +15,9 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
) override;
};
diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp
index f6f049e4e2..4384abb557 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;
@@ -90,18 +93,17 @@ 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,
- boost::asio::yield_context& yc,
- HttpServerConnection& server
+ const HttpRequest& request,
+ HttpResponse& response,
+ boost::asio::yield_context& yc
)
{
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;
@@ -145,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());
@@ -166,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) {
@@ -188,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