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/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) :
|
2020-10-20 22:17:55 +02:00
|
|
|
version(Version::VERSION_2),
|
2020-09-11 14:57:05 +02:00
|
|
|
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()
|
|
|
|
{
|
2020-10-20 22:17:55 +02:00
|
|
|
logger->debug("Running API request: {}", request->toString());
|
2020-08-17 17:03:54 +02:00
|
|
|
|
|
|
|
try {
|
2020-10-20 22:17:55 +02:00
|
|
|
response = std::unique_ptr<Response>(request->execute());
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2020-10-20 22:17:55 +02:00
|
|
|
logger->debug("Completed API request: {}", request->toString());
|
2020-08-17 17:03:54 +02:00
|
|
|
} catch (const Error &e) {
|
2020-10-20 22:17:55 +02:00
|
|
|
response = std::make_unique<ErrorResponse>(this, e);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2020-10-20 22:17:55 +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) {
|
2020-10-20 22:17:55 +02:00
|
|
|
response = std::make_unique<ErrorResponse>(this, e);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2020-10-20 22:17:55 +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];
|
|
|
|
|
2020-10-20 22:17:55 +02:00
|
|
|
auto uri = reinterpret_cast<char *>(in);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
|
|
|
try {
|
2020-10-20 22:17:55 +02:00
|
|
|
unsigned int len;
|
2020-09-30 17:56:12 +02:00
|
|
|
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 {
|
2020-10-20 22:17:55 +02:00
|
|
|
len = std::stoull(buf);
|
2020-08-17 17:03:54 +02:00
|
|
|
} catch (const std::invalid_argument &) {
|
2020-10-20 22:17:55 +02:00
|
|
|
len = 0;
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
2020-08-28 15:58:10 +02:00
|
|
|
|
2020-10-20 22:17:55 +02:00
|
|
|
request = std::unique_ptr<Request>(RequestFactory::create(this, uri, method, len));
|
|
|
|
|
2020-08-28 15:58:10 +02:00
|
|
|
/* This is an OPTIONS request.
|
|
|
|
*
|
|
|
|
* We immediatly send headers and close the connection
|
|
|
|
* without waiting for a POST body */
|
2020-09-30 17:56:12 +02:00
|
|
|
if (method == Method::OPTIONS)
|
2020-08-28 15:58:10 +02:00
|
|
|
lws_callback_on_writable(wsi);
|
|
|
|
/* This request has no body.
|
|
|
|
* We can reply immediatly */
|
2020-10-20 22:17:55 +02:00
|
|
|
else if (len == 0)
|
2020-08-28 15:58:10 +02:00
|
|
|
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) {
|
2020-10-20 22:17:55 +02:00
|
|
|
response = std::make_unique<ErrorResponse>(this, e);
|
2020-08-17 17:03:54 +02:00
|
|
|
lws_callback_on_writable(wsi);
|
|
|
|
} catch (const RuntimeError &e) {
|
2020-10-20 22:17:55 +02:00
|
|
|
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)
|
|
|
|
{
|
2020-10-20 22:17:55 +02:00
|
|
|
request->buffer.append((const char *) in, len);
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Session::bodyComplete()
|
|
|
|
{
|
|
|
|
try {
|
2020-10-20 22:17:55 +02:00
|
|
|
request->decode();
|
2020-08-17 17:03:54 +02:00
|
|
|
|
|
|
|
api->pending.push(this);
|
|
|
|
} catch (const Error &e) {
|
2020-10-20 22:17:55 +02:00
|
|
|
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) {
|
2020-10-20 22:17:55 +02:00
|
|
|
if (response)
|
|
|
|
response->writeHeaders(wsi);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2020-10-20 22:17:55 +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;
|
|
|
|
}
|
2020-10-20 22:17:55 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-09-30 17:56:12 +02:00
|
|
|
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))
|
2020-09-30 17:56:12 +02:00
|
|
|
return Method::GET;
|
2020-08-17 17:03:54 +02:00
|
|
|
else if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
|
2020-09-30 17:56:12 +02:00
|
|
|
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))
|
2020-09-30 17:56:12 +02:00
|
|
|
return Method::PUT;
|
2020-08-17 17:03:54 +02:00
|
|
|
else if (lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI))
|
2020-09-30 17:56:12 +02:00
|
|
|
return Method::PATCH;
|
2020-08-17 17:03:54 +02:00
|
|
|
else if (lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI))
|
2020-09-30 17:56:12 +02:00
|
|
|
return Method::OPTIONS;
|
2020-08-17 17:03:54 +02:00
|
|
|
#endif
|
|
|
|
else
|
2020-09-30 17:56:12 +02:00
|
|
|
return Method::UNKNOWN;
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2020-09-30 17:56:12 +02:00
|
|
|
std::string Session::methodToString(Method method)
|
2020-08-17 17:03:54 +02:00
|
|
|
{
|
|
|
|
switch (method) {
|
2020-09-30 17:56:12 +02:00
|
|
|
case Method::POST:
|
2020-08-17 17:03:54 +02:00
|
|
|
return "POST";
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2020-09-30 17:56:12 +02:00
|
|
|
case Method::GET:
|
2020-08-17 17:03:54 +02:00
|
|
|
return "GET";
|
|
|
|
|
2020-09-30 17:56:12 +02:00
|
|
|
case Method::DELETE:
|
2020-08-17 17:03:54 +02:00
|
|
|
return "DELETE";
|
|
|
|
|
2020-09-30 17:56:12 +02:00
|
|
|
case Method::PUT:
|
2020-08-17 17:03:54 +02:00
|
|
|
return "PUT";
|
|
|
|
|
2020-09-30 17:56:12 +02:00
|
|
|
case Method::PATCH:
|
2020-08-17 17:03:54 +02:00
|
|
|
return "GPATCHET";
|
|
|
|
|
2020-09-30 17:56:12 +02:00
|
|
|
case Method::OPTIONS:
|
2020-08-17 17:03:54 +02:00
|
|
|
return "OPTIONS";
|
|
|
|
|
|
|
|
default:
|
|
|
|
return "UNKNOWN";
|
|
|
|
}
|
2018-10-20 14:20:06 +02:00
|
|
|
}
|