From a56388940e490a089b6a5b260e0e6b42e03ebcd6 Mon Sep 17 00:00:00 2001
From: "Alexander A. Klimov"
Date: Thu, 19 Mar 2026 15:07:51 +0100
Subject: [PATCH 1/2] Always call `HttpUtility::SendJsonBody()` with a
`boost::asio::yield_context`
to respect `l_FlushThreshold` in all messages.
Especially `l_FlushThreshold` may now be lowered in the future
without re-evaluating all possible responses.
Side effect: all HTTP responses use chunked encoding now.
---
lib/remote/actionshandler.cpp | 8 +++----
lib/remote/configfileshandler.cpp | 12 +++++------
lib/remote/configpackageshandler.cpp | 26 +++++++++++-----------
lib/remote/configpackageshandler.hpp | 4 ++--
lib/remote/configstageshandler.cpp | 32 ++++++++++++++--------------
lib/remote/configstageshandler.hpp | 4 ++--
lib/remote/consolehandler.cpp | 22 +++++++++----------
lib/remote/consolehandler.hpp | 4 ++--
lib/remote/createobjecthandler.cpp | 14 ++++++------
lib/remote/deleteobjecthandler.cpp | 8 +++----
lib/remote/eventshandler.cpp | 4 ++--
lib/remote/httphandler.cpp | 4 ++--
lib/remote/httpserverconnection.cpp | 8 +++----
lib/remote/httputility.cpp | 14 +++---------
lib/remote/httputility.hpp | 3 +--
lib/remote/infohandler.cpp | 4 ++--
lib/remote/mallocinfohandler.cpp | 4 ++--
lib/remote/modifyobjecthandler.cpp | 14 ++++++------
lib/remote/objectqueryhandler.cpp | 12 +++++------
lib/remote/statushandler.cpp | 6 +++---
lib/remote/templatequeryhandler.cpp | 4 ++--
lib/remote/typequeryhandler.cpp | 2 +-
lib/remote/variablequeryhandler.cpp | 2 +-
test/remote-httpmessage.cpp | 8 +++----
24 files changed, 107 insertions(+), 116 deletions(-)
diff --git a/lib/remote/actionshandler.cpp b/lib/remote/actionshandler.cpp
index 76874cee48f..8d40e7ea127 100644
--- a/lib/remote/actionshandler.cpp
+++ b/lib/remote/actionshandler.cpp
@@ -37,7 +37,7 @@ bool ActionsHandler::HandleRequest(
ApiAction::Ptr action = ApiAction::GetByName(actionName);
if (!action) {
- HttpUtility::SendJsonError(response, params, 404, "Action '" + actionName + "' does not exist.");
+ HttpUtility::SendJsonError(response, params, 404, yc, "Action '" + actionName + "' does not exist.");
return true;
}
@@ -55,14 +55,14 @@ bool ActionsHandler::HandleRequest(
try {
objs = FilterUtility::GetFilterTargets(qd, params, user);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No objects found.",
DiagnosticInformation(ex));
return true;
}
if (objs.empty()) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No objects found.");
return true;
}
@@ -83,7 +83,7 @@ bool ActionsHandler::HandleRequest(
std::shared_lock wgLock{*waitGroup, std::try_to_lock};
if (!wgLock) {
- HttpUtility::SendJsonError(response, params, 503, "Shutting down.");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Shutting down.");
return true;
}
diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp
index 28ea8be750d..90baa03d9cb 100644
--- a/lib/remote/configfileshandler.cpp
+++ b/lib/remote/configfileshandler.cpp
@@ -44,7 +44,7 @@ bool ConfigFilesHandler::HandleRequest(
}
if (request[http::field::accept] == "application/json") {
- HttpUtility::SendJsonError(response, params, 400, "Invalid Accept header. Either remove the Accept header or set it to 'application/octet-stream'.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid Accept header. Either remove the Accept header or set it to 'application/octet-stream'.");
return true;
}
@@ -54,26 +54,26 @@ bool ConfigFilesHandler::HandleRequest(
String stageName = HttpUtility::GetLastParameter(params, "stage");
if (!ConfigPackageUtility::ValidatePackageName(packageName)) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid package name.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid package name.");
return true;
}
if (!ConfigPackageUtility::ValidateStageName(stageName)) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid stage name.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid stage name.");
return true;
}
String relativePath = HttpUtility::GetLastParameter(params, "path");
if (ConfigPackageUtility::ContainsDotDot(relativePath)) {
- HttpUtility::SendJsonError(response, params, 400, "Path contains '..' (not allowed).");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Path contains '..' (not allowed).");
return true;
}
String path = ConfigPackageUtility::GetPackageDir() + "/" + packageName + "/" + stageName + "/" + relativePath;
if (!Utility::PathExists(path)) {
- HttpUtility::SendJsonError(response, params, 404, "Path not found.");
+ HttpUtility::SendJsonError(response, params, 404, yc, "Path not found.");
return true;
}
@@ -82,7 +82,7 @@ bool ConfigFilesHandler::HandleRequest(
response.set(http::field::content_type, "application/octet-stream");
response.SendFile(path, yc);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 500, "Could not read file.",
+ HttpUtility::SendJsonError(response, params, 500, yc, "Could not read file.",
DiagnosticInformation(ex));
}
diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp
index 7e0dc976d1d..67707145525 100644
--- a/lib/remote/configpackageshandler.cpp
+++ b/lib/remote/configpackageshandler.cpp
@@ -31,9 +31,9 @@ bool ConfigPackagesHandler::HandleRequest(
if (request.method() == http::verb::get)
HandleGet(request, response, yc);
else if (request.method() == http::verb::post)
- HandlePost(request, response);
+ HandlePost(request, response, yc);
else if (request.method() == http::verb::delete_)
- HandleDelete(request, response);
+ HandleDelete(request, response, yc);
else
return false;
@@ -55,7 +55,7 @@ void ConfigPackagesHandler::HandleGet(const HttpApiRequest& request, HttpApiResp
try {
packages = ConfigPackageUtility::GetPackages();
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 500, "Could not retrieve packages.",
+ HttpUtility::SendJsonError(response, params, 500, yc, "Could not retrieve packages.",
DiagnosticInformation(ex));
return;
}
@@ -90,7 +90,7 @@ void ConfigPackagesHandler::HandleGet(const HttpApiRequest& request, HttpApiResp
HttpUtility::SendJsonBody(response, params, result, yc);
}
-void ConfigPackagesHandler::HandlePost(const HttpApiRequest& request, HttpApiResponse& response)
+void ConfigPackagesHandler::HandlePost(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@@ -106,13 +106,13 @@ void ConfigPackagesHandler::HandlePost(const HttpApiRequest& request, HttpApiRes
String packageName = HttpUtility::GetLastParameter(params, "package");
if (!ConfigPackageUtility::ValidatePackageName(packageName)) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid package name '" + packageName + "'.");
return;
}
ConfigObjectsSharedLock configObjectsSharedLock(std::try_to_lock);
if (!configObjectsSharedLock) {
- HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Icinga is reloading");
return;
}
@@ -121,7 +121,7 @@ void ConfigPackagesHandler::HandlePost(const HttpApiRequest& request, HttpApiRes
ConfigPackageUtility::CreatePackage(packageName);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 500, "Could not create package '" + packageName + "'.",
+ HttpUtility::SendJsonError(response, params, 500, yc, "Could not create package '" + packageName + "'.",
DiagnosticInformation(ex));
return;
}
@@ -137,10 +137,10 @@ void ConfigPackagesHandler::HandlePost(const HttpApiRequest& request, HttpApiRes
});
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
}
-void ConfigPackagesHandler::HandleDelete(const HttpApiRequest& request, HttpApiResponse& response)
+void ConfigPackagesHandler::HandleDelete(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@@ -156,20 +156,20 @@ void ConfigPackagesHandler::HandleDelete(const HttpApiRequest& request, HttpApiR
String packageName = HttpUtility::GetLastParameter(params, "package");
if (!ConfigPackageUtility::ValidatePackageName(packageName)) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid package name '" + packageName + "'.");
return;
}
ConfigObjectsSharedLock lock(std::try_to_lock);
if (!lock) {
- HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Icinga is reloading");
return;
}
try {
ConfigPackageUtility::DeletePackage(packageName);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 500, "Failed to delete package '" + packageName + "'.",
+ HttpUtility::SendJsonError(response, params, 500, yc, "Failed to delete package '" + packageName + "'.",
DiagnosticInformation(ex));
return;
}
@@ -185,5 +185,5 @@ void ConfigPackagesHandler::HandleDelete(const HttpApiRequest& request, HttpApiR
});
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
}
diff --git a/lib/remote/configpackageshandler.hpp b/lib/remote/configpackageshandler.hpp
index e8742ebd1b0..47aeb46c8f1 100644
--- a/lib/remote/configpackageshandler.hpp
+++ b/lib/remote/configpackageshandler.hpp
@@ -23,8 +23,8 @@ class ConfigPackagesHandler final : public HttpHandler
private:
void HandleGet(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc);
- void HandlePost(const HttpApiRequest& request, HttpApiResponse& response);
- void HandleDelete(const HttpApiRequest& request, HttpApiResponse& response);
+ void HandlePost(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc);
+ void HandleDelete(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc);
};
diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp
index 8bcd16bbeb0..09389f139b5 100644
--- a/lib/remote/configstageshandler.cpp
+++ b/lib/remote/configstageshandler.cpp
@@ -38,9 +38,9 @@ bool ConfigStagesHandler::HandleRequest(
if (request.method() == http::verb::get)
HandleGet(request, response, yc);
else if (request.method() == http::verb::post)
- HandlePost(request, response);
+ HandlePost(request, response, yc);
else if (request.method() == http::verb::delete_)
- HandleDelete(request, response);
+ HandleDelete(request, response, yc);
else
return false;
@@ -67,10 +67,10 @@ void ConfigStagesHandler::HandleGet(const HttpApiRequest& request, HttpApiRespon
String stageName = HttpUtility::GetLastParameter(params, "stage");
if (!ConfigPackageUtility::ValidatePackageName(packageName))
- return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
+ return HttpUtility::SendJsonError(response, params, 400, yc, "Invalid package name '" + packageName + "'.");
if (!ConfigPackageUtility::ValidateStageName(stageName))
- return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
+ return HttpUtility::SendJsonError(response, params, 400, yc, "Invalid stage name '" + stageName + "'.");
std::vector > paths = ConfigPackageUtility::GetFiles(packageName, stageName);
@@ -90,7 +90,7 @@ void ConfigStagesHandler::HandleGet(const HttpApiRequest& request, HttpApiRespon
HttpUtility::SendJsonBody(response, params, result, yc);
}
-void ConfigStagesHandler::HandlePost(const HttpApiRequest& request, HttpApiResponse& response)
+void ConfigStagesHandler::HandlePost(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@@ -106,7 +106,7 @@ void ConfigStagesHandler::HandlePost(const HttpApiRequest& request, HttpApiRespo
String packageName = HttpUtility::GetLastParameter(params, "package");
if (!ConfigPackageUtility::ValidatePackageName(packageName))
- return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
+ return HttpUtility::SendJsonError(response, params, 400, yc, "Invalid package name '" + packageName + "'.");
bool reload = true;
@@ -131,7 +131,7 @@ void ConfigStagesHandler::HandlePost(const HttpApiRequest& request, HttpApiRespo
ConfigObjectsSharedLock configObjectsSharedLock(std::try_to_lock);
if (!configObjectsSharedLock) {
- HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Icinga is reloading");
return;
}
@@ -152,7 +152,7 @@ void ConfigStagesHandler::HandlePost(const HttpApiRequest& request, HttpApiRespo
*/
if (l_RunningPackageUpdates && l_LastReloadFailedTime == currentReloadFailedTime) {
return HttpUtility::SendJsonError(
- response, params, 423,
+ response, params, 423, yc,
"Conflicting request, there is already an ongoing package update in progress. Please try it again later."
);
}
@@ -173,7 +173,7 @@ void ConfigStagesHandler::HandlePost(const HttpApiRequest& request, HttpApiRespo
/* validate the config. on success, activate stage and reload */
ConfigPackageUtility::AsyncTryActivateStage(packageName, stageName, activate, reload, resetPackageUpdates);
} catch (const std::exception& ex) {
- return HttpUtility::SendJsonError(response, params, 500,
+ return HttpUtility::SendJsonError(response, params, 500, yc,
"Stage creation failed.",
DiagnosticInformation(ex));
}
@@ -198,10 +198,10 @@ void ConfigStagesHandler::HandlePost(const HttpApiRequest& request, HttpApiRespo
});
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
}
-void ConfigStagesHandler::HandleDelete(const HttpApiRequest& request, HttpApiResponse& response)
+void ConfigStagesHandler::HandleDelete(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@@ -221,21 +221,21 @@ void ConfigStagesHandler::HandleDelete(const HttpApiRequest& request, HttpApiRes
String stageName = HttpUtility::GetLastParameter(params, "stage");
if (!ConfigPackageUtility::ValidatePackageName(packageName))
- return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
+ return HttpUtility::SendJsonError(response, params, 400, yc, "Invalid package name '" + packageName + "'.");
if (!ConfigPackageUtility::ValidateStageName(stageName))
- return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
+ return HttpUtility::SendJsonError(response, params, 400, yc, "Invalid stage name '" + stageName + "'.");
ConfigObjectsSharedLock lock(std::try_to_lock);
if (!lock) {
- HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Icinga is reloading");
return;
}
try {
ConfigPackageUtility::DeleteStage(packageName, stageName);
} catch (const std::exception& ex) {
- return HttpUtility::SendJsonError(response, params, 500,
+ return HttpUtility::SendJsonError(response, params, 500, yc,
"Failed to delete stage '" + stageName + "' in package '" + packageName + "'.",
DiagnosticInformation(ex));
}
@@ -252,5 +252,5 @@ void ConfigStagesHandler::HandleDelete(const HttpApiRequest& request, HttpApiRes
});
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
}
diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp
index 8dda34122db..1c9cd6eaa79 100644
--- a/lib/remote/configstageshandler.hpp
+++ b/lib/remote/configstageshandler.hpp
@@ -23,8 +23,8 @@ class ConfigStagesHandler final : public HttpHandler
private:
void HandleGet(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc);
- void HandlePost(const HttpApiRequest& request, HttpApiResponse& response);
- void HandleDelete(const HttpApiRequest& request, HttpApiResponse& response);
+ void HandlePost(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc);
+ void HandleDelete(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc);
};
}
diff --git a/lib/remote/consolehandler.cpp b/lib/remote/consolehandler.cpp
index 9490d2420fa..e548744d88d 100644
--- a/lib/remote/consolehandler.cpp
+++ b/lib/remote/consolehandler.cpp
@@ -73,7 +73,7 @@ bool ConsoleHandler::HandleRequest(
const WaitGroup::Ptr&,
const HttpApiRequest& request,
HttpApiResponse& response,
- boost::asio::yield_context&
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
@@ -106,21 +106,21 @@ bool ConsoleHandler::HandleRequest(
ConfigObjectsSharedLock lock (std::try_to_lock);
if (!lock) {
- HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading.");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Icinga is reloading.");
return true;
}
if (methodName == "execute-script")
- return ExecuteScriptHelper(request, response, command, session, sandboxed);
+ return ExecuteScriptHelper(request, response, command, session, sandboxed, yc);
else if (methodName == "auto-complete-script")
- return AutocompleteScriptHelper(request, response, command, session, sandboxed);
+ return AutocompleteScriptHelper(request, response, command, session, sandboxed, yc);
- HttpUtility::SendJsonError(response, params, 400, "Invalid method specified: " + methodName);
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid method specified: " + methodName);
return true;
}
bool ConsoleHandler::ExecuteScriptHelper(const HttpApiRequest& request, HttpApiResponse& response,
- const String& command, const String& session, bool sandboxed)
+ const String& command, const String& session, bool sandboxed, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@@ -133,7 +133,7 @@ bool ConsoleHandler::ExecuteScriptHelper(const HttpApiRequest& request, HttpApiR
std::unique_lock frameLock(lsf->Mutex, std::try_to_lock);
if (!frameLock) {
- HttpUtility::SendJsonError(response, request.Params(), 409, "Session is currently in use by another request.");
+ HttpUtility::SendJsonError(response, request.Params(), 409, yc, "Session is currently in use by another request.");
return true;
}
@@ -195,13 +195,13 @@ bool ConsoleHandler::ExecuteScriptHelper(const HttpApiRequest& request, HttpApiR
});
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, request.Params(), result);
+ HttpUtility::SendJsonBody(response, request.Params(), result, yc);
return true;
}
bool ConsoleHandler::AutocompleteScriptHelper(const HttpApiRequest& request, HttpApiResponse& response,
- const String& command, const String& session, bool sandboxed)
+ const String& command, const String& session, bool sandboxed, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@@ -214,7 +214,7 @@ bool ConsoleHandler::AutocompleteScriptHelper(const HttpApiRequest& request, Htt
std::unique_lock frameLock(lsf->Mutex, std::try_to_lock);
if (!frameLock) {
- HttpUtility::SendJsonError(response, request.Params(), 409, "Session is currently in use by another request.");
+ HttpUtility::SendJsonError(response, request.Params(), 409, yc, "Session is currently in use by another request.");
return true;
}
@@ -240,7 +240,7 @@ bool ConsoleHandler::AutocompleteScriptHelper(const HttpApiRequest& request, Htt
});
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, request.Params(), result);
+ HttpUtility::SendJsonBody(response, request.Params(), result, yc);
return true;
}
diff --git a/lib/remote/consolehandler.hpp b/lib/remote/consolehandler.hpp
index 17f8a71c1e0..100f7ecf5db 100644
--- a/lib/remote/consolehandler.hpp
+++ b/lib/remote/consolehandler.hpp
@@ -36,9 +36,9 @@ class ConsoleHandler final : public HttpHandler
private:
static bool ExecuteScriptHelper(const HttpApiRequest& request, HttpApiResponse& response,
- const String& command, const String& session, bool sandboxed);
+ const String& command, const String& session, bool sandboxed, boost::asio::yield_context& yc);
static bool AutocompleteScriptHelper(const HttpApiRequest& request, HttpApiResponse& response,
- const String& command, const String& session, bool sandboxed);
+ const String& command, const String& session, bool sandboxed, boost::asio::yield_context& yc);
};
diff --git a/lib/remote/createobjecthandler.cpp b/lib/remote/createobjecthandler.cpp
index 29d1d52b520..7ae9eecbef9 100644
--- a/lib/remote/createobjecthandler.cpp
+++ b/lib/remote/createobjecthandler.cpp
@@ -20,7 +20,7 @@ bool CreateObjectHandler::HandleRequest(
const WaitGroup::Ptr& waitGroup,
const HttpApiRequest& request,
HttpApiResponse& response,
- boost::asio::yield_context&
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
@@ -38,7 +38,7 @@ bool CreateObjectHandler::HandleRequest(
Type::Ptr type = FilterUtility::TypeFromPluralName(url->GetPath()[2]);
if (!type) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid type specified.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid type specified.");
return true;
}
@@ -99,13 +99,13 @@ bool CreateObjectHandler::HandleRequest(
ConfigObjectsSharedLock lock (std::try_to_lock);
if (!lock) {
- HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Icinga is reloading");
return true;
}
std::shared_lock wgLock{*waitGroup, std::try_to_lock};
if (!wgLock) {
- HttpUtility::SendJsonError(response, params, 503, "Shutting down.");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Shutting down.");
return true;
}
@@ -126,7 +126,7 @@ bool CreateObjectHandler::HandleRequest(
result1->Set("status", "Object could not be created.");
response.result(http::status::internal_server_error);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}
@@ -143,7 +143,7 @@ bool CreateObjectHandler::HandleRequest(
result1->Set("diagnostic_information", diagnosticInformation);
response.result(http::status::internal_server_error);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}
@@ -159,7 +159,7 @@ bool CreateObjectHandler::HandleRequest(
result1->Set("status", "Object was not created but 'ignore_on_error' was set to true");
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}
diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp
index a918d4a3002..10872194f08 100644
--- a/lib/remote/deleteobjecthandler.cpp
+++ b/lib/remote/deleteobjecthandler.cpp
@@ -38,7 +38,7 @@ bool DeleteObjectHandler::HandleRequest(
Type::Ptr type = FilterUtility::TypeFromPluralName(url->GetPath()[2]);
if (!type) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid type specified.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid type specified.");
return true;
}
@@ -59,7 +59,7 @@ bool DeleteObjectHandler::HandleRequest(
try {
objs = FilterUtility::GetFilterTargets(qd, params, user);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No objects found.",
DiagnosticInformation(ex));
return true;
@@ -71,7 +71,7 @@ bool DeleteObjectHandler::HandleRequest(
ConfigObjectsSharedLock lock (std::try_to_lock);
if (!lock) {
- HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Icinga is reloading");
return true;
}
@@ -81,7 +81,7 @@ bool DeleteObjectHandler::HandleRequest(
std::shared_lock wgLock{*waitGroup, std::try_to_lock};
if (!wgLock) {
- HttpUtility::SendJsonError(response, params, 503, "Shutting down.");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Shutting down.");
return true;
}
diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp
index e1c354b9611..93b180851bd 100644
--- a/lib/remote/eventshandler.cpp
+++ b/lib/remote/eventshandler.cpp
@@ -61,14 +61,14 @@ bool EventsHandler::HandleRequest(
return false;
if (request.version() == 10) {
- HttpUtility::SendJsonError(response, params, 400, "HTTP/1.0 not supported for event streams.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "HTTP/1.0 not supported for event streams.");
return true;
}
Array::Ptr types = params->Get("types");
if (!types) {
- HttpUtility::SendJsonError(response, params, 400, "'types' query parameter is required.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "'types' query parameter is required.");
return true;
}
diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp
index 3413251a31b..e6d5bcb355d 100644
--- a/lib/remote/httphandler.cpp
+++ b/lib/remote/httphandler.cpp
@@ -91,7 +91,7 @@ void HttpHandler::ProcessRequest(
try {
request.DecodeParams();
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, request.Params(), 400, "Invalid request body: " + DiagnosticInformation(ex, false));
+ HttpUtility::SendJsonError(response, request.Params(), 400, yc, "Invalid request body: " + DiagnosticInformation(ex, false));
return;
}
@@ -131,7 +131,7 @@ void HttpHandler::ProcessRequest(
}
if (!processed) {
- HttpUtility::SendJsonError(response, request.Params(), 404, "The requested path '" + boost::algorithm::join(path, "/") +
+ HttpUtility::SendJsonError(response, request.Params(), 404, yc, "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/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp
index 07becaaf58c..457242ac565 100644
--- a/lib/remote/httpserverconnection.cpp
+++ b/lib/remote/httpserverconnection.cpp
@@ -198,7 +198,7 @@ bool EnsureValidHeaders(
response.result(http::status::bad_request);
if (!httpError && request[http::field::accept] == "application/json") {
- HttpUtility::SendJsonError(response, nullptr, 400, "Bad Request: " + errorMsg);
+ HttpUtility::SendJsonError(response, nullptr, 400, yc, "Bad Request: " + errorMsg);
} else {
response.set(http::field::content_type, "text/html");
response.body() << "Bad Request
" << errorMsg << "
";
@@ -315,7 +315,7 @@ bool EnsureAuthenticatedUser(
response.set(http::field::connection, "close");
if (request[http::field::accept] == "application/json") {
- HttpUtility::SendJsonError(response, nullptr, 401, "Unauthorized. Please check your user credentials.");
+ HttpUtility::SendJsonError(response, nullptr, 401, yc, "Unauthorized. Please check your user credentials.");
} else {
response.set(http::field::content_type, "text/html");
response.body() << "Unauthorized. Please check your user credentials.
";
@@ -396,7 +396,7 @@ bool EnsureValidBody(
response.result(http::status::bad_request);
if (request[http::field::accept] == "application/json") {
- HttpUtility::SendJsonError(response, nullptr, 400, "Bad Request: " + ec.message());
+ HttpUtility::SendJsonError(response, nullptr, 400, yc, "Bad Request: " + ec.message());
} else {
response.set(http::field::content_type, "text/html");
response.body() << "Bad Request
" << ec.message() << "
";
@@ -438,7 +438,7 @@ void ProcessRequest(
throw;
}
- HttpUtility::SendJsonError(response, request.Params(), 500, "Unhandled exception", DiagnosticInformation(ex));
+ HttpUtility::SendJsonError(response, request.Params(), 500, yc, "Unhandled exception", DiagnosticInformation(ex));
}
response.Flush(yc);
diff --git a/lib/remote/httputility.cpp b/lib/remote/httputility.cpp
index 5e466bb8ab2..5ce4c7d1a15 100644
--- a/lib/remote/httputility.cpp
+++ b/lib/remote/httputility.cpp
@@ -75,16 +75,8 @@ void HttpUtility::SendJsonBody(HttpApiResponse& response, const Dictionary::Ptr&
response.GetJsonEncoder(params && GetLastParameter(params, "pretty")).Encode(val, &yc);
}
-void HttpUtility::SendJsonBody(HttpApiResponse& response, const Dictionary::Ptr& params, const Value& val)
-{
- namespace http = boost::beast::http;
-
- response.set(http::field::content_type, "application/json");
- response.GetJsonEncoder(params && GetLastParameter(params, "pretty")).Encode(val);
-}
-
-void HttpUtility::SendJsonError(HttpApiResponse& response,
- const Dictionary::Ptr& params, int code, const String& info, const String& diagnosticInformation)
+void HttpUtility::SendJsonError(HttpApiResponse& response, const Dictionary::Ptr& params,
+ int code, boost::asio::yield_context& yc, const String& info, const String& diagnosticInformation)
{
Dictionary::Ptr result = new Dictionary({ { "error", code } });
@@ -99,7 +91,7 @@ void HttpUtility::SendJsonError(HttpApiResponse& response,
response.Clear();
response.result(code);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
}
/**
diff --git a/lib/remote/httputility.hpp b/lib/remote/httputility.hpp
index c8e5ccf2439..d4c1387ceea 100644
--- a/lib/remote/httputility.hpp
+++ b/lib/remote/httputility.hpp
@@ -26,9 +26,8 @@ class HttpUtility
static Value GetLastParameter(const Dictionary::Ptr& params, const String& key);
static void SendJsonBody(HttpApiResponse& response, const Dictionary::Ptr& params, const Value& val, boost::asio::yield_context& yc);
- static void SendJsonBody(HttpApiResponse& response, const Dictionary::Ptr& params, const Value& val);
static void SendJsonError(HttpApiResponse& response, const Dictionary::Ptr& params, const int code,
- const String& info = {}, const String& diagnosticInformation = {});
+ boost::asio::yield_context& yc, const String& info = {}, const String& diagnosticInformation = {});
static bool IsValidHeaderName(std::string_view name);
static bool IsValidHeaderValue(std::string_view value);
diff --git a/lib/remote/infohandler.cpp b/lib/remote/infohandler.cpp
index e463b369c5f..a0b5d6a0c10 100644
--- a/lib/remote/infohandler.cpp
+++ b/lib/remote/infohandler.cpp
@@ -13,7 +13,7 @@ bool InfoHandler::HandleRequest(
const WaitGroup::Ptr&,
const HttpApiRequest& request,
HttpApiResponse& response,
- boost::asio::yield_context&
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
@@ -73,7 +73,7 @@ bool InfoHandler::HandleRequest(
{ "results", new Array({ result1 }) }
});
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
} else {
response.set(http::field::content_type, "text/html");
diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp
index ee76f0b0d8c..b5a34a3da7e 100644
--- a/lib/remote/mallocinfohandler.cpp
+++ b/lib/remote/mallocinfohandler.cpp
@@ -22,7 +22,7 @@ bool MallocInfoHandler::HandleRequest(
const WaitGroup::Ptr&,
const HttpApiRequest& request,
HttpApiResponse& response,
- boost::asio::yield_context&
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
@@ -42,7 +42,7 @@ bool MallocInfoHandler::HandleRequest(
FilterUtility::CheckPermission(user, "debug");
#ifndef HAVE_MALLOC_INFO
- HttpUtility::SendJsonError(response, params, 501, "malloc_info(3) not available.");
+ HttpUtility::SendJsonError(response, params, 501, yc, "malloc_info(3) not available.");
#else /* HAVE_MALLOC_INFO */
char* buf = nullptr;
size_t bufSize = 0;
diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp
index 5d97919dde9..840a7731355 100644
--- a/lib/remote/modifyobjecthandler.cpp
+++ b/lib/remote/modifyobjecthandler.cpp
@@ -36,7 +36,7 @@ bool ModifyObjectHandler::HandleRequest(
Type::Ptr type = FilterUtility::TypeFromPluralName(url->GetPath()[2]);
if (!type) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid type specified.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid type specified.");
return true;
}
@@ -57,7 +57,7 @@ bool ModifyObjectHandler::HandleRequest(
try {
objs = FilterUtility::GetFilterTargets(qd, params, user);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No objects found.",
DiagnosticInformation(ex));
return true;
@@ -66,7 +66,7 @@ bool ModifyObjectHandler::HandleRequest(
Value attrsVal = params->Get("attrs");
if (attrsVal.GetReflectionType() != Dictionary::TypeInstance && attrsVal.GetType() != ValueEmpty) {
- HttpUtility::SendJsonError(response, params, 400,
+ HttpUtility::SendJsonError(response, params, 400, yc,
"Invalid type for 'attrs' attribute specified. Dictionary type is required."
"Or is this a POST query and you missed adding a 'X-HTTP-Method-Override: GET' header?");
return true;
@@ -77,7 +77,7 @@ bool ModifyObjectHandler::HandleRequest(
Value restoreAttrsVal = params->Get("restore_attrs");
if (restoreAttrsVal.GetReflectionType() != Array::TypeInstance && restoreAttrsVal.GetType() != ValueEmpty) {
- HttpUtility::SendJsonError(response, params, 400,
+ HttpUtility::SendJsonError(response, params, 400, yc,
"Invalid type for 'restore_attrs' attribute specified. Array type is required.");
return true;
}
@@ -85,7 +85,7 @@ bool ModifyObjectHandler::HandleRequest(
Array::Ptr restoreAttrs = restoreAttrsVal;
if (!(attrs || restoreAttrs)) {
- HttpUtility::SendJsonError(response, params, 400,
+ HttpUtility::SendJsonError(response, params, 400, yc,
"Missing both 'attrs' and 'restore_attrs'. "
"Or is this a POST query and you missed adding a 'X-HTTP-Method-Override: GET' header?");
return true;
@@ -99,7 +99,7 @@ bool ModifyObjectHandler::HandleRequest(
ConfigObjectsSharedLock lock (std::try_to_lock);
if (!lock) {
- HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Icinga is reloading");
return true;
}
@@ -107,7 +107,7 @@ bool ModifyObjectHandler::HandleRequest(
std::shared_lock wgLock{*waitGroup, std::try_to_lock};
if (!wgLock) {
- HttpUtility::SendJsonError(response, params, 503, "Shutting down.");
+ HttpUtility::SendJsonError(response, params, 503, yc, "Shutting down.");
return true;
}
diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp
index 2c81c6a73bb..74eea1c80e8 100644
--- a/lib/remote/objectqueryhandler.cpp
+++ b/lib/remote/objectqueryhandler.cpp
@@ -114,7 +114,7 @@ bool ObjectQueryHandler::HandleRequest(
Type::Ptr type = FilterUtility::TypeFromPluralName(url->GetPath()[2]);
if (!type) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid type specified.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid type specified.");
return true;
}
@@ -127,7 +127,7 @@ bool ObjectQueryHandler::HandleRequest(
try {
uattrs = params->Get("attrs");
} catch (const std::exception&) {
- HttpUtility::SendJsonError(response, params, 400,
+ HttpUtility::SendJsonError(response, params, 400, yc,
"Invalid type for 'attrs' attribute specified. Array type is required.");
return true;
}
@@ -135,7 +135,7 @@ bool ObjectQueryHandler::HandleRequest(
try {
ujoins = params->Get("joins");
} catch (const std::exception&) {
- HttpUtility::SendJsonError(response, params, 400,
+ HttpUtility::SendJsonError(response, params, 400, yc,
"Invalid type for 'joins' attribute specified. Array type is required.");
return true;
}
@@ -143,7 +143,7 @@ bool ObjectQueryHandler::HandleRequest(
try {
umetas = params->Get("meta");
} catch (const std::exception&) {
- HttpUtility::SendJsonError(response, params, 400,
+ HttpUtility::SendJsonError(response, params, 400, yc,
"Invalid type for 'meta' attribute specified. Array type is required.");
return true;
}
@@ -158,7 +158,7 @@ bool ObjectQueryHandler::HandleRequest(
} else if (meta == "location") {
includeLocation = true;
} else {
- HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for meta: " + meta);
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid field specified for meta: " + meta);
return true;
}
}
@@ -179,7 +179,7 @@ bool ObjectQueryHandler::HandleRequest(
try {
objs = FilterUtility::GetFilterTargets(qd, params, user);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No objects found.",
DiagnosticInformation(ex));
return true;
diff --git a/lib/remote/statushandler.cpp b/lib/remote/statushandler.cpp
index 1b0aa17170e..6a5d4dd30f2 100644
--- a/lib/remote/statushandler.cpp
+++ b/lib/remote/statushandler.cpp
@@ -73,7 +73,7 @@ bool StatusHandler::HandleRequest(
const WaitGroup::Ptr&,
const HttpApiRequest& request,
HttpApiResponse& response,
- boost::asio::yield_context&
+ boost::asio::yield_context& yc
)
{
namespace http = boost::beast::http;
@@ -103,7 +103,7 @@ bool StatusHandler::HandleRequest(
try {
objs = FilterUtility::GetFilterTargets(qd, params, user);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No objects found.",
DiagnosticInformation(ex));
return true;
@@ -114,7 +114,7 @@ bool StatusHandler::HandleRequest(
});
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}
diff --git a/lib/remote/templatequeryhandler.cpp b/lib/remote/templatequeryhandler.cpp
index 1aaba790622..7de879d2a55 100644
--- a/lib/remote/templatequeryhandler.cpp
+++ b/lib/remote/templatequeryhandler.cpp
@@ -98,7 +98,7 @@ bool TemplateQueryHandler::HandleRequest(
Type::Ptr type = FilterUtility::TypeFromPluralName(url->GetPath()[2]);
if (!type) {
- HttpUtility::SendJsonError(response, params, 400, "Invalid type specified.");
+ HttpUtility::SendJsonError(response, params, 400, yc, "Invalid type specified.");
return true;
}
@@ -120,7 +120,7 @@ bool TemplateQueryHandler::HandleRequest(
try {
objs = FilterUtility::GetFilterTargets(qd, params, user, "tmpl");
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No templates found.",
DiagnosticInformation(ex));
return true;
diff --git a/lib/remote/typequeryhandler.cpp b/lib/remote/typequeryhandler.cpp
index 203dfbf41f5..c98ffa95f55 100644
--- a/lib/remote/typequeryhandler.cpp
+++ b/lib/remote/typequeryhandler.cpp
@@ -82,7 +82,7 @@ bool TypeQueryHandler::HandleRequest(
try {
objs = FilterUtility::GetFilterTargets(qd, params, user);
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No objects found.",
DiagnosticInformation(ex));
return true;
diff --git a/lib/remote/variablequeryhandler.cpp b/lib/remote/variablequeryhandler.cpp
index 505983c45e2..f8672e84d4b 100644
--- a/lib/remote/variablequeryhandler.cpp
+++ b/lib/remote/variablequeryhandler.cpp
@@ -92,7 +92,7 @@ bool VariableQueryHandler::HandleRequest(
try {
objs = FilterUtility::GetFilterTargets(qd, params, user, "variable");
} catch (const std::exception& ex) {
- HttpUtility::SendJsonError(response, params, 404,
+ HttpUtility::SendJsonError(response, params, 404, yc,
"No variables found.",
DiagnosticInformation(ex));
return true;
diff --git a/test/remote-httpmessage.cpp b/test/remote-httpmessage.cpp
index 96b1948e861..4e2ff1d316c 100644
--- a/test/remote-httpmessage.cpp
+++ b/test/remote-httpmessage.cpp
@@ -267,7 +267,7 @@ BOOST_AUTO_TEST_CASE(response_sendjsonbody)
HttpApiResponse response(server);
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, nullptr, new Dictionary{{"test", 1}});
+ HttpUtility::SendJsonBody(response, nullptr, new Dictionary{{"test", 1}}, yc);
BOOST_REQUIRE_NO_THROW(response.Flush(yc));
@@ -285,7 +285,7 @@ BOOST_AUTO_TEST_CASE(response_sendjsonbody)
BOOST_REQUIRE(!ec);
BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok);
- BOOST_REQUIRE_EQUAL(parser.get().chunked(), false);
+ BOOST_REQUIRE_EQUAL(parser.get().chunked(), true);
Dictionary::Ptr body = JsonDecode(parser.get().body());
BOOST_REQUIRE_EQUAL(body->Get("test"), 1);
}
@@ -298,7 +298,7 @@ BOOST_AUTO_TEST_CASE(response_sendjsonerror)
// This has to be overwritten in SendJsonError.
response.result(http::status::ok);
- HttpUtility::SendJsonError(response, nullptr, 404, "Not found.");
+ HttpUtility::SendJsonError(response, nullptr, 404, yc, "Not found.");
BOOST_REQUIRE_NO_THROW(response.Flush(yc));
@@ -316,7 +316,7 @@ BOOST_AUTO_TEST_CASE(response_sendjsonerror)
BOOST_REQUIRE(!ec);
BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::not_found);
- BOOST_REQUIRE_EQUAL(parser.get().chunked(), false);
+ BOOST_REQUIRE_EQUAL(parser.get().chunked(), true);
Dictionary::Ptr body = JsonDecode(parser.get().body());
BOOST_REQUIRE_EQUAL(body->Get("error"), 404);
BOOST_REQUIRE_EQUAL(body->Get("status"), "Not found.");
From c56cbaa15e52fc4abab49d886e4fe1d217fe4614 Mon Sep 17 00:00:00 2001
From: "Alexander A. Klimov"
Date: Thu, 26 Mar 2026 15:42:49 +0100
Subject: [PATCH 2/2] OutgoingHttpMessage: reset m_CpuBoundWork only once in
`#StartStreaming()`
not in every `#Flush()`.
---
lib/remote/httpmessage.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lib/remote/httpmessage.cpp b/lib/remote/httpmessage.cpp
index 7641e75ab23..744cc49bdfc 100644
--- a/lib/remote/httpmessage.cpp
+++ b/lib/remote/httpmessage.cpp
@@ -146,8 +146,6 @@ void OutgoingHttpMessage::Clear()
template
void OutgoingHttpMessage::Flush(boost::asio::yield_context yc, bool finish)
{
- m_CpuBoundWork.reset();
-
if (!Base::chunked() && !Base::has_content_length()) {
ASSERT(!m_SerializationStarted);
Base::prepare_payload();
@@ -185,6 +183,7 @@ void OutgoingHttpMessage::Flush(boost::asio::yie
template
void OutgoingHttpMessage::StartStreaming()
{
+ m_CpuBoundWork.reset();
ASSERT(Base::body().Size() == 0 && !m_SerializationStarted);
Base::body().Start();
Base::chunked(true);