mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
api: rewrite API to v2
This commit is contained in:
parent
c191b15809
commit
b58573f123
28 changed files with 913 additions and 1358 deletions
|
@ -30,19 +30,49 @@
|
|||
#include <thread>
|
||||
#include <list>
|
||||
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/common.hpp>
|
||||
#include <villas/api/server.hpp>
|
||||
#include <villas/queue_signalled.hpp>
|
||||
#include <villas/exceptions.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
const int version = 1;
|
||||
const int version = 2;
|
||||
|
||||
/* Forward declarations */
|
||||
class Session;
|
||||
class Response;
|
||||
class Request;
|
||||
|
||||
class Error : public RuntimeError {
|
||||
|
||||
public:
|
||||
Error(int c = HTTP_STATUS_INTERNAL_SERVER_ERROR, const std::string &msg = "Invalid API request") :
|
||||
RuntimeError(msg),
|
||||
code(c)
|
||||
{ }
|
||||
|
||||
int code;
|
||||
};
|
||||
|
||||
class BadRequest : public Error {
|
||||
|
||||
public:
|
||||
BadRequest(const std::string &msg = "Bad API request") :
|
||||
Error(HTTP_STATUS_BAD_REQUEST, msg)
|
||||
{ }
|
||||
};
|
||||
|
||||
class InvalidMethod : public BadRequest {
|
||||
|
||||
public:
|
||||
InvalidMethod(Request *req);
|
||||
};
|
||||
|
||||
} /* namespace api */
|
||||
|
||||
|
@ -60,7 +90,6 @@ protected:
|
|||
std::atomic<bool> running; /**< Atomic flag for signalizing thread termination. */
|
||||
|
||||
SuperNode *super_node;
|
||||
api::Server server;
|
||||
|
||||
void run();
|
||||
void worker();
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/** REST-API-releated functions.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/plugin.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
/* Forward declarations */
|
||||
class Session;
|
||||
|
||||
/** API action descriptor */
|
||||
class Action {
|
||||
|
||||
protected:
|
||||
Session *session;
|
||||
|
||||
Logger logger;
|
||||
|
||||
public:
|
||||
Action(Session *s) :
|
||||
session(s)
|
||||
{
|
||||
logger = logging.get("api:action");
|
||||
}
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp) = 0;
|
||||
};
|
||||
|
||||
class ActionFactory : public plugin::Plugin {
|
||||
|
||||
public:
|
||||
using plugin::Plugin::Plugin;
|
||||
|
||||
virtual Action * make(Session *s) = 0;
|
||||
};
|
||||
|
||||
template<typename T, const char *name, const char *desc>
|
||||
class ActionPlugin : public ActionFactory {
|
||||
|
||||
public:
|
||||
using ActionFactory::ActionFactory;
|
||||
|
||||
virtual Action * make(Session *s) {
|
||||
return new T(s);
|
||||
}
|
||||
|
||||
// Get plugin name
|
||||
virtual std::string
|
||||
getName() const
|
||||
{ return name; }
|
||||
|
||||
// Get plugin description
|
||||
virtual std::string
|
||||
getDescription() const
|
||||
{ return desc; }
|
||||
};
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
|
@ -1,81 +0,0 @@
|
|||
/** Socket API endpoint.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <villas/common.hpp>
|
||||
#include <villas/log.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
|
||||
/* Forward declarations */
|
||||
class Api;
|
||||
|
||||
namespace api {
|
||||
namespace sessions {
|
||||
|
||||
/* Forward declarations */
|
||||
class Socket;
|
||||
|
||||
} /* namespace sessions */
|
||||
|
||||
class Server {
|
||||
|
||||
protected:
|
||||
enum State state;
|
||||
|
||||
Api *api;
|
||||
Logger logger;
|
||||
|
||||
int sd;
|
||||
|
||||
std::vector<pollfd> pfds;
|
||||
std::map<int, sessions::Socket *> sessions;
|
||||
|
||||
void acceptNewSession();
|
||||
void closeSession(sessions::Socket *s);
|
||||
|
||||
struct sockaddr_un getSocketAddress();
|
||||
|
||||
public:
|
||||
Server(Api *a);
|
||||
~Server();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void run(int timeout = 100);
|
||||
};
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
#include <villas/queue.h>
|
||||
|
@ -35,10 +37,15 @@ namespace node {
|
|||
/* Forward declarations */
|
||||
class SuperNode;
|
||||
class Api;
|
||||
class Web;
|
||||
|
||||
namespace api {
|
||||
|
||||
/** A connection via HTTP REST or WebSockets to issue API actions. */
|
||||
/* Forward declarations */
|
||||
class Request;
|
||||
class Response;
|
||||
|
||||
/** A connection via HTTP REST or WebSockets to issue API requests. */
|
||||
class Session {
|
||||
|
||||
public:
|
||||
|
@ -48,47 +55,54 @@ public:
|
|||
};
|
||||
|
||||
enum Version {
|
||||
UNKOWN = 0,
|
||||
VERSION_1 = 1
|
||||
UNKNOWN_VERSION = 0,
|
||||
VERSION_1 = 1,
|
||||
VERSION_2 = 2
|
||||
};
|
||||
|
||||
protected:
|
||||
enum State state;
|
||||
enum Version version;
|
||||
|
||||
Logger logger;
|
||||
|
||||
int runs;
|
||||
|
||||
struct {
|
||||
JsonBuffer buffer;
|
||||
Queue<json_t *> queue;
|
||||
} request, response;
|
||||
lws *wsi;
|
||||
|
||||
Web *web;
|
||||
Api *api;
|
||||
|
||||
Logger logger;
|
||||
|
||||
JsonBuffer requestBuffer;
|
||||
JsonBuffer responseBuffer;
|
||||
|
||||
std::atomic<Request *> request;
|
||||
std::atomic<Response *> response;
|
||||
|
||||
bool headersSent;
|
||||
|
||||
public:
|
||||
Session(Api *a);
|
||||
Session(struct lws *w);
|
||||
~Session();
|
||||
|
||||
virtual ~Session();
|
||||
std::string getName() const;
|
||||
|
||||
int runAction(json_t *req, json_t **resp);
|
||||
virtual void runPendingActions();
|
||||
|
||||
virtual std::string getName();
|
||||
|
||||
int getRuns()
|
||||
{
|
||||
return runs;
|
||||
}
|
||||
|
||||
SuperNode * getSuperNode()
|
||||
SuperNode * getSuperNode() const
|
||||
{
|
||||
return api->getSuperNode();
|
||||
}
|
||||
|
||||
virtual void shutdown()
|
||||
{ }
|
||||
void open(void *in, size_t len);
|
||||
int writeable();
|
||||
void body(void *in, size_t len);
|
||||
void bodyComplete();
|
||||
void execute();
|
||||
void shutdown();
|
||||
|
||||
static int protocolCallback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
|
||||
int getRequestMethod(struct lws *wsi);
|
||||
|
||||
static std::string methodToString(int meth);
|
||||
|
||||
};
|
||||
|
||||
} /* namespace api */
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/** HTTP Api session.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <villas/api/sessions/wsi.hpp>
|
||||
|
||||
extern "C" int api_http_protocol_cb(lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
|
||||
/* Forward declarations */
|
||||
class SuperNode;
|
||||
class Api;
|
||||
|
||||
namespace api {
|
||||
namespace sessions {
|
||||
|
||||
class Http : public Wsi {
|
||||
|
||||
protected:
|
||||
bool headersSent;
|
||||
|
||||
public:
|
||||
Http(Api *s, lws *w);
|
||||
virtual ~Http() { };
|
||||
|
||||
void read(void *in, size_t len);
|
||||
|
||||
int complete();
|
||||
|
||||
int write();
|
||||
|
||||
virtual std::string getName();
|
||||
};
|
||||
|
||||
} /* namespace sessions */
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
|
@ -1,55 +0,0 @@
|
|||
/** WebSockets API session.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <villas/api/sessions/wsi.hpp>
|
||||
|
||||
extern "C" int api_ws_protocol_cb(lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
|
||||
/* Forward declarations */
|
||||
class Api;
|
||||
|
||||
namespace api {
|
||||
namespace sessions {
|
||||
|
||||
class WebSocket : public Wsi {
|
||||
|
||||
public:
|
||||
WebSocket(Api *a, lws *w);
|
||||
virtual ~WebSocket() { };
|
||||
|
||||
virtual std::string getName();
|
||||
|
||||
int read(void *in, size_t len);
|
||||
|
||||
int write();
|
||||
};
|
||||
|
||||
} /* namespace sessions */
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
48
lib/api.cpp
48
lib/api.cpp
|
@ -21,7 +21,9 @@
|
|||
*********************************************************************************/
|
||||
|
||||
#include <villas/api.hpp>
|
||||
#include <villas/web.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/node/config.h>
|
||||
#include <villas/memory.h>
|
||||
|
@ -31,10 +33,17 @@ using namespace villas;
|
|||
using namespace villas::node;
|
||||
using namespace villas::node::api;
|
||||
|
||||
InvalidMethod::InvalidMethod(Request *req) :
|
||||
BadRequest(
|
||||
fmt::format("The '{}' API endpoint does not support {} requests",
|
||||
req->factory->getName(), Session::methodToString(req->method)
|
||||
)
|
||||
)
|
||||
{ }
|
||||
|
||||
Api::Api(SuperNode *sn) :
|
||||
state(State::INITIALIZED),
|
||||
super_node(sn),
|
||||
server(this)
|
||||
super_node(sn)
|
||||
{
|
||||
logger = logging.get("api");
|
||||
}
|
||||
|
@ -50,8 +59,6 @@ void Api::start()
|
|||
|
||||
logger->info("Starting sub-system");
|
||||
|
||||
server.start();
|
||||
|
||||
running = true;
|
||||
thread = std::thread(&Api::worker, this);
|
||||
|
||||
|
@ -76,37 +83,24 @@ void Api::stop()
|
|||
pending.push(nullptr); /* unblock thread */
|
||||
thread.join();
|
||||
|
||||
server.stop();
|
||||
|
||||
state = State::STOPPED;
|
||||
}
|
||||
|
||||
void Api::run()
|
||||
{
|
||||
if (pending.empty())
|
||||
return;
|
||||
|
||||
/* Process pending actions */
|
||||
while (!pending.empty()) {
|
||||
Session *s = pending.pop();
|
||||
if (s) {
|
||||
/* Check that the session is still alive */
|
||||
auto it = std::find(sessions.begin(), sessions.end(), s);
|
||||
if (it == sessions.end())
|
||||
return;
|
||||
|
||||
s->runPendingActions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Api::worker()
|
||||
{
|
||||
logger->info("Started worker");
|
||||
|
||||
while (running) {
|
||||
run();
|
||||
server.run();
|
||||
/* Process pending requests */
|
||||
while (!pending.empty()) {
|
||||
Session *s = pending.pop();
|
||||
if (s) {
|
||||
/* Check that the session is still alive */
|
||||
auto it = std::find(sessions.begin(), sessions.end(), s);
|
||||
if (it != sessions.end())
|
||||
s->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger->info("Stopped worker");
|
||||
|
|
|
@ -23,30 +23,21 @@
|
|||
|
||||
set(API_SRC
|
||||
session.cpp
|
||||
server.cpp
|
||||
request.cpp
|
||||
response.cpp
|
||||
|
||||
sessions/socket.cpp
|
||||
|
||||
actions/capabiltities.cpp
|
||||
actions/shutdown.cpp
|
||||
actions/status.cpp
|
||||
actions/config.cpp
|
||||
actions/nodes.cpp
|
||||
actions/paths.cpp
|
||||
actions/restart.cpp
|
||||
actions/node.cpp
|
||||
actions/stats.cpp
|
||||
requests/capabiltities.cpp
|
||||
requests/shutdown.cpp
|
||||
requests/status.cpp
|
||||
requests/config.cpp
|
||||
requests/nodes.cpp
|
||||
requests/paths.cpp
|
||||
requests/restart.cpp
|
||||
requests/node_action.cpp
|
||||
requests/file.cpp
|
||||
requests/stats_reset.cpp
|
||||
)
|
||||
|
||||
if(WITH_WEB)
|
||||
list(APPEND API_SRC
|
||||
sessions/wsi.cpp
|
||||
sessions/http.cpp
|
||||
sessions/websocket.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(api STATIC ${API_SRC})
|
||||
target_include_directories(api PUBLIC ${INCLUDE_DIRS})
|
||||
target_link_libraries(api PUBLIC ${LIBRARIES})
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/** LWS Api session.
|
||||
/** API Request.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
|
@ -21,43 +21,29 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <villas/plugin.hpp>
|
||||
#include <villas/api.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
|
||||
#include <string>
|
||||
using namespace villas;
|
||||
using namespace villas::node::api;
|
||||
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/web.hpp>
|
||||
Request * RequestFactory::make(Session *s, const std::string &uri, int meth)
|
||||
{
|
||||
for (auto *rf : plugin::Registry::lookup<RequestFactory>()) {
|
||||
std::smatch mr;
|
||||
if (not rf->match(uri, mr))
|
||||
continue;
|
||||
|
||||
/* Forward declarations */
|
||||
struct lws;
|
||||
auto *r = rf->make(s);
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
r->uri = uri;
|
||||
r->method = meth;
|
||||
r->matches = mr;
|
||||
r->factory = rf;
|
||||
|
||||
/* Forward declarations */
|
||||
class Api;
|
||||
return r;
|
||||
}
|
||||
|
||||
namespace api {
|
||||
namespace sessions {
|
||||
|
||||
class Wsi : public Session {
|
||||
|
||||
protected:
|
||||
lws *wsi;
|
||||
|
||||
Web *web;
|
||||
|
||||
public:
|
||||
Wsi(Api *a, lws *w);
|
||||
|
||||
virtual void runPendingActions();
|
||||
|
||||
virtual std::string getName();
|
||||
|
||||
virtual void shutdown();
|
||||
};
|
||||
|
||||
} /* namespace sessions */
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
||||
throw BadRequest("Unknown API request");
|
||||
}
|
|
@ -21,19 +21,20 @@
|
|||
*********************************************************************************/
|
||||
|
||||
#include <villas/config.h>
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/hook.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class CapabilitiesAction : public Action {
|
||||
class CapabilitiesRequest : public Request {
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
json_t *json_hooks = json_array();
|
||||
json_t *json_apis = json_array();
|
||||
|
@ -41,7 +42,13 @@ public:
|
|||
json_t *json_formats = json_array();
|
||||
json_t *json_name;
|
||||
|
||||
for (auto f : plugin::Registry::lookup<ActionFactory>()) {
|
||||
if (method != Method::GET)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Capabilities endpoint does not accept any body data");
|
||||
|
||||
for (auto f : plugin::Registry::lookup<RequestFactory>()) {
|
||||
json_name = json_string(f->getName().c_str());
|
||||
|
||||
json_array_append_new(json_apis, json_name);
|
||||
|
@ -67,21 +74,22 @@ public:
|
|||
}
|
||||
#endif
|
||||
|
||||
*resp = json_pack("{ s: s, s: o, s: o, s: o, s: o }",
|
||||
auto *json_capabilities = json_pack("{ s: s, s: o, s: o, s: o, s: o }",
|
||||
"build", PROJECT_BUILD_ID,
|
||||
"hooks", json_hooks,
|
||||
"node-types", json_nodes,
|
||||
"apis", json_apis,
|
||||
"formats", json_formats);
|
||||
|
||||
return 0;
|
||||
return new Response(session, json_capabilities);
|
||||
}
|
||||
};
|
||||
|
||||
/* Register action */
|
||||
/* Register API request */
|
||||
static char n[] = "capabilities";
|
||||
static char r[] = "/capabilities";
|
||||
static char d[] = "get capabiltities and details about this VILLASnode instance";
|
||||
static ActionPlugin<CapabilitiesAction, n, d> p;
|
||||
static RequestPlugin<CapabilitiesRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
|
@ -20,35 +20,43 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class ConfigAction : public Action {
|
||||
class ConfigRequest : public Request {
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
json_t *cfg = session->getSuperNode()->getConfig();
|
||||
|
||||
*resp = cfg
|
||||
if (method != Method::GET)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Config endpoint does not accept any body data");
|
||||
|
||||
auto *json_config = cfg
|
||||
? json_incref(cfg)
|
||||
: json_object();
|
||||
|
||||
return 0;
|
||||
return new Response(session, json_config);
|
||||
}
|
||||
};
|
||||
|
||||
/* Register action */
|
||||
/* Register API request */
|
||||
static char n[] = "config";
|
||||
static char r[] = "/config";
|
||||
static char d[] = "get configuration of this VILLASnode instance";
|
||||
static ActionPlugin<ConfigAction, n, d> p;
|
||||
static RequestPlugin<ConfigRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
77
lib/api/requests/file.cpp
Normal file
77
lib/api/requests/file.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/** The "file" API ressource.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
#include <villas/nodes/file.hpp>
|
||||
#include <villas/io.h>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class FileRequest : public Request {
|
||||
|
||||
public:
|
||||
using Request::Request;
|
||||
|
||||
virtual Response * execute()
|
||||
{
|
||||
if (method != Method::GET && method != Method::POST)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
if (body != nullptr)
|
||||
throw BadRequest("File endpoint does not accept any body data");
|
||||
|
||||
const auto &nodeName = matches[1].str();
|
||||
|
||||
struct vlist *nodes = session->getSuperNode()->getNodes();
|
||||
struct node *n = (struct node *) vlist_lookup(nodes, nodeName.c_str());
|
||||
if (!n)
|
||||
throw BadRequest("Invalid node");
|
||||
|
||||
struct node_type *vt = node_type_lookup("file");
|
||||
|
||||
if (n->_vt != vt)
|
||||
throw BadRequest("This node is not a file node");
|
||||
|
||||
struct file *f = (struct file *) n->_vd;
|
||||
|
||||
if (matches[1].str() == "rewind")
|
||||
io_rewind(&f->io);
|
||||
|
||||
return new Response(session);
|
||||
}
|
||||
};
|
||||
|
||||
/* Register API request */
|
||||
static char n[] = "file";
|
||||
static char r[] = "/node/([^/]+)/file(?:/([^/]+))?";
|
||||
static char d[] = "control instances of 'file' node-type";
|
||||
static RequestPlugin<FileRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
|
@ -26,63 +26,69 @@
|
|||
#include <villas/node.h>
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
template<int (*A)(struct node *)>
|
||||
class NodeAction : public Action {
|
||||
class NodeActionRequest : public Request {
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
int ret;
|
||||
json_error_t err;
|
||||
const char *node_str;
|
||||
if (method != Method::POST)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
ret = json_unpack_ex(args, &err, 0, "{ s: s }",
|
||||
"node", &node_str
|
||||
);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Node endpoints do not accept any body data");
|
||||
|
||||
const auto &nodeName = matches[1].str();
|
||||
|
||||
struct vlist *nodes = session->getSuperNode()->getNodes();
|
||||
struct node *n = (struct node *) vlist_lookup(nodes, node_str);
|
||||
struct node *n = (struct node *) vlist_lookup(nodes, nodeName.c_str());
|
||||
|
||||
if (!n)
|
||||
return -1;
|
||||
throw Error(HTTP_STATUS_NOT_FOUND, "Node not found");
|
||||
|
||||
return A(n);
|
||||
A(n);
|
||||
|
||||
return new Response(session);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* Register actions */
|
||||
char n1[] = "node.start";
|
||||
/* Register API requests */
|
||||
char n1[] = "node/start";
|
||||
char r1[] = "/node/([^/]+)/start";
|
||||
char d1[] = "start a node";
|
||||
static ActionPlugin<NodeAction<node_start>, n1, d1> p1;
|
||||
static RequestPlugin<NodeActionRequest<node_start>, n1, r1, d1> p1;
|
||||
|
||||
char n2[] = "node.stop";
|
||||
char n2[] = "node/stop";
|
||||
char r2[] = "/node/([^/]+)/stop";
|
||||
char d2[] = "stop a node";
|
||||
static ActionPlugin<NodeAction<node_stop>, n2, d2> p2;
|
||||
static RequestPlugin<NodeActionRequest<node_stop>, n2, r2, d2> p2;
|
||||
|
||||
char n3[] = "node.pause";
|
||||
char n3[] = "node/pause";
|
||||
char r3[] = "/node/([^/]+)/pause";
|
||||
char d3[] = "pause a node";
|
||||
static ActionPlugin<NodeAction<node_pause>, n3, d3> p3;
|
||||
static RequestPlugin<NodeActionRequest<node_pause>, n3, r3, d3> p3;
|
||||
|
||||
char n4[] = "node.resume";
|
||||
char n4[] = "node/resume";
|
||||
char r4[] = "/node/([^/]+)/resume";
|
||||
char d4[] = "resume a node";
|
||||
static ActionPlugin<NodeAction<node_resume>, n4, d4> p4;
|
||||
static RequestPlugin<NodeActionRequest<node_resume>, n4, r4, d4> p4;
|
||||
|
||||
char n5[] = "node.restart";
|
||||
char n5[] = "node/restart";
|
||||
char r5[] = "/node/([^/]+)/restart";
|
||||
char d5[] = "restart a node";
|
||||
static ActionPlugin<NodeAction<node_restart>, n5, d5> p5;
|
||||
static RequestPlugin<NodeActionRequest<node_restart>, n5, r5, d5> p5;
|
||||
|
||||
|
||||
} /* namespace api */
|
|
@ -27,20 +27,27 @@
|
|||
#include <villas/node.h>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/stats.hpp>
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class NodesAction : public Action {
|
||||
class NodesRequest : public Request {
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
if (method != Method::GET)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Nodes endpoint does not accept any body data");
|
||||
|
||||
json_t *json_nodes = json_array();
|
||||
|
||||
struct vlist *nodes = session->getSuperNode()->getNodes();
|
||||
|
@ -86,16 +93,15 @@ public:
|
|||
json_array_append_new(json_nodes, json_node);
|
||||
}
|
||||
|
||||
*resp = json_nodes;
|
||||
|
||||
return 0;
|
||||
return new Response(session, json_nodes);
|
||||
}
|
||||
};
|
||||
|
||||
/* Register action */
|
||||
/* Register API request */
|
||||
static char n[] = "nodes";
|
||||
static char r[] = "/nodes";
|
||||
static char d[] = "retrieve list of all known nodes";
|
||||
static ActionPlugin<NodesAction, n, d> p;
|
||||
static RequestPlugin<NodesRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
88
lib/api/requests/path_action.cpp
Normal file
88
lib/api/requests/path_action.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
/** The API ressource for start/stop/pause/resume paths.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
#include <villas/plugin.h>
|
||||
#include <villas/node.h>
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/api.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
template<int (*A)(struct node *)>
|
||||
class PathActionRequest : public Request {
|
||||
|
||||
public:
|
||||
using Request::Request;
|
||||
|
||||
virtual Response * execute()
|
||||
{
|
||||
if (method != Method::POST)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Path endpoints do not accept any body data");
|
||||
|
||||
unsigned long pathIndex;
|
||||
const auto &pathIndexStr = matches[1].str();
|
||||
|
||||
try {
|
||||
pathIndex = std::atoul(pathIndexStr);
|
||||
} catch (const std::invalid_argument &e) {
|
||||
throw BadRequest("Invalid argument");
|
||||
}
|
||||
|
||||
struct vlist *paths = session->getSuperNode()->getPaths();
|
||||
struct vpath *p = (struct path *) vlist_at_safe(pathIndex);
|
||||
|
||||
if (!p)
|
||||
throw Error(HTTP_STATUS_NOT_FOUND, "Node not found");
|
||||
|
||||
A(p);
|
||||
|
||||
return new Response(session);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* Register API requests */
|
||||
char n1[] = "path/start";
|
||||
char r1[] = "/path/([^/]+)/start";
|
||||
char d1[] = "start a path";
|
||||
static RequestPlugin<PathActionRequest<path_start>, n1, r1, d1> p1;
|
||||
|
||||
char n2[] = "path/stop";
|
||||
char r2[] = "/path/([^/]+)/stop";
|
||||
char d2[] = "stop a path";
|
||||
static RequestPlugin<PathActionRequest<path_stop>, n2, r2, d2> p2;
|
||||
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
123
lib/api/requests/paths.cpp
Normal file
123
lib/api/requests/paths.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
/** The "paths" API ressource.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/path.h>
|
||||
#include <villas/path_source.h>
|
||||
#include <villas/path_destination.h>
|
||||
#include <villas/hook.hpp>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class PathsRequest : public Request {
|
||||
|
||||
public:
|
||||
using Request::Request;
|
||||
|
||||
virtual Response * execute()
|
||||
{
|
||||
if (method != Method::GET)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Paths endpoint does not accept any body data");
|
||||
|
||||
json_t *json_paths = json_array();
|
||||
|
||||
struct vlist *paths = session->getSuperNode()->getPaths();
|
||||
|
||||
for (size_t i = 0; i < vlist_length(paths); i++) {
|
||||
struct vpath *p = (struct vpath *) vlist_at(paths, i);
|
||||
|
||||
char uuid[37];
|
||||
uuid_unparse(p->uuid, uuid);
|
||||
|
||||
json_t *json_signals = json_array();
|
||||
json_t *json_hooks = json_array();
|
||||
json_t *json_sources = json_array();
|
||||
json_t *json_destinations = json_array();
|
||||
|
||||
for (size_t i = 0; i < vlist_length(&p->signals); i++) {
|
||||
struct signal *sig = (struct signal *) vlist_at_safe(&p->signals, i);
|
||||
|
||||
json_array_append(json_signals, signal_to_json(sig));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < vlist_length(&p->hooks); i++) {
|
||||
Hook *h = (Hook *) vlist_at_safe(&p->hooks, i);
|
||||
|
||||
json_array_append(json_hooks, h->getConfig());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < vlist_length(&p->sources); i++) {
|
||||
struct vpath_source *pd = (struct vpath_source *) vlist_at_safe(&p->sources, i);
|
||||
|
||||
json_array_append_new(json_sources, json_string(node_name_short(pd->node)));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < vlist_length(&p->destinations); i++) {
|
||||
struct vpath_destination *pd = (struct vpath_destination *) vlist_at_safe(&p->destinations, i);
|
||||
|
||||
json_array_append_new(json_destinations, json_string(node_name_short(pd->node)));
|
||||
}
|
||||
|
||||
json_t *json_path = json_pack("{ s: s, s: s, s: s, s: b, s: b s: b, s: b, s: b, s: b s: i, s: o, s: o, s: o, s: o }",
|
||||
"uuid", uuid,
|
||||
"state", state_print(p->state),
|
||||
"mode", p->mode == PathMode::ANY ? "any" : "all",
|
||||
"enabled", p->enabled,
|
||||
"builtin", p->builtin,
|
||||
"reverse", p->reverse,
|
||||
"original_sequence_no", p->original_sequence_no,
|
||||
"last_sequence", p->last_sequence,
|
||||
"poll", p->poll,
|
||||
"queuelen", p->queuelen,
|
||||
"signals", json_signals,
|
||||
"hooks", json_hooks,
|
||||
"in", json_sources,
|
||||
"out", json_destinations
|
||||
);
|
||||
|
||||
json_array_append_new(json_paths, json_path);
|
||||
}
|
||||
|
||||
return new Response(session, json_paths);
|
||||
}
|
||||
};
|
||||
|
||||
/* Register API request */
|
||||
static char n[] = "paths";
|
||||
static char r[] = "/paths";
|
||||
static char d[] = "retrieve list of all paths with details";
|
||||
static RequestPlugin<PathsRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
|
@ -1,4 +1,4 @@
|
|||
/** The "restart" API action.
|
||||
/** The "restart" API request.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
|
@ -21,17 +21,18 @@
|
|||
*********************************************************************************/
|
||||
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/node/exceptions.hpp>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class RestartAction : public Action {
|
||||
class RestartRequest : public Request {
|
||||
|
||||
protected:
|
||||
static std::string configUri;
|
||||
|
@ -55,19 +56,22 @@ protected:
|
|||
}
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
int ret;
|
||||
json_error_t err;
|
||||
|
||||
if (method != Method::POST)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
const char *cfg = nullptr;
|
||||
|
||||
if (args) {
|
||||
ret = json_unpack_ex(args, &err, 0, "{ s?: s }", "config", &cfg);
|
||||
if (body) {
|
||||
ret = json_unpack_ex(body, &err, 0, "{ s?: s }", "config", &cfg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
throw Error();
|
||||
}
|
||||
|
||||
/* If no config is provided via request, we will use the previous one */
|
||||
|
@ -86,7 +90,7 @@ public:
|
|||
/* We pass some env variables to the new process */
|
||||
setenv("VILLAS_API_RESTART_COUNT", buf, 1);
|
||||
|
||||
*resp = json_pack("{ s: i, s: o }",
|
||||
auto *json_response = json_pack("{ s: i, s: o }",
|
||||
"restarts", cnt,
|
||||
"config", configUri.empty()
|
||||
? json_null()
|
||||
|
@ -96,21 +100,22 @@ public:
|
|||
/* Register exit handler */
|
||||
ret = atexit(handler);
|
||||
if (ret)
|
||||
return ret;
|
||||
throw Error(HTTP_STATUS_INTERNAL_SERVER_ERROR, "Failed to restart VILLASnode instance");
|
||||
|
||||
/* Properly terminate current instance */
|
||||
utils::killme(SIGTERM);
|
||||
|
||||
return 0;
|
||||
return new Response(session, json_response);
|
||||
}
|
||||
};
|
||||
|
||||
std::string RestartAction::configUri;
|
||||
std::string RestartRequest::configUri;
|
||||
|
||||
/* Register action */
|
||||
/* Register API request */
|
||||
static char n[] = "restart";
|
||||
static char r[] = "/restart";
|
||||
static char d[] = "restart VILLASnode with new configuration";
|
||||
static ActionPlugin<RestartAction, n, d> p;
|
||||
static RequestPlugin<RestartRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
|
@ -1,4 +1,4 @@
|
|||
/** The "shutdown" API action.
|
||||
/** The "shutdown" API request.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
|
@ -23,29 +23,37 @@
|
|||
#include <signal.h>
|
||||
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class ShutdownAction : public Action {
|
||||
class ShutdownRequest : public Request {
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
if (method != Method::GET)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Shutdown endpoint does not accept any body data");
|
||||
|
||||
utils::killme(SIGTERM);
|
||||
|
||||
return 0;
|
||||
return new Response(session);
|
||||
}
|
||||
};
|
||||
|
||||
/* Register action */
|
||||
/* Register API request */
|
||||
static char n[] = "shutdown";
|
||||
static char r[] = "/shutdown";
|
||||
static char d[] = "quit VILLASnode";
|
||||
static ActionPlugin<ShutdownAction, n, d> p;
|
||||
static RequestPlugin<ShutdownRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
|
@ -1,4 +1,4 @@
|
|||
/** The "paths" API ressource.
|
||||
/** The API ressource for querying statistics.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
|
@ -22,51 +22,58 @@
|
|||
|
||||
#include <jansson.h>
|
||||
|
||||
#include <villas/log.h>
|
||||
#include <villas/node.h>
|
||||
#include <villas/stats.hpp>
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/path.h>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class PathsAction : public Action {
|
||||
class StatsRequest : public Request {
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
json_t *json_paths = json_array();
|
||||
int ret;
|
||||
json_error_t err;
|
||||
|
||||
struct vlist *paths = session->getSuperNode()->getPaths();
|
||||
if (method != Method::GET)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
for (size_t i = 0; i < vlist_length(paths); i++) {
|
||||
struct vpath *p = (struct vpath *) vlist_at(paths, i);
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Stats endpoint does not accept any body data");
|
||||
|
||||
json_t *json_path = json_pack("{ s: s }",
|
||||
"state", state_print(p->state)
|
||||
);
|
||||
json_t *json_stats = json_array();
|
||||
|
||||
/* Add all additional fields of node here.
|
||||
* This can be used for metadata */
|
||||
json_object_update(json_path, p->cfg);
|
||||
struct vlist *nodes = session->getSuperNode()->getNodes();
|
||||
for (size_t i = 0; i < vlist_length(nodes); i++) {
|
||||
struct node *n = (struct node *) vlist_at(nodes, i);
|
||||
|
||||
json_array_append_new(json_paths, json_path);
|
||||
if (node && strcmp(node, node_name(n)))
|
||||
continue;
|
||||
|
||||
if (n->stats)
|
||||
json_array_append(json_stats, n->stats->toJson());
|
||||
}
|
||||
|
||||
*resp = json_paths;
|
||||
|
||||
return 0;
|
||||
return new Response(session, json_stats);
|
||||
}
|
||||
};
|
||||
|
||||
/* Register action */
|
||||
static char n[] = "paths";
|
||||
static char d[] = "retrieve list of all paths with details";
|
||||
static ActionPlugin<PathsAction, n, d> p;
|
||||
/* Register API requests */
|
||||
static char n[] = "stats";
|
||||
static char r[] = "/node/([^/]+)/stats";
|
||||
static char d[] = "get internal statistics counters";
|
||||
static RequestPlugin<StatsRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
|
@ -1,4 +1,4 @@
|
|||
/** The API ressource for getting and resetting statistics.
|
||||
/** The API ressource for resetting statistics.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
|
@ -27,54 +27,56 @@
|
|||
#include <villas/stats.hpp>
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api.hpp>
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class StatsAction : public Action {
|
||||
class StatsRequest : public Request {
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
int ret, reset = 0;
|
||||
int ret;
|
||||
json_error_t err;
|
||||
|
||||
ret = json_unpack_ex(args, &err, 0, "{ s?: b }",
|
||||
"reset", &reset
|
||||
char *node = nullptr;
|
||||
|
||||
ret = json_unpack_ex(body, &err, 0, "{ s?: s }",
|
||||
"node", &node
|
||||
);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
throw Error();
|
||||
|
||||
struct vlist *nodes = session->getSuperNode()->getNodes();
|
||||
|
||||
for (size_t i = 0; i < vlist_length(nodes); i++) {
|
||||
struct node *n = (struct node *) vlist_at(nodes, i);
|
||||
|
||||
if (node && strcmp(node, node_name(n)))
|
||||
continue;
|
||||
|
||||
if (n->stats) {
|
||||
if (reset) {
|
||||
n->stats->reset();
|
||||
info("Stats resetted for node %s", node_name(n));
|
||||
}
|
||||
n->stats->reset();
|
||||
info("Stats resetted for node %s", node_name(n));
|
||||
}
|
||||
}
|
||||
|
||||
*resp = json_object();
|
||||
|
||||
return 0;
|
||||
return new Response(session);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* Register actions */
|
||||
static char n[] = "stats";
|
||||
static char d[] = "get or reset internal statistics counters";
|
||||
static ActionPlugin<StatsAction, n, d> p;
|
||||
/* Register API requests */
|
||||
static char n[] = "stats/reset";
|
||||
static char r[] = "/stats/reset";
|
||||
static char d[] = "reset internal statistics counters";
|
||||
static RequestPlugin<StatsRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
|
@ -1,4 +1,4 @@
|
|||
/** The "stats" API action.
|
||||
/** The "stats" API request.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
|
@ -24,37 +24,45 @@
|
|||
|
||||
#include <jansson.h>
|
||||
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
namespace api {
|
||||
|
||||
class StatusAction : public Action {
|
||||
class StatusRequest : public Request {
|
||||
|
||||
public:
|
||||
using Action::Action;
|
||||
using Request::Request;
|
||||
|
||||
virtual int execute(json_t *args, json_t **resp)
|
||||
virtual Response * execute()
|
||||
{
|
||||
int ret;
|
||||
struct lws_context *ctx = lws_get_context(s->wsi);
|
||||
char buf[4096];
|
||||
|
||||
if (method != Method::GET)
|
||||
throw InvalidMethod(this);
|
||||
|
||||
if (body != nullptr)
|
||||
throw BadRequest("Status endpoint does not accept any body data");
|
||||
|
||||
ret = lws_json_dump_context(ctx, buf, sizeof(buf), 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
throw Error();
|
||||
|
||||
*resp = json_loads(buf, 0, nullptr);
|
||||
auto *json_status = json_loads(buf, 0, nullptr);
|
||||
|
||||
return 0;
|
||||
return new Response(session, json_status);
|
||||
}
|
||||
};
|
||||
|
||||
/* Register action */
|
||||
/* Register API request */
|
||||
static char n[] = "status";
|
||||
static char r[] = "/status";
|
||||
static char d[] = "get status and statistics of web server";
|
||||
static ActionPlugin<StatusAction, n, d> p;
|
||||
static RequestPlugin<StatusRequest, n, r, d> p;
|
||||
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
|
@ -1,4 +1,4 @@
|
|||
/** Socket Api session.
|
||||
/** API Response.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
|
@ -21,37 +21,28 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <villas/api/response.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
|
||||
#include <villas/api/session.hpp>
|
||||
using namespace villas::node::api;
|
||||
|
||||
namespace villas {
|
||||
namespace node {
|
||||
Response::Response(Session *s, json_t *resp) :
|
||||
session(s),
|
||||
response(resp),
|
||||
code(HTTP_STATUS_OK)
|
||||
{
|
||||
logger = logging.get("api:response");
|
||||
}
|
||||
|
||||
/* Forward declarations */
|
||||
class Api;
|
||||
Response::~Response()
|
||||
{
|
||||
if (response)
|
||||
json_decref(response);
|
||||
}
|
||||
|
||||
namespace api {
|
||||
namespace sessions {
|
||||
|
||||
class Socket : public Session {
|
||||
|
||||
protected:
|
||||
int sd;
|
||||
|
||||
public:
|
||||
Socket(Api *a, int s);
|
||||
~Socket();
|
||||
|
||||
int read();
|
||||
int write();
|
||||
|
||||
virtual std::string getName();
|
||||
|
||||
int getSd() const { return sd; }
|
||||
};
|
||||
|
||||
} /* namespace sessions */
|
||||
} /* namespace api */
|
||||
} /* namespace node */
|
||||
} /* namespace villas */
|
||||
json_t * ErrorResponse::toJson()
|
||||
{
|
||||
return json_pack("{ s: s }",
|
||||
"error", error.c_str()
|
||||
);
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
/** Socket API endpoint.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <exception>
|
||||
#include <algorithm>
|
||||
|
||||
#if __GNUC__ <= 7 && !defined(__clang__)
|
||||
#include <experimental/filesystem>
|
||||
#else
|
||||
#include <filesystem>
|
||||
#endif
|
||||
|
||||
#include <villas/config.h>
|
||||
#include <villas/node/exceptions.hpp>
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/super_node.hpp>
|
||||
#include <villas/api/server.hpp>
|
||||
#include <villas/api/sessions/socket.hpp>
|
||||
|
||||
using namespace villas;
|
||||
using namespace villas::node::api;
|
||||
|
||||
#if __GNUC__ <= 7 && !defined(__clang__)
|
||||
namespace fs = std::experimental::filesystem;
|
||||
#else
|
||||
namespace fs = std::filesystem;
|
||||
#endif
|
||||
|
||||
Server::Server(Api *a) :
|
||||
state(State::INITIALIZED),
|
||||
api(a)
|
||||
{
|
||||
logger = logging.get("api:server");
|
||||
}
|
||||
|
||||
Server::~Server()
|
||||
{
|
||||
assert(state != State::STARTED);
|
||||
}
|
||||
|
||||
void Server::start()
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(state != State::STARTED);
|
||||
|
||||
sd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sd < 0)
|
||||
throw SystemError("Failed to create Api socket");
|
||||
|
||||
pollfd pfd = {
|
||||
.fd = sd,
|
||||
.events = POLLIN
|
||||
};
|
||||
|
||||
pfds.push_back(pfd);
|
||||
|
||||
struct sockaddr_un sun = getSocketAddress();
|
||||
|
||||
ret = bind(sd, (struct sockaddr *) &sun, sizeof(struct sockaddr_un));
|
||||
if (ret)
|
||||
throw SystemError("Failed to bind API socket");
|
||||
|
||||
ret = listen(sd, 5);
|
||||
if (ret)
|
||||
throw SystemError("Failed to listen on API socket");
|
||||
|
||||
logger->info("Listening on UNIX socket: {}", sun.sun_path);
|
||||
|
||||
state = State::STARTED;
|
||||
}
|
||||
|
||||
struct sockaddr_un Server::getSocketAddress()
|
||||
{
|
||||
struct sockaddr_un sun = { .sun_family = AF_UNIX };
|
||||
|
||||
fs::path socketPath;
|
||||
|
||||
if (getuid() == 0) {
|
||||
socketPath = PREFIX "/var/lib/villas";
|
||||
}
|
||||
else {
|
||||
std::string homeDir = getenv("HOME");
|
||||
|
||||
socketPath = homeDir + "/.villas";
|
||||
}
|
||||
|
||||
if (!fs::exists(socketPath)) {
|
||||
logging.get("api")->info("Creating directory for API socket: {}", socketPath);
|
||||
fs::create_directories(socketPath);
|
||||
}
|
||||
|
||||
socketPath += "/node-" + api->getSuperNode()->getName() + ".sock";
|
||||
|
||||
if (fs::exists(socketPath)) {
|
||||
logging.get("api")->info("Removing existing socket: {}", socketPath);
|
||||
fs::remove(socketPath);
|
||||
}
|
||||
|
||||
strncpy(sun.sun_path, socketPath.c_str(), sizeof(sun.sun_path) - 1);
|
||||
|
||||
return sun;
|
||||
}
|
||||
|
||||
void Server::stop()
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(state == State::STARTED);
|
||||
|
||||
ret = close(sd);
|
||||
if (ret)
|
||||
throw SystemError("Failed to close API socket");
|
||||
|
||||
state = State::STOPPED;
|
||||
}
|
||||
|
||||
void Server::run(int timeout)
|
||||
{
|
||||
int ret;
|
||||
|
||||
assert(state == State::STARTED);
|
||||
|
||||
auto len = pfds.size();
|
||||
|
||||
ret = poll(pfds.data(), len, timeout);
|
||||
if (ret < 0)
|
||||
throw SystemError("Failed to poll on API socket");
|
||||
|
||||
std::vector<sessions::Socket *> closing;
|
||||
|
||||
for (unsigned i = 1; i < len; i++) {
|
||||
auto &pfd = pfds[i];
|
||||
|
||||
/* pfds[0] is the server socket */
|
||||
auto s = sessions[pfd.fd];
|
||||
|
||||
if (pfd.revents & POLLIN) {
|
||||
ret = s->read();
|
||||
if (ret < 0)
|
||||
closing.push_back(s);
|
||||
}
|
||||
|
||||
if (pfd.revents & POLLOUT) {
|
||||
s->write();
|
||||
}
|
||||
}
|
||||
|
||||
/* Destroy closed sessions */
|
||||
for (auto *s : closing)
|
||||
closeSession(s);
|
||||
|
||||
/* Accept new connections */
|
||||
if (pfds[0].revents & POLLIN)
|
||||
acceptNewSession();
|
||||
}
|
||||
|
||||
void Server::acceptNewSession() {
|
||||
int fd = ::accept(sd, nullptr, nullptr);
|
||||
|
||||
auto s = new sessions::Socket(api, fd);
|
||||
if (!s)
|
||||
throw MemoryAllocationError();
|
||||
|
||||
pollfd pfd = {
|
||||
.fd = fd,
|
||||
.events = POLLIN | POLLOUT
|
||||
};
|
||||
|
||||
pfds.push_back(pfd);
|
||||
sessions[fd] = s;
|
||||
|
||||
api->sessions.push_back(s);
|
||||
}
|
||||
|
||||
void Server::closeSession(sessions::Socket *s)
|
||||
{
|
||||
int sd = s->getSd();
|
||||
|
||||
sessions.erase(sd);
|
||||
api->sessions.remove(s);
|
||||
|
||||
pfds.erase(std::remove_if(pfds.begin(), pfds.end(),
|
||||
[sd](const pollfd &p){ return p.fd == sd; })
|
||||
);
|
||||
|
||||
delete s;
|
||||
}
|
|
@ -29,109 +29,334 @@
|
|||
#include <villas/memory.h>
|
||||
|
||||
#include <villas/api/session.hpp>
|
||||
#include <villas/api/action.hpp>
|
||||
#include <villas/api/request.hpp>
|
||||
#include <villas/api/response.hpp>
|
||||
|
||||
using namespace villas;
|
||||
using namespace villas::node;
|
||||
using namespace villas::node::api;
|
||||
|
||||
Session::Session(Api *a) :
|
||||
runs(0),
|
||||
api(a)
|
||||
Session::Session(lws *w) :
|
||||
wsi(w)
|
||||
{
|
||||
logger = logging.get("api:session");
|
||||
|
||||
lws_context *ctx = lws_get_context(wsi);
|
||||
void *user_ctx = lws_context_user(ctx);
|
||||
|
||||
web = static_cast<Web *>(user_ctx);
|
||||
api = web->getApi();
|
||||
|
||||
if (!api)
|
||||
throw RuntimeError("API is disabled");
|
||||
|
||||
api->sessions.push_back(this);
|
||||
|
||||
logger->debug("Initiated API session: {}", getName());
|
||||
|
||||
state = Session::State::ESTABLISHED;
|
||||
}
|
||||
|
||||
Session::~Session()
|
||||
{
|
||||
api->sessions.remove(this);
|
||||
|
||||
if (request)
|
||||
delete request;
|
||||
|
||||
if (response)
|
||||
delete response;
|
||||
|
||||
logger->debug("Destroyed API session: {}", getName());
|
||||
}
|
||||
|
||||
|
||||
void Session::runPendingActions()
|
||||
void Session::execute()
|
||||
{
|
||||
json_t *req, *resp;
|
||||
Request *req = request.exchange(nullptr);
|
||||
|
||||
while (!request.queue.empty()) {
|
||||
req = request.queue.pop();
|
||||
logger->debug("Running API request: uri={}, method={}", req->uri, req->method);
|
||||
|
||||
runAction(req, &resp);
|
||||
try {
|
||||
response = req->execute();
|
||||
|
||||
json_decref(req);
|
||||
logger->debug("Completed API request: request={}", req->uri, req->method);
|
||||
} catch (const Error &e) {
|
||||
response = new ErrorResponse(this, e);
|
||||
|
||||
response.queue.push(resp);
|
||||
logger->warn("API request failed: uri={}, method={}, code={}: {}", req->uri, req->method, e.code, e.what());
|
||||
} catch (const RuntimeError &e) {
|
||||
response = new ErrorResponse(this, e);
|
||||
|
||||
logger->warn("API request failed: uri={}, method={}: {}", req->uri, req->method, e.what());
|
||||
}
|
||||
|
||||
logger->debug("Ran pending API requests. Triggering on_writeable callback: wsi={}", (void *) wsi);
|
||||
|
||||
web->callbackOnWritable(wsi);
|
||||
}
|
||||
|
||||
std::string Session::getName() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "version=" << version;
|
||||
|
||||
if (wsi) {
|
||||
char name[128];
|
||||
char ip[128];
|
||||
|
||||
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), ip, sizeof(ip));
|
||||
|
||||
ss << ", remote.name=" << name << ", remote.ip=" << ip;
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void Session::shutdown()
|
||||
{
|
||||
state = State::SHUTDOWN;
|
||||
|
||||
web->callbackOnWritable(wsi);
|
||||
}
|
||||
|
||||
void Session::open(void *in, size_t len)
|
||||
{
|
||||
int ret;
|
||||
char buf[32];
|
||||
unsigned long contentLength;
|
||||
|
||||
int meth;
|
||||
std::string uri = reinterpret_cast<char *>(in);
|
||||
|
||||
try {
|
||||
meth = getRequestMethod(wsi);
|
||||
if (meth == Request::Method::UNKNOWN_METHOD)
|
||||
throw RuntimeError("Invalid request method");
|
||||
|
||||
request = RequestFactory::make(this, uri, meth);
|
||||
|
||||
ret = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_CONTENT_LENGTH);
|
||||
if (ret < 0)
|
||||
throw RuntimeError("Failed to get content length");
|
||||
|
||||
try {
|
||||
contentLength = std::stoull(buf);
|
||||
} catch (const std::invalid_argument &) {
|
||||
contentLength = 0;
|
||||
}
|
||||
} catch (const Error &e) {
|
||||
response = new ErrorResponse(this, e);
|
||||
lws_callback_on_writable(wsi);
|
||||
} catch (const RuntimeError &e) {
|
||||
response = new ErrorResponse(this, e);
|
||||
lws_callback_on_writable(wsi);
|
||||
}
|
||||
|
||||
/* This is an OPTIONS request.
|
||||
*
|
||||
* We immediatly send headers and close the connection
|
||||
* without waiting for a POST body */
|
||||
if (meth == Request::Method::OPTIONS)
|
||||
lws_callback_on_writable(wsi);
|
||||
/* This request has no body.
|
||||
* We can reply immediatly */
|
||||
else if (contentLength == 0)
|
||||
api->pending.push(this);
|
||||
else {
|
||||
/* This request has a HTTP body. We wait for more data to arrive */
|
||||
}
|
||||
}
|
||||
|
||||
int Session::runAction(json_t *json_in, json_t **json_out)
|
||||
void Session::body(void *in, size_t len)
|
||||
{
|
||||
requestBuffer.append((const char *) in, len);
|
||||
}
|
||||
|
||||
void Session::bodyComplete()
|
||||
{
|
||||
try {
|
||||
auto *j = requestBuffer.decode();
|
||||
if (!j)
|
||||
throw BadRequest("Failed to decode request payload");
|
||||
|
||||
requestBuffer.clear();
|
||||
|
||||
(*request).setBody(j);
|
||||
|
||||
api->pending.push(this);
|
||||
} catch (const Error &e) {
|
||||
response = new ErrorResponse(this, e);
|
||||
|
||||
logger->warn("Failed to decode API request: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
int Session::writeable()
|
||||
{
|
||||
int ret;
|
||||
const char *action;
|
||||
char *id;
|
||||
|
||||
json_error_t err;
|
||||
json_t *json_args = nullptr;
|
||||
json_t *json_resp = nullptr;
|
||||
if (!headersSent) {
|
||||
int code = HTTP_STATUS_OK;
|
||||
const char *contentType = "application/json";
|
||||
|
||||
ret = json_unpack_ex(json_in, &err, 0, "{ s: s, s: s, s?: o }",
|
||||
"action", &action,
|
||||
"id", &id,
|
||||
"request", &json_args);
|
||||
if (ret) {
|
||||
ret = -100;
|
||||
*json_out = json_pack("{ s: s, s: i }",
|
||||
"error", "invalid request",
|
||||
"code", ret);
|
||||
uint8_t headers[2048], *p = headers, *end = &headers[sizeof(headers) - 1];
|
||||
|
||||
responseBuffer.clear();
|
||||
if (response) {
|
||||
Response *resp = response;
|
||||
|
||||
json_t *json_response = resp->toJson();
|
||||
|
||||
responseBuffer.encode(json_response);
|
||||
|
||||
code = resp->getCode();
|
||||
}
|
||||
|
||||
if (lws_add_http_common_headers(wsi, code, contentType,
|
||||
responseBuffer.size(), /* no content len */
|
||||
&p, end))
|
||||
return 1;
|
||||
|
||||
std::map<const char *, const char *> constantHeaders = {
|
||||
{ "Server:", USER_AGENT },
|
||||
{ "Access-Control-Allow-Origin:", "*" },
|
||||
{ "Access-Control-Allow-Methods:", "GET, POST, OPTIONS" },
|
||||
{ "Access-Control-Allow-Headers:", "Content-Type" },
|
||||
{ "Access-Control-Max-Age:", "86400" }
|
||||
};
|
||||
|
||||
for (auto &hdr : constantHeaders) {
|
||||
if (lws_add_http_header_by_name (wsi,
|
||||
reinterpret_cast<const unsigned char *>(hdr.first),
|
||||
reinterpret_cast<const unsigned char *>(hdr.second),
|
||||
strlen(hdr.second), &p, end))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lws_finalize_write_http_header(wsi, headers, &p, end))
|
||||
return 1;
|
||||
|
||||
/* No wait, until we can send the body */
|
||||
headersSent = true;
|
||||
|
||||
/* Do we have a body to send */
|
||||
if (responseBuffer.size() > 0)
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
logger->debug("Completed API request: action={}, id={}, code={}", action, id, ret);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
ret = lws_write(wsi, (unsigned char *) responseBuffer.data(), responseBuffer.size(), LWS_WRITE_HTTP_FINAL);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
auto acf = plugin::Registry::lookup<ActionFactory>(action);
|
||||
if (!acf) {
|
||||
ret = -101;
|
||||
*json_out = json_pack("{ s: s, s: s, s: i, s: s }",
|
||||
"action", action,
|
||||
"id", id,
|
||||
"code", ret,
|
||||
"error", "action not found");
|
||||
|
||||
logger->debug("Completed API request: action={}, id={}, code={}", action, id, ret);
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
logger->debug("Running API request: action={}, id={}", action, id);
|
||||
int Session::protocolCallback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
{
|
||||
int ret;
|
||||
Session *s = reinterpret_cast<Session *>(user);
|
||||
|
||||
Action *act = acf->make(this);
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
|
||||
try {
|
||||
new (s) Session(wsi);
|
||||
} catch (const RuntimeError &e) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = act->execute(json_args, &json_resp);
|
||||
if (ret)
|
||||
*json_out = json_pack("{ s: s, s: s, s: i, s: s }",
|
||||
"action", action,
|
||||
"id", id,
|
||||
"code", ret,
|
||||
"error", "action failed");
|
||||
else
|
||||
*json_out = json_pack("{ s: s, s: s }",
|
||||
"action", action,
|
||||
"id", id);
|
||||
break;
|
||||
|
||||
if (json_resp)
|
||||
json_object_set_new(*json_out, "response", json_resp);
|
||||
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
|
||||
if (s == nullptr)
|
||||
return -1;
|
||||
|
||||
logger->debug("Completed API request: action={}, id={}, code={}", action, id, ret);
|
||||
s->~Session();
|
||||
|
||||
runs++;
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP:
|
||||
s->open(in, len);
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_BODY:
|
||||
s->body(in, len);
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
|
||||
s->bodyComplete();
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_WRITEABLE:
|
||||
ret = s->writeable();
|
||||
|
||||
/*
|
||||
* HTTP/1.0 no keepalive: close network connection
|
||||
* HTTP/1.1 or HTTP1.0 + KA: wait / process next transaction
|
||||
* HTTP/2: stream ended, parent connection remains up
|
||||
*/
|
||||
if (ret) {
|
||||
if (lws_http_transaction_completed(wsi))
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string Session::getName()
|
||||
int Session::getRequestMethod(struct lws *wsi)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "version=" << version << ", runs=" << runs;
|
||||
|
||||
return ss.str();
|
||||
if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI))
|
||||
return Request::Method::GET;
|
||||
else if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
|
||||
return Request::Method::POST;
|
||||
#if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS) || defined(LWS_HTTP_HEADERS_ALL)
|
||||
else if (lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI))
|
||||
return Request::Method::PUT;
|
||||
else if (lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI))
|
||||
return Request::Method::PATCH;
|
||||
else if (lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI))
|
||||
return Request::Method::OPTIONS;
|
||||
#endif
|
||||
else
|
||||
return Request::Method::UNKNOWN_METHOD;
|
||||
}
|
||||
|
||||
std::string Session::methodToString(int method)
|
||||
{
|
||||
switch (method) {
|
||||
case Request::Method::POST:
|
||||
return "POST";
|
||||
|
||||
case Request::Method::GET:
|
||||
return "GET";
|
||||
|
||||
case Request::Method::DELETE:
|
||||
return "DELETE";
|
||||
|
||||
case Request::Method::PUT:
|
||||
return "PUT";
|
||||
|
||||
case Request::Method::PATCH:
|
||||
return "GPATCHET";
|
||||
|
||||
case Request::Method::OPTIONS:
|
||||
return "OPTIONS";
|
||||
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,212 +0,0 @@
|
|||
/** HTTP Api session.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <villas/node/exceptions.hpp>
|
||||
#include <villas/web.hpp>
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/config.h>
|
||||
#include <villas/api/sessions/http.hpp>
|
||||
|
||||
using namespace villas;
|
||||
using namespace villas::node;
|
||||
using namespace villas::node::api::sessions;
|
||||
|
||||
Http::Http(Api *a, lws *w) :
|
||||
Wsi(a, w),
|
||||
headersSent(false)
|
||||
{
|
||||
int ret, hdrlen, options = -1, version;
|
||||
char *uri;
|
||||
|
||||
if ((hdrlen = lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI)))
|
||||
options = 1;
|
||||
else if ((hdrlen = lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)))
|
||||
options = 0;
|
||||
else
|
||||
throw RuntimeError("Invalid request");
|
||||
|
||||
uri = new char[hdrlen + 1];
|
||||
if (!uri)
|
||||
throw MemoryAllocationError();
|
||||
|
||||
lws_hdr_copy(wsi, uri, hdrlen + 1, options ? WSI_TOKEN_OPTIONS_URI : WSI_TOKEN_POST_URI);
|
||||
|
||||
/* Parse request URI */
|
||||
ret = sscanf(uri, "/api/v%d", (int *) &version);
|
||||
if (ret != 1 || version != api::version)
|
||||
throw RuntimeError("Unsupported API version: {}", version);
|
||||
|
||||
/* This is an OPTIONS request.
|
||||
*
|
||||
* We immediatly send headers and close the connection
|
||||
* without waiting for a POST body */
|
||||
if (options)
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
delete uri;
|
||||
}
|
||||
|
||||
void Http::read(void *in, size_t len)
|
||||
{
|
||||
request.buffer.append((const char *) in, len);
|
||||
}
|
||||
|
||||
int Http::complete()
|
||||
{
|
||||
json_t *req;
|
||||
|
||||
req = request.buffer.decode();
|
||||
if (!req)
|
||||
return 0;
|
||||
|
||||
request.buffer.clear();
|
||||
request.queue.push(req);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Http::write()
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!headersSent) {
|
||||
std::stringstream headers;
|
||||
|
||||
json_t *resp = response.queue.pop();
|
||||
|
||||
response.buffer.clear();
|
||||
response.buffer.encode(resp);
|
||||
|
||||
json_decref(resp);
|
||||
|
||||
headers << "HTTP/1.1 200 OK\r\n"
|
||||
<< "Content-type: application/json\r\n"
|
||||
<< "User-agent: " USER_AGENT "\r\n"
|
||||
<< "Connection: close\r\n"
|
||||
<< "Content-Length: " << response.buffer.size() << "\r\n"
|
||||
<< "Access-Control-Allow-Origin: *\r\n"
|
||||
<< "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"
|
||||
<< "Access-Control-Allow-Headers: Content-Type\r\n"
|
||||
<< "Access-Control-Max-Age: 86400\r\n"
|
||||
<< "\r\n";
|
||||
|
||||
ret = lws_write(wsi, (unsigned char *) headers.str().data(), headers.str().size(), LWS_WRITE_HTTP_HEADERS);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
/* No wait, until we can send the body */
|
||||
headersSent = true;
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
ret = lws_write(wsi, (unsigned char *) response.buffer.data(), response.buffer.size(), LWS_WRITE_HTTP_FINAL);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
headersSent = false;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Http::getName()
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << Wsi::getName() << ", mode=http";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
int api_http_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lws_context *ctx = lws_get_context(wsi);
|
||||
void *user_ctx = lws_context_user(ctx);
|
||||
|
||||
Web *w = static_cast<Web *>(user_ctx);
|
||||
Http *s = static_cast<Http *>(user);
|
||||
Api *a = w->getApi();
|
||||
|
||||
if (a == nullptr)
|
||||
return -1;
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
|
||||
if (a == nullptr)
|
||||
return -1;
|
||||
|
||||
try {
|
||||
new (s) Http(a, wsi);
|
||||
|
||||
a->sessions.push_back(s);
|
||||
}
|
||||
catch (RuntimeError &e) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
|
||||
if (s == nullptr)
|
||||
return -1;
|
||||
|
||||
a->sessions.remove(s);
|
||||
|
||||
s->~Http();
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_BODY:
|
||||
s->read(in, len);
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
|
||||
ret = s->complete();
|
||||
if (ret)
|
||||
a->pending.push(s);
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_WRITEABLE:
|
||||
ret = s->write();
|
||||
if (ret) {
|
||||
if (lws_http_transaction_completed(wsi))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
/** Unix domain socket Api session.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <villas/compat.hpp>
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/api/sessions/socket.hpp>
|
||||
|
||||
using namespace villas::node::api::sessions;
|
||||
|
||||
Socket::Socket(Api *a, int s) :
|
||||
Session(a),
|
||||
sd(s)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Socket::~Socket()
|
||||
{
|
||||
close(sd);
|
||||
}
|
||||
|
||||
std::string Socket::getName()
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << Session::getName() << ", mode=socket";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
int Socket::read()
|
||||
{
|
||||
json_t *j;
|
||||
json_error_t err;
|
||||
|
||||
j = json_loadfd(sd, JSON_DISABLE_EOF_CHECK, &err);
|
||||
if (!j)
|
||||
return -1;
|
||||
|
||||
request.queue.push(j);
|
||||
|
||||
api->pending.push(this);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Socket::write()
|
||||
{
|
||||
int ret;
|
||||
json_t *j;
|
||||
|
||||
while (!response.queue.empty()) {
|
||||
j = response.queue.pop();
|
||||
|
||||
ret = json_dumpfd(j, sd, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
char nl = '\n';
|
||||
send(sd, &nl, 1, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
/** WebSockets Api session.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <villas/web.hpp>
|
||||
#include <villas/node/exceptions.hpp>
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/api/sessions/websocket.hpp>
|
||||
|
||||
using namespace villas::node;
|
||||
using namespace villas::node::api::sessions;
|
||||
|
||||
WebSocket::WebSocket(Api *a, lws *w) :
|
||||
Wsi(a, w)
|
||||
{
|
||||
/* Parse request URI */
|
||||
int version;
|
||||
char uri[64];
|
||||
lws_hdr_copy(wsi, uri, sizeof(uri), WSI_TOKEN_GET_URI);
|
||||
|
||||
sscanf(uri, "/v%d", (int *) &version);
|
||||
|
||||
if (version != api::version)
|
||||
throw RuntimeError("Unsupported API version: {}", version);
|
||||
}
|
||||
|
||||
int WebSocket::read(void *in, size_t len)
|
||||
{
|
||||
json_t *req;
|
||||
|
||||
if (lws_is_first_fragment(wsi))
|
||||
request.buffer.clear();
|
||||
|
||||
request.buffer.append((const char *) in, len);
|
||||
|
||||
if (lws_is_final_fragment(wsi)) {
|
||||
req = request.buffer.decode();
|
||||
if (!req)
|
||||
return 0;
|
||||
|
||||
request.queue.push(req);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocket::write()
|
||||
{
|
||||
json_t *resp;
|
||||
|
||||
if (state == State::SHUTDOWN)
|
||||
return -1;
|
||||
|
||||
resp = response.queue.pop();
|
||||
|
||||
char pad[LWS_PRE];
|
||||
|
||||
response.buffer.clear();
|
||||
response.buffer.append(pad, sizeof(pad));
|
||||
response.buffer.encode(resp);
|
||||
|
||||
json_decref(resp);
|
||||
|
||||
return lws_write(wsi, (unsigned char *) response.buffer.data() + LWS_PRE, response.buffer.size() - LWS_PRE, LWS_WRITE_TEXT);
|
||||
}
|
||||
|
||||
std::string WebSocket::getName()
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << Wsi::getName() << ", mode=ws";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
int api_ws_protocol_cb(lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lws_context *ctx = lws_get_context(wsi);
|
||||
void *user_ctx = lws_context_user(ctx);
|
||||
|
||||
Web *w = static_cast<Web *>(user_ctx);
|
||||
WebSocket *s = static_cast<WebSocket *>(user);
|
||||
Api *a = w->getApi();
|
||||
|
||||
if (a == nullptr)
|
||||
return -1;
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_ESTABLISHED:
|
||||
if (a == nullptr) {
|
||||
std::string err = "API disabled";
|
||||
lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (unsigned char *) err.data(), err.length());
|
||||
return -1;
|
||||
}
|
||||
|
||||
new (s) WebSocket(a, wsi);
|
||||
|
||||
a->sessions.push_back(s);
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLOSED:
|
||||
|
||||
s->~WebSocket();
|
||||
|
||||
a->sessions.remove(s);
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
ret = s->read(in, len);
|
||||
if (ret)
|
||||
a->pending.push(s);
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_SERVER_WRITEABLE:
|
||||
ret = s->write();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/** LWS Api session.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASnode
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <villas/api/sessions/wsi.hpp>
|
||||
|
||||
using namespace villas::node::api::sessions;
|
||||
|
||||
void Wsi::runPendingActions()
|
||||
{
|
||||
Session::runPendingActions();
|
||||
|
||||
logger->debug("Ran pending actions. Triggering on_writeable callback: wsi={}", (void *) wsi);
|
||||
|
||||
web->callbackOnWritable(wsi);
|
||||
}
|
||||
|
||||
void Wsi::shutdown()
|
||||
{
|
||||
state = State::SHUTDOWN;
|
||||
|
||||
web->callbackOnWritable(wsi);
|
||||
}
|
||||
|
||||
std::string Wsi::getName()
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << Session::getName();
|
||||
|
||||
if (wsi) {
|
||||
char name[128];
|
||||
char ip[128];
|
||||
|
||||
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), ip, sizeof(ip));
|
||||
|
||||
ss << ", remote.name=" << name << ", remote.ip=" << ip;
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
|
||||
}
|
||||
|
||||
Wsi::Wsi(Api *a, lws *w) :
|
||||
Session(a),
|
||||
wsi(w)
|
||||
{
|
||||
state = Session::State::ESTABLISHED;
|
||||
|
||||
lws_context *user_ctx = lws_get_context(wsi);
|
||||
void *ctx = lws_context_user(user_ctx);
|
||||
|
||||
web = static_cast<Web*>(ctx);
|
||||
}
|
Loading…
Add table
Reference in a new issue