HTTP: stream responses where appropriate

This commit is contained in:
Yonas Habteab 2025-09-11 13:19:35 +02:00
parent 047f44a3b3
commit e7c088b05b
10 changed files with 113 additions and 157 deletions

View File

@ -6,7 +6,9 @@
#include "remote/apiaction.hpp"
#include "base/defer.hpp"
#include "base/exception.hpp"
#include "base/generator.hpp"
#include "base/logger.hpp"
#include <optional>
#include <set>
using namespace icinga;
@ -70,13 +72,10 @@ bool ActionsHandler::HandleRequest(
objs.emplace_back(nullptr);
}
ArrayData results;
Log(LogNotice, "ApiActionHandler")
<< "Running action " << actionName;
bool verbose = false;
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");
@ -86,73 +85,43 @@ bool ActionsHandler::HandleRequest(
return true;
}
for (ConfigObject::Ptr obj : objs) {
auto generatorFunc = [&action, &user, &params, &waitGroup, &wgLock, verbose](
const ConfigObject::Ptr& obj
) -> std::optional<Value> {
if (!waitGroup->IsLockable()) {
if (wgLock) {
wgLock.unlock();
}
results.emplace_back(new Dictionary({
return new Dictionary{
{ "type", obj->GetReflectionType()->GetName() },
{ "name", obj->GetName() },
{ "code", 503 },
{ "status", "Action skipped: Shutting down."}
}));
continue;
};
}
try {
results.emplace_back(action->Invoke(obj, user, params));
return action->Invoke(obj, user, params);
} catch (const std::exception& ex) {
Dictionary::Ptr fail = new Dictionary({
Dictionary::Ptr fail = new Dictionary{
{ "code", 500 },
{ "status", "Action execution failed: '" + DiagnosticInformation(ex, false) + "'." }
});
};
/* Exception for actions. Normally we would handle this inside SendJsonError(). */
if (verbose)
fail->Set("diagnostic_information", DiagnosticInformation(ex));
results.emplace_back(std::move(fail));
return fail;
}
}
};
int statusCode = 500;
std::set<int> okStatusCodes, nonOkStatusCodes;
Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
result->Freeze();
for (Dictionary::Ptr res : results) {
if (!res->Contains("code")) {
continue;
}
auto code = res->Get("code");
if (code >= 200 && code <= 299) {
okStatusCodes.insert(code);
} else {
nonOkStatusCodes.insert(code);
}
}
size_t okSize = okStatusCodes.size();
size_t nonOkSize = nonOkStatusCodes.size();
if (okSize == 1u && nonOkSize == 0u) {
statusCode = *okStatusCodes.begin();
} else if (nonOkSize == 1u) {
statusCode = *nonOkStatusCodes.begin();
} else if (okSize >= 2u && nonOkSize == 0u) {
statusCode = 200;
}
response.result(statusCode);
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(results)) }
});
HttpUtility::SendJsonBody(response, params, result);
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}

View File

@ -28,7 +28,7 @@ bool ConfigPackagesHandler::HandleRequest(
return false;
if (request.method() == http::verb::get)
HandleGet(request, response);
HandleGet(request, response, yc);
else if (request.method() == http::verb::post)
HandlePost(request, response);
else if (request.method() == http::verb::delete_)
@ -39,7 +39,7 @@ bool ConfigPackagesHandler::HandleRequest(
return true;
}
void ConfigPackagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response)
void ConfigPackagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@ -79,12 +79,14 @@ void ConfigPackagesHandler::HandleGet(const HttpRequest& request, HttpResponse&
}
}
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(results)) }
});
Array::Ptr resultsArr = new Array(std::move(results));
resultsArr->Freeze();
Dictionary::Ptr result = new Dictionary{{"results", resultsArr}};
result->Freeze();
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
HttpUtility::SendJsonBody(response, params, result, yc);
}
void ConfigPackagesHandler::HandlePost(const HttpRequest& request, HttpResponse& response)

View File

@ -21,7 +21,7 @@ public:
) override;
private:
void HandleGet(const HttpRequest& request, HttpResponse& response);
void HandleGet(const HttpRequest& request, HttpResponse& response, boost::asio::yield_context& yc);
void HandlePost(const HttpRequest& request, HttpResponse& response);
void HandleDelete(const HttpRequest& request, HttpResponse& response);

View File

