summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimotej S <6674623+underhood@users.noreply.github.com>2023-05-10 13:37:44 +0200
committerGitHub <noreply@github.com>2023-05-10 13:37:44 +0200
commit66c47355b4c0a33f7cef0615fe7331bbce463361 (patch)
tree728e4ac73f09d893d32d6c851c9bd316f158430e
parent836a56a956f252935dd2ed4908aab57e1a95ccaf (diff)
initial minimal h2o webserver integration (#14585)
Introduces h2o based web server as an alternative
-rw-r--r--.github/codeql/c-cpp-config.yml2
-rw-r--r--.github/workflows/codeql.yml1
-rw-r--r--.gitmodules4
-rw-r--r--CMakeLists.txt1
-rw-r--r--Makefile.am127
-rw-r--r--configure.ac40
-rw-r--r--daemon/buildinfo.c10
-rw-r--r--daemon/common.h5
-rw-r--r--daemon/main.c11
-rw-r--r--daemon/main.h3
-rw-r--r--daemon/static_threads.c12
m---------httpd/h2o0
-rw-r--r--httpd/h2o_utils.c60
-rw-r--r--httpd/h2o_utils.h38
-rw-r--r--httpd/http_server.c339
-rw-r--r--httpd/http_server.h10
-rw-r--r--libnetdata/buffer/buffer.c8
-rw-r--r--libnetdata/buffer/buffer.h8
-rw-r--r--libnetdata/http/http_defs.h30
-rw-r--r--libnetdata/libnetdata.h1
-rw-r--r--web/server/web_client.h24
21 files changed, 706 insertions, 28 deletions
diff --git a/.github/codeql/c-cpp-config.yml b/.github/codeql/c-cpp-config.yml
new file mode 100644
index 0000000000..cd7c240116
--- /dev/null
+++ b/.github/codeql/c-cpp-config.yml
@@ -0,0 +1,2 @@
+paths-ignore:
+ - httpd/h2o
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index b2af615e41..174f650eac 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -84,6 +84,7 @@ jobs:
uses: github/codeql-action/init@v2
with:
languages: cpp
+ config-file: ./.github/codeql/c-cpp-config.yml
- name: Prepare environment
run: ./packaging/installer/install-required-packages.sh --dont-wait --non-interactive netdata
- name: Build netdata
diff --git a/.gitmodules b/.gitmodules
index dd687fee89..d2730eb8a4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -9,3 +9,7 @@
url = https://github.com/davisking/dlib.git
shallow = true
ignore = dirty
+[submodule "httpd/h2o"]
+ path = httpd/h2o
+ url = https://github.com/h2o/h2o.git
+ ignore = untracked
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2447abb6cc..b220af9f1a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -513,6 +513,7 @@ set(LIBNETDATA_FILES
libnetdata/worker_utilization/worker_utilization.h
libnetdata/parser/parser.h
libnetdata/parser/parser.c
+ libnetdata/http/http_defs.h
)
IF(ENABLE_PLUGIN_EBPF)
diff --git a/Makefile.am b/Makefile.am
index 7d9abd549c..7ade016cb7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -83,6 +83,7 @@ dist_noinst_DATA = \
packaging/protobuf.version \
packaging/version \
database/engine/journalfile_v2.ksy.in \
+ httpd/h2o \
$(NULL)
# until integrated within build
@@ -197,6 +198,7 @@ LIBNETDATA_FILES = \
libnetdata/string/utf8.h \
libnetdata/worker_utilization/worker_utilization.c \
libnetdata/worker_utilization/worker_utilization.h \
+ libnetdata/http/http_defs.h \
$(NULL)
if ENABLE_PLUGIN_EBPF
@@ -749,7 +751,8 @@ libmqttwebsockets_a_SOURCES = \
mqtt_websockets/c-rbuf/src/ringbuffer_internal.h \
mqtt_websockets/c_rhash/src/c_rhash.c \
mqtt_websockets/c_rhash/include/c_rhash.h \
- mqtt_websockets/c_rhash/src/c_rhash_internal.h
+ mqtt_websockets/c_rhash/src/c_rhash_internal.h \
+ $(NULL)
libmqttwebsockets_a_CFLAGS = $(CFLAGS) -DMQTT_WSS_CUSTOM_ALLOC -DRBUF_CUSTOM_MALLOC -DMQTT_WSS_CPUSTATS -I$(srcdir)/aclk/helpers -I$(srcdir)/mqtt_websockets/c_rhash/include
@@ -939,6 +942,123 @@ DAEMON_FILES = \
daemon/unit_test.h \
$(NULL)
+HTTPD_FILES = \
+ httpd/http_server.c \
+ httpd/http_server.h \
+ httpd/h2o_utils.c \
+ httpd/h2o_utils.h \
+ $(NULL)
+
+libh2o_dir = httpd/h2o
+
+libh2o_a_SOURCES = \
+ $(libh2o_dir)/deps/cloexec/cloexec.c \
+ $(libh2o_dir)/deps/libgkc/gkc.c \
+ $(libh2o_dir)/deps/libyrmcds/close.c \
+ $(libh2o_dir)/deps/libyrmcds/connect.c \
+ $(libh2o_dir)/deps/libyrmcds/recv.c \
+ $(libh2o_dir)/deps/libyrmcds/send.c \
+ $(libh2o_dir)/deps/libyrmcds/send_text.c \
+ $(libh2o_dir)/deps/libyrmcds/socket.c \
+ $(libh2o_dir)/deps/libyrmcds/strerror.c \
+ $(libh2o_dir)/deps/libyrmcds/text_mode.c \
+ $(libh2o_dir)/deps/picohttpparser/picohttpparser.c \
+ $(libh2o_dir)/lib/common/cache.c \
+ $(libh2o_dir)/lib/common/file.c \
+ $(libh2o_dir)/lib/common/filecache.c \
+ $(libh2o_dir)/lib/common/hostinfo.c \
+ $(libh2o_dir)/lib/common/http1client.c \
+ $(libh2o_dir)/lib/common/memcached.c \
+ $(libh2o_dir)/lib/common/memory.c \
+ $(libh2o_dir)/lib/common/multithread.c \
+ $(libh2o_dir)/lib/common/serverutil.c \
+ $(libh2o_dir)/lib/common/socket.c \
+ $(libh2o_dir)/lib/common/socketpool.c \
+ $(libh2o_dir)/lib/common/string.c \
+ $(libh2o_dir)/lib/common/time.c \
+ $(libh2o_dir)/lib/common/timeout.c \
+ $(libh2o_dir)/lib/common/url.c \
+ $(libh2o_dir)/lib/core/config.c \
+ $(libh2o_dir)/lib/core/configurator.c \
+ $(libh2o_dir)/lib/core/context.c \
+ $(libh2o_dir)/lib/core/headers.c \
+ $(libh2o_dir)/lib/core/logconf.c \
+ $(libh2o_dir)/lib/core/proxy.c \
+ $(libh2o_dir)/lib/core/request.c \
+ $(libh2o_dir)/lib/core/token.c \
+ $(libh2o_dir)/lib/core/util.c \
+ $(libh2o_dir)/lib/handler/access_log.c \
+ $(libh2o_dir)/lib/handler/chunked.c \
+ $(libh2o_dir)/lib/handler/compress.c \
+ $(libh2o_dir)/lib/handler/compress/gzip.c \
+ $(libh2o_dir)/lib/handler/errordoc.c \
+ $(libh2o_dir)/lib/handler/expires.c \
+ $(libh2o_dir)/lib/handler/fastcgi.c \
+ $(libh2o_dir)/lib/handler/file.c \
+ $(libh2o_dir)/lib/handler/headers.c \
+ $(libh2o_dir)/lib/handler/mimemap.c \
+ $(libh2o_dir)/lib/handler/proxy.c \
+ $(libh2o_dir)/lib/handler/redirect.c \
+ $(libh2o_dir)/lib/handler/reproxy.c \
+ $(libh2o_dir)/lib/handler/throttle_resp.c \
+ $(libh2o_dir)/lib/handler/status.c \
+ $(libh2o_dir)/lib/handler/headers_util.c \
+ $(libh2o_dir)/lib/handler/status/events.c \
+ $(libh2o_dir)/lib/handler/status/requests.c \
+ $(libh2o_dir)/lib/handler/http2_debug_state.c \
+ $(libh2o_dir)/lib/handler/status/durations.c \
+ $(libh2o_dir)/lib/handler/configurator/access_log.c \
+ $(libh2o_dir)/lib/handler/configurator/compress.c \
+ $(libh2o_dir)/lib/handler/configurator/errordoc.c \
+ $(libh2o_dir)/lib/handler/configurator/expires.c \
+ $(libh2o_dir)/lib/handler/configurator/fastcgi.c \
+ $(libh2o_dir)/lib/handler/configurator/file.c \
+ $(libh2o_dir)/lib/handler/configurator/headers.c \
+ $(libh2o_dir)/lib/handler/configurator/proxy.c \
+ $(libh2o_dir)/lib/handler/configurator/redirect.c \
+ $(libh2o_dir)/lib/handler/configurator/reproxy.c \
+ $(libh2o_dir)/lib/handler/configurator/throttle_resp.c \
+ $(libh2o_dir)/lib/handler/configurator/status.c \
+ $(libh2o_dir)/lib/handler/configurator/http2_debug_state.c \
+ $(libh2o_dir)/lib/handler/configurator/headers_util.c \
+ $(libh2o_dir)/lib/http1.c \
+ $(libh2o_dir)/lib/tunnel.c \
+ $(libh2o_dir)/lib/http2/cache_digests.c \
+ $(libh2o_dir)/lib/http2/casper.c \
+ $(libh2o_dir)/lib/http2/connection.c \
+ $(libh2o_dir)/lib/http2/frame.c \
+ $(libh2o_dir)/lib/http2/hpack.c \
+ $(libh2o_dir)/lib/http2/scheduler.c \
+ $(libh2o_dir)/lib/http2/stream.c \
+ $(libh2o_dir)/lib/http2/http2_debug_state.c \
+ $(NULL)
+
+libh2o_a_INCLUDES = \
+ -I$(srcdir)/$(libh2o_dir)/include \
+ -I$(srcdir)/$(libh2o_dir)/deps/cloexec \
+ -I$(srcdir)/$(libh2o_dir)/deps/brotli/enc \
+ -I$(srcdir)/$(libh2o_dir)/deps/golombset \
+ -I$(srcdir)/$(libh2o_dir)/deps/libgkc \
+ -I$(srcdir)/$(libh2o_dir)/deps/libyrmcds \
+ -I$(srcdir)/$(libh2o_dir)/deps/klib \
+ -I$(srcdir)/$(libh2o_dir)/deps/neverbleed \
+ -I$(srcdir)/$(libh2o_dir)/deps/picohttpparser \
+ -I$(srcdir)/$(libh2o_dir)/deps/picotest \
+ -I$(srcdir)/$(libh2o_dir)/deps/yaml/include \
+ -I$(srcdir)/$(libh2o_dir)/deps/yoml \
+ $(NULL)
+
+if ENABLE_HTTPD
+noinst_LIBRARIES += libh2o.a
+
+# until h2o updates support for OpenSSL 3.0 we silence the warnings
+libh2o_a_CFLAGS = $(CFLAGS) -Wno-deprecated-declarations -Wno-unused-parameter -Wno-sign-compare -Wno-missing-field-initializers -DH2O_USE_LIBUV=0 $(libh2o_a_INCLUDES)
+
+if LINUX
+ libh2o_a_CFLAGS += -D_GNU_SOURCE
+endif
+endif #ENABLE_HTTPD
+
NETDATA_FILES = \
collectors/all.h \
$(DAEMON_FILES) \
@@ -1007,6 +1127,11 @@ if ENABLE_ACLK
NETDATA_COMMON_LIBS += libmqttwebsockets.a
endif
+if ENABLE_HTTPD
+ NETDATA_FILES += $(HTTPD_FILES)
+ NETDATA_COMMON_LIBS += libh2o.a
+endif
+
if LINK_STATIC_JSONC
NETDATA_COMMON_LIBS += $(abs_top_srcdir)/externaldeps/jsonc/libjson-c.a
endif
diff --git a/configure.ac b/configure.ac
index 3c4a75ae35..49015b297f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -213,6 +213,12 @@ AC_ARG_ENABLE(
[aclk_ssl_debug="yes"],
[aclk_ssl_debug="no"]
)
+AC_ARG_ENABLE(
+ [httpd],
+ [AS_HELP_STRING([--disable-httpd], [Disable webserver (h2o based) @<:@default autodetect@:>@])],
+ ,
+ [enable_httpd="detect"]
+)
# -----------------------------------------------------------------------------
# Enforce building with C99, bail early if we can't.
@@ -800,6 +806,38 @@ AC_MSG_RESULT([${with_libcap}])
AM_CONDITIONAL([ENABLE_CAPABILITY], [test "${with_libcap}" = "yes"])
# -----------------------------------------------------------------------------
+# HTTPD and h2o related
+
+can_build_httpd="no"
+if test "${enable_httpd}" != "no"; then
+ can_build_httpd="yes"
+ AC_MSG_CHECKING([can build HTTPD])
+ if test -z "${UV_LIBS}"; then
+ can_build_httpd="no"
+ fi
+ if test -n "${SSL_LIBS}"; then
+ OPTIONAL_SSL_CFLAGS="${SSL_CFLAGS}"
+ OPTIONAL_SSL_LIBS="${SSL_LIBS}"
+ else
+ can_build_httpd="no"
+ fi
+ if test "${with_zlib}" != "yes"; then
+ can_build_httpd="no"
+ fi
+ AC_MSG_RESULT([${can_build_httpd}])
+
+ if test "${can_build_httpd}" = "no" -a "${enable_httpd}" = "yes"; then
+ AC_MSG_ERROR([HTTPD was requested but it cannot be built])
+ fi
+
+ if test "${can_build_httpd}" = "yes"; then
+ AC_DEFINE([ENABLE_HTTPD], [1], [HTTPD (h2o based web server)])
+ HTTPD_CFLAGS="-I\$(abs_top_srcdir)/httpd/h2o/include -I\$(abs_top_srcdir)/httpd/h2o/deps/picotls/include -I\$(abs_top_srcdir)/httpd/h2o/deps/quicly/include -DH2O_USE_LIBUV=0"
+ fi
+fi
+AM_CONDITIONAL([ENABLE_HTTPD], [test "${can_build_httpd}" = "yes"])
+
+# -----------------------------------------------------------------------------
# ACLK
bundled_proto_avail="no"
@@ -1689,7 +1727,7 @@ CFLAGS="${originalCFLAGS} ${OPTIONAL_LTO_CFLAGS} ${OPTIONAL_PROTOBUF_CFLAGS} ${O
${OPTIONAL_LIBCAP_CFLAGS} ${OPTIONAL_IPMIMONITORING_CFLAGS} ${OPTIONAL_CUPS_CFLAGS} ${OPTIONAL_XENSTAT_FLAGS} \
${OPTIONAL_KINESIS_CFLAGS} ${OPTIONAL_PUBSUB_CFLAGS} ${OPTIONAL_PROMETHEUS_REMOTE_WRITE_CFLAGS} \
${OPTIONAL_MONGOC_CFLAGS} ${LWS_CFLAGS} ${OPTIONAL_JSONC_STATIC_CFLAGS} ${OPTIONAL_YAML_STATIC_CFLAGS} ${OPTIONAL_BPF_CFLAGS} ${JUDY_CFLAGS} \
- ${OPTIONAL_ACLK_CFLAGS} ${OPTIONAL_ML_CFLAGS} ${OPTIONAL_OS_DEP_CFLAGS}"
+ ${OPTIONAL_ACLK_CFLAGS} ${OPTIONAL_ML_CFLAGS} ${OPTIONAL_OS_DEP_CFLAGS} ${HTTPD_CFLAGS}"
CXXFLAGS="${CFLAGS} ${OPTIONAL_KINESIS_CXXFLAGS} ${CPP_STD_FLAG}"
diff --git a/daemon/buildinfo.c b/daemon/buildinfo.c
index ef813a9617..50f3b3a8e5 100644
--- a/daemon/buildinfo.c
+++ b/daemon/buildinfo.c
@@ -20,6 +20,12 @@
#endif
#endif
+#ifdef ENABLE_HTTPD
+#define FEAT_HTTPD 1
+#else
+#define FEAT_HTTPD 0
+#endif
+
#ifdef ENABLE_DBENGINE
#define FEAT_DBENGINE 1
#else
@@ -275,6 +281,7 @@ void print_build_info(void) {
printf(" TLS Host Verification: %s\n", FEAT_YES_NO(FEAT_TLS_HOST_VERIFY));
printf(" Machine Learning: %s\n", FEAT_YES_NO(FEAT_ML));
printf(" Stream Compression: %s\n", FEAT_YES_NO(FEAT_STREAM_COMPRESSION));
+ printf(" HTTPD (h2o): %s\n", FEAT_YES_NO(FEAT_HTTPD));
printf("Libraries:\n");
printf(" protobuf: %s%s\n", FEAT_YES_NO(FEAT_PROTOBUF), FEAT_PROTOBUF_BUNDLED);
@@ -328,7 +335,8 @@ void print_build_info_json(void) {
printf(" \"tls-host-verify\": %s,\n", FEAT_JSON_BOOL(FEAT_TLS_HOST_VERIFY));
printf(" \"machine-learning\": %s\n", FEAT_JSON_BOOL(FEAT_ML));
- printf(" \"stream-compression\": %s\n", FEAT_JSON_BOOL(FEAT_STREAM_COMPRESSION));
+ printf(" \"stream-compression\": %s\n", FEAT_JSON_BOOL(FEAT_STREAM_COMPRESSION));
+ printf(" \"httpd-h2o\": %s\n", FEAT_JSON_BOOL(FEAT_HTTPD));
printf(" },\n");
printf(" \"libs\": {\n");
diff --git a/daemon/common.h b/daemon/common.h
index 66ffd4a749..aeaf01637a 100644
--- a/daemon/common.h
+++ b/daemon/common.h
@@ -41,6 +41,11 @@
// the netdata webserver(s)
#include "web/server/web_server.h"
+// the new h2o based netdata webserver
+#ifdef ENABLE_HTTPD
+#include "httpd/http_server.h"
+#endif
+
// streaming metrics between netdata servers
#include "streaming/rrdpush.h"
diff --git a/daemon/main.c b/daemon/main.c
index 606de128bd..19688c725b 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -170,6 +170,8 @@ static void service_to_buffer(BUFFER *wb, SERVICE_TYPE service) {
buffer_strcat(wb, "ANALYTICS ");
if(service & SERVICE_EXPORTERS)
buffer_strcat(wb, "EXPORTERS ");
+ if(service & SERVICE_HTTPD)
+ buffer_strcat(wb, "HTTPD ");
}
static bool service_wait_exit(SERVICE_TYPE service, usec_t timeout_ut) {
@@ -365,6 +367,7 @@ void netdata_cleanup_and_exit(int ret) {
| SERVICE_EXPORTERS
| SERVICE_HEALTH
| SERVICE_WEB_SERVER
+ | SERVICE_HTTPD
, 3 * USEC_PER_SEC);
delta_shutdown_time("stop collectors and streaming threads");
@@ -1979,6 +1982,14 @@ int main(int argc, char **argv) {
if(web_server_mode != WEB_SERVER_MODE_NONE)
api_listen_sockets_setup();
+
+#ifdef ENABLE_HTTPD
+ delta_startup_time("initialize httpd server");
+ for (int i = 0; static_threads[i].name; i++) {
+ if (static_threads[i].start_routine == httpd_main)
+ static_threads[i].enabled = httpd_is_enabled();
+ }
+#endif
}
delta_startup_time("set resource limits");
diff --git a/daemon/main.h b/daemon/main.h
index 3e32c5ad6d..232b7a98ac 100644
--- a/daemon/main.h
+++ b/daemon/main.h
@@ -41,7 +41,8 @@ typedef enum {
SERVICE_CONTEXT = (1 << 10),
SERVICE_ANALYTICS = (1 << 11),
SERVICE_EXPORTERS = (1 << 12),
- SERVICE_ACLKSYNC = (1 << 13)
+ SERVICE_ACLKSYNC = (1 << 13),
+ SERVICE_HTTPD = (1 << 14)
} SERVICE_TYPE;
typedef enum {
diff --git a/daemon/static_threads.c b/daemon/static_threads.c
index d93cfe9d03..fe83945cfe 100644
--- a/daemon/static_threads.c
+++ b/daemon/static_threads.c
@@ -142,6 +142,18 @@ const struct netdata_static_thread static_threads_common[] = {
.start_routine = socket_listen_main_static_threaded
},
+#ifdef ENABLE_HTTPD
+ {
+ .name = "httpd",
+ .config_section = NULL,
+ .config_name = NULL,
+ .enabled = 0,
+ .thread = NULL,
+ .init_routine = NULL,
+ .start_routine = httpd_main
+ },
+#endif
+
#ifdef ENABLE_ACLK
{
.name = "ACLK_MAIN",
diff --git a/httpd/h2o b/httpd/h2o
new file mode 160000
+Subproject 7359e98d78d018a35f5da7523feac69f64eddb4
diff --git a/httpd/h2o_utils.c b/httpd/h2o_utils.c
new file mode 100644
index 0000000000..943216f596
--- /dev/null
+++ b/httpd/h2o_utils.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "h2o_utils.h"
+
+#include "h2o/string_.h"
+
+#include "libnetdata/libnetdata.h"
+
+char *iovec_to_cstr(h2o_iovec_t *str)
+{
+ char *c_str = mallocz(str->len + 1);
+ memcpy(c_str, str->base, str->len);
+ c_str[str->len] = 0;
+ return c_str;
+}
+
+#define KEY_VAL_BUFFER_GROWTH_STEP 5
+h2o_iovec_pair_vector_t *parse_URL_params(h2o_mem_pool_t *pool, h2o_iovec_t params_string)
+{
+ h2o_iovec_pair_vector_t *params_vec = h2o_mem_alloc_shared(pool, sizeof(h2o_iovec_pair_vector_t), NULL);
+ memset(params_vec, 0, sizeof(h2o_iovec_pair_vector_t));
+
+ h2o_iovec_pair_t param;
+ while ((param.name.base = (char*)h2o_next_token(&params_string, '&', &param.name.len, &param.value)) != NULL) {
+ if (params_vec->capacity == params_vec->size)
+ h2o_vector_reserve(pool, params_vec, params_vec->capacity + KEY_VAL_BUFFER_GROWTH_STEP);
+
+ params_vec->entries[params_vec->size++] = param;
+ }
+
+ return params_vec;
+}
+
+h2o_iovec_pair_t *get_URL_param_by_name(h2o_iovec_pair_vector_t *params_vec, const void *needle, size_t needle_len)
+{
+ for (size_t i = 0; i < params_vec->size; i++) {
+ h2o_iovec_pair_t *ret = &params_vec->entries[i];
+ if (h2o_memis(ret->name.base, ret->name.len, needle, needle_len))
+ return ret;
+ }
+ return NULL;
+}
+
+char *url_unescape(const char *url)
+{
+ char *result = mallocz(strlen(url) + 1);
+
+ int i, j;
+ for (i = 0, j = 0; url[i] != 0; i++, j++) {
+ if (url[i] == '%' && isxdigit(url[i+1]) && isxdigit(url[i+2])) {
+ char hex[3] = { url[i+1], url[i+2], 0 };
+ result[j] = strtol(hex, NULL, 16);
+ i += 2;
+ } else
+ result[j] = url[i];
+ }
+ result[j] = 0;
+
+ return result;
+}
diff --git a/httpd/h2o_utils.h b/httpd/h2o_utils.h
new file mode 100644
index 0000000000..6760ed9a95
--- /dev/null
+++ b/httpd/h2o_utils.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_H2O_UTILS_H
+#define NETDATA_H2O_UTILS_H
+
+#include "h2o/memory.h"
+
+#define __HAS_URL_PARAMS(reqptr) ((reqptr)->query_at != SIZE_MAX && ((reqptr)->path.len - (reqptr)->query_at > 1))
+#define IF_HAS_URL_PARAMS(reqptr) if __HAS_URL_PARAMS(reqptr)
+#define UNLESS_HAS_URL_PARAMS(reqptr) if (!__HAS_URL_PARAMS(reqptr))
+#define URL_PARAMS_IOVEC_INIT(reqptr) { .base = &(reqptr)->path.base[(reqptr)->query_at + 1], \
+ .len = (reqptr)->path.len - (reqptr)->query_at - 1 }
+#define URL_PARAMS_IOVEC_INIT_WITH_QUESTIONMARK(reqptr) { .base = &(reqptr)->path.base[(reqptr)->query_at], \
+ .len = (reqptr)->path.len - (reqptr)->query_at }
+
+#define PRINTF_H2O_IOVEC_FMT "%.*s"
+#define PRINTF_H2O_IOVEC(iovec) ((int)(iovec)->len), ((iovec)->base)
+
+char *iovec_to_cstr(h2o_iovec_t *str);
+
+typedef struct h2o_iovec_pair {
+ h2o_iovec_t name;
+ h2o_iovec_t value;
+} h2o_iovec_pair_t;
+
+typedef H2O_VECTOR(h2o_iovec_pair_t) h2o_iovec_pair_vector_t;
+
+// Takes the part of url behind ? (the url encoded parameters)
+// and parse it to vector of name/value pairs without copying the actual strings
+h2o_iovec_pair_vector_t *parse_URL_params(h2o_mem_pool_t *pool, h2o_iovec_t params_string);
+
+// Searches for parameter by name (provided in needle)
+// returns pointer to it or NULL
+h2o_iovec_pair_t *get_URL_param_by_name(h2o_iovec_pair_vector_t *params_vec, const void *needle, size_t needle_len);
+
+char *url_unescape(const char *url);
+
+#endif /* NETDATA_H2O_UTILS_H */
diff --git a/httpd/http_server.c b/httpd/http_server.c
new file mode 100644
index 0000000000..24b168d92d
--- /dev/null
+++ b/httpd/http_server.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "daemon/common.h"
+#include "http_server.h"
+#include "h2o.h"
+
+#include "h2o_utils.h"
+
+static h2o_globalconf_t config;
+static h2o_context_t ctx;
+static h2o_accept_ctx_t accept_ctx;
+
+#define CONTENT_JSON_UTF8 H2O_STRLIT("application/json; charset=utf-8")
+#define CONTENT_TEXT_UTF8 H2O_STRLIT("text/plain; charset=utf-8")
+#define NBUF_INITIAL_SIZE_RESP (4096)
+#define API_V1_PREFIX "/api/v1/"
+#define HOST_SELECT_PREFIX "/host/"
+
+#define HTTPD_CONFIG_SECTION "httpd"
+#define HTTPD_ENABLED_DEFAULT false
+
+static void on_accept(h2o_socket_t *listener, const char *err)
+{
+ h2o_socket_t *sock;
+
+ if (err != NULL) {
+ return;
+ }
+
+ if ((sock = h2o_evloop_socket_accept(listener)) == NULL)
+ return;
+ h2o_accept(&accept_ctx, sock);
+}
+
+static int create_listener(const char *ip, int port)
+{
+ struct sockaddr_in addr;
+ int fd, reuseaddr_flag = 1;
+ h2o_socket_t *sock;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(ip);
+ addr.sin_port = htons(port);
+
+ if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ||
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_flag, sizeof(reuseaddr_flag)) != 0 ||
+ bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(fd, SOMAXCONN) != 0) {
+ return -1;
+ }
+
+ sock = h2o_evloop_socket_create(ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
+ h2o_socket_read_start(sock, on_accept);
+
+ return 0;
+}
+
+static int ssl_init()
+{
+ if (!config_get_boolean(HTTPD_CONFIG_SECTION, "ssl", false))
+ return 0;
+
+ char default_fn[FILENAME_MAX + 1];
+
+ snprintfz(default_fn, FILENAME_MAX, "%s/ssl/key.pem", netdata_configured_user_config_dir);
+ const char *key_fn = config_get(HTTPD_CONFIG_SECTION, "ssl key", default_fn);
+
+ snprintfz(default_fn, FILENAME_MAX, "%s/ssl/cert.pem", netdata_configured_user_config_dir);
+ const char *cert_fn = config_get(HTTPD_CONFIG_SECTION, "ssl certificate", default_fn);
+
+#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110
+ accept_ctx.ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+#else
+ accept_ctx.ssl_ctx = SSL_CTX_new(TLS_server_method());
+#endif
+
+ SSL_CTX_set_options(accept_ctx.ssl_ctx, SSL_OP_NO_SSLv2);
+
+ /* load certificate and private key */
+ if (SSL_CTX_use_PrivateKey_file(accept_ctx.ssl_ctx, key_fn, SSL_FILETYPE_PEM) != 1) {
+ error("Could not load server key from \"%s\"", key_fn);
+ return -1;
+ }
+ if (SSL_CTX_use_certificate_file(accept_ctx.ssl_ctx, cert_fn, SSL_FILETYPE_PEM) != 1) {
+ error("Could not load certificate from \"%s\"", cert_fn);
+ return -1;
+ }
+
+ h2o_ssl_register_alpn_protocols(accept_ctx.ssl_ctx, h2o_http2_alpn_protocols);
+
+ info("SSL support enabled");
+
+ return 0;
+}
+
+// I did not find a way to do wildcard paths to make common handler for urls like:
+// /api/v1/info
+// /host/child/api/v1/info
+// /host/uuid/api/v1/info
+// ideally we could do something like "/*/api/v1/info" subscription
+// so we do it "manually" here with uberhandler
+static inline int _netdata_uberhandler(h2o_req_t *req, RRDHOST **host)
+{
+ if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET")))
+ return -1;
+
+ static h2o_generator_t generator = { NULL, NULL };
+
+ h2o_iovec_t norm_path = req->path_normalized;
+
+ if (norm_path.len > strlen(HOST_SELECT_PREFIX) && !memcmp(norm_path.base, HOST_SELECT_PREFIX, strlen(HOST_SELECT_PREFIX))) {
+ h2o_iovec_t host_id; // host_id can be either and UUID or a hostname of the child
+
+ norm_path.base += strlen(HOST_SELECT_PREFIX);
+ norm_path.len -= strlen(HOST_SELECT_PREFIX);
+
+ host_id = norm_path;
+
+ size_t end_loc = h2o_strstr(host_id.base, host_id.len, "/", 1);
+ if (end_loc != SIZE_MAX) {
+ host_id.len = end_loc;
+ norm_path.base += end_loc;
+ norm_path.len -= end_loc;
+ }
+
+ char *c_host_id = iovec_to_cstr(&host_id);
+ *host = rrdhost_find_by_hostname(c_host_id);
+ if (!*host)
+ *host = rrdhost_find_by_guid(c_host_id);
+ if (!*host) {
+ req->res.status = HTTP_RESP_BAD_REQUEST;
+ req->res.reason = "Wrong host id";
+ h2o_send_inline(req, H2O_STRLIT("Host id provided was not found!\n"));
+ freez(c_host_id);
+ return 0;
+ }
+ freez(c_host_id);
+
+ // we have to rewrite URL here in case this is not an api call
+ // so that the subsequent file upload handler can send the correct
+ // files to the client
+ // if this is not an API call we will abort this handler later
+ // and let the internal serve file handler of h2o care for things
+
+ if (end_loc == SIZE_MAX) {
+ req->path.len = 1;
+ req->path_normalized.len = 1;
+ } else {
+ size_t offset = norm_path.base - req->path_normalized.base;
+ req->path.len -= offset;
+ req->path.base += offset;
+ req->query_at -= offset;
+ req->path_normalized.len -= offset;
+ req->path_normalized.base += offset;
+ }
+ }
+
+ // workaround for a dashboard bug which causes sometimes urls like
+ // "//api/v1/info" to be caled instead of "/api/v1/info"
+ if (norm_path.len > 2 &&
+ norm_path.base[0] == '/' &&
+ norm_path.base[1] == '/' ) {
+ norm_path.base++;
+ norm_path.len--;
+ }
+
+ size_t api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V1_PREFIX));
+ if (api_loc == SIZE_MAX)
+ return 1;
+
+ h2o_iovec_t api_command = norm_path;
+ api_command.base += api_loc + strlen(API_V1_PREFIX);
+ api_command.len -= api_loc + strlen(API_V1_PREFIX);
+
+ if (!api_command.len)
+ return 1;
+
+ // this (emulating struct web_client) is a hack and will be removed
+ // in future PRs but needs bigger changes in old http_api_v1
+ // we need to make the web_client_api_request_v1 to be web server
+ // agnostic and remove the old webservers dependency creep into the
+ // individual response generators and thus remove the need to "emulate"
+ // the old webserver calling this function here and in ACLK
+ struct web_client w;
+ w.response.data = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
+ w.response.header = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
+ w.url_query_string_decoded = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
+ w.acl = WEB_CLIENT_ACL_DASHBOARD;
+
+ char *path_c_str = iovec_to_cstr(&api_command);
+ char *path_unescaped = url_unescape(path_c_str);
+ freez(path_c_str);
+
+ IF_HAS_URL_PARAMS(req) {
+ h2o_iovec_t query_params = URL_PARAMS_IOVEC_INIT_WITH_QUESTIONMARK(req);
+ char *query_c_str = iovec_to_cstr(&query_params);
+ char *query_unescaped = url_unescape(query_c_str);
+ freez(query_c_str);
+ buffer_strcat(w.url_query_string_decoded, query_unescaped);
+ freez(query_unescaped);
+ }
+
+ web_client_api_request_v1(*host, &w, path_unescaped);
+ freez(path_unescaped);
+
+ h2o_iovec_t body = buffer_to_h2o_iovec(w.response.data);
+
+ // we move msg body to req->pool managed memory as it has to
+ // live until whole response has been encrypted and sent
+ // when req is finished memory will be freed with the pool
+ void *managed = h2o_mem_alloc_shared(&req->pool, body.len, NULL);
+ memcpy(managed, body.base, body.len);
+ body.base = managed;
+
+ req->res.status = HTTP_RESP_OK;
+ req->res.reason = "OK";
+ if (w.response.data->content_type == CT_APPLICATION_JSON)
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_JSON_UTF8);
+ else
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_TEXT_UTF8);
+ h2o_start_response(req, &generator);
+ h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL);
+
+ buffer_free(w.response.data);
+ buffer_free(w.response.header);
+ buffer_free(w.url_query_string_decoded);
+
+ return 0;
+}
+
+static int netdata_uberhandler(h2o_handler_t *self, h2o_req_t *req)
+{
+ UNUSED(self);
+ RRDHOST *host = localhost;
+
+ int ret = _netdata_uberhandler(req, &host);
+
+ char host_uuid_str[UUID_STR_LEN];
+ uuid_unparse_lower(host->host_uuid, host_uuid_str);
+
+ if (!ret) {
+ log_access("HTTPD OK method: " PRINTF_H2O_IOVEC_FMT
+ ", path: " PRINTF_H2O_IOVEC_FMT
+ ", as host: %s"
+ ", response: %d",
+ PRINTF_H2O_IOVEC(&req->method),
+ PRINTF_H2O_IOVEC(&req->input.path),
+ host == localhost ? "localhost" : host_uuid_str,
+ req->res.status);
+ } else {
+ log_access("HTTPD %d"
+ " method: " PRINTF_H2O_IOVEC_FMT
+ ", path: " PRINTF_H2O_IOVEC_FMT
+ ", forwarding to file handler as path: " PRINTF_H2O_IOVEC_FMT,
+ ret,
+ PRINTF_H2O_IOVEC(&req->method),
+ PRINTF_H2O_IOVEC(&req->input.path),
+ PRINTF_H2O_IOVEC(&req->path));
+ }
+
+ return ret;
+}
+
+static int hdl_netdata_conf(h2o_handler_t *self, h2o_req_t *req)
+{
+ UNUSED(self);
+ if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET")))
+ return -1;
+
+ BUFFER *buf = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
+ config_generate(buf, 0);
+
+ void *managed = h2o_mem_alloc_shared(&req->pool, buf->len, NULL);
+ memcpy(managed, buf->buffer, buf->len);
+
+ req->res.status = HTT