]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
stats: Add HTTP server support.
authorStephan Bosch <stephan.bosch@open-xchange.com>
Thu, 14 Nov 2019 23:32:00 +0000 (00:32 +0100)
committermartti.rannanjarvi <martti.rannanjarvi@open-xchange.com>
Sat, 18 Apr 2020 14:55:11 +0000 (14:55 +0000)
src/stats/Makefile.am
src/stats/client-http.c [new file with mode: 0644]
src/stats/client-http.h [new file with mode: 0644]
src/stats/main.c
src/stats/stats-settings.c
src/stats/stats-settings.h

index ae0c1ea678e9b2cf1d9ca9653c874ef7e1379d87..10aa86c594463d582849a8b1bab1907dfeaac7ef 100644 (file)
@@ -31,6 +31,7 @@ stats_SOURCES = \
 libstats_local_la_SOURCES = \
        client-reader.c \
        client-writer.c \
+       client-http.c \
        event-exporter-fmt.c \
        event-exporter-fmt-json.c \
        event-exporter-fmt-none.c \
@@ -46,6 +47,7 @@ noinst_HEADERS = \
        stats-common.h \
        client-reader.h \
        client-writer.h \
+       client-http.h\
        event-exporter.h \
        stats-event-category.h \
        stats-metrics.h \
