From f544fc6568a933d1c8000979ea2b93e26266ef7b Mon Sep 17 00:00:00 2001 From: Michael Furmur Date: Tue, 15 May 2018 22:20:05 +0300 Subject: [PATCH] add http server with status page for prometheus. show data received from nodes jsonRPC via SCTP --- cmake/FindEvent.cmake | 23 ++ server/CMakeLists.txt | 7 +- server/src/HttpServer.cpp | 211 ++++++++++++ server/src/HttpServer.h | 67 ++++ server/src/SctpServer.cpp | 211 +++++++++--- server/src/SctpServer.h | 64 ++-- server/src/cJSON.c | 610 ++++++++++++++++++++++++++++++++++ server/src/cJSON.h | 143 ++++++++ server/src/cfg.cpp | 2 - server/src/mgmt_server.cpp | 115 ++++++- server/src/mgmt_server.h | 11 +- server/src/opts/daemon_opts.h | 10 + 12 files changed, 1407 insertions(+), 67 deletions(-) create mode 100644 cmake/FindEvent.cmake create mode 100644 server/src/HttpServer.cpp create mode 100644 server/src/HttpServer.h create mode 100644 server/src/cJSON.c create mode 100644 server/src/cJSON.h diff --git a/cmake/FindEvent.cmake b/cmake/FindEvent.cmake new file mode 100644 index 0000000..0df0d26 --- /dev/null +++ b/cmake/FindEvent.cmake @@ -0,0 +1,23 @@ +#.rst: +# FindEvent +# -------- +# +# Find Event +# +# Find libevent headers and libraries. +# +# :: +# +# EVENT_LIBRARIES - List of libraries when using libevent. +# EVENT_FOUND - True if libevent found. +# EVENT_VERSION - Version of found libevent + +find_package(PkgConfig REQUIRED) +pkg_check_modules(EVENT libevent) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS( + EVENT + REQUIRED_VARS EVENT_LIBRARIES + VERSION_VAR EVENT_VERSION) + diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index b739998..a8a7e14 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -6,20 +6,23 @@ find_package(Threads REQUIRED) find_package(NanoMsg REQUIRED) find_package(Confuse REQUIRED) find_package(SCTP REQUIRED) +find_package(Event REQUIRED) include_directories(${CMAKE_SOURCE_DIR}/format/src ${CMAKE_CURRENT_SOURCE_DIR}/src) include_directories(${PB_INCLUDE_DIRS}) +include_directories(${EVENT_INCLUDE_DIRS}) configure_file(src/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/version.h) -file(GLOB_RECURSE SRCS src/*.cpp) +file(GLOB_RECURSE SRCS src/*.cpp src/*.c) add_executable (yeti_management ${SRCS}) target_link_libraries(yeti_management ${CMAKE_THREAD_LIBS_INIT} ${NanoMsg_LIBRARIES} ${Confuse_LIBRARIES} ${SCTP_LIBRARIES} - ${PB_LIBRARIES}) + ${PB_LIBRARIES} + ${EVENT_LIBRARIES}) install(TARGETS yeti_management DESTINATION ${RUNTIME_DIR}) diff --git a/server/src/HttpServer.cpp b/server/src/HttpServer.cpp new file mode 100644 index 0000000..b94a9ea --- /dev/null +++ b/server/src/HttpServer.cpp @@ -0,0 +1,211 @@ +#include "HttpServer.h" + +#include "log.h" + +#include +#include +#include +#include +#include +#include + +#include + +static void +stop_event_cb(evutil_socket_t, short, void *arg) +{ + struct event_base *ev_base = (struct event_base *)arg; + event_base_loopbreak(ev_base); +} + +HttpServer::HttpServer() + : ev_base(nullptr), + ev_http(nullptr), + ev_http_handle(nullptr) +{} + +HttpServer::~HttpServer() +{ + if(ev_base) + event_base_free(ev_base); +} + +int HttpServer::http_init(cfg_t *http_cfg) +{ + ev_base = event_base_new(); + if (!ev_base) { + err("couldn't create an event_base"); + return 1; + } + + ev_http = evhttp_new(ev_base); + if (!ev_http) { + err("couldn't create evhttp"); + return 1; + } + + evhttp_set_allowed_methods(ev_http, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_HEAD); + //evhttp_set_default_content_type(ev_http,"application/json"); + +#if 0 + evhttp_set_cb( + ev_http, "/rpc", + [](struct evhttp_request *req, void *arg) { + static_cast(arg)->rpc_request_cb(req); + }, this); +#endif + + evhttp_set_cb( + ev_http, "/status", + [](struct evhttp_request *req, void *arg) { + static_cast(arg)->status_request_cb(req); + }, this); + + char *address = cfg_getstr(http_cfg,"address"); + int port = cfg_getint(http_cfg,"port"); + + ev_http_handle = evhttp_bind_socket_with_handle(ev_http, address, port); + if(!ev_http_handle) { + err("couldn't bind http server to %s:%d",address,port); + return 1; + } + + dbg("bind socket to: %s:%d",address,port); + + int flags = EFD_NONBLOCK | EFD_SEMAPHORE; + if((http_queue_event_fd = eventfd(0, flags)) == -1) + err("failed to create eventfd"); + + struct event * http_queue_event = + event_new(ev_base, http_queue_event_fd, EV_READ | EV_PERSIST, + [](evutil_socket_t, short, void *arg) { + static_cast(arg)->on_http_queue_event_cb(); + }, this); + + if(!http_queue_event) + err("failed to create http queue event"); + + event_add(http_queue_event, NULL); + + return 0; +} + +void HttpServer::http_start() +{ + std::thread t([this] { http_run(); }); + http_thread.swap(t); +} + +void HttpServer::http_stop() +{ + /*uint64_t u = 1; + write(http_stop_fd, &u, sizeof(uint64_t));*/ + http_post_event(new HttpEventTerminate()); + http_thread.join(); +} + +void HttpServer::http_run() +{ + pthread_setname_np(__gthread_self(), "http-server"); + event_base_dispatch(ev_base); + dbg("HTTP server stopped"); +} + +#if 0 +void HttpServer::rpc_request_cb(struct evhttp_request *req) +{ + struct evkeyvalq *headers; + struct evkeyval *header; + struct evbuffer *buf; + + dbg("received /rpc request"); + + if(EVHTTP_REQ_HEAD==evhttp_request_get_command(req)) { + evhttp_send_reply(req, HTTP_OK, "OK", nullptr); + return; + } + + headers = evhttp_request_get_input_headers(req); + /*for (header = headers->tqh_first; header; + header = header->next.tqe_next) { + dbg(" %s: %s\n", header->key, header->value); + }*/ + + buf = evhttp_request_get_input_buffer(req); + + dbg("Input data: <<<"); + while (evbuffer_get_length(buf)) { + int n; + char cbuf[128]; + n = evbuffer_remove(buf, cbuf, sizeof(cbuf)); + if (n > 0) + dbg("%d: %.*s",n,n,cbuf); + } + dbg(">>>"); + + struct evbuffer *out = evhttp_request_get_output_buffer(req); + evbuffer_add_printf(out, "{ \"test\": 2 }\n"); + evhttp_send_reply(req, HTTP_OK, "OK", out); +} +#endif + +void HttpServer::status_request_cb(struct evhttp_request *req) +{ + //dbg("received /status request"); + + if(EVHTTP_REQ_HEAD==evhttp_request_get_command(req)) { + evhttp_send_reply(req, HTTP_OK, "OK", nullptr); + return; + } + + evhttp_send_reply_start(req, HTTP_OK, "OK"); + + on_http_stats_request(req); +} + +void HttpServer::http_post_event(HttpEventBase *ev) +{ + uint64_t u = 1; + + std::lock_guard lk(http_events_queue_mutex); + + http_events_queue.push(ev); + write(http_queue_event_fd, &u, sizeof(uint64_t)); +} + +void HttpServer::on_http_queue_event_cb() +{ + uint64_t u; + + ::read(http_queue_event_fd, &u, sizeof(uint64_t)); + + std::lock_guard lk(http_events_queue_mutex); + + while(!http_events_queue.empty()) { + HttpEventBase *ev = http_events_queue.front(); + http_events_queue.pop(); + http_process(ev); + delete ev; + } +} + +#define ON_EVENT_TYPE(type) if(type *e = dynamic_cast(ev)) + +void HttpServer::http_process(HttpEventBase *ev) +{ + ON_EVENT_TYPE(HttpEventTerminate) { + event_base_loopbreak(ev_base); + return; + } + + ON_EVENT_TYPE(HttpEventReply) { + /*evhttp_send_reply(e->req, 200, "OK", e->reply_body);*/ + if(e->reply_body) + evhttp_send_reply_chunk(e->req,e->reply_body); + evhttp_send_reply_end(e->req); + if(e->reply_body) + evbuffer_free(e->reply_body); + return; + } +} + diff --git a/server/src/HttpServer.h b/server/src/HttpServer.h new file mode 100644 index 0000000..42811e2 --- /dev/null +++ b/server/src/HttpServer.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +struct HttpEventBase { + virtual ~HttpEventBase() = default; +}; + +struct HttpEventTerminate + : HttpEventBase +{ }; + +struct HttpEventReply + : HttpEventBase +{ + struct evhttp_request *req; + struct evbuffer *reply_body; + HttpEventReply(struct evhttp_request *req, struct evbuffer *reply_body) + : req(req), + reply_body(reply_body) + { } +}; + +class HttpServer +{ + struct event_base *ev_base; + struct evhttp *ev_http; + struct evhttp_bound_socket *ev_http_handle; + + std::thread http_thread; + + std::queue http_events_queue; + std::mutex http_events_queue_mutex; + int http_queue_event_fd; + + public: + HttpServer(); + virtual ~HttpServer(); + + int http_init(cfg_t *http_cfg); + + void http_start(); + void http_stop(); + void http_run(); + + void http_post_event(HttpEventBase *ev); + void http_process(HttpEventBase *ev); + void on_http_queue_event_cb(); + + //void rpc_request_cb(struct evhttp_request *req); + void status_request_cb(struct evhttp_request *req); + + virtual void on_http_stats_request(struct evhttp_request *req) = 0; +}; + diff --git a/server/src/SctpServer.cpp b/server/src/SctpServer.cpp index 91ba5e6..18096fa 100644 --- a/server/src/SctpServer.cpp +++ b/server/src/SctpServer.cpp @@ -6,9 +6,12 @@ #include #include #include +#include #include "YetiEvent.pb.h" +#include "cJSON.h" + #include using std::vector; @@ -35,7 +38,8 @@ static void longlong2timespec(struct timespec &ts,unsigned long long msec) SctpServer::SctpServer() : epoll_fd(-1), sctp_fd(-1), - state(Closed) + state(Closed), + jsonrpc_cseq(1) {} SctpServer::~SctpServer() @@ -101,20 +105,12 @@ int SctpServer::sctp_configure(cfg_t *cfg) if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sctp_fd, &ev) == -1) sctp_sys_err("epoll_ctl(EPOLL_CTL_ADD)"); - /*if(0!=server_connection.init(epoll_fd,a)) { - err("failed to init sctp server connection"); - return -1; - }*/ - - dbg("SctpBus configured"); - return 0; } int SctpServer::sctp_init(cfg_t *cfg) { dbg_func(); - //AmPlugIn::registerDIInterface(getName(),this); if((epoll_fd = epoll_create(10)) == -1) { throw std::string("epoll_create failed"); @@ -150,8 +146,6 @@ int SctpServer::sctp_init(cfg_t *cfg) { if(-1==sctp_configure(cfg)) return -1; - dbg("SctpBus initialized"); - return 0; } @@ -166,6 +160,7 @@ void SctpServer::run() int ret,f; bool running; uint64_t u; + struct timeval now; struct epoll_event events[EPOLL_MAX_EVENTS]; pthread_setname_np(__gthread_self(), "sctp-bus"); @@ -185,7 +180,8 @@ void SctpServer::run() struct epoll_event &e = events[n]; f = e.data.fd; if(f==-timer_fd) { - on_timer(); + gettimeofday(&now, nullptr); + on_timer(now); ::read(timer_fd, &u, sizeof(uint64_t)); /*} else if(f== -queue_fd()){ clear_pending(); @@ -206,29 +202,31 @@ void SctpServer::run() close(stop_event_fd); close(timer_fd); - dbg("SctpBus stopped"); + dbg("SCTP server stopped"); } -void SctpServer::on_stop() +void SctpServer::sctp_stop() { uint64_t u = 1; write(stop_event_fd, &u, sizeof(uint64_t)); _t.join(); } -void SctpServer::on_timer() +void SctpServer::on_timer(struct timeval &now) { //dbg("on_timer"); +#if 0 clients_mutex.lock(); for(const auto &client : clients) { const client_info &info = client.second; - dbg("assoc_id: %d, remote_host: %s, remote_port: %d, " + /*dbg("assoc_id: %d, remote_host: %s, remote_port: %d, " "node_id: %d, events_received: %ld", client.first, info.host.c_str(), info.port, - info.node_id, info.events_received); + info.node_id, info.events_received);*/ } clients_mutex.unlock(); +#endif } void SctpServer::handle_notification(const sockaddr_storage &from) @@ -373,13 +371,12 @@ int SctpServer::process(uint32_t events) return -1; } - dbg("RECV sctp_bus event %d:%s -> %d:%s/%d, seq: %ld", + dbg("RECV sctp_bus event %d:%s -> %d:%s/%d", e.src_node_id(), e.src_session_id().c_str(), e.dst_node_id(), e.dst_session_id().c_str(), - sinfo.sinfo_assoc_id, - e.has_sequence()? e.sequence() : -1); + sinfo.sinfo_assoc_id); clients_mutex.lock(); ClientsMap::iterator it = clients.find(sinfo.sinfo_assoc_id); @@ -390,41 +387,61 @@ int SctpServer::process(uint32_t events) return -1; } - client_info &cinfo = it->second; + struct client_info &cinfo = it->second; cinfo.node_id = e.src_node_id(); cinfo.events_received++; - clients_mutex.unlock(); + if(e.src_session_id()=="jsonrpc") { + onIncomingJsonPDU(sinfo.sinfo_assoc_id,cinfo,e); + } else { + onIncomingYetiPDU(sinfo.sinfo_assoc_id,cinfo,e); + } - onIncomingPDU(sinfo.sinfo_assoc_id,e); + clients_mutex.unlock(); return 0; } -void SctpServer::onIncomingPDU(sctp_assoc_t assoc_id, const SctpBusPDU &e) +void SctpServer::onIncomingYetiPDU(sctp_assoc_t assoc_id, struct client_info &cinfo, const SctpBusPDU &e) { - //dbg("on incoming PDU"); + dbg("on incoming yeti PDU"); YetiEvent y_ev; if(!y_ev.ParseFromString(e.payload())) { err("failed to deserialize SctpBusPDU payload with size: %ld",e.payload().size()); return; } - dbg("got yeti event: %d",y_ev.data_case()); - switch(y_ev.data_case()) { - case YetiEvent::kCfgRequest: - process_sctp_cfg_request(assoc_id,e,y_ev.cfg_request()); - break; - case YetiEvent::kJson: - dbg("got json event: %s", - y_ev.json().c_str()); - break; - default: - err("got unsupported yeti event: %d",y_ev.data_case()); + + if(e.type()==SctpBusPDU::REQUEST) { + switch(y_ev.data_case()) { + case YetiEvent::kCfgRequest: + process_sctp_cfg_request(assoc_id,e,y_ev.cfg_request()); + break; + default: + err("got unsupported yeti request event: %d",y_ev.data_case()); + } + } else if(e.type()==SctpBusPDU::REPLY) { + switch(y_ev.data_case()) { + case YetiEvent::kJson: + //dbg("got json reply"); + process_sctp_json_reply(assoc_id,cinfo,y_ev.json()); + break; + default: + err("got unsupported yeti reply event: %d",y_ev.data_case()); + } } } +void SctpServer::onIncomingJsonPDU(sctp_assoc_t assoc_id, struct client_info &cinfo, const SctpBusPDU &e) +{ + if(e.type()==SctpBusPDU::REQUEST) { + dbg("ignore unexpected request from jsonrpc session"); + return; + } + process_sctp_json_reply(assoc_id,cinfo, e.payload()); +} + static void fill_sctp_reply(SctpBusPDU &reply, const SctpBusPDU &req) { reply.set_type(SctpBusPDU::REPLY); @@ -438,7 +455,6 @@ static void fill_sctp_reply(SctpBusPDU &reply, const SctpBusPDU &req) void SctpServer::process_sctp_cfg_request(sctp_assoc_t assoc_id, const SctpBusPDU &e, const CfgRequest &req) { SctpBusPDU reply; - //string cfg_reply; YetiEvent y; string reply_payload; @@ -469,6 +485,127 @@ void SctpServer::process_sctp_cfg_request(sctp_assoc_t assoc_id, const SctpBusPD } } +static inline void serialize_reply_for_prometheus( + cJSON *j, const std::string &prefix, + const std::string &label, std::string &out, int level = 0) +{ + char *s; + + //dbg("%p %s %s", j,prefix.c_str(),label.c_str()); + + switch(j->type) { + case cJSON_Object: { + string new_prefix = level ? (prefix+j->string+"_") : string(); + for(cJSON *c=j->child; c; c = c->next) { + serialize_reply_for_prometheus(c,new_prefix,label,out,level+1); + } + } break; + case cJSON_Number: + s = cJSON_Print(j); + out+=prefix+j->string+label+s+'\n'; + free(s); + break; + default: + break; + } +} + +void SctpServer::process_sctp_json_reply(sctp_assoc_t assoc_id, struct client_info &cinfo, const string &json) +{ + //dbg("process sctp json reply: %s",json.c_str()); + + cJSON *j = cJSON_Parse(json.c_str()); + if(!j) { + err("failed to parse jsonrpc reply"); + return; + } + + if(j->type != cJSON_Object) { + err("unexpected json type in jsonrpc reply: %d",j->type); + cJSON_Delete(j); + return; + } + + cJSON *json_id = cJSON_GetObjectItem(j,"id"); + if(!json_id) { + err("no id in json response"); + cJSON_Delete(j); + return; + } + + int id; + switch(json_id->type) { + case cJSON_String: + try { + id = std::stoi(json_id->valuestring); + } catch(...) { + err("failed to cast id: '%s' to integer",json_id->valuestring); + cJSON_Delete(j); + return; + } + break; + case cJSON_Number: + id = json_id->type; + break; + default: + err("unexpected id type in json response: %d",json_id->type); + cJSON_Delete(j); + return; + } + + //dbg("json_id: %d",id); + + jsonrpc_requests_mutex.lock(); + auto it = jsonrpc_requests_by_cseq.find(id); + if(it==jsonrpc_requests_by_cseq.end()) { + dbg("id %d is not found in sent requests. ignore reply",id); + jsonrpc_requests_mutex.unlock(); + cJSON_Delete(j); + return; + } + + json_request_info &info = it->second; + info.sent_sctp_requests_assoc_id.erase(assoc_id); + + //serialize collected replies to the prometheus format + // https://prometheus.io/docs/instrumenting/writing_exporters/ + //info.result.reserve(info.result.size() + json.size()); + if(cJSON *result = cJSON_GetObjectItem(j,"result")) { + string label = "{node_id=" + std::to_string(cinfo.node_id) + "} "; + serialize_reply_for_prometheus(result,string(),label,info.result); + } + + cJSON_Delete(j); + + if(process_collected_json_replies(info,false)) + jsonrpc_requests_by_cseq.erase(it); + + jsonrpc_requests_mutex.unlock(); +} + +void SctpServer::broadcast_json_request_unsafe(SctpBusPDU &request, struct json_request_info &req_info) +{ + string buf; + for(auto &client: clients) { + client_info &cinfo = client.second; + if(-1==cinfo.node_id) { + dbg("not initialized association. skip it"); + continue; + } + cinfo.cseq++; + request.set_sequence(cinfo.cseq); + request.set_dst_node_id(cinfo.node_id); + request.SerializeToString(&buf); + if(buf.size()==send_to_assoc(client.first,(void *)buf.data(),buf.size())) { + //sent to assoc. add assoc to the waiting list + req_info.sent_sctp_requests_assoc_id.insert(client.first); + } else { + dbg("failed to send to the assoc: %d",client.first); + } + } + //process_collected_json_replies +} + int SctpServer::send_to_assoc(int assoc_id, void *payload, size_t payload_len) { struct sctp_sndrcvinfo sinfo = { }; diff --git a/server/src/SctpServer.h b/server/src/SctpServer.h index f53d505..9a0cc1b 100644 --- a/server/src/SctpServer.h +++ b/server/src/SctpServer.h @@ -15,6 +15,7 @@ #include #include #include +#include #include using std::string; @@ -23,22 +24,6 @@ using std::map; #define STATIC_BUFFER_SIZE USHRT_MAX class SctpServer { - struct client_info { - string host; - short unsigned int port; - int node_id; - unsigned long events_received; - client_info(const string &host, short unsigned int port) - : host(host), - port(port), - node_id(-1), - events_received(0) - {} - }; - typedef std::unordered_map ClientsMap; - ClientsMap clients; - std::mutex clients_mutex; - sockaddr_storage addr; char payload[STATIC_BUFFER_SIZE]; @@ -60,10 +45,6 @@ class SctpServer int process(uint32_t events); void handle_notification(const sockaddr_storage &from); - void onIncomingPDU(sctp_assoc_t assoc_id, const SctpBusPDU &e); - void process_sctp_cfg_request(sctp_assoc_t assoc_id, const SctpBusPDU &e, const CfgRequest &req); - int send_to_assoc(int assoc_id, void *payload, size_t payload_len); - virtual void create_reply(CfgResponse &reply, const CfgRequest &req) = 0; virtual void create_error_reply(CfgResponse &reply,int code, std::string description) = 0; @@ -75,13 +56,52 @@ class SctpServer c(code), e(error) {} }; + struct client_info { + string host; + short unsigned int port; + int node_id; + unsigned long cseq; + unsigned long events_received; + client_info(const string &host, short unsigned int port) + : host(host), + port(port), + node_id(-1), + events_received(0), + cseq(0) + {} + }; + typedef std::unordered_map ClientsMap; + ClientsMap clients; + std::mutex clients_mutex; + + struct json_request_info { + struct timeval expire_at; + struct evhttp_request *req; + unsigned int cseq; + std::unordered_set sent_sctp_requests_assoc_id; + std::string result; + }; + unsigned int jsonrpc_cseq; + std::unordered_map jsonrpc_requests_by_cseq; + std::mutex jsonrpc_requests_mutex; + + void broadcast_json_request_unsafe(SctpBusPDU &request, struct json_request_info &req_info); + virtual bool process_collected_json_replies(json_request_info &req_info, bool timeout) = 0; + + private: + void onIncomingYetiPDU(sctp_assoc_t assoc_id, struct client_info &cinfo, const SctpBusPDU &e); + void onIncomingJsonPDU(sctp_assoc_t assoc_id, struct client_info &cinfo, const SctpBusPDU &e); + void process_sctp_cfg_request(sctp_assoc_t assoc_id, const SctpBusPDU &e, const CfgRequest &req); + void process_sctp_json_reply(sctp_assoc_t assoc_id, struct client_info &cinfo, const string &json); + int send_to_assoc(int assoc_id, void *payload, size_t payload_len); + public: SctpServer(); ~SctpServer(); int sctp_init(cfg_t *cfg); void sctp_start(); - void on_stop(); - void on_timer(); + void sctp_stop(); + virtual void on_timer(struct timeval &now); }; diff --git a/server/src/cJSON.c b/server/src/cJSON.c new file mode 100644 index 0000000..9b9b2d9 --- /dev/null +++ b/server/src/cJSON.c @@ -0,0 +1,610 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +#include +#include +#include +#include +#include +#include +#include +#include "cJSON.h" + +static const char *ep; + +const char *cJSON_GetErrorPtr(void) {return ep;} + +static int cJSON_strcasecmp(const char *s1,const char *s2) +{ + if (!s1) return (s1==s2)?0:1; + if (!s2) return 1; + + for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) + if(*s1 == 0) return 0; + + return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); +} + +static void *(*cJSON_malloc)(size_t sz) = malloc; +static void (*cJSON_free)(void *ptr) = free; + +static char* cJSON_strdup(const char* str) +{ + size_t len; + char* copy; + + len = strlen(str) + 1; + if (!(copy = (char*)cJSON_malloc(len))) return 0; + memcpy(copy,str,len); + return copy; +} + +void cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (!hooks) { /* Reset hooks */ + cJSON_malloc = malloc; + cJSON_free = free; + return; + } + + cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc; + cJSON_free = (hooks->free_fn)?hooks->free_fn:free; +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(void) +{ + cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); + if (node) memset(node,0,sizeof(cJSON)); + return node; +} + +/* Delete a cJSON structure. */ +void cJSON_Delete(cJSON *c) +{ + cJSON *next; + while (c) + { + next=c->next; + if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); + if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); + if (c->string) cJSON_free(c->string); + cJSON_free(c); + c=next; + } +} + +/* Parse the input text to generate a number, and populate the result into item. */ +static const char *parse_number(cJSON *item,const char *num) +{ + double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; + + if (*num=='-') sign=-1,num++; /* Has sign? */ + if (*num=='0') num++; /* is zero */ + if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ + if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */ + if (*num=='e' || *num=='E') /* Exponent? */ + { num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */ + while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ + } + + n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ + + item->valuedouble=n; + item->valueint=(int)n; + item->type=cJSON_Number; + return num; +} + +/* Render the number nicely from the given item into a string. */ +static char *print_number(cJSON *item) +{ + char *str; + double d=item->valuedouble; + if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN) + { + str=(char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ + if (str) sprintf(str,"%d",item->valueint); + } + else + { + str=(char*)cJSON_malloc(64); /* This is a nice tradeoff. */ + if (str) + { + sprintf(str,"%f",d); + /*if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,"%.0f",d); + else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d); + else sprintf(str,"%f",d);*/ + } + } + return str; +} + +static unsigned parse_hex4(const char *str) +{ + unsigned h=0; + if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; + h=h<<4;str++; + if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; + h=h<<4;str++; + if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; + h=h<<4;str++; + if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; + return h; +} + +/* Parse the input text into an unescaped cstring, and populate item. */ +static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; +static const char *parse_string(cJSON *item,const char *str) +{ + const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2; + if (*str!='\"') {ep=str;return 0;} /* not a string! */ + + while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */ + + out=(char*)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */ + if (!out) return 0; + + ptr=str+1;ptr2=out; + while (*ptr!='\"' && *ptr) + { + if (*ptr!='\\') *ptr2++=*ptr++; + else + { + ptr++; + switch (*ptr) + { + case 'b': *ptr2++='\b'; break; + case 'f': *ptr2++='\f'; break; + case 'n': *ptr2++='\n'; break; + case 'r': *ptr2++='\r'; break; + case 't': *ptr2++='\t'; break; + case 'u': /* transcode utf16 to utf8. */ + uc=parse_hex4(ptr+1);ptr+=4; /* get the unicode char. */ + + if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; /* check for invalid. */ + + if (uc>=0xD800 && uc<=0xDBFF) /* UTF16 surrogate pairs. */ + { + if (ptr[1]!='\\' || ptr[2]!='u') break; /* missing second-half of surrogate. */ + uc2=parse_hex4(ptr+3);ptr+=6; + if (uc2<0xDC00 || uc2>0xDFFF) break; /* invalid second-half of surrogate. */ + uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF)); + } + + len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len; + + switch (len) { + case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 1: *--ptr2 =(uc | firstByteMark[len]); + } + ptr2+=len; + break; + default: *ptr2++=*ptr; break; + } + ptr++; + } + } + *ptr2=0; + if (*ptr=='\"') ptr++; + item->valuestring=out; + item->type=cJSON_String; + return ptr; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static char *print_string_ptr(const char *str) +{ + const char *ptr;char *ptr2,*out;int len=0;unsigned char token; + + if (!str) return cJSON_strdup(""); + ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;} + + out=(char*)cJSON_malloc(len+3); + if (!out) return 0; + + ptr2=out;ptr=str; + *ptr2++='\"'; + while (*ptr) + { + if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++; + else + { + *ptr2++='\\'; + switch (token=*ptr++) + { + case '\\': *ptr2++='\\'; break; + case '\"': *ptr2++='\"'; break; + case '\b': *ptr2++='b'; break; + case '\f': *ptr2++='f'; break; + case '\n': *ptr2++='n'; break; + case '\r': *ptr2++='r'; break; + case '\t': *ptr2++='t'; break; + default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */ + } + } + } + *ptr2++='\"';*ptr2++=0; + return out; +} +/* Invote print_string_ptr (which is useful) on an item. */ +static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);} + +/* Predeclare these prototypes. */ +static const char *parse_value(cJSON *item,const char *value); +static char *print_value(cJSON *item,int depth,int fmt); +static const char *parse_array(cJSON *item,const char *value); +static char *print_array(cJSON *item,int depth,int fmt); +static const char *parse_object(cJSON *item,const char *value); +static char *print_object(cJSON *item,int depth,int fmt); + +/* Utility to jump whitespace and cr/lf */ +static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;} + +/* Parse an object - create a new root, and populate. */ +cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated) +{ + const char *end=0; + cJSON *c=cJSON_New_Item(); + ep=0; + if (!c) return 0; /* memory fail */ + + end=parse_value(c,skip(value)); + if (!end) {cJSON_Delete(c);return 0;} /* parse failure. ep is set. */ + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}} + if (return_parse_end) *return_parse_end=end; + return c; +} +/* Default options for cJSON_Parse */ +cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);} + +/* Render a cJSON item/entity/structure to text. */ +char *cJSON_Print(cJSON *item) {return print_value(item,0,1);} +char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0);} + +/* Parser core - when encountering text, process appropriately. */ +static const char *parse_value(cJSON *item,const char *value) +{ + if (!value) return 0; /* Fail on null. */ + if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } + if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; } + if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; } + if (*value=='\"') { return parse_string(item,value); } + if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } + if (*value=='[') { return parse_array(item,value); } + if (*value=='{') { return parse_object(item,value); } + + ep=value;return 0; /* failure. */ +} + +/* Render a value to text. */ +static char *print_value(cJSON *item,int depth,int fmt) +{ + char *out=0; + if (!item) return 0; + switch ((item->type)&255) + { + case cJSON_NULL: out=cJSON_strdup("null"); break; + case cJSON_False: out=cJSON_strdup("false");break; + case cJSON_True: out=cJSON_strdup("true"); break; + case cJSON_Number: out=print_number(item);break; + case cJSON_String: out=print_string(item);break; + case cJSON_Array: out=print_array(item,depth,fmt);break; + case cJSON_Object: out=print_object(item,depth,fmt);break; + } + return out; +} + +/* Build an array from input text. */ +static const char *parse_array(cJSON *item,const char *value) +{ + cJSON *child; + if (*value!='[') {ep=value;return 0;} /* not an array! */ + + item->type=cJSON_Array; + value=skip(value+1); + if (*value==']') return value+1; /* empty array. */ + + item->child=child=cJSON_New_Item(); + if (!item->child) return 0; /* memory fail */ + value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */ + if (!value) return 0; + + while (*value==',') + { + cJSON *new_item; + if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ + child->next=new_item;new_item->prev=child;child=new_item; + value=skip(parse_value(child,skip(value+1))); + if (!value) return 0; /* memory fail */ + } + + if (*value==']') return value+1; /* end of array */ + ep=value;return 0; /* malformed. */ +} + +/* Render an array to text */ +static char *print_array(cJSON *item,int depth,int fmt) +{ + char **entries; + char *out=0,*ptr,*ret;int len=5; + cJSON *child=item->child; + int numentries=0,i=0,fail=0; + + /* How many entries in the array? */ + while (child) numentries++,child=child->next; + /* Explicitly handle numentries==0 */ + if (!numentries) + { + out=(char*)cJSON_malloc(3); + if (out) strcpy(out,"[]"); + return out; + } + /* Allocate an array to hold the values for each */ + entries=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!entries) return 0; + memset(entries,0,numentries*sizeof(char*)); + /* Retrieve all the results: */ + child=item->child; + while (child && !fail) + { + ret=print_value(child,depth+1,fmt); + entries[i++]=ret; + if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1; + child=child->next; + } + + /* If we didn't fail, try to malloc the output string */ + if (!fail) out=(char*)cJSON_malloc(len); + /* If that fails, we fail. */ + if (!out) fail=1; + + /* Handle failure. */ + if (fail) + { + for (i=0;itype=cJSON_Object; + value=skip(value+1); + if (*value=='}') return value+1; /* empty array. */ + + item->child=child=cJSON_New_Item(); + if (!item->child) return 0; + value=skip(parse_string(child,skip(value))); + if (!value) return 0; + child->string=child->valuestring;child->valuestring=0; + if (*value!=':') {ep=value;return 0;} /* fail! */ + value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ + if (!value) return 0; + + while (*value==',') + { + cJSON *new_item; + if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ + child->next=new_item;new_item->prev=child;child=new_item; + value=skip(parse_string(child,skip(value+1))); + if (!value) return 0; + child->string=child->valuestring;child->valuestring=0; + if (*value!=':') {ep=value;return 0;} /* fail! */ + value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ + if (!value) return 0; + } + + if (*value=='}') return value+1; /* end of array */ + ep=value;return 0; /* malformed. */ +} + +/* Render an object to text. */ +static char *print_object(cJSON *item,int depth,int fmt) +{ + char **entries=0,**names=0; + char *out=0,*ptr,*ret,*str;int len=7,i=0,j; + cJSON *child=item->child; + int numentries=0,fail=0; + /* Count the number of entries. */ + while (child) numentries++,child=child->next; + /* Explicitly handle empty object case */ + if (!numentries) + { + out=(char*)cJSON_malloc(fmt?depth+4:3); + if (!out) return 0; + ptr=out;*ptr++='{'; + if (fmt) {*ptr++='\n';for (i=0;ichild;depth++;if (fmt) len+=depth; + while (child) + { + names[i]=str=print_string_ptr(child->string); + entries[i++]=ret=print_value(child,depth,fmt); + if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; + child=child->next; + } + + /* Try to allocate the output string */ + if (!fail) out=(char*)cJSON_malloc(len); + if (!out) fail=1; + + /* Handle failure */ + if (fail) + { + for (i=0;ichild;int i=0;while(c)i++,c=c->next;return i;} +cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;} +cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} +/* Utility for handling references. */ +static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;} + +/* Add item to array/object. */ +void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} +void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} +void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} +void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));} + +cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) { + cJSON *c=array->child; + while (c && which>0) c=c->next,which--; + if (!c) return 0; + if (c->prev) c->prev->next=c->next; + if (c->next) c->next->prev=c->prev; + if (c==array->child) array->child=c->next; + c->prev=c->next=0; + return c; +} +void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));} +cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;} +void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));} + +/* Replace array/object items with new ones. */ +void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return; + newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem; + if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);} +void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}} + +/* Create basic types: */ +cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} +cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} +cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} +cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} +cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} +cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} +cJSON *cJSON_CreateArray(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} +cJSON *cJSON_CreateObject(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;} + +/* Create Arrays: */ +cJSON *cJSON_CreateIntArray(const int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateFloatArray(const float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateDoubleArray(const double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} + +/* Duplication */ +cJSON *cJSON_Duplicate(cJSON *item,int recurse) +{ + cJSON *newitem,*cptr,*nptr=0,*newchild; + /* Bail on bad ptr */ + if (!item) return 0; + /* Create new item */ + newitem=cJSON_New_Item(); + if (!newitem) return 0; + /* Copy over all vars */ + newitem->type=item->type&(~cJSON_IsReference),newitem->valueint=item->valueint,newitem->valuedouble=item->valuedouble; + if (item->valuestring) {newitem->valuestring=cJSON_strdup(item->valuestring); if (!newitem->valuestring) {cJSON_Delete(newitem);return 0;}} + if (item->string) {newitem->string=cJSON_strdup(item->string); if (!newitem->string) {cJSON_Delete(newitem);return 0;}} + /* If non-recursive, then we're done! */ + if (!recurse) return newitem; + /* Walk the ->next chain for the child. */ + cptr=item->child; + while (cptr) + { + newchild=cJSON_Duplicate(cptr,1); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) {cJSON_Delete(newitem);return 0;} + if (nptr) {nptr->next=newchild,newchild->prev=nptr;nptr=newchild;} /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + else {newitem->child=newchild;nptr=newchild;} /* Set newitem->child and move to it */ + cptr=cptr->next; + } + return newitem; +} + +void cJSON_Minify(char *json) +{ + char *into=json; + while (*json) + { + if (*json==' ') json++; + else if (*json=='\t') json++; // Whitespace characters. + else if (*json=='\r') json++; + else if (*json=='\n') json++; + else if (*json=='/' && json[1]=='/') while (*json && *json!='\n') json++; // double-slash comments, to end of line. + else if (*json=='/' && json[1]=='*') {while (*json && !(*json=='*' && json[1]=='/')) json++;json+=2;} // multiline comments. + else if (*json=='\"'){*into++=*json++;while (*json && *json!='\"'){if (*json=='\\') *into++=*json++;*into++=*json++;}*into++=*json++;} // string literals, which are \" sensitive. + else *into++=*json++; // All other characters. + } + *into=0; // and null-terminate. +} diff --git a/server/src/cJSON.h b/server/src/cJSON.h new file mode 100644 index 0000000..867b7c3 --- /dev/null +++ b/server/src/cJSON.h @@ -0,0 +1,143 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* cJSON Types: */ +#define cJSON_False 0 +#define cJSON_True 1 +#define cJSON_NULL 2 +#define cJSON_Number 3 +#define cJSON_String 4 +#define cJSON_Array 5 +#define cJSON_Object 6 + +#define cJSON_IsReference 256 + +/* The cJSON structure: */ +typedef struct cJSON { + struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int type; /* The type of the item, as above. */ + + char *valuestring; /* The item's string, if type==cJSON_String */ + int valueint; /* The item's number, if type==cJSON_Number */ + double valuedouble; /* The item's number, if type==cJSON_Number */ + + char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ +} cJSON; + +typedef struct cJSON_Hooks { + void *(*malloc_fn)(size_t sz); + void (*free_fn)(void *ptr); +} cJSON_Hooks; + +/* Supply malloc, realloc and free functions to cJSON */ +extern void cJSON_InitHooks(cJSON_Hooks* hooks); + + +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ +extern cJSON *cJSON_Parse(const char *value); +/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ +extern char *cJSON_Print(cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ +extern char *cJSON_PrintUnformatted(cJSON *item); +/* Delete a cJSON entity and all subentities. */ +extern void cJSON_Delete(cJSON *c); + +/* Returns the number of items in an array (or object). */ +extern int cJSON_GetArraySize(cJSON *array); +/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ +extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); +/* Get item "string" from object. Case insensitive. */ +extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); + +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +extern const char *cJSON_GetErrorPtr(void); + +/* These calls create a cJSON item of the appropriate type. */ +extern cJSON *cJSON_CreateNull(void); +extern cJSON *cJSON_CreateTrue(void); +extern cJSON *cJSON_CreateFalse(void); +extern cJSON *cJSON_CreateBool(int b); +extern cJSON *cJSON_CreateNumber(double num); +extern cJSON *cJSON_CreateString(const char *string); +extern cJSON *cJSON_CreateArray(void); +extern cJSON *cJSON_CreateObject(void); + +/* These utilities create an Array of count items. */ +extern cJSON *cJSON_CreateIntArray(const int *numbers,int count); +extern cJSON *cJSON_CreateFloatArray(const float *numbers,int count); +extern cJSON *cJSON_CreateDoubleArray(const double *numbers,int count); +extern cJSON *cJSON_CreateStringArray(const char **strings,int count); + +/* Append item to the specified array/object. */ +extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item); + +/* Remove/Detatch items from Arrays/Objects. */ +extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); +extern void cJSON_DeleteItemFromArray(cJSON *array,int which); +extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); +extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string); + +/* Update array items. */ +extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); +extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +extern cJSON *cJSON_Duplicate(cJSON *item,int recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will +need to be released. With recurse!=0, it will duplicate any children connected to the item. +The item->next and ->prev pointers are always zero on return from Duplicate. */ + +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +extern cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated); + +extern void cJSON_Minify(char *json); + +/* Macros for creating things quickly. */ +#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) +#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) +#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) +#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) +#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) +#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/server/src/cfg.cpp b/server/src/cfg.cpp index 474acd8..bf72dfb 100644 --- a/server/src/cfg.cpp +++ b/server/src/cfg.cpp @@ -5,8 +5,6 @@ #include "opts/lnp_opts.h" #include "opts/daemon_opts.h" -#define DEFAULT_PID_FILE "/var/run/yeti_management.pid" - cfg_opt_t system_opts[] = { CFG_SEC((char*)"signalling",sig_opts,CFGF_NONE), CFG_SEC((char*)"lnp",lnp_opts,CFGF_NONE), diff --git a/server/src/mgmt_server.cpp b/server/src/mgmt_server.cpp index b1e0cef..c0b615a 100644 --- a/server/src/mgmt_server.cpp +++ b/server/src/mgmt_server.cpp @@ -8,6 +8,8 @@ #include "cfg.h" +#define JSONRPC_TIMEOUT_SEC 3 + std::unique_ptr mgmt_server::_self(nullptr); class daemon_cfg_reader: public cfg_reader { @@ -74,15 +76,21 @@ void mgmt_server::configure() } cfg_t *daemon_section = cfg_getsec(cr.get_cfg(),"daemon"); - cfg_t *sctp_cfg = cfg_getsec(daemon_section,"sctp"); + cfg_t *sctp_cfg = cfg_getsec(daemon_section,"sctp"); if(!sctp_cfg) throw cfg_exception("missed 'daemon.sctp' section"); - if(sctp_init(sctp_cfg)) { throw std::string("failed to init sctp server"); } + cfg_t *http_cfg = cfg_getsec(daemon_section,"http"); + if(!http_cfg) + throw cfg_exception("missed 'daemon.http' section"); + if(http_init(http_cfg)) { + throw std::string("failed to init http server"); + } + system_cfg_reader scr; if(!scr.load("/etc/yeti/system.cfg")){ throw cfg_exception("can't load system config"); @@ -110,6 +118,7 @@ void mgmt_server::loop() pthread_setname_np(__gthread_self(), "mgmt-server"); sctp_start(); + http_start(); s = nn_socket(AF_SP,NN_REP); if(s < 0){ @@ -151,7 +160,8 @@ void mgmt_server::loop() nn_freemsg(msg); } - on_stop(); + sctp_stop(); + http_stop(); } int mgmt_server::process_peer(char *msg, int len) @@ -238,3 +248,102 @@ void mgmt_server::show_config(){ cfg_mutex.unlock(); } +void mgmt_server::on_http_stats_request(struct evhttp_request *req) +{ + std::lock_guard lk(clients_mutex); + + if(clients.empty()) { + //no connected nodes. sent empty reply + dbg("no connected nodes. send empty reply"); + http_post_event(new HttpEventReply(req,nullptr)); + return; + } + + SctpBusPDU request; + + jsonrpc_cseq++; + + std::string &json = *request.mutable_payload(); + json = "{\"jsonrpc\":\"2.0\"," + "\"method\":\"yeti.show.stats\"," + "\"params\":{}," + "\"id\":\"" + std::to_string(jsonrpc_cseq) + "\"}"; + + //dbg("created json: %s",json.c_str()); + + request.set_type(SctpBusPDU::REQUEST); + request.set_src_node_id(0); + request.set_src_session_id("mgmt"); + request.set_dst_session_id("jsonrpc"); + + jsonrpc_requests_mutex.lock(); + auto ret = + jsonrpc_requests_by_cseq.emplace(jsonrpc_cseq,json_request_info()); + json_request_info &req_info = ret.first->second; + + gettimeofday(&req_info.expire_at, nullptr); + req_info.expire_at.tv_sec += JSONRPC_TIMEOUT_SEC; + req_info.req = req; + req_info.cseq = jsonrpc_cseq; + + broadcast_json_request_unsafe(request,req_info); + + if(req_info.sent_sctp_requests_assoc_id.empty()) { + dbg("did not sent successfully to the any of the client. reply immediately"); + jsonrpc_requests_by_cseq.erase(jsonrpc_cseq); + jsonrpc_requests_mutex.unlock(); + http_post_event(new HttpEventReply(req,nullptr)); + return; + } + + jsonrpc_requests_mutex.unlock(); +} + +void mgmt_server::on_timer(struct timeval &now) +{ + jsonrpc_requests_mutex.lock(); + //check for jsonrpc requests timeout + for(auto it = jsonrpc_requests_by_cseq.begin(); + it != jsonrpc_requests_by_cseq.end(); ) + { + json_request_info &i = it->second; + if(timercmp(&now,&i.expire_at,>)) { + dbg("request with cseq %d expired",it->first); + process_collected_json_replies(i,true); + it = jsonrpc_requests_by_cseq.erase(it); + } else { + ++it; + } + } + jsonrpc_requests_mutex.unlock(); + + SctpServer::on_timer(now); +} + +bool mgmt_server::process_collected_json_replies(json_request_info &req_info, bool timeout) +{ + if(!timeout && !req_info.sent_sctp_requests_assoc_id.empty()) { + dbg("we have more assocs without answer. " + "skip processing and keep request in the map"); + return false; + } + + //debug only + for(auto assoc : req_info.sent_sctp_requests_assoc_id) { + dbg("request %d reply timeout from assoc %d", + req_info.cseq, assoc); + } + + //serialize collected replies to the prometheus format + // https://prometheus.io/docs/instrumenting/writing_exporters/ + + struct evbuffer *buf = evbuffer_new(); + /*for(auto reply_it: req_info.replies_by_node_id) { + evbuffer_add_printf(buf,"node_id: %d",reply_it.first); + evbuffer_add(buf,reply_it.second.data(),reply_it.second.length()); + }*/ + evbuffer_add(buf,req_info.result.data(),req_info.result.size()); + http_post_event(new HttpEventReply(req_info.req,buf)); + + return true; //remove request from the map +} diff --git a/server/src/mgmt_server.h b/server/src/mgmt_server.h index 214998e..26ceb41 100644 --- a/server/src/mgmt_server.h +++ b/server/src/mgmt_server.h @@ -19,12 +19,15 @@ #include "node_cfg_providers/cfg_provider.h" #include "cfg_reader.h" #include "thread.h" + #include "SctpServer.h" +#include "HttpServer.h" using std::string; class mgmt_server - : public SctpServer + : public SctpServer, + public HttpServer { bool _stop; int s; @@ -49,6 +52,9 @@ class mgmt_server void create_reply(CfgResponse &reply, const CfgRequest &req); void create_error_reply(CfgResponse &reply,int code, std::string description); + protected: + bool process_collected_json_replies(json_request_info &req_info, bool timeout); + public: static mgmt_server& instance(){ if(_self.get()==0) _self.reset(new mgmt_server()); @@ -58,4 +64,7 @@ class mgmt_server void show_config(); void loop(); void stop() { dbg_func(); _stop = true; } + + void on_http_stats_request(struct evhttp_request *req) override; + void on_timer(struct timeval &) override; }; diff --git a/server/src/opts/daemon_opts.h b/server/src/opts/daemon_opts.h index 32873d7..c38da9f 100644 --- a/server/src/opts/daemon_opts.h +++ b/server/src/opts/daemon_opts.h @@ -5,13 +5,16 @@ #include #define SCTP_BUS_DEFAULT_PORT 10101 +#define HTTP_DEFAULT_PORT 3000 char sctp_bus_default_address[] = "127.0.0.1"; +char http_default_address[] = "127.0.0.1"; char opt_name_address[] = "address"; char opt_name_port[] = "port"; char section_name_sctp[] = "sctp"; +char section_name_http[] = "http"; cfg_opt_t sctp_section_opts[] = { CFG_STR(opt_name_address,sctp_bus_default_address,CFGF_NONE), @@ -19,9 +22,16 @@ cfg_opt_t sctp_section_opts[] = { CFG_END() }; +cfg_opt_t http_section_opts[] = { + CFG_STR(opt_name_address,http_default_address,CFGF_NONE), + CFG_INT(opt_name_port,HTTP_DEFAULT_PORT,CFGF_NONE), + CFG_END() +}; + cfg_opt_t daemon_section_opts[] = { CFG_STR_LIST((char*)"listen",(char *)"{tcp://127.0.0.1:4444}",CFGF_NODEFAULT), CFG_SEC(section_name_sctp,sctp_section_opts, CFGF_NODEFAULT), + CFG_SEC(section_name_http,http_section_opts, CFGF_NODEFAULT), CFG_INT((char *)"log_level",L_INFO, CFGF_NODEFAULT), CFG_END() };