1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00
VILLASnode/lib/api/session.cpp

300 lines
6.6 KiB
C++
Raw Permalink Normal View History

2018-10-20 14:20:06 +02:00
/** API session.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
2020-01-20 17:17:00 +01:00
* @copyright 2014-2020, Institute for Automation of Complex Power Systems, EONERC
2018-10-20 14:20:06 +02:00
* @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/plugin.h>
#include <villas/memory.h>
#include <villas/api/session.hpp>
2020-08-17 17:03:54 +02:00
#include <villas/api/request.hpp>
#include <villas/api/response.hpp>
2018-10-20 14:20:06 +02:00
using namespace villas;
2020-08-17 17:03:54 +02:00
using namespace villas::node;
2018-10-20 14:20:06 +02:00
using namespace villas::node::api;
2020-08-17 17:03:54 +02:00
Session::Session(lws *w) :
version(Version::VERSION_2),
wsi(w),
logger(logging.get("api:session"))
2018-10-20 14:20:06 +02:00
{
2020-08-17 17:03:54 +02:00
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);
2018-10-20 14:20:06 +02:00
logger->debug("Initiated API session: {}", getName());
2020-08-17 17:03:54 +02:00
state = Session::State::ESTABLISHED;
2018-10-20 14:20:06 +02:00
}
Session::~Session()
{
2020-08-17 17:03:54 +02:00
api->sessions.remove(this);
2018-12-02 03:18:09 +01:00
logger->debug("Destroyed API session: {}", getName());
2018-10-20 14:20:06 +02:00
}
2020-08-17 17:03:54 +02:00
void Session::execute()
{
logger->debug("Running API request: {}", request->toString());
2020-08-17 17:03:54 +02:00
try {
response = std::unique_ptr<Response>(request->execute());
2018-10-20 14:20:06 +02:00
logger->debug("Completed API request: {}", request->toString());
2020-08-17 17:03:54 +02:00
} catch (const Error &e) {
response = std::make_unique<ErrorResponse>(this, e);
2020-08-17 17:03:54 +02:00
logger->warn("API request failed: {}, code={}: {}", request->toString(), e.code, e.what());
2020-08-17 17:03:54 +02:00
} catch (const RuntimeError &e) {
response = std::make_unique<ErrorResponse>(this, e);
2020-08-17 17:03:54 +02:00
logger->warn("API request failed: {}: {}", request->toString(), e.what());
2020-08-17 17:03:54 +02:00
}
logger->debug("Ran pending API requests. Triggering on_writeable callback: wsi={}", (void *) wsi);
web->callbackOnWritable(wsi);
}
std::string Session::getName() const
2018-10-20 14:20:06 +02:00
{
2020-08-17 17:03:54 +02:00
std::stringstream ss;
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
ss << "version=" << version;
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
if (wsi) {
char name[128];
char ip[128];
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), ip, sizeof(ip));
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
ss << ", remote.name=" << name << ", remote.ip=" << ip;
2018-10-20 14:20:06 +02:00
}
2020-08-17 17:03:54 +02:00
return ss.str();
}
void Session::shutdown()
{
state = State::SHUTDOWN;
web->callbackOnWritable(wsi);
2018-10-20 14:20:06 +02:00
}
2020-08-17 17:03:54 +02:00
void Session::open(void *in, size_t len)
2018-10-20 14:20:06 +02:00
{
int ret;
2020-08-17 17:03:54 +02:00
char buf[32];
auto uri = reinterpret_cast<char *>(in);
2020-08-17 17:03:54 +02:00
try {
unsigned int len;
auto method = getRequestMethod();
if (method == Method::UNKNOWN)
2020-08-17 17:03:54 +02:00
throw RuntimeError("Invalid request method");
ret = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_CONTENT_LENGTH);
if (ret < 0)
throw RuntimeError("Failed to get content length");
try {
len = std::stoull(buf);
2020-08-17 17:03:54 +02:00
} catch (const std::invalid_argument &) {
len = 0;
2020-08-17 17:03:54 +02:00
}
request = std::unique_ptr<Request>(RequestFactory::create(this, uri, method, len));
/* This is an OPTIONS request.
*
* We immediatly send headers and close the connection
* without waiting for a POST body */
if (method == Method::OPTIONS)
lws_callback_on_writable(wsi);
/* This request has no body.
* We can reply immediatly */
else if (len == 0)
api->pending.push(this);
else {
/* This request has a HTTP body. We wait for more data to arrive */
}
2020-08-17 17:03:54 +02:00
} catch (const Error &e) {
response = std::make_unique<ErrorResponse>(this, e);
2020-08-17 17:03:54 +02:00
lws_callback_on_writable(wsi);
} catch (const RuntimeError &e) {
response = std::make_unique<ErrorResponse>(this, e);
2020-08-17 17:03:54 +02:00
lws_callback_on_writable(wsi);
}
}
void Session::body(void *in, size_t len)
{
request->buffer.append((const char *) in, len);
2020-08-17 17:03:54 +02:00
}
void Session::bodyComplete()
{
try {
request->decode();
2020-08-17 17:03:54 +02:00
api->pending.push(this);
} catch (const Error &e) {
response = std::make_unique<ErrorResponse>(this, e);
2020-08-17 17:03:54 +02:00
logger->warn("Failed to decode API request: {}", e.what());
}
}
int Session::writeable()
{
if (!headersSent) {
if (response)
response->writeHeaders(wsi);
2020-08-17 17:03:54 +02:00
/* Now wait, until we can send the body */
2020-08-17 17:03:54 +02:00
headersSent = true;
2018-10-20 14:20:06 +02:00
return 0;
}
else
return response->writeBody(wsi);
2020-08-17 17:03:54 +02:00
}
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
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);
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
switch (reason) {
case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
try {
new (s) Session(wsi);
} catch (const RuntimeError &e) {
return -1;
}
break;
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
if (s == nullptr)
return -1;
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
s->~Session();
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
break;
2018-10-20 14:20:06 +02:00
2020-08-17 17:03:54 +02:00
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;
}
2018-10-20 14:20:06 +02:00
return 0;
}
Session::Method Session::getRequestMethod() const
2018-10-20 14:20:06 +02:00
{
2020-08-17 17:03:54 +02:00
if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI))
return Method::GET;
2020-08-17 17:03:54 +02:00
else if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
return Method::POST;
2020-08-17 17:03:54 +02:00
#if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS) || defined(LWS_HTTP_HEADERS_ALL)
else if (lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI))
return Method::PUT;
2020-08-17 17:03:54 +02:00
else if (lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI))
return Method::PATCH;
2020-08-17 17:03:54 +02:00
else if (lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI))
return Method::OPTIONS;
2020-08-17 17:03:54 +02:00
#endif
else
return Method::UNKNOWN;
2020-08-17 17:03:54 +02:00
}
2018-10-20 14:20:06 +02:00
std::string Session::methodToString(Method method)
2020-08-17 17:03:54 +02:00
{
switch (method) {
case Method::POST:
2020-08-17 17:03:54 +02:00
return "POST";
2018-10-20 14:20:06 +02:00
case Method::GET:
2020-08-17 17:03:54 +02:00
return "GET";
case Method::DELETE:
2020-08-17 17:03:54 +02:00
return "DELETE";
case Method::PUT:
2020-08-17 17:03:54 +02:00
return "PUT";
case Method::PATCH:
2020-08-17 17:03:54 +02:00
return "GPATCHET";
case Method::OPTIONS:
2020-08-17 17:03:54 +02:00
return "OPTIONS";
default:
return "UNKNOWN";
}
2018-10-20 14:20:06 +02:00
}