diff --git a/src/stats/client-http.c b/src/stats/client-http.c
new file mode 100644 (file)
index 0000000..6ffcb93
--- /dev/null
@@ -0,0 +1,233 @@
+/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */
+
+#include "stats-common.h"
+#include "str.h"
+#include "array.h"
+#include "strescape.h"
+#include "connection.h"
+#include "ostream.h"
+#include "master-service.h"
+#include "http-server.h"
+#include "http-url.h"
+#include "stats-metrics.h"
+#include "client-http.h"
+
+struct stats_http_client;
+
+struct stats_http_client {
+       struct http_server_connection *http_conn;
+};
+
+struct stats_http_resource {
+       pool_t pool;
+       const char *title;
+       struct http_server_resource *resource;
+
+       stats_http_resource_callback_t *callback;
+       void *context;
+};
+
+static struct http_server *stats_http_server;
+static ARRAY(struct stats_http_resource *) stats_http_resources;
+
+/*
+ * Request
+ */
+
+static void
+stats_http_server_handle_request(void *context ATTR_UNUSED,
+                                struct http_server_request *http_sreq)
+{
+       http_server_request_fail(http_sreq, 404, "Path Not Found");
+}
+
+/*
+ * Connection
+ */
+
+static void
+stats_http_server_connection_destroy(void *context, const char *reason);
+
+static const struct http_server_callbacks stats_http_callbacks = {
+        .connection_destroy = stats_http_server_connection_destroy,
+        .handle_request = stats_http_server_handle_request
+};
+
+void client_http_create(struct master_service_connection *conn)
+{
+       struct stats_http_client *client;
+
+       client = i_new(struct stats_http_client, 1);
+
+       client->http_conn = http_server_connection_create(
+               stats_http_server, conn->fd, conn->fd, conn->ssl,
+               &stats_http_callbacks, client);
+}
+
+static void stats_http_client_destroy(struct stats_http_client *client)
+{
+       i_free(client);
+
+       master_service_client_connection_destroyed(master_service);
+}
+
+static void
+stats_http_server_connection_destroy(void *context,
+                                    const char *reason ATTR_UNUSED)
+{
+       struct stats_http_client *client = context;
+
+       if (client->http_conn == NULL) {
+               /* Already destroying client directly */
+               return;
+       }
+
+       /* HTTP connection is destroyed already now */
+       client->http_conn = NULL;
+
+       /* Destroy the connection itself */
+       stats_http_client_destroy(client);
+}
+
+/*
+ * Resources
+ */
+
+/* Registry */
+
+static void
+stats_http_resource_callback(struct stats_http_resource *res,
+                            struct http_server_request *req,
+                            const char *sub_path)
+{
+       res->callback(res->context, req, sub_path);
+}
+
+#undef stats_http_resource_add
+void stats_http_resource_add(const char *path, const char *title,
+                            stats_http_resource_callback_t *callback,
+                            void *context)
+{
+       struct stats_http_resource *res;
+       pool_t pool;
+
+       pool = pool_alloconly_create("stats http resource", 2048);
+       res = p_new(pool, struct stats_http_resource, 1);
+       res->pool = pool;
+       res->title = p_strdup(pool, title);
+       res->callback = callback;
+       res->context = context;
+
+       res->resource = http_server_resource_create(
+               stats_http_server, pool, stats_http_resource_callback, res);
+       http_server_resource_add_location(res->resource, path);
+
+       pool_unref(&pool);
+       array_append(&stats_http_resources, &res, 1);
+}
+
+/* Root */
+
+static void
+stats_http_resource_root_make_response(struct http_server_response *resp,
+                                      const struct http_request *hreq)
+{
+       struct stats_http_resource *const *res_p;
+       struct http_url url;
+       string_t *msg;
+
+       http_url_init_authority_from(&url, hreq->target.url);
+
+       msg = t_str_new(1024);
+
+       str_append(msg, "<!DOCTYPE html>\n");
+       str_append(msg, "<html lang=\"en\">\n");
+       str_append(msg, "\n");
+       str_append(msg, "<head>\n");
+       str_append(msg, "<meta charset=\"utf-8\">\n");
+       str_append(msg, "<title>Dovecot Stats</title>\n");
+       str_append(msg, "</head>\n");
+       str_append(msg, "\n");
+       str_append(msg, "<body>\n");
+
+       str_append(msg, "<h1>Dovecot Stats:</h1>\n");
+       str_append(msg, "<p><ul>\n");
+
+       array_foreach(&stats_http_resources, res_p) {
+               struct stats_http_resource *res = *res_p;
+
+               if (res->title == NULL)
+                       continue;
+
+               /* List the resource at its primary location. */
+               url.path = http_server_resource_get_path(res->resource);
+
+               str_append(msg, "<li><a href=\"");
+               str_append(msg, http_url_create(&url));
+               str_append(msg, "\">");
+               str_append(msg, res->title);
+               str_append(msg, "</a></li>\n");
+       }
+
+       str_append(msg, "</ul></p>\n");
+       str_append(msg, "</body>\n");
+       str_append(msg, "\n");
+       str_append(msg, "</html>\n");
+
+       http_server_response_set_payload_data(
+               resp, str_data(msg), str_len(msg));
+}
+
+static void
+stats_http_resource_root_request(void *context ATTR_UNUSED,
+                                struct http_server_request *req,
+                                const char *sub_path)
+{
+       const struct http_request *hreq = http_server_request_get(req);
+       struct http_server_response *resp;
+
+       if (strcmp(hreq->method, "OPTIONS") == 0) {
+               resp = http_server_response_create(req, 200, "OK");
+               http_server_response_submit(resp);
+               return;
+       }
+       if (strcmp(hreq->method, "GET") != 0) {
+               http_server_request_fail(req, 405, "Method Not Allowed");
+               return;
+       }
+       if (*sub_path != '\0') {
+               http_server_request_fail(req, 404, "Not Found");
+               return;
+       }
+
+       resp = http_server_response_create(req, 200, "OK");
+       http_server_response_add_header(resp, "Content-Type",
+                                       "text/html; charset=utf-8");
+
+       stats_http_resource_root_make_response(resp, hreq);
+
+       http_server_response_submit(resp);
+}
+
+/*
+ * Server
+ */
+
+void client_http_init(void)
+{
+       struct http_server_settings http_set = {
+               .rawlog_dir = stats_settings->stats_http_rawlog_dir,
+       };
+
+       i_array_init(&stats_http_resources, 8);
+
+       stats_http_server = http_server_init(&http_set);
+       stats_http_resource_add("/", NULL,
+                               stats_http_resource_root_request, NULL);
+}
+
+void client_http_deinit(void)
+{
+       http_server_deinit(&stats_http_server);
+       array_free(&stats_http_resources);
+}
diff --git a/src/stats/client-http.h b/src/stats/client-http.h
new file mode 100644 (file)
index 0000000..ecbe515
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef CLIENT_HTTP_H
+#define CLIENT_HTTP_H
+
+struct master_service_connection;
+struct http_server_request;
+
+typedef void
+(stats_http_resource_callback_t)(void *context,
+                                struct http_server_request *req,
+                                const char *sub_path);
+
+void client_http_create(struct master_service_connection *conn);
+
+void stats_http_resource_add(const char *path, const char *title,
+                            stats_http_resource_callback_t *callback,
+                            void *context);
+#define stats_http_resource_add(path, title, callback, context) \
+       stats_http_resource_add(path, title, \
+               (stats_http_resource_callback_t *)callback, \
+               (TRUE ? context : \
+                CALLBACK_TYPECHECK(callback, void (*)( \
+                       typeof(context), struct http_server_request *req, \
+                       const char *sub_path))))
+
+void client_http_init(void);
+void client_http_deinit(void);
+
+#endif
index c3eeca6b590d77fbc339eca1a8ac90f14d02ac14..f9204568fa3e7f2862b928a4d60aedfe86ea0673 100644 (file)
@@ -10,6 +10,7 @@
 #include "stats-metrics.h"
 #include "client-writer.h"
 #include "client-reader.h"