@ -8,6 +8,7 @@
#include "base/application.hpp"
#include "base/defer.hpp"
#include "base/exception.hpp"
#include <optional>
using namespace icinga;
@ -35,7 +36,7 @@ bool ConfigStagesHandler::HandleRequest(
return false;
if (request.method() == http::verb::get)
HandleGet(request, response);
HandleGet(request, response, yc);
else if (request.method() == http::verb::post)
HandlePost(request, response);
else if (request.method() == http::verb::delete_)
@ -46,7 +47,7 @@ bool ConfigStagesHandler::HandleRequest(
return true;
}
void ConfigStagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response)
void ConfigStagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@ -71,25 +72,22 @@ void ConfigStagesHandler::HandleGet(const HttpRequest& request, HttpResponse& re
if (!ConfigPackageUtility::ValidateStageName(stageName))
return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
ArrayData results;
std::vector<std::pair<String, bool> > paths = ConfigPackageUtility::GetFiles(packageName, stageName);
String prefixPath = ConfigPackageUtility::GetPackageDir() + "/" + packageName + "/" + stageName + "/";
for (const auto& kv : paths) {
results.push_back(new Dictionary({
auto generatorFunc = [&prefixPath](const std::pair<String, bool>& kv) -> std::optional<Value> {
return new Dictionary{
{ "type", kv.second ? "directory" : "file" },
{ "name", kv.first.SubStr(prefixPath.GetLength()) }
}));
}
{ "name", kv.first.SubStr(prefixPath.GetLength()) },
};
};
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(results)) }
});
Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{paths, generatorFunc}}};
result->Freeze();
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
HttpUtility::SendJsonBody(response, params, result, yc);
}
void ConfigStagesHandler::HandlePost(const HttpRequest& request, HttpResponse& response)

View File

@ -21,7 +21,7 @@ public:
) override;
private:
void HandleGet(const HttpRequest& request, HttpResponse& response);
void HandleGet(const HttpRequest& request, HttpResponse& response, boost::asio::yield_context& yc);
void HandlePost(const HttpRequest& request, HttpResponse& response);
void HandleDelete(const HttpRequest& request, HttpResponse& response);
};

View File

@ -9,6 +9,7 @@
#include "config/configitem.hpp"
#include "base/exception.hpp"
#include <boost/algorithm/string/case_conv.hpp>
#include <optional>
#include <set>
using namespace icinga;
@ -74,32 +75,26 @@ bool DeleteObjectHandler::HandleRequest(
return true;
}
ArrayData results;
bool success = true;
std::shared_lock wgLock{*waitGroup, std::try_to_lock};
if (!wgLock) {
HttpUtility::SendJsonError(response, params, 503, "Shutting down.");
return true;
}
for (ConfigObject::Ptr obj : objs) {
auto generatorFunc = [&type, &waitGroup, &wgLock, cascade, verbose](
const ConfigObject::Ptr& obj
) -> std::optional<Value> {
if (!waitGroup->IsLockable()) {
if (wgLock) {
wgLock.unlock();
}
results.emplace_back(new Dictionary({
return new Dictionary{
{ "type", type->GetName() },
{ "name", obj->GetName() },
{ "code", 503 },
{ "status", "Action skipped: Shutting down."}
}));
success = false;
continue;
};
}
int code;
@ -113,36 +108,30 @@ bool DeleteObjectHandler::HandleRequest(
if (!ConfigObjectUtility::DeleteObject(obj, cascade, errors, diagnosticInformation)) {
code = 500;
status = "Object could not be deleted.";
success = false;
} else {
code = 200;
status = "Object was deleted.";
}
Dictionary::Ptr result = new Dictionary({
Dictionary::Ptr result = new Dictionary{
{ "type", type->GetName() },
{ "name", obj->GetName() },
{ "code", code },
{ "status", status },
{ "errors", errors }
});
};
if (verbose)
result->Set("diagnostic_information", diagnosticInformation);
results.push_back(result);
}
return result;
};
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(results)) }
});
Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
result->Freeze();
if (!success)
response.result(http::status::internal_server_error);
else
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}

View File

