Implement vitastor-cli serve command to serve simple HTTP API

This commit is contained in:
Vitaliy Filippov
2026-04-22 01:41:10 +03:00
parent 157191b770
commit 54feea5234
9 changed files with 561 additions and 38 deletions
+1
View File
@@ -27,6 +27,7 @@ add_library(vitastor_cli STATIC
cli_pool_ls.cpp
cli_pool_modify.cpp
cli_pool_rm.cpp
cli_serve.cpp
)
target_compile_options(vitastor_cli PUBLIC -fPIC)
+50 -30
View File
@@ -225,6 +225,14 @@ static const char* help_text =
" -r|--reverse Sort in descending order\n"
" -n|--count N Only list first N items\n"
"\n"
"vitastor-cli serve\n"
" Start HTTP server able to handle CLI commands over a REST API. Options:\n"
" --bind_address ADDR Specify server IP address or addresses, separated by space. Default is 127.0.0.1.\n"
" --port 8080 Specify server port.\n"
" --ssl_cert FILE Path to server SSL certificate file (PEM format).\n"
" --ssl_key FILE Path to server SSL private key file.\n"
" --ssl_ca FILE Path to file with SSL CA certificates used to validate client connections.\n"
"\n"
"Use vitastor-cli --help <command> for command details or vitastor-cli --help --all for all details.\n"
"\n"
"GLOBAL OPTIONS:\n"
@@ -319,27 +327,24 @@ static json11::Json::object parse_args(int narg, const char *args[])
return cfg;
}
static int run(cli_tool_t *p, json11::Json::object cfg)
std::function<bool(cli_result_t &)> cli_tool_t::start(json11::Json::object cfg, cli_result_t & result)
{
cli_result_t result = {};
p->is_command_line = true;
p->parse_config(cfg);
json11::Json::array cmd = cfg["command"].array_items();
cfg.erase("command");
std::function<bool(cli_result_t &)> action_cb;
if (!cmd.size())
{
result = { .err = EINVAL, .text = "command is missing" };
result = { .err = EOPNOTSUPP, .text = "command is missing" };
}
else if (cmd[0] == "status")
{
// Show cluster status
action_cb = p->start_status(cfg);
action_cb = start_status(cfg);
}
else if (cmd[0] == "df")
{
// Show pool space stats
action_cb = p->start_pool_ls(cfg);
action_cb = start_pool_ls(cfg);
}
else if (cmd[0] == "ls")
{
@@ -349,7 +354,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["names"] = cmd;
}
action_cb = p->start_ls(cfg);
action_cb = start_ls(cfg);
}
else if (cmd[0] == "snap-create")
{
@@ -364,7 +369,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["image"] = name.substr(0, pos);
cfg["snapshot"] = name.substr(pos + 1);
action_cb = p->start_create(cfg);
action_cb = start_create(cfg);
}
}
else if (cmd[0] == "create")
@@ -374,7 +379,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["image"] = cmd[1];
}
action_cb = p->start_create(cfg);
action_cb = start_create(cfg);
}
else if (cmd[0] == "modify")
{
@@ -383,12 +388,12 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["image"] = cmd[1];
}
action_cb = p->start_modify(cfg);
action_cb = start_modify(cfg);
}
else if (cmd[0] == "rm-data")
{
// Delete inode data
action_cb = p->start_rm_data(cfg);
action_cb = start_rm_data(cfg);
}
else if (cmd[0] == "rm-osd")
{
@@ -398,7 +403,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["osd_id"] = cmd;
}
action_cb = p->start_rm_osd(cfg);
action_cb = start_rm_osd(cfg);
}
else if (cmd[0] == "merge-data")
{
@@ -409,7 +414,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
if (cmd.size() > 2)
cfg["to"] = cmd[2];
}
action_cb = p->start_merge(cfg);
action_cb = start_merge(cfg);
}
else if (cmd[0] == "flatten")
{
@@ -418,7 +423,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["image"] = cmd[1];
}
action_cb = p->start_flatten(cfg);
action_cb = start_flatten(cfg);
}
else if (cmd[0] == "dd")
{
@@ -432,7 +437,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cfg[arg.substr(0, p)] = arg.substr(p+1);
}
}
action_cb = p->start_dd(cfg);
action_cb = start_dd(cfg);
}
else if (cmd[0] == "rm")
{
@@ -441,7 +446,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["globs"] = cmd;
action_cb = p->start_rm_wildcard(cfg);
action_cb = start_rm_wildcard(cfg);
}
else
{
@@ -451,41 +456,41 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
if (cmd.size() > 2)
cfg["to"] = cmd[2];
}
action_cb = p->start_rm(cfg);
action_cb = start_rm(cfg);
}
}
else if (cmd[0] == "describe")
{
// Describe unclean objects
action_cb = p->start_describe(cfg);
action_cb = start_describe(cfg);
}
else if (cmd[0] == "fix")
{
// Fix inconsistent objects (by deleting some copies)
action_cb = p->start_fix(cfg);
action_cb = start_fix(cfg);
}
else if (cmd[0] == "alloc-osd")
{
// Allocate a new OSD number
action_cb = p->start_alloc_osd(cfg);
action_cb = start_alloc_osd(cfg);
}
else if (cmd[0] == "osd-tree")
{
// Print OSD tree
action_cb = p->start_osd_tree(cfg);
action_cb = start_osd_tree(cfg);
}
else if (cmd[0] == "osds" || cmd[0] == "ls-osds" || cmd[0] == "ls-osd" || cmd[0] == "osd-ls")
{
// Print OSD list
cfg["flat"] = true;
action_cb = p->start_osd_tree(cfg);
action_cb = start_osd_tree(cfg);
}
else if (cmd[0] == "modify-osd")
{
// Modify OSD configuration
if (cmd.size() > 1)
cfg["osd_num"] = cmd[1];
action_cb = p->start_modify_osd(cfg);
action_cb = start_modify_osd(cfg);
}
else if (cmd[0] == "pg-list" || cmd[0] == "pg-ls" || cmd[0] == "list-pg" || cmd[0] == "ls-pg" || cmd[0] == "ls-pgs" || cmd[0] == "pgs")
{
@@ -495,7 +500,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["pg_state"] = cmd;
}
action_cb = p->start_pg_list(cfg);
action_cb = start_pg_list(cfg);
}
else if (cmd[0] == "create-pool" || cmd[0] == "pool-create")
{
@@ -504,7 +509,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["name"] = cmd[1];
}
action_cb = p->start_pool_create(cfg);
action_cb = start_pool_create(cfg);
}
else if (cmd[0] == "modify-pool" || cmd[0] == "pool-modify")
{
@@ -513,7 +518,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["old_name"] = cmd[1];
}
action_cb = p->start_pool_modify(cfg);
action_cb = start_pool_modify(cfg);
}
else if (cmd[0] == "rm-pool" || cmd[0] == "pool-rm")
{
@@ -522,7 +527,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["pool"] = cmd[1];
}
action_cb = p->start_pool_rm(cfg);
action_cb = start_pool_rm(cfg);
}
else if (cmd[0] == "ls-pool" || cmd[0] == "pool-ls" || cmd[0] == "ls-pools" || cmd[0] == "pools")
{
@@ -533,12 +538,26 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["names"] = cmd;
}
action_cb = p->start_pool_ls(cfg);
action_cb = start_pool_ls(cfg);
}
else if (cmd[0] == "serve")
{
// Start HTTP server
action_cb = start_serve(cfg);
}
else
{
result = { .err = EINVAL, .text = "unknown command: "+cmd[0].string_value() };
result = { .err = EOPNOTSUPP, .text = "unknown command: "+cmd[0].string_value() };
}
return action_cb;
}
static int run(cli_tool_t *p, json11::Json::object cfg)
{
cli_result_t result = {};
p->is_command_line = true;
p->parse_config(cfg);
auto action_cb = p->start(cfg, result);
if (action_cb != NULL)
{
// Create client
@@ -550,6 +569,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
result = r;
action_cb = NULL;
p->ringloop->submit();
});
// Loop until it completes
while (action_cb != NULL)
+4 -1
View File
@@ -46,6 +46,7 @@ public:
json11::Json etcd_result;
void parse_config(json11::Json::object & cfg);
void parse_api_opts(json11::Json::object & cfg);
json11::Json parse_tags(std::string tags);
void change_parent(inode_t cur, inode_t new_parent, cli_result_t *result);
@@ -56,8 +57,10 @@ public:
friend struct snap_flattener_t;
friend struct snap_remover_t;
std::function<bool(cli_result_t &)> start(json11::Json::object cfg, cli_result_t & result);
std::function<bool(cli_result_t &)> start_alloc_osd(json11::Json);
std::function<bool(cli_result_t &)> start_create(json11::Json);
std::function<bool(cli_result_t &)> start_dd(json11::Json);
std::function<bool(cli_result_t &)> start_describe(json11::Json);
std::function<bool(cli_result_t &)> start_fix(json11::Json);
std::function<bool(cli_result_t &)> start_flatten(json11::Json);
@@ -75,8 +78,8 @@ public:
std::function<bool(cli_result_t &)> start_rm_data(json11::Json);
std::function<bool(cli_result_t &)> start_rm_osd(json11::Json);
std::function<bool(cli_result_t &)> start_rm_wildcard(json11::Json);
std::function<bool(cli_result_t &)> start_serve(json11::Json);
std::function<bool(cli_result_t &)> start_status(json11::Json);
std::function<bool(cli_result_t &)> start_dd(json11::Json);
// Should be called like loop_and_wait(start_status(), <completion callback>)
void loop_and_wait(std::function<bool(cli_result_t &)> loop_cb, std::function<void(const cli_result_t &)> complete_cb);
+11 -7
View File
@@ -101,6 +101,16 @@ inode_config_t* cli_tool_t::get_inode_cfg(const std::string & name)
return NULL;
}
void cli_tool_t::parse_api_opts(json11::Json::object & cfg)
{
iodepth = cfg["iodepth"].uint64_value();
if (!iodepth)
iodepth = 32;
parallel_osds = cfg["parallel_osds"].uint64_value();
if (!parallel_osds)
parallel_osds = 4;
}
void cli_tool_t::parse_config(json11::Json::object & cfg)
{
for (auto kv_it = cfg.begin(); kv_it != cfg.end();)
@@ -121,15 +131,10 @@ void cli_tool_t::parse_config(json11::Json::object & cfg)
else
color = isatty(1);
json_output = cfg["json"].bool_value();
iodepth = cfg["iodepth"].uint64_value();
if (!iodepth)
iodepth = 32;
parallel_osds = cfg["parallel_osds"].uint64_value();
if (!parallel_osds)
parallel_osds = 4;
log_level = cfg["log_level"].int64_value();
progress = cfg["progress"].uint64_value() ? true : false;
list_first = cfg["wait_list"].uint64_value() ? true : false;
parse_api_opts(cfg);
}
struct cli_result_looper_t
@@ -153,7 +158,6 @@ void cli_tool_t::loop_and_wait(std::function<bool(cli_result_t &)> loop_cb, std:
ringloop->unregister_consumer(&looper->consumer);
looper->loop_cb = NULL;
looper->complete_cb(looper->result);
ringloop->submit();
delete looper;
return;
}
+428
View File
@@ -0,0 +1,428 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include <signal.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <stdexcept>
#include "cli.h"
#include "cluster_client.h"
#include "epoll_manager.h"
#include "http_client.h"
#include "str_util.h"
#include "json_util.h"
#include "addr_util.h"
struct cli_serve_conn_t
{
int peer_fd = 0;
std::string peer_addr;
http_co_t *co = NULL;
cli_tool_t *p = NULL;
cli_result_t result;
bool keepalive = false;
bool closed = false;
timespec request_time;
std::string request_method;
std::string request_path;
std::string request_body;
std::function<bool(cli_result_t &)> action_cb;
};
struct cli_serve_path_t
{
std::string cmd;
bool allow_get;
};
// Serve vitastor-cli commands over HTTP in JSON format
struct cli_serve_t
{
std::map<std::string, cli_serve_path_t> cmd_paths = {
{"data/delete", {"rm-data", false}},
{"data/describe", {"describe", true}},
{"data/fix", {"fix", false}},
{"data/merge", {"merge-data", false}},
{"image/create", {"create", false}},
{"image/delete", {"rm", false}},
{"image/flatten", {"flatten", false}},
{"image/list", {"ls", true}},
{"image/modify", {"modify", false}},
{"osd/alloc", {"alloc-osd", false}},
{"osd/delete", {"rm-osd", false}},
{"osd/list", {"ls-osd", true}},
{"osd/modify", {"modify-osd", false}},
{"pg/list", {"ls-pgs", true}},
{"pool/create", {"create-pool", false}},
{"pool/delete", {"rm-pool", false}},
{"pool/list", {"pools", true}},
{"pool/modify", {"modify-pool", false}},
{"status", {"status", true}},
};
cli_tool_t *parent = NULL;
json11::Json options;
cli_result_t result;
bool log_body = false;
bool stop = false;
std::vector<std::string> bind_addresses;
int port = 0;
int listen_backlog = 0;
bool ssl = false;
std::vector<int> listen_fds;
http_context_t *http_ctx = NULL;
std::set<cli_serve_conn_t*> connections;
int state = 0;
bool is_done()
{
return state == 100;
}
void loop()
{
if (state == 1)
goto resume_1;
else if (state == 2)
goto resume_2;
else if (state == 100)
return;
if (options["bind_address"].is_string())
bind_addresses = explode(" ", options["bind_address"].string_value(), true);
else
bind_addresses.push_back("127.0.0.1");
port = options["port"].uint64_value();
if (!port)
port = 8080;
else if (port < 0 || port > 65535)
{
result = (cli_result_t){ .err = EINVAL, .text = "HTTP port can't be larger than 65536" };
state = 100;
return;
}
listen_backlog = options["listen_backlog"].uint64_value();
if (!listen_backlog)
listen_backlog = 128;
ssl = json_is_true(options["ssl"]);
if (ssl)
{
std::string ssl_cert = options["ssl_cert"].string_value();
std::string ssl_key = options["ssl_key"].string_value();
std::string ssl_ca = options["ssl_ca"].string_value();
std::string error;
http_ctx = http_context_init(ssl_cert, ssl_key, ssl_ca, ssl_ca != "", error);
if (error != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
}
}
for (auto & bind_address: bind_addresses)
{
int listen_fd = create_and_bind_socket(bind_address, port, listen_backlog, NULL);
fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL, 0) | O_NONBLOCK);
parent->epmgr->set_fd_handler(listen_fd, false, [this](int fd, int events)
{
accept_connections(fd);
});
listen_fds.push_back(listen_fd);
}
resume_1:
if (!stop)
{
state = 1;
return;
}
for (auto conn: connections)
{
http_close(conn->co);
}
resume_2:
// Wait for all connections to finish
if (connections.size() > 0)
{
state = 2;
return;
}
if (http_ctx)
{
http_context_destroy(http_ctx);
http_ctx = NULL;
}
state = 100;
}
void accept_connections(int listen_fd)
{
sockaddr_storage addr;
socklen_t peer_addr_size = sizeof(addr);
int peer_fd;
while ((peer_fd = accept(listen_fd, (sockaddr*)&addr, &peer_addr_size)) >= 0)
{
auto peer_addr_str = addr_to_string(addr);
assert(peer_fd != 0);
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("[%s.%03ju] New connection %d from %s\n", format_datetime(ts.tv_sec).c_str(), (uint64_t)ts.tv_nsec/1000000,
peer_fd, peer_addr_str.c_str());
fcntl(peer_fd, F_SETFL, fcntl(peer_fd, F_GETFL, 0) | O_NONBLOCK);
int one = 1;
setsockopt(peer_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
cli_serve_conn_t *conn = new cli_serve_conn_t;
conn->peer_fd = peer_fd;
conn->peer_addr = peer_addr_str;
conn->co = http_init(parent->epmgr->tfd, http_ctx);
http_serve(conn->co, peer_fd, (http_options_t){ .ssl = ssl }, [this, conn](http_message_t *msg)
{
process_request(conn, msg);
});
connections.insert(conn);
// Try to accept next connection
peer_addr_size = sizeof(addr);
}
if (peer_fd == -1 && errno != EAGAIN)
{
throw std::runtime_error(std::string("accept: ") + strerror(errno));
}
}
int map_to_http(int err, std::string *text)
{
int code = 0;
if (err == EINVAL)
{
code = 400;
if (text)
*text = "Bad Request";
}
else if (err == EOPNOTSUPP)
{
code = 404;
if (text)
*text = "Not Found";
}
else if (err == ENOSYS)
{
code = 405;
if (text)
*text = "Method Not Allowed";
}
else if (err == EAGAIN)
{
code = 409;
if (text)
*text = "Update Conflict";
}
else if (err == ENOTEMPTY || err == EEXIST || err == ENOENT || err == EBUSY)
{
code = 412;
if (text)
*text = "Precondition Failed";
}
else /*if (err == EIO || err == EBADF)*/
{
code = 500;
if (text)
*text = "Internal Server Error";
}
return code;
}
std::string cli_http_response(cli_serve_conn_t *conn)
{
timespec now;
clock_gettime(CLOCK_REALTIME, &now);
int code = 200;
std::string response;
if (conn->result.err)
{
std::string status_line;
code = map_to_http(conn->result.err, &status_line);
response = "HTTP/1.1 "+std::to_string(code)+" "+status_line+"\r\n";
}
else
{
response = "HTTP/1.1 200 OK\r\n";
}
response += (conn->keepalive
? "Connection: keep-alive\r\n"
: "Connection: close\r\n");
std::string body;
if (!conn->result.data.is_null())
{
response += "Content-Type: application/json\r\n";
body = conn->result.data.dump();
}
else
{
response += "Content-Type: text/plain; charset=utf-8\r\n";
body = conn->result.text;
}
response += "Content-Length: "+std::to_string(body.size())+"\r\n\r\n";
response += body;
if (conn->request_method.find("\n") != std::string::npos)
conn->request_method = str_replace(conn->request_method, "\n", "%0a");
if (conn->request_method.find(" ") != std::string::npos)
conn->request_method = str_replace(conn->request_method, " ", "%20");
if (conn->request_path.find("\n") != std::string::npos)
conn->request_path = str_replace(conn->request_path, "\n", "%0a");
if (conn->request_path.find(" ") != std::string::npos)
conn->request_path = str_replace(conn->request_path, " ", "%20");
uint64_t response_time = (now.tv_sec-conn->request_time.tv_sec)*1000 + (now.tv_nsec-conn->request_time.tv_nsec)/1000000;
printf("[%s.%03ju] %s %s %s %d %.03f sec\n",
format_datetime(now.tv_sec).c_str(), (uint64_t)now.tv_nsec/1000000,
conn->peer_addr.c_str(), conn->request_method.c_str(), conn->request_path.c_str(), code,
response_time/1000.0);
if (log_body)
{
if (conn->request_body.find("\n") != std::string::npos)
conn->request_body = str_replace(conn->request_body, "\n", " ");
if (conn->request_body.size())
printf(" %s\n", conn->request_body.c_str());
printf(" %s\n", body.c_str());
}
return response;
}
void process_request(cli_serve_conn_t *conn, http_message_t *msg)
{
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
if (!msg->error.empty())
{
// connection is closed
fprintf(stderr, "[%s.%03ju] Connection %d closed: %s\n", format_datetime(ts.tv_sec).c_str(),
(uint64_t)ts.tv_nsec/1000000, conn->peer_fd, msg->error.c_str());
if (conn->p)
conn->closed = true;
else
{
connections.erase(conn);
http_destroy(conn->co);
delete conn;
}
return;
}
conn->keepalive = msg->headers.find("connection") != msg->headers.end() &&
msg->headers.at("connection") == "keep-alive";
conn->p = new cli_tool_t;
conn->p->iodepth = parent->iodepth;
conn->p->parallel_osds = parent->parallel_osds;
conn->p->json_output = true;
conn->p->ringloop = parent->ringloop;
conn->p->epmgr = parent->epmgr;
conn->p->cli = parent->cli;
conn->p->is_command_line = false;
// Parse request
auto req_line = explode(" ", msg->status_line, true);
if (req_line.size() < 2)
{
if (req_line[0] == "")
req_line[0] = "-";
req_line.push_back("-");
}
conn->request_time = ts;
conn->request_method = std::move(req_line[0]);
conn->request_path = std::move(req_line[1]);
conn->request_body = std::move(msg->body);
auto ctype = msg->headers["content-type"];
if (conn->request_method != "GET" && conn->request_method != "POST")
{
conn->result = { .err = ENOSYS, .text = "Unsupported request method "+conn->request_method };
}
else if (ctype != (conn->request_method == "GET" ? "" : "application/json"))
{
conn->result = { .err = EINVAL, .text = "Unsupported Content-Type: "+ctype+" for "+conn->request_method+" requests" };
}
else
{
auto uri = explode("?", conn->request_path, true);
uri[0] = trim(uri[0], "/");
auto cmd_it = cmd_paths.find(uri[0]);
if (uri[0] == "")
{
std::string text = "Supported APIs:\n\n";
for (auto & pp: cmd_paths)
{
text += (pp.second.allow_get ? "- GET" : "- POST") + (" /" + pp.first) + "\n";
}
conn->result = { .text = text };
}
else if (cmd_it == cmd_paths.end())
{
conn->result = { .err = EOPNOTSUPP, .text = "unknown command: "+uri[0] };
}
else if (conn->request_method == "GET" && !cmd_it->second.allow_get)
{
conn->result = { .err = ENOSYS, .text = "method /"+uri[0]+" only allows POST requests" };
}
else
{
std::string error;
json11::Json::object cfg;
if (conn->request_method == "POST")
{
cfg = json11::Json::parse(conn->request_body, error).object_items();
}
else
{
// Parse URI
cfg = parse_uri_params(uri[1]);
}
if (error != "")
{
conn->result = { .err = EINVAL, .text = "Invalid JSON in body: "+error };
}
else
{
cfg["command"] = json11::Json::array{cmd_it->second.cmd};
conn->p->parse_api_opts(cfg);
conn->action_cb = conn->p->start(cfg, conn->result);
}
}
}
if (!conn->action_cb)
{
http_reply(conn->co, cli_http_response(conn));
delete conn->p;
conn->p = NULL;
return;
}
conn->p->loop_and_wait(conn->action_cb, [this, conn](const cli_result_t & r)
{
conn->result = r;
conn->action_cb = NULL;
delete conn->p;
conn->p = NULL;
if (!conn->closed)
http_reply(conn->co, cli_http_response(conn));
else
{
connections.erase(conn);
http_destroy(conn->co);
delete conn;
}
});
}
};
std::function<bool(cli_result_t &)> cli_tool_t::start_serve(json11::Json cfg)
{
auto server = new cli_serve_t();
server->parent = this;
server->options = cfg;
return [server](cli_result_t & result)
{
server->loop();
if (server->is_done())
{
result = server->result;
delete server;
return true;
}
return false;
};
}
+34
View File
@@ -1,6 +1,7 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include "str_util.h"
#include "json_util.h"
std::map<std::string, std::string> json_to_string_map(const json11::Json::object & config)
@@ -49,3 +50,36 @@ std::string implode(const std::string & sep, json11::Json array)
}
return res;
}
static void json_array_add(json11::Json & to, json11::Json item)
{
if (to.is_null())
to = item;
else if (to.is_array())
{
auto a = to.array_items();
a.push_back(item);
to = a;
}
else
{
json11::Json::array a = {to};
a.push_back(item);
to = a;
}
}
json11::Json::object parse_uri_params(const std::string & params)
{
json11::Json::object obj;
auto list = explode("&", params, true);
for (auto & param: list)
{
auto pos = param.find("=");
if (pos != std::string::npos)
json_array_add(obj[urldecode(param.substr(0, pos))], urldecode(param.substr(pos+1)));
else
json_array_add(obj[urldecode(param)], true);
}
return obj;
}
+1
View File
@@ -14,5 +14,6 @@ std::map<std::string, std::string> json_to_string_map(const json11::Json::object
bool json_is_true(const json11::Json & val);
bool json_is_false(const json11::Json & val);
std::string implode(const std::string & sep, json11::Json array);
json11::Json::object parse_uri_params(const std::string & params);
#pragma GCC visibility pop
+31
View File
@@ -503,3 +503,34 @@ bool is_zero(void *buf, size_t size)
}
return true;
}
char hextochar(char h)
{
if (h >= 'a' && h <= 'f')
return h-'a'+10;
else if (h >= 'A' && h <= 'F')
return h-'A'+10;
else if (h >= '0' && h <= '9')
return h-'0';
return 0;
}
std::string urldecode(const std::string & orig)
{
size_t len = orig.size();
std::string res;
res.reserve(len);
for (size_t i = 0; i < len; i++)
{
if (orig[i] == '+')
res.push_back(' ');
else if (orig[i] == '%' && i < len-2)
{
res.push_back(hextochar(orig[i+1])*16 + hextochar(orig[i+2]));
i += 2;
}
else
res.push_back(orig[i]);
}
return res;
}
+1
View File
@@ -34,5 +34,6 @@ std::string addslashes(const std::string & str, const char *toescape = "\\\"");
std::string realpath_str(std::string path, bool nofail = true);
std::string format_datetime(uint64_t unixtime);
bool is_zero(void *buf, size_t size);
std::string urldecode(const std::string & orig);
#pragma GCC visibility pop