2018-10-20 14:20:06 +02:00
|
|
|
/* API session.
|
|
|
|
*
|
2022-03-15 09:18:01 -04:00
|
|
|
* Author: Steffen Vogel <post@steffenvogel.de>
|
2022-03-15 09:28:57 -04:00
|
|
|
* SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
|
2022-07-04 18:20:03 +02:00
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
2018-10-20 14:20:06 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
#include <libwebsockets.h>
|
|
|
|
|
2021-08-10 10:12:48 -04:00
|
|
|
#include <villas/node/memory.hpp>
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/web.hpp>
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2020-08-17 17:03:54 +02:00
|
|
|
#include <villas/api/request.hpp>
|
|
|
|
#include <villas/api/response.hpp>
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/api/session.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;
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
Session::Session(lws *w)
|
|
|
|
: version(Version::VERSION_2), wsi(w), logger(logging.get("api:session")) {
|
|
|
|
lws_context *ctx = lws_get_context(wsi);
|
|
|
|
void *user_ctx = lws_context_user(ctx);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
web = static_cast<Web *>(user_ctx);
|
|
|
|
api = web->getApi();
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (!api)
|
|
|
|
throw RuntimeError("API is disabled");
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
api->sessions.push_back(this);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
logger->debug("Initiated API session: {}", getName());
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
state = Session::State::ESTABLISHED;
|
2018-10-20 14:20:06 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
Session::~Session() {
|
|
|
|
api->sessions.remove(this);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
logger->debug("Destroyed API session: {}", getName());
|
2018-10-20 14:20:06 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void Session::execute() {
|
|
|
|
logger->debug("Running API request: {}", request->toString());
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
try {
|
|
|
|
response = std::unique_ptr<Response>(request->execute());
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
logger->debug("Completed API request: {}", request->toString());
|
|
|
|
} catch (const Error &e) {
|
|
|
|
response = std::make_unique<ErrorResponse>(this, e);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
logger->warn("API request failed: {}, code={}: {}", request->toString(),
|
|
|
|
e.code, e.what());
|
|
|
|
} catch (const RuntimeError &e) {
|
|
|
|
response = std::make_unique<ErrorResponse>(this, e);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
logger->warn("API request failed: {}: {}", request->toString(), e.what());
|
|
|
|
}
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
logger->debug(
|
|
|
|
"Ran pending API requests. Triggering on_writeable callback: wsi={}",
|
|
|
|
(void *)wsi);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
web->callbackOnWritable(wsi);
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
std::string Session::getName() const {
|
|
|
|
std::stringstream ss;
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ss << "version=" << version;
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
if (wsi) {
|
|
|
|
char name[128];
|
|
|
|
char ip[128];
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +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
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ss << ", remote.name=" << name << ", remote.ip=" << ip;
|
|
|
|
}
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return ss.str();
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void Session::shutdown() {
|
|
|
|
state = State::SHUTDOWN;
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
web->callbackOnWritable(wsi);
|
2018-10-20 14:20:06 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void Session::open(void *in, size_t len) {
|
|
|
|
int ret;
|
|
|
|
char buf[32];
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
auto uri = reinterpret_cast<char *>(in);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
try {
|
|
|
|
unsigned int len;
|
|
|
|
auto method = getRequestMethod();
|
|
|
|
if (method == Method::UNKNOWN)
|
|
|
|
throw RuntimeError("Invalid request method");
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ret = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_CONTENT_LENGTH);
|
|
|
|
if (ret < 0)
|
|
|
|
throw RuntimeError("Failed to get content length");
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
try {
|
|
|
|
len = std::stoull(buf);
|
|
|
|
} catch (const std::invalid_argument &) {
|
|
|
|
len = 0;
|
|
|
|
}
|
2020-08-28 15:58:10 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
request = std::unique_ptr<Request>(
|
|
|
|
RequestFactory::create(this, uri, method, len));
|
2020-10-20 22:17:55 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
/* This is an OPTIONS request.
|
2024-02-29 21:47:13 +01:00
|
|
|
*
|
|
|
|
* We immediatly send headers and close the connection
|
|
|
|
* without waiting for a POST body */
|
2023-09-07 11:46:39 +02:00
|
|
|
if (method == Method::OPTIONS)
|
|
|
|
lws_callback_on_writable(wsi);
|
|
|
|
/* This request has no body.
|
2024-02-29 21:47:13 +01:00
|
|
|
* We can reply immediatly */
|
2023-09-07 11:46:39 +02:00
|
|
|
else if (len == 0)
|
|
|
|
api->pending.push(this);
|
|
|
|
else {
|
|
|
|
// This request has a HTTP body. We wait for more data to arrive
|
|
|
|
}
|
|
|
|
} catch (const Error &e) {
|
|
|
|
response = std::make_unique<ErrorResponse>(this, e);
|
|
|
|
lws_callback_on_writable(wsi);
|
|
|
|
} catch (const RuntimeError &e) {
|
|
|
|
response = std::make_unique<ErrorResponse>(this, e);
|
|
|
|
lws_callback_on_writable(wsi);
|
|
|
|
}
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void Session::body(void *in, size_t len) {
|
|
|
|
request->buffer.append((const char *)in, len);
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
void Session::bodyComplete() {
|
|
|
|
try {
|
|
|
|
request->decode();
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +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
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
logger->warn("Failed to decode API request: {}", e.what());
|
|
|
|
}
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int Session::writeable() {
|
|
|
|
if (!response)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!headersSent) {
|
|
|
|
response->writeHeaders(wsi);
|
|
|
|
|
|
|
|
// Now wait, until we can send the body
|
|
|
|
headersSent = true;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
if (response)
|
|
|
|
return response->writeBody(wsi);
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +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
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
switch (reason) {
|
|
|
|
case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
|
|
|
|
try {
|
|
|
|
new (s) Session(wsi);
|
|
|
|
} catch (const RuntimeError &e) {
|
|
|
|
return -1;
|
|
|
|
}
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
|
|
|
|
if (s == nullptr)
|
|
|
|
return -1;
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
s->~Session();
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case LWS_CALLBACK_HTTP:
|
|
|
|
s->open(in, len);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case LWS_CALLBACK_HTTP_BODY:
|
|
|
|
s->body(in, len);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
|
|
|
|
s->bodyComplete();
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case LWS_CALLBACK_HTTP_WRITEABLE:
|
|
|
|
ret = s->writeable();
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
/*
|
2024-02-29 21:47:13 +01:00
|
|
|
* 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
|
|
|
|
*/
|
2023-09-07 11:46:39 +02:00
|
|
|
if (ret) {
|
|
|
|
if (lws_http_transaction_completed(wsi))
|
|
|
|
return -1;
|
|
|
|
} else
|
|
|
|
lws_callback_on_writable(wsi);
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return 0;
|
2018-10-20 14:20:06 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
Session::Method Session::getRequestMethod() const {
|
|
|
|
if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI))
|
|
|
|
return Method::GET;
|
|
|
|
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)
|
2023-09-07 11:46:39 +02:00
|
|
|
else if (lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI))
|
|
|
|
return Method::PUT;
|
|
|
|
else if (lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI))
|
|
|
|
return Method::PATCH;
|
|
|
|
else if (lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI))
|
|
|
|
return Method::OPTIONS;
|
2020-08-17 17:03:54 +02:00
|
|
|
#endif
|
2023-09-07 11:46:39 +02:00
|
|
|
else
|
|
|
|
return Method::UNKNOWN;
|
2020-08-17 17:03:54 +02:00
|
|
|
}
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
std::string Session::methodToString(Method method) {
|
|
|
|
switch (method) {
|
|
|
|
case Method::POST:
|
|
|
|
return "POST";
|
2018-10-20 14:20:06 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case Method::GET:
|
|
|
|
return "GET";
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case Method::DELETE:
|
|
|
|
return "DELETE";
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case Method::PUT:
|
|
|
|
return "PUT";
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case Method::PATCH:
|
|
|
|
return "GPATCHET";
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
case Method::OPTIONS:
|
|
|
|
return "OPTIONS";
|
2020-08-17 17:03:54 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
default:
|
|
|
|
return "UNKNOWN";
|
|
|
|
}
|
2018-10-20 14:20:06 +02:00
|
|
|
}
|