ac3a506a5c
- POST /api/fabric/create-target: add_writer to target node, poll for target_info - POST /api/fabric/connect: get target_info from target, configure initiator node
596 lines
23 KiB
C++
596 lines
23 KiB
C++
#include <dmf-engine/api_server.hpp>
|
|
#include <dmf-engine/graph.hpp>
|
|
#include <dmf-engine/flow_manager.hpp>
|
|
#include <dmf-engine/process_manager.hpp>
|
|
#include <dmf-engine/node_control_client.hpp>
|
|
|
|
#include <libwebsockets.h>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
namespace dmf_engine {
|
|
|
|
struct ApiServerImpl {
|
|
uint16_t port = 0;
|
|
Graph* graph = nullptr;
|
|
FlowManager* flow_manager = nullptr;
|
|
ProcessManager* process_manager = nullptr;
|
|
NodeControlClient* control_client = nullptr;
|
|
std::string mxl_domain = "/dev/shm/mxl";
|
|
struct lws_context* context = nullptr;
|
|
};
|
|
|
|
struct ApiServer::Impl {
|
|
std::unique_ptr<ApiServerImpl> data;
|
|
};
|
|
|
|
struct HttpRequest {
|
|
std::string method;
|
|
std::string path;
|
|
std::string body;
|
|
};
|
|
|
|
static ApiServerImpl* g_impl = nullptr;
|
|
|
|
static int send_json_response(struct lws* wsi, const std::string& status_str,
|
|
const std::string& json_body) {
|
|
auto headers = "HTTP/1.1 " + status_str + "\r\n"
|
|
"Content-Type: application/json\r\n"
|
|
"Access-Control-Allow-Origin: *\r\n"
|
|
"Access-Control-Allow-Methods: GET, POST, DELETE, PUT, OPTIONS\r\n"
|
|
"Access-Control-Allow-Headers: Content-Type\r\n"
|
|
"Content-Length: " + std::to_string(json_body.size()) + "\r\n"
|
|
"\r\n";
|
|
|
|
std::vector<uint8_t> buf(LWS_PRE + headers.size() + json_body.size());
|
|
std::memcpy(buf.data() + LWS_PRE, headers.data(), headers.size());
|
|
std::memcpy(buf.data() + LWS_PRE + headers.size(), json_body.data(), json_body.size());
|
|
|
|
lws_write(wsi, buf.data() + LWS_PRE, headers.size() + json_body.size(), LWS_WRITE_HTTP);
|
|
|
|
if (lws_http_transaction_completed(wsi)) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void handle_request(const std::string& method, const std::string& path,
|
|
const std::string& body,
|
|
std::string& status_str, std::string& response_body) {
|
|
if (!g_impl || !g_impl->graph) {
|
|
status_str = "500 Internal Server Error";
|
|
response_body = nlohmann::json({{"error", "Server not initialized"}}).dump();
|
|
return;
|
|
}
|
|
|
|
auto ok = [&](const nlohmann::json& j) {
|
|
status_str = "200 OK";
|
|
response_body = j.dump();
|
|
};
|
|
auto created = [&](const nlohmann::json& j) {
|
|
status_str = "201 Created";
|
|
response_body = j.dump();
|
|
};
|
|
auto error_resp = [&](int code, const std::string& msg) {
|
|
status_str = std::to_string(code) + " Error";
|
|
response_body = nlohmann::json({{"error", msg}}).dump();
|
|
};
|
|
|
|
try {
|
|
nlohmann::json req_body = body.empty() ? nlohmann::json::object() : nlohmann::json::parse(body);
|
|
|
|
auto& graph = *g_impl->graph;
|
|
auto& fm = *g_impl->flow_manager;
|
|
auto& pm = *g_impl->process_manager;
|
|
auto& cc = *g_impl->control_client;
|
|
|
|
if (path == "/api/graph" && method == "GET") {
|
|
ok(graph.serialize());
|
|
} else if (path == "/api/graph/nodes" && method == "POST") {
|
|
if (!req_body.contains("type")) {
|
|
error_resp(400, "Missing 'type' field");
|
|
} else {
|
|
auto type = req_body["type"].get<std::string>();
|
|
auto config = req_body.value("config", nlohmann::json::object());
|
|
if (req_body.contains("id") && req_body["id"].is_string()) {
|
|
config["id"] = req_body["id"];
|
|
}
|
|
auto id = graph.add_node(type, config);
|
|
created({{"id", id}});
|
|
}
|
|
} else if (path.find("/api/graph/nodes/") == 0 && method == "DELETE") {
|
|
auto node_id = path.substr(std::string("/api/graph/nodes/").length());
|
|
cc.unregister_node(node_id);
|
|
if (graph.remove_node(node_id)) {
|
|
ok({{"deleted", node_id}});
|
|
} else {
|
|
error_resp(404, "Node not found: " + node_id);
|
|
}
|
|
} else if (path == "/api/graph/edges" && method == "POST") {
|
|
if (!req_body.contains("from_node") || !req_body.contains("to_node")) {
|
|
error_resp(400, "Missing from_node/to_node");
|
|
} else {
|
|
auto from_node = req_body["from_node"].get<std::string>();
|
|
auto from_port = req_body.value("from_port", "video_out");
|
|
auto to_node = req_body["to_node"].get<std::string>();
|
|
auto to_port = req_body.value("to_port", "video_in");
|
|
|
|
auto flow_id = fm.create_flow_id();
|
|
auto flow_def = fm.create_v210_flow_def(flow_id, 1920, 1080, 50, 1);
|
|
|
|
auto edge_id = graph.add_edge(from_node, from_port, to_node, to_port, flow_id, flow_def);
|
|
|
|
auto from_port_num = cc.get_port(from_node);
|
|
auto to_port_num = cc.get_port(to_node);
|
|
|
|
if (from_port_num > 0) {
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "add_writer";
|
|
cmd["port_id"] = from_port;
|
|
cmd["flow_id"] = flow_id;
|
|
cmd["flow_def"] = flow_def;
|
|
cc.send_command(from_port_num, cmd.dump());
|
|
}
|
|
|
|
if (to_port_num > 0) {
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "add_reader";
|
|
cmd["port_id"] = to_port;
|
|
cmd["flow_id"] = flow_id;
|
|
cc.send_command(to_port_num, cmd.dump());
|
|
}
|
|
|
|
created({{"id", edge_id}, {"flow_id", flow_id}});
|
|
}
|
|
} else if (path.find("/api/graph/edges/") == 0 && method == "DELETE") {
|
|
auto edge_id = path.substr(std::string("/api/graph/edges/").length());
|
|
auto edges = graph.get_edges();
|
|
const GraphEdge* edge = nullptr;
|
|
for (auto& e : edges) {
|
|
if (e.id == edge_id) { edge = &e; break; }
|
|
}
|
|
|
|
if (graph.remove_edge(edge_id)) {
|
|
if (edge) {
|
|
auto from_port_num = cc.get_port(edge->from_node);
|
|
auto to_port_num = cc.get_port(edge->to_node);
|
|
if (from_port_num > 0) {
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "remove_writer";
|
|
cmd["port_id"] = edge->from_port;
|
|
cc.send_command(from_port_num, cmd.dump());
|
|
}
|
|
if (to_port_num > 0) {
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "remove_reader";
|
|
cmd["port_id"] = edge->to_port;
|
|
cc.send_command(to_port_num, cmd.dump());
|
|
}
|
|
}
|
|
ok({{"deleted", edge_id}});
|
|
} else {
|
|
error_resp(404, "Edge not found: " + edge_id);
|
|
}
|
|
} else if (path == "/api/graph/start" && method == "POST") {
|
|
auto nodes = graph.get_nodes();
|
|
uint16_t port = g_impl->port + 100;
|
|
for (auto& node : nodes) {
|
|
pm.start_node(const_cast<GraphNode&>(node), g_impl->mxl_domain, port);
|
|
cc.register_node(node.id, port);
|
|
port++;
|
|
}
|
|
|
|
for (auto& edge : graph.get_edges()) {
|
|
auto from_port_num = cc.get_port(edge.from_node);
|
|
auto to_port_num = cc.get_port(edge.to_node);
|
|
|
|
if (from_port_num > 0) {
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "add_writer";
|
|
cmd["port_id"] = edge.from_port;
|
|
cmd["flow_id"] = edge.flow_id;
|
|
cmd["flow_def"] = edge.flow_def;
|
|
cc.send_command(from_port_num, cmd.dump());
|
|
}
|
|
|
|
if (to_port_num > 0) {
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "add_reader";
|
|
cmd["port_id"] = edge.to_port;
|
|
cmd["flow_id"] = edge.flow_id;
|
|
cc.send_command(to_port_num, cmd.dump());
|
|
}
|
|
}
|
|
|
|
ok({{"status", "started"}});
|
|
} else if (path == "/api/graph/stop" && method == "POST") {
|
|
auto nodes = graph.get_nodes();
|
|
for (auto& node : nodes) {
|
|
cc.unregister_node(node.id);
|
|
pm.stop_node(const_cast<GraphNode&>(node));
|
|
}
|
|
ok({{"status", "stopped"}});
|
|
} else if (path.find("/api/graph/nodes/") == 0 && path.find("/connect-input") != std::string::npos && method == "POST") {
|
|
auto prefix = std::string("/api/graph/nodes/");
|
|
auto suffix_start = path.find("/connect-input");
|
|
auto node_id = path.substr(prefix.length(), suffix_start - prefix.length());
|
|
|
|
if (!req_body.contains("flow_id") || !req_body.contains("port_id")) {
|
|
error_resp(400, "Missing flow_id/port_id");
|
|
return;
|
|
}
|
|
|
|
auto port_num = cc.get_port(node_id);
|
|
if (port_num == 0) {
|
|
error_resp(404, "Node not running or not found: " + node_id);
|
|
return;
|
|
}
|
|
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "add_reader";
|
|
cmd["port_id"] = req_body["port_id"].get<std::string>();
|
|
cmd["flow_id"] = req_body["flow_id"].get<std::string>();
|
|
|
|
if (cc.send_command(port_num, cmd.dump())) {
|
|
ok({{"node_id", node_id}, {"connected_input", req_body["port_id"]}, {"flow_id", req_body["flow_id"]}});
|
|
} else {
|
|
error_resp(500, "Failed to send command to node");
|
|
}
|
|
} else if (path.find("/api/graph/nodes/") == 0 && path.find("/connect-output") != std::string::npos && method == "POST") {
|
|
auto prefix = std::string("/api/graph/nodes/");
|
|
auto suffix_start = path.find("/connect-output");
|
|
auto node_id = path.substr(prefix.length(), suffix_start - prefix.length());
|
|
|
|
auto port_id = req_body.value("port_id", "video_out");
|
|
|
|
auto port_num = cc.get_port(node_id);
|
|
if (port_num == 0) {
|
|
error_resp(404, "Node not running or not found: " + node_id);
|
|
return;
|
|
}
|
|
|
|
int fps_num = 50, fps_den = 1;
|
|
int width = 1920, height = 1080;
|
|
|
|
nlohmann::json status_cmd;
|
|
status_cmd["cmd"] = "status";
|
|
auto status_resp = cc.send_command_with_response(port_num, status_cmd.dump());
|
|
if (!status_resp.empty()) {
|
|
try {
|
|
auto sr = nlohmann::json::parse(status_resp);
|
|
if (sr.contains("data")) {
|
|
auto& d = sr["data"];
|
|
if (d.contains("grain_rate")) {
|
|
fps_num = d["grain_rate"].value("numerator", fps_num);
|
|
fps_den = d["grain_rate"].value("denominator", fps_den);
|
|
}
|
|
width = d.value("width", width);
|
|
height = d.value("height", height);
|
|
}
|
|
} catch (...) {}
|
|
}
|
|
|
|
auto flow_id = fm.create_flow_id();
|
|
auto flow_def = fm.create_v210_flow_def(flow_id, width, height, fps_num, fps_den);
|
|
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "add_writer";
|
|
cmd["port_id"] = port_id;
|
|
cmd["flow_id"] = flow_id;
|
|
cmd["flow_def"] = flow_def;
|
|
|
|
if (cc.send_command(port_num, cmd.dump())) {
|
|
ok({{"node_id", node_id}, {"connected_output", port_id}, {"flow_id", flow_id}});
|
|
} else {
|
|
error_resp(500, "Failed to send command to node");
|
|
}
|
|
} else if (path.find("/api/graph/nodes/") == 0 && path.find("/disconnect-port") != std::string::npos && method == "POST") {
|
|
auto prefix = std::string("/api/graph/nodes/");
|
|
auto suffix_start = path.find("/disconnect-port");
|
|
auto node_id = path.substr(prefix.length(), suffix_start - prefix.length());
|
|
|
|
if (!req_body.contains("port_id")) {
|
|
error_resp(400, "Missing port_id");
|
|
return;
|
|
}
|
|
|
|
auto port_id = req_body["port_id"].get<std::string>();
|
|
auto port_num = cc.get_port(node_id);
|
|
if (port_num == 0) {
|
|
error_resp(404, "Node not running or not found: " + node_id);
|
|
return;
|
|
}
|
|
|
|
auto direction = req_body.value("direction", "input");
|
|
nlohmann::json cmd;
|
|
if (direction == "output") {
|
|
cmd["cmd"] = "remove_writer";
|
|
} else {
|
|
cmd["cmd"] = "remove_reader";
|
|
}
|
|
cmd["port_id"] = port_id;
|
|
|
|
if (cc.send_command(port_num, cmd.dump())) {
|
|
ok({{"node_id", node_id}, {"disconnected", port_id}});
|
|
} else {
|
|
error_resp(500, "Failed to send command to node");
|
|
}
|
|
} else if (path == "/api/fabric/create-target" && method == "POST") {
|
|
if (!req_body.contains("node_id")) {
|
|
error_resp(400, "Missing node_id");
|
|
return;
|
|
}
|
|
|
|
auto node_id = req_body["node_id"].get<std::string>();
|
|
auto port_id = req_body.value("port_id", "video_out");
|
|
auto flow_id = req_body.value("flow_id", fm.create_flow_id());
|
|
|
|
auto port_num = cc.get_port(node_id);
|
|
if (port_num == 0) {
|
|
error_resp(404, "Node not running: " + node_id);
|
|
return;
|
|
}
|
|
|
|
int fps_num = 25, fps_den = 1;
|
|
int width = 1920, height = 1080;
|
|
|
|
nlohmann::json status_cmd;
|
|
status_cmd["cmd"] = "status";
|
|
auto status_resp = cc.send_command_with_response(port_num, status_cmd.dump());
|
|
if (!status_resp.empty()) {
|
|
try {
|
|
auto sr = nlohmann::json::parse(status_resp);
|
|
if (sr.contains("data")) {
|
|
auto& d = sr["data"];
|
|
if (d.contains("grain_rate")) {
|
|
fps_num = d["grain_rate"].value("numerator", fps_num);
|
|
fps_den = d["grain_rate"].value("denominator", fps_den);
|
|
}
|
|
width = d.value("width", width);
|
|
height = d.value("height", height);
|
|
}
|
|
} catch (...) {}
|
|
}
|
|
|
|
auto flow_def = fm.create_v210_flow_def(flow_id, width, height, fps_num, fps_den);
|
|
|
|
nlohmann::json cmd;
|
|
cmd["cmd"] = "add_writer";
|
|
cmd["port_id"] = port_id;
|
|
cmd["flow_id"] = flow_id;
|
|
cmd["flow_def"] = flow_def;
|
|
if (!cc.send_command(port_num, cmd.dump())) {
|
|
error_resp(500, "Failed to add writer to target node");
|
|
return;
|
|
}
|
|
|
|
std::string target_info;
|
|
constexpr int max_poll_attempts = 30;
|
|
for (int i = 0; i < max_poll_attempts; ++i) {
|
|
auto resp = cc.send_command_with_response(port_num, status_cmd.dump());
|
|
if (!resp.empty()) {
|
|
try {
|
|
auto sr = nlohmann::json::parse(resp);
|
|
if (sr.contains("data") && sr["data"].contains("target_info") && !sr["data"]["target_info"].get<std::string>().empty()) {
|
|
target_info = sr["data"]["target_info"].get<std::string>();
|
|
break;
|
|
}
|
|
} catch (...) {}
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
}
|
|
|
|
if (target_info.empty()) {
|
|
error_resp(504, "Target node did not produce target_info in time");
|
|
return;
|
|
}
|
|
|
|
ok({
|
|
{"node_id", node_id},
|
|
{"flow_id", flow_id},
|
|
{"target_info", target_info}
|
|
});
|
|
} else if (path == "/api/fabric/connect" && method == "POST") {
|
|
if (!req_body.contains("target_node_id") || !req_body.contains("initiator_node_id")) {
|
|
error_resp(400, "Missing target_node_id/initiator_node_id");
|
|
return;
|
|
}
|
|
|
|
auto target_node_id = req_body["target_node_id"].get<std::string>();
|
|
auto initiator_node_id = req_body["initiator_node_id"].get<std::string>();
|
|
|
|
auto target_port = cc.get_port(target_node_id);
|
|
auto initiator_port = cc.get_port(initiator_node_id);
|
|
|
|
if (target_port == 0) {
|
|
error_resp(404, "Target node not running: " + target_node_id);
|
|
return;
|
|
}
|
|
if (initiator_port == 0) {
|
|
error_resp(404, "Initiator node not running: " + initiator_node_id);
|
|
return;
|
|
}
|
|
|
|
nlohmann::json status_cmd;
|
|
status_cmd["cmd"] = "status";
|
|
std::string target_info;
|
|
constexpr int max_poll_attempts = 30;
|
|
for (int i = 0; i < max_poll_attempts; ++i) {
|
|
auto resp = cc.send_command_with_response(target_port, status_cmd.dump());
|
|
if (!resp.empty()) {
|
|
try {
|
|
auto sr = nlohmann::json::parse(resp);
|
|
if (sr.contains("data") && sr["data"].contains("target_info") && !sr["data"]["target_info"].get<std::string>().empty()) {
|
|
target_info = sr["data"]["target_info"].get<std::string>();
|
|
break;
|
|
}
|
|
} catch (...) {}
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
}
|
|
|
|
if (target_info.empty()) {
|
|
error_resp(504, "Target node did not produce target_info in time");
|
|
return;
|
|
}
|
|
|
|
nlohmann::json configure_cmd;
|
|
configure_cmd["cmd"] = "configure";
|
|
configure_cmd["params"]["target_info"] = target_info;
|
|
if (!cc.send_command(initiator_port, configure_cmd.dump())) {
|
|
error_resp(500, "Failed to send target_info to initiator node");
|
|
return;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
|
|
bool initiator_running = false;
|
|
auto init_resp = cc.send_command_with_response(initiator_port, status_cmd.dump());
|
|
if (!init_resp.empty()) {
|
|
try {
|
|
auto ir = nlohmann::json::parse(init_resp);
|
|
if (ir.contains("data") && ir["data"].contains("running")) {
|
|
initiator_running = ir["data"]["running"].get<bool>();
|
|
}
|
|
} catch (...) {}
|
|
}
|
|
|
|
ok({
|
|
{"target_node_id", target_node_id},
|
|
{"initiator_node_id", initiator_node_id},
|
|
{"target_info_length", target_info.size()},
|
|
{"initiator_running", initiator_running}
|
|
});
|
|
} else if (path.find("/api/graph/nodes/") == 0 && path.find("/command") != std::string::npos && method == "POST") {
|
|
auto prefix = std::string("/api/graph/nodes/");
|
|
auto suffix_start = path.find("/command");
|
|
auto node_id = path.substr(prefix.length(), suffix_start - prefix.length());
|
|
|
|
auto port_num = cc.get_port(node_id);
|
|
if (port_num == 0) {
|
|
error_resp(404, "Node not running or not found: " + node_id);
|
|
return;
|
|
}
|
|
|
|
if (cc.send_command(port_num, req_body.dump())) {
|
|
ok({{"node_id", node_id}, {"sent", true}});
|
|
} else {
|
|
error_resp(500, "Failed to send command to node");
|
|
}
|
|
} else {
|
|
error_resp(404, "Not found: " + method + " " + path);
|
|
}
|
|
} catch (const nlohmann::json::exception& e) {
|
|
error_resp(400, std::string("JSON error: ") + e.what());
|
|
} catch (const std::exception& e) {
|
|
error_resp(500, e.what());
|
|
}
|
|
}
|
|
|
|
static int callback_http(struct lws* wsi, enum lws_callback_reasons reason,
|
|
void* user, void* in, size_t len) {
|
|
auto* req = static_cast<HttpRequest*>(user);
|
|
|
|
switch (reason) {
|
|
case LWS_CALLBACK_HTTP: {
|
|
new (req) HttpRequest();
|
|
|
|
char* uri_ptr = nullptr;
|
|
int uri_len = 0;
|
|
int method = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len);
|
|
|
|
switch (method) {
|
|
case LWSHUMETH_GET: req->method = "GET"; break;
|
|
case LWSHUMETH_POST: req->method = "POST"; break;
|
|
case LWSHUMETH_PUT: req->method = "PUT"; break;
|
|
case LWSHUMETH_DELETE: req->method = "DELETE"; break;
|
|
default: req->method = "GET"; break;
|
|
}
|
|
|
|
if (uri_ptr && uri_len > 0) {
|
|
req->path.assign(uri_ptr, uri_len);
|
|
}
|
|
|
|
if (req->method == "GET" || req->method == "DELETE") {
|
|
std::string status_str, response_body;
|
|
handle_request(req->method, req->path, "", status_str, response_body);
|
|
return send_json_response(wsi, status_str, response_body);
|
|
}
|
|
|
|
int body_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH);
|
|
if (body_len == 0) {
|
|
std::string status_str, response_body;
|
|
handle_request(req->method, req->path, "", status_str, response_body);
|
|
return send_json_response(wsi, status_str, response_body);
|
|
}
|
|
req->body.reserve(body_len);
|
|
break;
|
|
}
|
|
case LWS_CALLBACK_HTTP_BODY: {
|
|
req->body.append(static_cast<char*>(in), len);
|
|
break;
|
|
}
|
|
case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
|
|
std::string status_str, response_body;
|
|
handle_request(req->method, req->path, req->body, status_str, response_body);
|
|
return send_json_response(wsi, status_str, response_body);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct lws_protocols protocols[] = {
|
|
{"http-api", callback_http, sizeof(HttpRequest), 4096},
|
|
{nullptr, nullptr, 0, 0},
|
|
};
|
|
|
|
ApiServer::ApiServer(uint16_t port, Graph& graph, FlowManager& flow_manager, ProcessManager& process_manager, NodeControlClient& control_client, const std::string& mxl_domain)
|
|
: impl_(std::make_unique<Impl>()) {
|
|
impl_->data = std::make_unique<ApiServerImpl>();
|
|
impl_->data->port = port;
|
|
impl_->data->graph = &graph;
|
|
impl_->data->flow_manager = &flow_manager;
|
|
impl_->data->process_manager = &process_manager;
|
|
impl_->data->control_client = &control_client;
|
|
impl_->data->mxl_domain = mxl_domain;
|
|
|
|
g_impl = impl_->data.get();
|
|
|
|
struct lws_context_creation_info info;
|
|
std::memset(&info, 0, sizeof(info));
|
|
info.port = port;
|
|
info.protocols = protocols;
|
|
info.gid = -1;
|
|
info.uid = -1;
|
|
|
|
impl_->data->context = lws_create_context(&info);
|
|
if (!impl_->data->context) {
|
|
spdlog::error("Failed to create HTTP context on port {}", port);
|
|
throw std::runtime_error("Failed to create HTTP context");
|
|
}
|
|
spdlog::info("API server: listening on port {}", port);
|
|
}
|
|
|
|
ApiServer::~ApiServer() {
|
|
if (impl_->data && impl_->data->context) {
|
|
lws_context_destroy(impl_->data->context);
|
|
}
|
|
if (g_impl == impl_->data.get()) {
|
|
g_impl = nullptr;
|
|
}
|
|
}
|
|
|
|
void ApiServer::poll(int timeout_ms) {
|
|
lws_service(impl_->data->context, timeout_ms);
|
|
}
|
|
|
|
} // namespace dmf_engine
|