@ -6,7 +6,9 @@
#include "remote/filterutility.hpp"
#include "remote/apiaction.hpp"
#include "base/exception.hpp"
#include "base/generator.hpp"
#include <boost/algorithm/string/case_conv.hpp>
#include <optional>
#include <set>
using namespace icinga;
@ -102,31 +104,28 @@ bool ModifyObjectHandler::HandleRequest(
return true;
}
ArrayData results;
std::shared_lock wgLock{*waitGroup, std::try_to_lock};
if (!wgLock) {
HttpUtility::SendJsonError(response, params, 503, "Shutting down.");
return true;
}
for (ConfigObject::Ptr obj : objs) {
Dictionary::Ptr result1 = new Dictionary();
auto generatorFunc = [&waitGroup, &wgLock, &type, &attrs, &restoreAttrs, verbose](
const ConfigObject::Ptr& obj
) -> std::optional<Value> {
Dictionary::Ptr result = new Dictionary();
result1->Set("type", type->GetName());
result1->Set("name", obj->GetName());
result->Set("type", type->GetName());
result->Set("name", obj->GetName());
if (!waitGroup->IsLockable()) {
if (wgLock) {
wgLock.unlock();
}
result1->Set("code", 503);
result1->Set("status", "Action skipped: Shutting down.");
results.emplace_back(std::move(result1));
continue;
result->Set("code", 503);
result->Set("status", "Action skipped: Shutting down.");
return result;
}
String key;
@ -144,14 +143,13 @@ bool ModifyObjectHandler::HandleRequest(
}
}
} catch (const std::exception& ex) {
result1->Set("code", 500);
result1->Set("status", "Attribute '" + key + "' could not be restored: " + DiagnosticInformation(ex, false));
result->Set("code", 500);
result->Set("status", "Attribute '" + key + "' could not be restored: " + DiagnosticInformation(ex, false));
if (verbose)
result1->Set("diagnostic_information", DiagnosticInformation(ex));
result->Set("diagnostic_information", DiagnosticInformation(ex));
results.push_back(std::move(result1));
continue;
return result;
}
try {
@ -163,28 +161,25 @@ bool ModifyObjectHandler::HandleRequest(
}
}
} catch (const std::exception& ex) {
result1->Set("code", 500);
result1->Set("status", "Attribute '" + key + "' could not be set: " + DiagnosticInformation(ex, false));
result->Set("code", 500);
result->Set("status", "Attribute '" + key + "' could not be set: " + DiagnosticInformation(ex, false));
if (verbose)
result1->Set("diagnostic_information", DiagnosticInformation(ex));
result->Set("diagnostic_information", DiagnosticInformation(ex));
results.push_back(std::move(result1));
continue;
return result;
}
result1->Set("code", 200);
result1->Set("status", "Attributes updated.");
result->Set("code", 200);
result->Set("status", "Attributes updated.");
return result;
};
results.push_back(std::move(result1));
}
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(results)) }
});
Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
result->Freeze();
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}

View File

@ -125,12 +125,14 @@ bool TemplateQueryHandler::HandleRequest(
return true;
}
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(objs)) }
});
Array::Ptr resultArr = new Array(std::move(objs));
resultArr->Freeze();
Dictionary::Ptr result = new Dictionary{{"results", resultArr}};
result->Freeze();
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}

View File

@ -4,8 +4,10 @@
#include "remote/httputility.hpp"
#include "remote/filterutility.hpp"
#include "base/configtype.hpp"
#include "base/generator.hpp"
#include "base/scriptglobal.hpp"
#include "base/logger.hpp"
#include <optional>
#include <set>
using namespace icinga;
@ -89,23 +91,19 @@ bool TypeQueryHandler::HandleRequest(
return true;
}
ArrayData results;
for (Type::Ptr obj : objs) {
Dictionary::Ptr result1 = new Dictionary();
results.push_back(result1);
auto generatorFunc = [](const Type::Ptr& obj) -> std::optional<Value> {
Dictionary::Ptr result = new Dictionary();
Dictionary::Ptr resultAttrs = new Dictionary();
result1->Set("name", obj->GetName());
result1->Set("plural_name", obj->GetPluralName());
result->Set("name", obj->GetName());
result->Set("plural_name", obj->GetPluralName());
if (obj->GetBaseType())
result1->Set("base", obj->GetBaseType()->GetName());
result1->Set("abstract", obj->IsAbstract());
result1->Set("fields", resultAttrs);
result->Set("base", obj->GetBaseType()->GetName());
result->Set("abstract", obj->IsAbstract());
result->Set("fields", resultAttrs);
Dictionary::Ptr prototype = dynamic_pointer_cast<Dictionary>(obj->GetPrototype());
Array::Ptr prototypeKeys = new Array();
result1->Set("prototype_keys", prototypeKeys);
result->Set("prototype_keys", prototypeKeys);
if (prototype) {
ObjectLock olock(prototype);
@ -143,14 +141,14 @@ bool TypeQueryHandler::HandleRequest(
{ "deprecated", static_cast<bool>(field.Attributes & FADeprecated) }
}));
}
}
return result;
};
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(results)) }
});
Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
result->Freeze();
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}

View File

@ -4,10 +4,12 @@
#include "remote/httputility.hpp"
#include "remote/filterutility.hpp"
#include "base/configtype.hpp"
#include "base/generator.hpp"
#include "base/scriptglobal.hpp"
#include "base/logger.hpp"
#include "base/serializer.hpp"
#include "base/namespace.hpp"
#include <optional>
#include <set>
using namespace icinga;
@ -96,25 +98,26 @@ bool VariableQueryHandler::HandleRequest(
return true;
}
ArrayData results;
auto generatorFunc = [](const Dictionary::Ptr& var) -> std::optional<Value> {
if (var->Get("name") == "TicketSalt") {
// Returning a nullopt here will cause the generator to skip this entry.
// Meaning, when this variable wasn't the last element in the container,
// the generator will directly call us again with the next element.
return std::nullopt;
}
for (Dictionary::Ptr var : objs) {
if (var->Get("name") == "TicketSalt")
continue;
results.emplace_back(new Dictionary({
return new Dictionary{
{ "name", var->Get("name") },
{ "type", var->Get("type") },
{ "value", Serialize(var->Get("value"), 0) }
}));
}
};
};
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(results)) }
});
Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
result->Freeze();
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}