155 lines
7.7 KiB
C++
155 lines
7.7 KiB
C++
#pragma once
|
|
#include "adminive/http.hpp"
|
|
#include <drogon/drogon.h>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
namespace adminive {
|
|
template <Json_Type Json>
|
|
drogon::HttpResponsePtr make_drogon_response(Http_Response<Json> value) {
|
|
auto response = drogon::HttpResponse::newHttpResponse();
|
|
response->setStatusCode(static_cast<drogon::HttpStatusCode>(value.status));
|
|
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
|
response->setBody(dump_json(value.body, 2));
|
|
return response;
|
|
}
|
|
using Drogon_Executor = std::function<void(std::function<void()>)>;
|
|
using Drogon_Request_Context_Factory = std::function<Request_Context(const drogon::HttpRequestPtr&)>;
|
|
struct Drogon_Bind_Options {
|
|
Drogon_Executor executor;
|
|
Drogon_Request_Context_Factory context_factory;
|
|
std::vector<std::string> filters;
|
|
};
|
|
inline Request_Context make_drogon_request_context(const drogon::HttpRequestPtr& request, const Drogon_Request_Context_Factory& factory) {
|
|
return factory ? factory(request) : Request_Context{};
|
|
}
|
|
inline std::vector<drogon::internal::HttpConstraint> make_drogon_constraints(drogon::HttpMethod method, const std::vector<std::string>& filters) {
|
|
std::vector<drogon::internal::HttpConstraint> result;
|
|
result.reserve(filters.size() + 1);
|
|
result.emplace_back(method);
|
|
for(const auto& filter : filters) {
|
|
result.emplace_back(filter);
|
|
}
|
|
return result;
|
|
}
|
|
template <Json_Type Json, class Function>
|
|
void complete_drogon_request(std::function<void(const drogon::HttpResponsePtr&)>& callback, Function&& function) noexcept {
|
|
try {
|
|
callback(make_drogon_response(std::forward<Function>(function)()));
|
|
} catch(const std::exception& error) {
|
|
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
|
|
} catch(...) {
|
|
callback(make_drogon_response(make_http_error<Json>(500, "unknown server error")));
|
|
}
|
|
}
|
|
template <Json_Type Json, class Function>
|
|
void schedule_drogon_request(const Drogon_Executor& executor, std::function<void(const drogon::HttpResponsePtr&)>&& callback, Function&& function) noexcept {
|
|
auto response_callback = std::make_shared<std::function<void(const drogon::HttpResponsePtr&)>>(std::move(callback));
|
|
std::function<void()> task = [response_callback, function = std::forward<Function>(function)]() mutable {
|
|
complete_drogon_request<Json>(*response_callback, std::move(function));
|
|
};
|
|
try {
|
|
if(executor) {
|
|
executor(task);
|
|
} else {
|
|
task();
|
|
}
|
|
} catch(const std::exception& error) {
|
|
(*response_callback)(make_drogon_response(make_http_error<Json>(500, error.what())));
|
|
} catch(...) {
|
|
(*response_callback)(make_drogon_response(make_http_error<Json>(500, "failed to schedule request")));
|
|
}
|
|
}
|
|
template <class T, Json_Type Json>
|
|
requires Writable_Adapted_Object<T> && std::copy_constructible<Object_Model_Type<T>>
|
|
class Drogon_Resource {
|
|
public:
|
|
using Service = Resource_Service<T, Json>;
|
|
using Transaction = typename Service::Transaction;
|
|
Drogon_Resource(T& value, std::string path, Transaction transaction = {}, std::mutex* shared_mutex = nullptr) : service_(std::make_shared<Service>(value, std::move(path), std::move(transaction), shared_mutex)) {}
|
|
Json amis_schema() const {
|
|
return service_->amis_schema();
|
|
}
|
|
void bind(drogon::HttpAppFramework& app, Drogon_Bind_Options options = {}) const {
|
|
const auto service = service_;
|
|
const auto get_constraints = make_drogon_constraints(drogon::Get, options.filters);
|
|
const auto post_constraints = make_drogon_constraints(drogon::Post, options.filters);
|
|
app.registerHandler(service->path() + "/descriptor", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
complete_drogon_request<Json>(callback, [service] {
|
|
return service->descriptor_response();
|
|
});
|
|
}, get_constraints);
|
|
app.registerHandler(service->path() + "/data", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
complete_drogon_request<Json>(callback, [service] {
|
|
return service->data_response();
|
|
});
|
|
}, get_constraints);
|
|
app.registerHandler(service->path() + "/amis", [service](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
complete_drogon_request<Json>(callback, [service] {
|
|
return service->amis_response();
|
|
});
|
|
}, get_constraints);
|
|
app.registerHandler(service->path() + "/data", [service, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
Request_Context context;
|
|
try {
|
|
context = make_drogon_request_context(request, options.context_factory);
|
|
} catch(const std::exception& error) {
|
|
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
|
|
return;
|
|
}
|
|
schedule_drogon_request<Json>(options.executor, std::move(callback), [service, body = std::string(request->getBody()), context] {
|
|
return service->update_response(body, context);
|
|
});
|
|
}, post_constraints);
|
|
}
|
|
private:
|
|
std::shared_ptr<Service> service_;
|
|
};
|
|
template <Described_Type Status, Json_Type Json>
|
|
class Drogon_Status_Resource {
|
|
public:
|
|
using Reader = std::function<Status(const Request_Context&)>;
|
|
Drogon_Status_Resource(std::string path, Reader reader, std::uint64_t interval = 2000) : state_(std::make_shared<State>(State{std::move(path), std::move(reader), interval, to_status_descriptor_json<Json, Status>()})) {}
|
|
Json amis_schema() const {
|
|
return make_amis_status_service<Json>(state_->descriptor, state_->path + "/data", state_->interval);
|
|
}
|
|
void bind(drogon::HttpAppFramework& app, Drogon_Bind_Options options = {}) const {
|
|
const auto state = state_;
|
|
const auto constraints = make_drogon_constraints(drogon::Get, options.filters);
|
|
app.registerHandler(state->path + "/descriptor", [state](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
complete_drogon_request<Json>(callback, [state] {
|
|
return make_http_success<Json>(state->descriptor);
|
|
});
|
|
}, constraints);
|
|
app.registerHandler(state->path + "/amis", [state](const drogon::HttpRequestPtr&, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
complete_drogon_request<Json>(callback, [state] {
|
|
return make_http_success<Json>(make_amis_status_service<Json>(state->descriptor, state->path + "/data", state->interval));
|
|
});
|
|
}, constraints);
|
|
app.registerHandler(state->path + "/data", [state, options](const drogon::HttpRequestPtr& request, std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
|
|
Request_Context context;
|
|
try {
|
|
context = make_drogon_request_context(request, options.context_factory);
|
|
} catch(const std::exception& error) {
|
|
callback(make_drogon_response(make_http_error<Json>(500, error.what())));
|
|
return;
|
|
}
|
|
schedule_drogon_request<Json>(options.executor, std::move(callback), [state, context] {
|
|
return make_http_success<Json>(to_status_json<Json>(state->reader(context)));
|
|
});
|
|
}, constraints);
|
|
}
|
|
private:
|
|
struct State {
|
|
std::string path;
|
|
Reader reader;
|
|
std::uint64_t interval;
|
|
Json descriptor;
|
|
};
|
|
std::shared_ptr<State> state_;
|
|
};
|
|
}
|