mirror of
https://github.com/Icinga/icinga2.git
synced 2026-02-19 02:29:16 -05:00
Merge pull request #10554 from Icinga/stream-http-response
Some checks are pending
Container Image / Container Image (push) Waiting to run
Linux / alpine:bash (push) Waiting to run
Linux / amazonlinux:2 (push) Waiting to run
Linux / amazonlinux:2023 (push) Waiting to run
Linux / debian:11 (linux/386) (push) Waiting to run
Linux / debian:11 (push) Waiting to run
Linux / debian:12 (linux/386) (push) Waiting to run
Linux / debian:12 (push) Waiting to run
Linux / debian:13 (push) Waiting to run
Linux / fedora:41 (push) Waiting to run
Linux / fedora:42 (push) Waiting to run
Linux / fedora:43 (push) Waiting to run
Linux / opensuse/leap:15.6 (push) Waiting to run
Linux / opensuse/leap:16.0 (push) Waiting to run
Linux / registry.suse.com/bci/bci-base:16.0 (push) Waiting to run
Linux / registry.suse.com/suse/sle15:15.6 (push) Waiting to run
Linux / registry.suse.com/suse/sle15:15.7 (push) Waiting to run
Linux / rockylinux/rockylinux:10 (push) Waiting to run
Linux / rockylinux:8 (push) Waiting to run
Linux / rockylinux:9 (push) Waiting to run
Linux / ubuntu:22.04 (push) Waiting to run
Linux / ubuntu:24.04 (push) Waiting to run
Linux / ubuntu:25.04 (push) Waiting to run
Linux / ubuntu:25.10 (push) Waiting to run
Windows / Windows (push) Waiting to run
Some checks are pending
Container Image / Container Image (push) Waiting to run
Linux / alpine:bash (push) Waiting to run
Linux / amazonlinux:2 (push) Waiting to run
Linux / amazonlinux:2023 (push) Waiting to run
Linux / debian:11 (linux/386) (push) Waiting to run
Linux / debian:11 (push) Waiting to run
Linux / debian:12 (linux/386) (push) Waiting to run
Linux / debian:12 (push) Waiting to run
Linux / debian:13 (push) Waiting to run
Linux / fedora:41 (push) Waiting to run
Linux / fedora:42 (push) Waiting to run
Linux / fedora:43 (push) Waiting to run
Linux / opensuse/leap:15.6 (push) Waiting to run
Linux / opensuse/leap:16.0 (push) Waiting to run
Linux / registry.suse.com/bci/bci-base:16.0 (push) Waiting to run
Linux / registry.suse.com/suse/sle15:15.6 (push) Waiting to run
Linux / registry.suse.com/suse/sle15:15.7 (push) Waiting to run
Linux / rockylinux/rockylinux:10 (push) Waiting to run
Linux / rockylinux:8 (push) Waiting to run
Linux / rockylinux:9 (push) Waiting to run
Linux / ubuntu:22.04 (push) Waiting to run
Linux / ubuntu:24.04 (push) Waiting to run
Linux / ubuntu:25.04 (push) Waiting to run
Linux / ubuntu:25.10 (push) Waiting to run
Windows / Windows (push) Waiting to run
Stream responses where appropriate
This commit is contained in:
commit
ef3f8f598f
18 changed files with 213 additions and 124 deletions
|
|
@ -11,39 +11,84 @@ namespace icinga
|
|||
{
|
||||
|
||||
/**
|
||||
* ValueGenerator is a class that defines a generator function type for producing Values on demand.
|
||||
* Abstract base class for generators that produce a sequence of Values.
|
||||
*
|
||||
* This class is used to create generator functions that can yield any values that can be represented by the
|
||||
* Icinga Value type. The generator function is exhausted when it returns `std::nullopt`, indicating that there
|
||||
* are no more values to produce. Subsequent calls to `Next()` will always return `std::nullopt` after exhaustion.
|
||||
* @note Any instance of a Generator should be treated as an @c Array -like object that produces its elements
|
||||
* on-the-fly.
|
||||
*
|
||||
* @ingroup base
|
||||
*/
|
||||
class ValueGenerator final : public Object
|
||||
class Generator : public Object
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(Generator);
|
||||
|
||||
/**
|
||||
* Produces the next Value in the sequence.
|
||||
*
|
||||
* This method returns the next Value produced by the generator. If the generator is exhausted,
|
||||
* it returns std::nullopt for all subsequent calls to this method.
|
||||
*
|
||||
* @return The next Value in the sequence, or std::nullopt if the generator is exhausted.
|
||||
*/
|
||||
virtual std::optional<Value> Next() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* A generator that transforms elements of a container into Values using a provided transformation function.
|
||||
*
|
||||
* This class takes a container and a transformation function as input. It uses the transformation function
|
||||
* to convert each element of the container into a Value. The generator produces Values on-the-fly as they
|
||||
* are requested via the `Next()` method.
|
||||
*
|
||||
* @tparam Container The type of the container holding the elements to be transformed.
|
||||
*
|
||||
* @ingroup base
|
||||
*/
|
||||
template<typename Container>
|
||||
class ValueGenerator final : public Generator
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(ValueGenerator);
|
||||
|
||||
/**
|
||||
* Generates a Value using the provided generator function.
|
||||
*
|
||||
* The generator function should return an `std::optional<Value>` which contains the produced Value or
|
||||
* `std::nullopt` when there are no more values to produce. After the generator function returns `std::nullopt`,
|
||||
* the generator is considered exhausted, and further calls to `Next()` will always return `std::nullopt`.
|
||||
*/
|
||||
using GenFunc = std::function<std::optional<Value>()>;
|
||||
// The type of elements in the container and the type to be passed to the transformation function.
|
||||
using InputType = typename Container::value_type;
|
||||
|
||||
explicit ValueGenerator(GenFunc generator): m_Generator(std::move(generator))
|
||||
// The type of the transformation function that takes an element of the container and returns a Value.
|
||||
using TransformFunc = std::function<Value (const InputType&)>;
|
||||
|
||||
/**
|
||||
* Constructs a ValueGenerator with the given container and transformation function.
|
||||
*
|
||||
* The generator will iterate over the elements of the container, applying the transformation function
|
||||
* to each element to produce values on-the-fly. You must ensure that the container remains valid for
|
||||
* the lifetime of the generator.
|
||||
*
|
||||
* @param container The container holding the elements to be transformed.
|
||||
* @param generator The transformation function to convert elements into Values.
|
||||
*/
|
||||
ValueGenerator(Container& container, TransformFunc generator)
|
||||
: m_It{container.begin()}, m_End{container.end()}, m_Func{std::move(generator)}
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<Value> Next() const
|
||||
std::optional<Value> Next() override
|
||||
{
|
||||
return m_Generator();
|
||||
if (m_It == m_End) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto next = m_Func(*m_It);
|
||||
++m_It;
|
||||
return next;
|
||||
}
|
||||
|
||||
private:
|
||||
GenFunc m_Generator; // The generator function that produces Values.
|
||||
using Iterator = typename Container::iterator;
|
||||
Iterator m_It; // Current iterator position.
|
||||
Iterator m_End; // End iterator position.
|
||||
|
||||
TransformFunc m_Func; // The transformation function.
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace icinga
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ void JsonEncoder::Encode(const Value& value, boost::asio::yield_context* yc)
|
|||
EncodeObject(static_pointer_cast<Dictionary>(obj), extractor, yc);
|
||||
} else if (type == Array::TypeInstance) {
|
||||
EncodeArray(static_pointer_cast<Array>(obj), yc);
|
||||
} else if (auto gen(dynamic_pointer_cast<ValueGenerator>(obj)); gen) {
|
||||
} else if (auto gen(dynamic_pointer_cast<Generator>(obj)); gen) {
|
||||
EncodeValueGenerator(gen, yc);
|
||||
} else {
|
||||
// Some other non-serializable object type!
|
||||
|
|
@ -127,7 +127,7 @@ void JsonEncoder::EncodeArray(const Array::Ptr& array, boost::asio::yield_contex
|
|||
* @param yc The optional yield context for asynchronous operations. If provided, it allows the encoder
|
||||
* to flush the output stream safely when it has not acquired any object lock on the parent containers.
|
||||
*/
|
||||
void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc)
|
||||
void JsonEncoder::EncodeValueGenerator(const Generator::Ptr& generator, boost::asio::yield_context* yc)
|
||||
{
|
||||
BeginContainer('[');
|
||||
bool isEmpty = true;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public:
|
|||
|
||||
private:
|
||||
void EncodeArray(const Array::Ptr& array, boost::asio::yield_context* yc);
|
||||
void EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc);
|
||||
void EncodeValueGenerator(const Generator::Ptr& generator, boost::asio::yield_context* yc);
|
||||
|
||||
template<typename Iterable, typename ValExtractor>
|
||||
void EncodeObject(const Iterable& container, const ValExtractor& extractor, boost::asio::yield_context* yc);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ bool ActionsHandler::HandleRequest(
|
|||
const WaitGroup::Ptr& waitGroup,
|
||||
const HttpApiRequest& request,
|
||||
HttpApiResponse& response,
|
||||
boost::asio::yield_context&
|
||||
boost::asio::yield_context& yc
|
||||
)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
|
@ -126,6 +126,11 @@ bool ActionsHandler::HandleRequest(
|
|||
}
|
||||
}
|
||||
|
||||
if (wgLock.owns_lock()) {
|
||||
// Unlock before starting to stream the response, so that we don't block the shutdown process.
|
||||
wgLock.unlock();
|
||||
}
|
||||
|
||||
int statusCode = 500;
|
||||
std::set<int> okStatusCodes, nonOkStatusCodes;
|
||||
|
||||
|
|
@ -156,11 +161,13 @@ bool ActionsHandler::HandleRequest(
|
|||
|
||||
response.result(statusCode);
|
||||
|
||||
Dictionary::Ptr result = new Dictionary({
|
||||
{ "results", new Array(std::move(results)) }
|
||||
});
|
||||
Array::Ptr resultArray{new Array{std::move(results)}};
|
||||
resultArray->Freeze(); // Allows the JSON encoder to yield while encoding the array.
|
||||
|
||||
HttpUtility::SendJsonBody(response, params, result);
|
||||
Dictionary::Ptr result = new Dictionary{{"results", std::move(resultArray)}};
|
||||
result->Freeze();
|
||||
|
||||
HttpUtility::SendJsonBody(response, params, result, yc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ public:
|
|||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr void Unlock()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
|
@ -69,6 +73,11 @@ public:
|
|||
return m_Lock.owns();
|
||||
}
|
||||
|
||||
void Unlock()
|
||||
{
|
||||
m_Lock.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
boost::interprocess::sharable_lock<boost::interprocess::interprocess_sharable_mutex> m_Lock;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ bool ConfigPackagesHandler::HandleRequest(
|
|||
const WaitGroup::Ptr&,
|
||||
const HttpApiRequest& request,
|
||||
HttpApiResponse& response,
|
||||
boost::asio::yield_context&
|
||||
boost::asio::yield_context& yc
|
||||
)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
|
@ -29,7 +29,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_)
|
||||
|
|
@ -40,7 +40,7 @@ bool ConfigPackagesHandler::HandleRequest(
|
|||
return true;
|
||||
}
|
||||
|
||||
void ConfigPackagesHandler::HandleGet(const HttpApiRequest& request, HttpApiResponse& response)
|
||||
void ConfigPackagesHandler::HandleGet(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
|
|
@ -80,12 +80,14 @@ void ConfigPackagesHandler::HandleGet(const HttpApiRequest& request, HttpApiResp
|
|||
}
|
||||
}
|
||||
|
||||
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 HttpApiRequest& request, HttpApiResponse& response)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public:
|
|||
) override;
|
||||
|
||||
private:
|
||||
void HandleGet(const HttpApiRequest& request, HttpApiResponse& response);
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ bool ConfigStagesHandler::HandleRequest(
|
|||
const WaitGroup::Ptr&,
|
||||
const HttpApiRequest& request,
|
||||
HttpApiResponse& response,
|
||||
boost::asio::yield_context&
|
||||
boost::asio::yield_context& yc
|
||||
)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
|
@ -36,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_)
|
||||
|
|
@ -47,7 +47,7 @@ bool ConfigStagesHandler::HandleRequest(
|
|||
return true;
|
||||
}
|
||||
|
||||
void ConfigStagesHandler::HandleGet(const HttpApiRequest& request, HttpApiResponse& response)
|
||||
void ConfigStagesHandler::HandleGet(const HttpApiRequest& request, HttpApiResponse& response, boost::asio::yield_context& yc)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
|
|
@ -72,25 +72,22 @@ void ConfigStagesHandler::HandleGet(const HttpApiRequest& request, HttpApiRespon
|
|||
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) -> 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 HttpApiRequest& request, HttpApiResponse& response)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public:
|
|||
) override;
|
||||
|
||||
private:
|
||||
void HandleGet(const HttpApiRequest& request, HttpApiResponse& response);
|
||||
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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ bool DeleteObjectHandler::HandleRequest(
|
|||
const WaitGroup::Ptr& waitGroup,
|
||||
const HttpApiRequest& request,
|
||||
HttpApiResponse& response,
|
||||
boost::asio::yield_context&
|
||||
boost::asio::yield_context& yc
|
||||
)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
|
@ -134,16 +134,24 @@ bool DeleteObjectHandler::HandleRequest(
|
|||
results.push_back(result);
|
||||
}
|
||||
|
||||
Dictionary::Ptr result = new Dictionary({
|
||||
{ "results", new Array(std::move(results)) }
|
||||
});
|
||||
// Unlock the mutexes before starting to stream the response, so that we don't block the shutdown process.
|
||||
lock.Unlock();
|
||||
if (wgLock.owns_lock()) {
|
||||
wgLock.unlock();
|
||||
}
|
||||
|
||||
Array::Ptr resultArray = new Array{std::move(results)};
|
||||
resultArray->Freeze();
|
||||
|
||||
Dictionary::Ptr result = new Dictionary({{ "results", std::move(resultArray)}});
|
||||
result->Freeze();
|
||||
|
||||
if (!success)
|
||||
response.result(http::status::internal_server_error);
|
||||
else
|
||||
response.result(http::status::ok);
|
||||
|
||||
HttpUtility::SendJsonBody(response, params, result);
|
||||
HttpUtility::SendJsonBody(response, params, result, yc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,28 @@ Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String&
|
|||
return arr->Get(arr->GetLength() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream a JSON-encoded body to the client.
|
||||
*
|
||||
* This function sets the Content-Type header to "application/json", starts the streaming of the response,
|
||||
* and encodes the given value as JSON to the client. If pretty-print is requested, the JSON output will be
|
||||
* formatted accordingly. It is assumed that the response status code and other necessary headers have already
|
||||
* been set.
|
||||
*
|
||||
* @param response The HTTP response to send the body to.
|
||||
* @param params The request parameters.
|
||||
* @param val The value to encode as JSON and stream to the client.
|
||||
* @param yc The yield context to use for asynchronous operations.
|
||||
*/
|
||||
void HttpUtility::SendJsonBody(HttpApiResponse& response, const Dictionary::Ptr& params, const Value& val, boost::asio::yield_context& yc)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
response.set(http::field::content_type, "application/json");
|
||||
response.StartStreaming(false);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "remote/url.hpp"
|
||||
#include "base/dictionary.hpp"
|
||||
#include "remote/httpmessage.hpp"
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace icinga
|
||||
|
|
@ -24,6 +25,7 @@ public:
|
|||
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(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 = {});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ bool ModifyObjectHandler::HandleRequest(
|
|||
const WaitGroup::Ptr& waitGroup,
|
||||
const HttpApiRequest& request,
|
||||
HttpApiResponse& response,
|
||||
boost::asio::yield_context&
|
||||
boost::asio::yield_context& yc
|
||||
)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
|
@ -179,13 +179,20 @@ bool ModifyObjectHandler::HandleRequest(
|
|||
|
||||
results.push_back(std::move(result1));
|
||||
}
|
||||
// Unlock the mutexes before starting to stream the response, so that we don't block the shutdown process.
|
||||
lock.Unlock();
|
||||
if (wgLock.owns_lock()) {
|
||||
wgLock.unlock();
|
||||
}
|
||||
|
||||
Dictionary::Ptr result = new Dictionary({
|
||||
{ "results", new Array(std::move(results)) }
|
||||
});
|
||||
Array::Ptr resultArray = new Array{std::move(results)};
|
||||
resultArray->Freeze();
|
||||
|
||||
Dictionary::Ptr result = new Dictionary{{"results", std::move(resultArray)}};
|
||||
result->Freeze();
|
||||
|
||||
response.result(http::status::ok);
|
||||
HttpUtility::SendJsonBody(response, params, result);
|
||||
HttpUtility::SendJsonBody(response, params, result, yc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,15 +210,7 @@ bool ObjectQueryHandler::HandleRequest(
|
|||
std::unordered_map<Type*, std::pair<bool, std::unique_ptr<Expression>>> typePermissions;
|
||||
std::unordered_map<Object*, bool> objectAccessAllowed;
|
||||
|
||||
auto it = objs.begin();
|
||||
auto generatorFunc = [&]() -> std::optional<Value> {
|
||||
if (it == objs.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ConfigObject::Ptr obj = *it;
|
||||
++it;
|
||||
|
||||
auto generatorFunc = [&](const ConfigObject::Ptr& obj) -> Value {
|
||||
DictionaryData result1{
|
||||
{ "name", obj->GetName() },
|
||||
{ "type", obj->GetReflectionType()->GetName() }
|
||||
|
|
@ -327,15 +319,11 @@ bool ObjectQueryHandler::HandleRequest(
|
|||
return new Dictionary{std::move(result1)};
|
||||
};
|
||||
|
||||
response.result(http::status::ok);
|
||||
response.set(http::field::content_type, "application/json");
|
||||
response.StartStreaming(false);
|
||||
|
||||
Dictionary::Ptr results = new Dictionary{{"results", new ValueGenerator{generatorFunc}}};
|
||||
Dictionary::Ptr results = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
|
||||
results->Freeze();
|
||||
|
||||
bool pretty = HttpUtility::GetLastParameter(params, "pretty");
|
||||
response.GetJsonEncoder(pretty).Encode(results, &yc);
|
||||
response.result(http::status::ok);
|
||||
HttpUtility::SendJsonBody(response, params, results, yc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ bool TemplateQueryHandler::HandleRequest(
|
|||
const WaitGroup::Ptr&,
|
||||
const HttpApiRequest& request,
|
||||
HttpApiResponse& response,
|
||||
boost::asio::yield_context&
|
||||
boost::asio::yield_context& yc
|
||||
)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
|
@ -126,12 +126,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@
|
|||
#include "remote/typequeryhandler.hpp"
|
||||
#include "remote/httputility.hpp"
|
||||
#include "remote/filterutility.hpp"
|
||||
#include "base/configtype.hpp"
|
||||
#include "base/scriptglobal.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include "base/generator.hpp"
|
||||
#include <set>
|
||||
|
||||
using namespace icinga;
|
||||
|
|
@ -51,7 +49,7 @@ bool TypeQueryHandler::HandleRequest(
|
|||
const WaitGroup::Ptr&,
|
||||
const HttpApiRequest& request,
|
||||
HttpApiResponse& response,
|
||||
boost::asio::yield_context&
|
||||
boost::asio::yield_context& yc
|
||||
)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
|
@ -90,23 +88,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) -> 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);
|
||||
|
|
@ -144,14 +138,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@
|
|||
#include "remote/variablequeryhandler.hpp"
|
||||
#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 <set>
|
||||
|
|
@ -63,7 +62,7 @@ bool VariableQueryHandler::HandleRequest(
|
|||
const WaitGroup::Ptr&,
|
||||
const HttpApiRequest& request,
|
||||
HttpApiResponse& response,
|
||||
boost::asio::yield_context&
|
||||
boost::asio::yield_context& yc
|
||||
)
|
||||
{
|
||||
namespace http = boost::beast::http;
|
||||
|
|
@ -99,22 +98,19 @@ bool VariableQueryHandler::HandleRequest(
|
|||
return true;
|
||||
}
|
||||
|
||||
ArrayData results;
|
||||
|
||||
for (Dictionary::Ptr var : objs) {
|
||||
results.emplace_back(new Dictionary({
|
||||
auto generatorFunc = [](const Dictionary::Ptr& var) -> Value {
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,10 @@ BOOST_AUTO_TEST_SUITE(base_json)
|
|||
|
||||
BOOST_AUTO_TEST_CASE(encode)
|
||||
{
|
||||
auto generate = []() -> std::optional<Value> {
|
||||
static int count = 0;
|
||||
if (++count == 4) {
|
||||
count = 0;
|
||||
return std::nullopt;
|
||||
}
|
||||
return Value(count);
|
||||
};
|
||||
int emptyGenCounter = 0;
|
||||
std::vector<int> empty;
|
||||
std::vector<int> vec{1, 2, 3};
|
||||
auto generate = [](int count) -> Value { return Value(count); };
|
||||
|
||||
Dictionary::Ptr input (new Dictionary({
|
||||
{ "array", new Array({ new Namespace() }) },
|
||||
|
|
@ -43,8 +39,17 @@ BOOST_AUTO_TEST_CASE(encode)
|
|||
{ "string", "LF\nTAB\tAUml\xC3\xA4Ill\xC3" },
|
||||
{ "true", true },
|
||||
{ "uint", 23u },
|
||||
{ "generator", new ValueGenerator(generate) },
|
||||
{ "empty_generator", new ValueGenerator([]() -> std::optional<Value> { return std::nullopt; }) },
|
||||
{ "generator", new ValueGenerator{vec, generate} },
|
||||
{
|
||||
"empty_generator",
|
||||
new ValueGenerator{
|
||||
empty,
|
||||
[&emptyGenCounter](int) -> Value {
|
||||
emptyGenCounter++;
|
||||
return Empty;
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
String output (R"EOF({
|
||||
|
|
@ -73,16 +78,21 @@ BOOST_AUTO_TEST_CASE(encode)
|
|||
|
||||
auto got(JsonEncode(input, true));
|
||||
BOOST_CHECK_EQUAL(output, got);
|
||||
BOOST_CHECK_EQUAL(emptyGenCounter, 0); // Ensure the transformation function was never invoked.
|
||||
input->Set("generator", new ValueGenerator{vec, generate});
|
||||
|
||||
std::ostringstream oss;
|
||||
JsonEncode(input, oss, true);
|
||||
BOOST_CHECK_EQUAL(emptyGenCounter, 0); // Ensure the transformation function was never invoked.
|
||||
BOOST_CHECK_EQUAL(output, oss.str());
|
||||
|
||||
boost::algorithm::replace_all(output, " ", "");
|
||||
boost::algorithm::replace_all(output, "Objectoftype'Function'", "Object of type 'Function'");
|
||||
boost::algorithm::replace_all(output, "\n", "");
|
||||
|
||||
input->Set("generator", new ValueGenerator{vec, generate});
|
||||
BOOST_CHECK(JsonEncode(input, false) == output);
|
||||
BOOST_CHECK_EQUAL(emptyGenCounter, 0); // Ensure the transformation function was never invoked.
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(decode)
|
||||
|
|
|
|||
Loading…
Reference in a new issue