mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
separated websocket node-type into web, api modules
This commit is contained in:
parent
f03fe00bff
commit
f92537172e
10 changed files with 692 additions and 276 deletions
106
include/villas/api.h
Normal file
106
include/villas/api.h
Normal file
|
@ -0,0 +1,106 @@
|
|||
/** REST-API-releated functions.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "list.h"
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
/* Forward declarations */
|
||||
enum lws_callback_reasons;
|
||||
struct lws;
|
||||
struct cfg;
|
||||
|
||||
struct api;
|
||||
struct api_ressource;
|
||||
struct api_buffer;
|
||||
struct api_session;
|
||||
|
||||
extern struct list apis;
|
||||
|
||||
/** Callback type of command function
|
||||
*
|
||||
* @param[inout] c Command handle
|
||||
* @param[in] args JSON command arguments.
|
||||
* @param[out] resp JSON command response.
|
||||
* @param[in] i Execution context.
|
||||
*/
|
||||
typedef int (*api_cb_t)(struct api_ressource *c, json_t *args, json_t **resp, struct api_session *s);
|
||||
|
||||
enum api_version {
|
||||
API_VERSION_UNKOWN = 0,
|
||||
API_VERSION_1 = 1
|
||||
};
|
||||
|
||||
enum api_mode {
|
||||
API_MODE_WS, /**< This API session was established over a WebSocket connection. */
|
||||
API_MODE_HTTP /**< This API session was established via a HTTP REST request. */
|
||||
};
|
||||
|
||||
struct api {
|
||||
struct list sessions; /**< List of currently active connections */
|
||||
|
||||
struct cfg *cfg;
|
||||
};
|
||||
|
||||
struct api_buffer {
|
||||
char *buf; /**< A pointer to the buffer. Usually resized via realloc() */
|
||||
size_t size; /**< The allocated size of the buffer. */
|
||||
size_t len; /**< The used length of the buffer. */
|
||||
size_t sent; /**< Pointer to api_buffer::buf to indicate how much has been sent. */
|
||||
};
|
||||
|
||||
/** A connection via HTTP REST or WebSockets to issue API actions. */
|
||||
struct api_session {
|
||||
enum api_mode mode;
|
||||
enum api_version version;
|
||||
|
||||
int runs;
|
||||
|
||||
struct {
|
||||
struct api_buffer body; /**< HTTP body / WS payload */
|
||||
} request;
|
||||
|
||||
struct {
|
||||
struct api_buffer body; /**< HTTP body / WS payload */
|
||||
struct api_buffer headers; /**< HTTP headers */
|
||||
} response;
|
||||
|
||||
struct api *api;
|
||||
};
|
||||
|
||||
/** Command descriptor
|
||||
*
|
||||
* Every command is described by a descriptor.
|
||||
*/
|
||||
struct api_ressource {
|
||||
char *name;
|
||||
char *description;
|
||||
api_cb_t cb;
|
||||
};
|
||||
|
||||
/** Initalize the API.
|
||||
*
|
||||
* Save references to list of paths / nodes for command execution.
|
||||
*/
|
||||
int api_init(struct api *a, struct cfg *cfg);
|
||||
|
||||
int api_destroy(struct api *a);
|
||||
|
||||
int api_deinit(struct api *a);
|
||||
|
||||
int api_session_init(struct api_session *s, enum api_mode m);
|
||||
|
||||
int api_session_destroy(struct api_session *s);
|
||||
|
||||
int api_session_run_command(struct api_session *s, json_t *req, json_t **resp);
|
||||
|
||||
/** Libwebsockets callback for "api" endpoint */
|
||||
int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
42
include/villas/web.h
Normal file
42
include/villas/web.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/** LWS-releated functions.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
/* Forward declarations */
|
||||
struct api;
|
||||
|
||||
struct web {
|
||||
struct api *api;
|
||||
|
||||
struct lws_context *context; /**< The libwebsockets server context. */
|
||||
struct lws_vhost *vhost; /**< The libwebsockets vhost. */
|
||||
|
||||
int port; /**< Port of the build in HTTP / WebSocket server. */
|
||||
const char *htdocs; /**< The root directory for files served via HTTP. */
|
||||
const char *ssl_cert; /**< Path to the SSL certitifcate for HTTPS / WSS. */
|
||||
const char *ssl_private_key; /**< Path to the SSL private key for HTTPS / WSS. */
|
||||
};
|
||||
|
||||
/** Initialize the web interface.
|
||||
*
|
||||
* The web interface is based on the libwebsockets library.
|
||||
*/
|
||||
int web_init(struct web *w, struct api *a);
|
||||
|
||||
int web_destroy(struct web *w);
|
||||
|
||||
/** Parse HTTPd and WebSocket related options */
|
||||
int web_parse(struct web *w, config_setting_t *lcs);
|
||||
|
||||
/** De-initializes the web interface. */
|
||||
int web_deinit(struct web *w);
|
||||
|
||||
/** libwebsockets service routine. Call periodically */
|
||||
int web_service(struct web *w);
|
|
@ -6,8 +6,8 @@ LIB_SRCS = $(addprefix lib/nodes/, file.c cbuilder.c) \
|
|||
$(addprefix lib/kernel/, kernel.c rt.c) \
|
||||
$(addprefix lib/, sample.c path.c node.c hooks.c \
|
||||
log.c utils.c cfg.c hist.c timing.c pool.c list.c \
|
||||
queue.c memory.c advio.c \
|
||||
) \
|
||||
queue.c memory.c advio.c web.c api.c \
|
||||
)
|
||||
|
||||
LIB_CFLAGS = $(CFLAGS) -fPIC
|
||||
LIB_LDFLAGS = -shared
|
||||
|
@ -47,12 +47,12 @@ endif
|
|||
endif
|
||||
|
||||
# Enable WebSocket support
|
||||
ifndef WITHOUT_WEBSOCKETS
|
||||
#ifndef WITHOUT_WEBSOCKETS
|
||||
ifeq ($(shell pkg-config libwebsockets jansson; echo $$?),0)
|
||||
LIB_SRCS += lib/nodes/websocket.c
|
||||
# LIB_SRCS += lib/nodes/websocket.c
|
||||
LIB_PKGS += libwebsockets jansson
|
||||
endif
|
||||
endif
|
||||
#endif
|
||||
|
||||
# Enable OPAL-RT Asynchronous Process support (will result in 32bit binary!!!)
|
||||
ifdef WITH_OPAL
|
||||
|
|
252
lib/api.c
Normal file
252
lib/api.c
Normal file
|
@ -0,0 +1,252 @@
|
|||
/** REST-API-releated functions.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include "api.h"
|
||||
#include "log.h"
|
||||
#include "config.h"
|
||||
|
||||
static int parse_request(struct api_buffer *b, json_t **req)
|
||||
{
|
||||
json_error_t e;
|
||||
|
||||
if (b->len <= 0)
|
||||
return -1;
|
||||
|
||||
*req = json_loadb(b->buf, b->len, JSON_DISABLE_EOF_CHECK, &e);
|
||||
if (!*req)
|
||||
return -1;
|
||||
|
||||
if (e.position < b->len) {
|
||||
void *dst = (void *) b->buf;
|
||||
void *src = (void *) (b->buf + e.position);
|
||||
|
||||
memmove(dst, src, b->len - e.position);
|
||||
|
||||
b->len -= e.position;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if JANSSON_VERSION_HEX < 0x020A00
|
||||
size_t json_dumpb(const json_t *json, char *buffer, size_t size, size_t flags)
|
||||
{
|
||||
char *str = json_dumps(json, flags);
|
||||
size_t len = strlen(str) - 1; // not \0 terminated
|
||||
|
||||
if (!buffer || len > size)
|
||||
return len;
|
||||
else
|
||||
memcpy(buffer, str, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int unparse_response(struct api_buffer *b, json_t *res)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
retry: len = json_dumpb(res, b->buf + b->len, b->size - b->len, 0);
|
||||
if (len > b->size - b->len) {
|
||||
b->buf = realloc(b->buf, b->len + len);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_session_run_command(struct api_session *s, json_t *req, json_t **resp)
|
||||
{
|
||||
int ret;
|
||||
const char *rstr;
|
||||
struct api_ressource *res;
|
||||
|
||||
json_t *args;
|
||||
|
||||
ret = json_unpack(req, "{ s: s, s: o }",
|
||||
"command", &rstr,
|
||||
"arguments", &args);
|
||||
if (ret)
|
||||
*resp = json_pack("{ s: s, s: d }",
|
||||
"error", "invalid request",
|
||||
"code", -1);
|
||||
|
||||
res = list_lookup(&apis, rstr);
|
||||
if (!res)
|
||||
*resp = json_pack("{ s: s, s: d, s: s }",
|
||||
"error", "command not found",
|
||||
"code", -2,
|
||||
"command", rstr);
|
||||
|
||||
ret = res->cb(res, args, resp, s);
|
||||
if (ret)
|
||||
*resp = json_pack("{ s: s, s: d }",
|
||||
"error", "command failed",
|
||||
"code", ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
struct api_session *s = (struct api_session *) user;
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_ESTABLISHED:
|
||||
api_session_init(s, API_MODE_WS);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP:
|
||||
if (len < 1) {
|
||||
lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *uri = (char *) in;
|
||||
|
||||
/* Parse request URI */
|
||||
ret = sscanf(uri, "/api/v%d", (int *) &s->version);
|
||||
if (ret != 1)
|
||||
return -1;
|
||||
|
||||
debug(LOG_API, "New REST API session initiated: version = %d", s->version);
|
||||
|
||||
api_session_init(s, API_MODE_HTTP);
|
||||
|
||||
/* Prepare HTTP response header */
|
||||
s->response.headers.buf = alloc(512);
|
||||
|
||||
unsigned char *p = (unsigned char *) s->response.headers.buf;
|
||||
unsigned char *e = (unsigned char *) s->response.headers.buf + 512;
|
||||
|
||||
ret = lws_add_http_header_status(wsi, 200, &p, e);
|
||||
if (ret)
|
||||
return 1;
|
||||
ret = lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *) USER_AGENT, strlen(USER_AGENT), &p, e);
|
||||
if (ret)
|
||||
return 1;
|
||||
ret = lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *) "application/json", strlen("application/json"), &p, e);
|
||||
if (ret)
|
||||
return 1;
|
||||
//ret = lws_add_http_header_content_length(wsi, file_len, &p, e);
|
||||
//if (ret)
|
||||
// return 1;
|
||||
ret = lws_finalize_http_header(wsi, &p, e);
|
||||
if (ret)
|
||||
return 1;
|
||||
|
||||
*p = '\0';
|
||||
|
||||
s->response.headers.len = p - (unsigned char *) s->response.headers.buf + 1;
|
||||
s->response.headers.sent = 0;
|
||||
|
||||
/* book us a LWS_CALLBACK_HTTP_WRITEABLE callback */
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLIENT_RECEIVE:
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
case LWS_CALLBACK_HTTP_BODY: {
|
||||
char *newbuf;
|
||||
|
||||
newbuf = realloc(s->request.body.buf, s->request.body.len + len);
|
||||
|
||||
s->request.body.buf = memcpy(newbuf + s->request.body.len, in, len);
|
||||
s->request.body.len += len;
|
||||
|
||||
json_t *req, *resp;
|
||||
while (parse_request(&s->request.body, &req) == 1) {
|
||||
|
||||
api_session_run_command(s, req, &resp);
|
||||
|
||||
unparse_response(&s->response.body, resp);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
|
||||
return -1; /* close connection */
|
||||
|
||||
case LWS_CALLBACK_SERVER_WRITEABLE:
|
||||
case LWS_CALLBACK_HTTP_WRITEABLE: {
|
||||
enum lws_write_protocol prot;
|
||||
struct api_buffer *protbuf;
|
||||
|
||||
if (s->mode == API_MODE_HTTP && s->response.headers.sent < s->response.headers.len) {
|
||||
prot = LWS_WRITE_HTTP_HEADERS;
|
||||
protbuf = &s->response.headers;
|
||||
}
|
||||
else if (s->response.body.sent < s->response.body.len) {
|
||||
prot = LWS_WRITE_HTTP;
|
||||
protbuf = &s->response.body;
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
||||
int sent;
|
||||
|
||||
void *buf = (void *) (protbuf->buf + protbuf->sent);
|
||||
size_t len = protbuf->len - protbuf->sent;
|
||||
|
||||
sent = lws_write(wsi, buf, len, prot);
|
||||
if (sent > 0)
|
||||
protbuf->sent += sent;
|
||||
else
|
||||
return -1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int api_init(struct api *a, struct cfg *cfg)
|
||||
{
|
||||
list_init(&a->sessions);
|
||||
|
||||
a->cfg = cfg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_deinit(struct api *a)
|
||||
{
|
||||
list_destroy(&a->sessions, (dtor_cb_t) api_session_destroy, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_session_init(struct api_session *s, enum api_mode m)
|
||||
{
|
||||
s->mode = m;
|
||||
|
||||
s->request.body =
|
||||
s->response.body =
|
||||
s->response.headers = (struct api_buffer) { 0 };
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_session_destroy(struct api_session *s)
|
||||
{
|
||||
return 0;
|
||||
}
|
1
lib/apis/Makefile.inc
Normal file
1
lib/apis/Makefile.inc
Normal file
|
@ -0,0 +1 @@
|
|||
LIB_SRCS += $(wildcard lib/cmds/*.c)
|
30
lib/apis/config.c
Normal file
30
lib/apis/config.c
Normal file
|
@ -0,0 +1,30 @@
|
|||
/** The "config" command
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*********************************************************************************/
|
||||
|
||||
#include "api.h"
|
||||
#include "utils.h"
|
||||
#include "plugin.h"
|
||||
|
||||
static int api_config(struct api_ressource *h, json_t *args, json_t **resp, struct api_info *i)
|
||||
{
|
||||
if (!i->settings.config)
|
||||
return -1;
|
||||
|
||||
*resp = config_to_json(config_setting_root(i->settings.config));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct plugin p = {
|
||||
.name = "config",
|
||||
.description = "retrieve current VILLASnode configuration",
|
||||
.type = PLUGIN_TYPE_API,
|
||||
.api.cb = api_config
|
||||
};
|
||||
|
||||
REGISTER_PLUGIN(&p)
|
48
lib/apis/nodes.c
Normal file
48
lib/apis/nodes.c
Normal file
|
@ -0,0 +1,48 @@
|
|||
/** The "nodes" command
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
#include "api.h"
|
||||
#include "node.h"
|
||||
#include "utils.h"
|
||||
|
||||
extern struct list nodes;
|
||||
|
||||
static int api_nodes(struct api_ressource *r, json_t *args, json_t **resp, struct api_info *i)
|
||||
{
|
||||
json_t *json_nodes = json_array();
|
||||
|
||||
list_foreach(struct node *n, i->nodes) {
|
||||
json_t *json_node = json_pack("{ s: s, s: i, s: i, s: i, s: i }",
|
||||
"name", node_name_short(n),
|
||||
"state", n->state,
|
||||
"vectorize", n->vectorize,
|
||||
"affinity", n->affinity
|
||||
);
|
||||
|
||||
/* Add all additional fields of node here.
|
||||
* This can be used for metadata */
|
||||
json_object_update(json_node, config_to_json(n->cfg));
|
||||
|
||||
json_array_append_new(json_nodes, json_node);
|
||||
}
|
||||
|
||||
*resp = json_nodes;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct plugin p = {
|
||||
.name = "nodes",
|
||||
.description = "retrieve list of all known nodes",
|
||||
.type = PLUGIN_TYPE_API,
|
||||
.api.cb = api_nodes
|
||||
};
|
||||
|
||||
REGISTER_PLUGIN(&p)
|
23
lib/apis/reload.c
Normal file
23
lib/apis/reload.c
Normal file
|
@ -0,0 +1,23 @@
|
|||
/** The "reload" command
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*********************************************************************************/
|
||||
|
||||
#include "api.h"
|
||||
|
||||
static int api_reload(struct api_ressource *h, json_t *args, json_t **resp, struct api_info *i)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static struct plugin p = {
|
||||
.name = "reload",
|
||||
.description = "restart VILLASnode with new configuration",
|
||||
.type = PLUGIN_TYPE_API,
|
||||
.api.cb = api_reload
|
||||
};
|
||||
|
||||
REGISTER_PLUGIN(&p)
|
|
@ -22,225 +22,10 @@
|
|||
#include "cfg.h"
|
||||
#include "config.h"
|
||||
|
||||
/* Private static storage */
|
||||
static config_setting_t *cfg_root; /**< Root config */
|
||||
static pthread_t thread; /**< All nodes are served by a single websocket server. This server is running in a dedicated thread. */
|
||||
static struct lws_context *context; /**< The libwebsockets server context. */
|
||||
|
||||
static int port; /**< Port of the build in HTTP / WebSocket server */
|
||||
|
||||
static const char *ssl_cert; /**< Path to the SSL certitifcate for HTTPS / WSS */
|
||||
static const char *ssl_private_key; /**< Path to the SSL private key for HTTPS / WSS */
|
||||
static const char *htdocs; /**< Path to the directory which should be served by build in HTTP server */
|
||||
|
||||
/* Forward declarations */
|
||||
static struct node_type vt;
|
||||
static int protocol_cb_http(struct lws *, enum lws_callback_reasons, void *, void *, size_t);
|
||||
static int protocol_cb_live(struct lws *, enum lws_callback_reasons, void *, void *, size_t);
|
||||
|
||||
static struct lws_protocols protocols[] = {
|
||||
{
|
||||
"http-only",
|
||||
protocol_cb_http,
|
||||
0,
|
||||
0
|
||||
},
|
||||
{
|
||||
"live",
|
||||
protocol_cb_live,
|
||||
sizeof(struct websocket_connection),
|
||||
0
|
||||
},
|
||||
{ 0 /* terminator */ }
|
||||
};
|
||||
|
||||
#if 0
|
||||
static const struct lws_extension exts[] = {
|
||||
{
|
||||
"permessage-deflate",
|
||||
lws_extension_callback_pm_deflate,
|
||||
"permessage-deflate"
|
||||
},
|
||||
{
|
||||
"deflate-frame",
|
||||
lws_extension_callback_pm_deflate,
|
||||
"deflate_frame"
|
||||
},
|
||||
{ NULL, NULL, NULL /* terminator */ }
|
||||
};
|
||||
#endif
|
||||
|
||||
static void logger(int level, const char *msg) {
|
||||
int len = strlen(msg);
|
||||
if (strchr(msg, '\n'))
|
||||
len -= 1;
|
||||
|
||||
/* Decrease severity for some errors. */
|
||||
if (strstr(msg, "Unable to open") == msg)
|
||||
level = LLL_WARN;
|
||||
|
||||
switch (level) {
|
||||
case LLL_ERR: error("LWS: %.*s", len, msg); break;
|
||||
case LLL_WARN: warn("LWS: %.*s", len, msg); break;
|
||||
case LLL_INFO: info("LWS: %.*s", len, msg); break;
|
||||
default: debug(DBG_WEBSOCKET | 1, "LWS: %.*s", len, msg); break;
|
||||
}
|
||||
}
|
||||
|
||||
static void * server_thread(void *ctx)
|
||||
{
|
||||
debug(DBG_WEBSOCKET | 3, "WebSocket: Started server thread");
|
||||
|
||||
while (lws_service(context, 10) >= 0);
|
||||
|
||||
debug(DBG_WEBSOCKET | 3, "WebSocket: shutdown voluntarily");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Choose mime type based on the file extension */
|
||||
static char * get_mimetype(const char *resource_path)
|
||||
{
|
||||
char *extension = strrchr(resource_path, '.');
|
||||
|
||||
if (extension == NULL)
|
||||
return "text/plain";
|
||||
else if (!strcmp(extension, ".png"))
|
||||
return "image/png";
|
||||
else if (!strcmp(extension, ".svg"))
|
||||
return "image/svg+xml";
|
||||
else if (!strcmp(extension, ".jpg"))
|
||||
return "image/jpg";
|
||||
else if (!strcmp(extension, ".gif"))
|
||||
return "image/gif";
|
||||
else if (!strcmp(extension, ".html"))
|
||||
return "text/html";
|
||||
else if (!strcmp(extension, ".css"))
|
||||
return "text/css";
|
||||
else if (!strcmp(extension, ".js"))
|
||||
return "application/javascript";
|
||||
else
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
int protocol_cb_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
{
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_HTTP:
|
||||
if (!htdocs) {
|
||||
lws_return_http_status(wsi, HTTP_STATUS_SERVICE_UNAVAILABLE, NULL);
|
||||
goto try_to_reuse;
|
||||
}
|
||||
|
||||
if (len < 1) {
|
||||
lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
|
||||
goto try_to_reuse;
|
||||
}
|
||||
|
||||
char *requested_uri = (char *) in;
|
||||
|
||||
debug(DBG_WEBSOCKET | 3, "LWS: New HTTP request: %s", requested_uri);
|
||||
|
||||
/* Handle default path */
|
||||
if (!strcmp(requested_uri, "/")) {
|
||||
char *response = "HTTP/1.1 302 Found\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Location: /index.html\r\n"
|
||||
"\r\n";
|
||||
|
||||
lws_write(wsi, (void *) response, strlen(response), LWS_WRITE_HTTP);
|
||||
|
||||
goto try_to_reuse;
|
||||
}
|
||||
#ifdef WITH_JANSSON
|
||||
/* Return list of websocket nodes */
|
||||
else if (!strcmp(requested_uri, "/nodes.json")) {
|
||||
json_t *json_body = json_array();
|
||||
|
||||
list_foreach(struct node *n, &vt.instances) {
|
||||
struct websocket *w = n->_vd;
|
||||
|
||||
json_t *json_node = json_pack("{ s: s, s: i, s: i, s: i, s: i }",
|
||||
"name", node_name_short(n),
|
||||
"connections", list_length(&w->connections),
|
||||
"state", n->state,
|
||||
"vectorize", n->vectorize,
|
||||
"affinity", n->affinity
|
||||
);
|
||||
|
||||
/* Add all additional fields of node here.
|
||||
* This can be used for metadata */
|
||||
json_object_update(json_node, config_to_json(n->cfg));
|
||||
|
||||
json_array_append_new(json_body, json_node);
|
||||
}
|
||||
|
||||
char *body = json_dumps(json_body, JSON_INDENT(4));
|
||||
|
||||
char *header = "HTTP/1.1 200 OK\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"\r\n";
|
||||
|
||||
lws_write(wsi, (void *) header, strlen(header), LWS_WRITE_HTTP);
|
||||
lws_write(wsi, (void *) body, strlen(body), LWS_WRITE_HTTP);
|
||||
|
||||
free(body);
|
||||
json_decref(json_body);
|
||||
|
||||
return -1;
|
||||
}
|
||||
else if (!strcmp(requested_uri, "/config.json")) {
|
||||
char *body = json_dumps(config_to_json(cfg_root), JSON_INDENT(4));
|
||||
|
||||
char *header = "HTTP/1.1 200 OK\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"\r\n";
|
||||
|
||||
lws_write(wsi, (void *) header, strlen(header), LWS_WRITE_HTTP);
|
||||
lws_write(wsi, (void *) body, strlen(body), LWS_WRITE_HTTP);
|
||||
|
||||
free(body);
|
||||
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
char path[4069];
|
||||
snprintf(path, sizeof(path), "%s%s", htdocs, requested_uri);
|
||||
|
||||
/* refuse to serve files we don't understand */
|
||||
char *mimetype = get_mimetype(path);
|
||||
if (!mimetype) {
|
||||
warn("HTTP: Unknown mimetype for %s", path);
|
||||
lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int n = lws_serve_http_file(wsi, path, mimetype, NULL, 0);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
else if (n == 0)
|
||||
break;
|
||||
else
|
||||
goto try_to_reuse;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
try_to_reuse:
|
||||
if (lws_http_transaction_completed(wsi))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int protocol_cb_live(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
int websocket_protocol_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
|
||||
{
|
||||
struct websocket_connection *c = user;
|
||||
struct websocket *w;
|
||||
|
@ -364,61 +149,6 @@ int protocol_cb_live(struct lws *wsi, enum lws_callback_reasons reason, void *us
|
|||
}
|
||||
}
|
||||
|
||||
int websocket_init(int argc, char * argv[], config_setting_t *cfg)
|
||||
{
|
||||
config_setting_t *cfg_http;
|
||||
|
||||
lws_set_log_level((1 << LLL_COUNT) - 1, logger);
|
||||
|
||||
/* Parse global config */
|
||||
cfg_http = config_setting_lookup(cfg, "http");
|
||||
if (cfg_http) {
|
||||
config_setting_lookup_string(cfg_http, "ssl_cert", &ssl_cert);
|
||||
config_setting_lookup_string(cfg_http, "ssl_private_key", &ssl_private_key);
|
||||
config_setting_lookup_string(cfg_http, "htdocs", &htdocs);
|
||||
config_setting_lookup_int(cfg_http, "port", &port);
|
||||
}
|
||||
|
||||
/* Default settings */
|
||||
if (!port)
|
||||
port = 80;
|
||||
if (!htdocs)
|
||||
htdocs = "/villas/contrib/websocket";
|
||||
|
||||
/* Start server */
|
||||
struct lws_context_creation_info info = {
|
||||
.port = port,
|
||||
.protocols = protocols,
|
||||
.extensions = NULL, //exts,
|
||||
.ssl_cert_filepath = ssl_cert,
|
||||
.ssl_private_key_filepath = ssl_private_key,
|
||||
.gid = -1,
|
||||
.uid = -1
|
||||
};
|
||||
|
||||
context = lws_create_context(&info);
|
||||
if (context == NULL)
|
||||
error("WebSocket: failed to initialize server");
|
||||
|
||||
/* Save root config for GET /config.json request */
|
||||
cfg_root = cfg;
|
||||
|
||||
pthread_create(&thread, NULL, server_thread, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int websocket_deinit()
|
||||
{
|
||||
lws_cancel_service(context);
|
||||
lws_context_destroy(context);
|
||||
|
||||
pthread_cancel(thread);
|
||||
pthread_join(thread, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int websocket_open(struct node *n)
|
||||
{
|
||||
struct websocket *w = n->_vd;
|
||||
|
|
184
lib/web.c
Normal file
184
lib/web.c
Normal file
|
@ -0,0 +1,184 @@
|
|||
/** LWS-releated functions.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASnode. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <libconfig.h>
|
||||
#include <libwebsockets.h>
|
||||
|
||||
#include <linux/limits.h>
|
||||
|
||||
#if 0
|
||||
#include "nodes/websocket.h"
|
||||
#endif
|
||||
|
||||
#include "utils.h"
|
||||
#include "log.h"
|
||||
#include "web.h"
|
||||
#include "api.h"
|
||||
|
||||
/* Forward declarations */
|
||||
lws_callback_function api_protocol_cb;
|
||||
|
||||
/** Path to the directory which should be served by build in HTTP server */
|
||||
static char htdocs[PATH_MAX] = "/usr/local/share/villas/node/htdocs";
|
||||
|
||||
/** List of libwebsockets protocols. */
|
||||
static struct lws_protocols protocols[] = {
|
||||
{
|
||||
.name = "http-only",
|
||||
.callback = api_protocol_cb,
|
||||
.per_session_data_size = sizeof(struct api_session),
|
||||
.rx_buffer_size = 0
|
||||
},
|
||||
#if 0
|
||||
{
|
||||
.name = "live",
|
||||
.callback = websocket_protocol_cb,
|
||||
.per_session_data_size = sizeof(struct websocket_connection),
|
||||
.rx_buffer_size = 0
|
||||
},
|
||||
#endif
|
||||
{
|
||||
.name = "api",
|
||||
.callback = api_protocol_cb,
|
||||
.per_session_data_size = sizeof(struct api_session),
|
||||
.rx_buffer_size = 0
|
||||
},
|
||||
{ 0 /* terminator */ }
|
||||
};
|
||||
|
||||
/** List of libwebsockets mounts. */
|
||||
static struct lws_http_mount mounts[] = {
|
||||
{
|
||||
.mount_next = &mounts[1],
|
||||
.mountpoint = "/api/v1/",
|
||||
.origin = "cmd",
|
||||
.def = NULL,
|
||||
.cgienv = NULL,
|
||||
.cgi_timeout = 0,
|
||||
.cache_max_age = 0,
|
||||
.cache_reusable = 0,
|
||||
.cache_revalidate = 0,
|
||||
.cache_intermediaries = 0,
|
||||
.origin_protocol = LWSMPRO_CALLBACK,
|
||||
.mountpoint_len = 8
|
||||
},
|
||||
{
|
||||
.mount_next = NULL,
|
||||
.mountpoint = "/",
|
||||
.origin = htdocs,
|
||||
.def = "/index.html",
|
||||
.cgienv = NULL,
|
||||
.cgi_timeout = 0,
|
||||
.cache_max_age = 0,
|
||||
.cache_reusable = 0,
|
||||
.cache_revalidate = 0,
|
||||
.cache_intermediaries = 0,
|
||||
.origin_protocol = LWSMPRO_FILE,
|
||||
.mountpoint_len = 1
|
||||
}
|
||||
};
|
||||
|
||||
/** List of libwebsockets extensions. */
|
||||
static const struct lws_extension extensions[] = {
|
||||
{
|
||||
"permessage-deflate",
|
||||
lws_extension_callback_pm_deflate,
|
||||
"permessage-deflate"
|
||||
},
|
||||
{
|
||||
"deflate-frame",
|
||||
lws_extension_callback_pm_deflate,
|
||||
"deflate_frame"
|
||||
},
|
||||
{ NULL /* terminator */ }
|
||||
};
|
||||
|
||||
static void logger(int level, const char *msg) {
|
||||
int len = strlen(msg);
|
||||
if (strchr(msg, '\n'))
|
||||
len -= 1;
|
||||
|
||||
/* Decrease severity for some errors. */
|
||||
if (strstr(msg, "Unable to open") == msg)
|
||||
level = LLL_WARN;
|
||||
|
||||
switch (level) {
|
||||
case LLL_ERR: error("LWS: %.*s", len, msg); break;
|
||||
case LLL_WARN: warn("LWS: %.*s", len, msg); break;
|
||||
case LLL_INFO: info("LWS: %.*s", len, msg); break;
|
||||
default: debug(LOG_WEBSOCKET | 1, "LWS: %.*s", len, msg); break;
|
||||
}
|
||||
}
|
||||
|
||||
int web_service(struct web *w)
|
||||
{
|
||||
return lws_service(w->context, 10);
|
||||
}
|
||||
|
||||
int web_parse(struct web *w, config_setting_t *lcs)
|
||||
{
|
||||
config_setting_t *lcs_http;
|
||||
|
||||
/* Parse global config */
|
||||
lcs_http = config_setting_lookup(lcs, "http");
|
||||
if (lcs_http) {
|
||||
const char *ht;
|
||||
|
||||
config_setting_lookup_string(lcs_http, "ssl_cert", &w->ssl_cert);
|
||||
config_setting_lookup_string(lcs_http, "ssl_private_key", &w->ssl_private_key);
|
||||
config_setting_lookup_int(lcs_http, "port", &w->port);
|
||||
|
||||
if (config_setting_lookup_string(lcs_http, "htdocs", &w->htdocs)) {
|
||||
strncpy(htdocs, ht, sizeof(htdocs));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int web_init(struct web *w, struct api *a)
|
||||
{
|
||||
w->api = a;
|
||||
|
||||
lws_set_log_level((1 << LLL_COUNT) - 1, logger);
|
||||
|
||||
/* Start server */
|
||||
struct lws_context_creation_info ctx_info = {
|
||||
.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS,
|
||||
.gid = -1,
|
||||
.uid = -1,
|
||||
.user = (void *) w
|
||||
};
|
||||
|
||||
struct lws_context_creation_info vhost_info = {
|
||||
.protocols = protocols,
|
||||
.mounts = mounts,
|
||||
.extensions = extensions,
|
||||
.port = w->port,
|
||||
.ssl_cert_filepath = w->ssl_cert,
|
||||
.ssl_private_key_filepath = w->ssl_private_key
|
||||
};
|
||||
|
||||
w->context = lws_create_context(&ctx_info);
|
||||
if (w->context == NULL)
|
||||
error("WebSocket: failed to initialize server");
|
||||
|
||||
w->vhost = lws_create_vhost(w->context, &vhost_info);
|
||||
if (w->vhost == NULL)
|
||||
error("WebSocket: failed to initialize server");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int web_deinit(struct web *w)
|
||||
{
|
||||
lws_cancel_service(w->context);
|
||||
lws_context_destroy(w->context);
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Reference in a new issue