+#include "client-http.h"
 
 const struct stats_settings *stats_settings;
 struct stats_metrics *stats_metrics;
@@ -36,7 +37,9 @@ static bool client_is_writer(const char *path)
 
 static void client_connected(struct master_service_connection *conn)
 {
-       if (client_is_writer(conn->name))
+       if (strcmp(conn->name, "http") == 0)
+               client_http_create(conn);
+       else if (client_is_writer(conn->name))
                client_writer_create(conn->fd);
        else
                client_reader_create(conn->fd);
@@ -64,12 +67,14 @@ static void main_init(void)
        stats_event_categories_init();
        client_readers_init();
        client_writers_init();
+       client_http_init();
 }
 
 static void main_deinit(void)
 {
        client_readers_deinit();
        client_writers_deinit();
+       client_http_deinit();
        stats_event_categories_deinit();
        stats_metrics_deinit(&stats_metrics);
 }
index 674a11f35a19533d9e51e959312430ff9420d269..ed42078720182202d75121ee01cc4a00738f13a2 100644 (file)
@@ -141,18 +141,25 @@ const struct setting_parser_info stats_metric_setting_parser_info = {
  * top-level settings
  */
 
+#undef DEF
+#define DEF(type, name) \
+       { type, #name, offsetof(struct stats_settings, name), NULL }
 #undef DEFLIST_UNIQUE
 #define DEFLIST_UNIQUE(field, name, defines) \
        { SET_DEFLIST_UNIQUE, name, \
          offsetof(struct stats_settings, field), defines }
 
 static const struct setting_define stats_setting_defines[] = {
+       DEF(SET_STR, stats_http_rawlog_dir),
+
        DEFLIST_UNIQUE(metrics, "metric", &stats_metric_setting_parser_info),
        DEFLIST_UNIQUE(exporters, "event_exporter", &stats_exporter_setting_parser_info),
        SETTING_DEFINE_LIST_END
 };
 
 const struct stats_settings stats_default_settings = {
+       .stats_http_rawlog_dir = "",
+
        .metrics = ARRAY_INIT,
        .exporters = ARRAY_INIT,
 };
index 821056c4e8cb3a47ff4c381607615c7b156c60f3..7346d360ea7b64ba45cd28bae433fa2d630d1007 100644 (file)
@@ -113,6 +113,8 @@ struct stats_metric_settings {
 };
 
 struct stats_settings {
+       const char *stats_http_rawlog_dir;
+
        ARRAY(struct stats_exporter_settings *) exporters;
        ARRAY(struct stats_metric_settings *) metrics;
 };