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/web.cpp

295 lines
6.7 KiB
C++
Raw Permalink Normal View History

2018-10-20 14:20:06 +02:00
/** LWS-releated functions.
*
* @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 <libwebsockets.h>
#include <cstring>
2018-10-20 14:20:06 +02:00
#include <villas/node/config.h>
#include <villas/utils.hpp>
2018-10-20 14:20:06 +02:00
#include <villas/web.hpp>
#include <villas/api.hpp>
#include <villas/api/session.hpp>
2019-03-26 07:11:27 +01:00
#include <villas/node/exceptions.hpp>
#include <villas/nodes/websocket.hpp>
2018-10-20 14:20:06 +02:00
using namespace villas;
using namespace villas::node;
/* Forward declarations */
lws_callback_function websocket_protocol_cb;
/** List of libwebsockets protocols. */
lws_protocols protocols[] = {
{
.name = "http",
.callback = lws_callback_http_dummy,
.per_session_data_size = 0,
.rx_buffer_size = 1024
},
#ifdef WITH_API
{
.name = "http-api",
.callback = api::Session::protocolCallback,
.per_session_data_size = sizeof(api::Session),
2018-10-20 14:20:06 +02:00
.rx_buffer_size = 1024
},
#endif /* WITH_API */
#ifdef WITH_NODE_WEBSOCKET
2018-10-20 14:20:06 +02:00
{
.name = "live",
.callback = websocket_protocol_cb,
.per_session_data_size = sizeof(websocket_connection),
.rx_buffer_size = 0
},
#endif /* WITH_NODE_WEBSOCKET */
{
.name = nullptr /* terminator */
}
2018-10-20 14:20:06 +02:00
};
/** List of libwebsockets mounts. */
static lws_http_mount mounts[] = {
#ifdef WITH_API
{
.mount_next = &mounts[1], /* linked-list "next" */
.mountpoint = "/api/v2", /* mountpoint URL */
.origin = "http-api", /* protocol */
.def = nullptr,
.protocol = "http-api",
.cgienv = nullptr,
.extra_mimetypes = nullptr,
.interpret = nullptr,
.cgi_timeout = 0,
.cache_max_age = 0,
.auth_mask = 0,
.cache_reusable = 0,
.cache_revalidate = 0,
.cache_intermediaries = 0,
.origin_protocol = LWSMPRO_CALLBACK, /* dynamic */
.mountpoint_len = 7 /* char count */
2018-10-20 14:20:06 +02:00
},
#endif /* WITH_API */
{
.mount_next = nullptr,
.mountpoint = "/",
.origin = nullptr,
.def = "/index.html",
.protocol = nullptr,
.cgienv = nullptr,
.extra_mimetypes = nullptr,
.interpret = nullptr,
.cgi_timeout = 0,
.cache_max_age = 0,
.auth_mask = 0,
.cache_reusable = 0,
.cache_revalidate = 0,
2018-10-20 14:20:06 +02:00
.cache_intermediaries = 0,
.origin_protocol = LWSMPRO_FILE,
.mountpoint_len = 1
2018-10-20 14:20:06 +02:00
}
};
/** List of libwebsockets extensions. */
static const lws_extension extensions[] = {
#ifdef LWS_DEFLATE_FOUND
2018-10-20 14:20:06 +02:00
{
.name = "permessage-deflate",
.callback = lws_extension_callback_pm_deflate,
.client_offer = "permessage-deflate"
2018-10-20 14:20:06 +02:00
},
{
.name = "deflate-frame",
.callback = lws_extension_callback_pm_deflate,
.client_offer = "deflate_frame"
2018-10-20 14:20:06 +02:00
},
#endif /* LWS_DEFLATE_FOUND */
2018-10-20 14:20:06 +02:00
{ nullptr /* terminator */ }
};
void Web::lwsLogger(int lws_lvl, const char *msg) {
char *nl;
nl = (char *) strchr(msg, '\n');
if (nl)
*nl = 0;
/* Decrease severity for some errors. */
if (strstr(msg, "Unable to open") == msg)
lws_lvl = LLL_WARN;
Logger logger = logging.get("lws");
switch (lws_lvl) {
case LLL_ERR:
logger->error("{}", msg);
break;
case LLL_WARN:
logger->warn("{}", msg);
break;
case LLL_NOTICE:
case LLL_INFO:
logger->info("{}", msg);
break;
default: /* Everything else is debug */
logger->debug("{}", msg);
}
}
void Web::worker()
{
lws *wsi;
2018-12-04 00:25:40 +01:00
logger->info("Started worker");
2018-11-02 14:59:22 +01:00
2018-10-20 14:20:06 +02:00
while (running) {
lws_service(context, 10);
2018-10-20 14:20:06 +02:00
while (!writables.empty()) {
wsi = writables.pop();
2018-10-20 14:20:06 +02:00
lws_callback_on_writable(wsi);
}
}
2018-12-04 00:25:40 +01:00
logger->info("Stopped worker");
2018-10-20 14:20:06 +02:00
}
Web::Web(Api *a) :
2019-06-23 16:13:23 +02:00
state(State::INITIALIZED),
logger(logging.get("web")),
context(nullptr),
vhost(nullptr),
port(getuid() > 0 ? 8080 : 80),
2018-10-20 14:20:06 +02:00
htdocs(WEB_PATH),
api(a)
{
/** @todo: Port to C++: add LLL_DEBUG and others if trace log level is activated */
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE, lwsLogger);
2018-10-20 14:20:06 +02:00
}
2021-02-16 14:15:14 +01:00
int Web::parse(json_t *json)
2018-10-20 14:20:06 +02:00
{
int ret, enabled = 1;
const char *cert = nullptr;
const char *pkey = nullptr;
const char *htd = nullptr;
json_error_t err;
2021-02-16 14:15:14 +01:00
ret = json_unpack_ex(json, &err, JSON_STRICT, "{ s?: s, s?: s, s?: s, s?: i, s?: b }",
2018-10-20 14:20:06 +02:00
"ssl_cert", &cert,
"ssl_private_key", &pkey,
"htdocs", &htd,
"port", &port,
"enabled", &enabled
);
if (ret)
2021-02-16 14:15:14 +01:00
throw ConfigError(json, err, "node-config-http");
2018-10-20 14:20:06 +02:00
if (cert)
ssl_cert = cert;
if (pkey)
ssl_private_key = pkey;
if (htd)
htdocs = htd;
if (!enabled)
port = CONTEXT_PORT_NO_LISTEN;
2019-06-23 16:13:23 +02:00
state = State::PARSED;
2018-10-20 14:20:06 +02:00
return 0;
}
void Web::start()
{
/* Start server */
lws_context_creation_info ctx_info;
2019-01-22 15:49:16 +01:00
memset(&ctx_info, 0, sizeof(ctx_info));
ctx_info.port = port;
ctx_info.protocols = protocols;
ctx_info.extensions = extensions;
ctx_info.ssl_cert_filepath = ssl_cert.empty() ? nullptr : ssl_cert.c_str();
ctx_info.ssl_private_key_filepath = ssl_private_key.empty() ? nullptr : ssl_private_key.c_str();
ctx_info.gid = -1;
ctx_info.uid = -1;
ctx_info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
ctx_info.user = (void *) this;
2018-12-04 00:26:13 +01:00
#if LWS_LIBRARY_VERSION_NUMBER <= 3000000
/* See: https://github.com/warmcat/libwebsockets/issues/1249 */
ctx_info.max_http_header_pool = 1024;
2018-12-04 00:26:13 +01:00
#endif
ctx_info.mounts = mounts;
2018-10-20 14:20:06 +02:00
2021-02-16 14:15:14 +01:00
logger->info("Starting sub-system: htdocs={}", htdocs);
2018-11-02 14:59:22 +01:00
2018-10-20 14:20:06 +02:00
/* update web root of mount point */
mounts[ARRAY_LEN(mounts)-1].origin = htdocs.c_str();
2018-10-20 14:20:06 +02:00
context = lws_create_context(&ctx_info);
if (context == nullptr)
throw RuntimeError("Failed to initialize server context");
2018-10-20 14:20:06 +02:00
for (int tries = 10; tries > 0; tries--) {
2018-10-21 10:27:42 +02:00
vhost = lws_create_vhost(context, &ctx_info);
if (vhost)
2018-10-20 14:20:06 +02:00
break;
ctx_info.port++;
logger->warn("WebSocket: failed to setup vhost. Trying another port: {}", ctx_info.port);
2018-10-20 14:20:06 +02:00
}
if (vhost == nullptr)
throw RuntimeError("Failed to initialize virtual host");
2018-10-20 14:20:06 +02:00
/* Start thread */
running = true;
thread = std::thread(&Web::worker, this);
2019-06-23 16:13:23 +02:00
state = State::STARTED;
2018-10-20 14:20:06 +02:00
}
void Web::stop()
{
2019-06-23 16:13:23 +02:00
assert(state == State::STARTED);
2018-10-20 14:20:06 +02:00
logger->info("Stopping sub-system");
running = false;
thread.join();
lws_context_destroy(context);
2019-06-23 16:13:23 +02:00
state = State::STOPPED;
2018-10-20 14:20:06 +02:00
}
void Web::callbackOnWritable(lws *wsi)
{
writables.push(wsi);
}