summaryrefslogtreecommitdiffstats
path: root/src/stats/client-http.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/stats/client-http.c')
-rw-r--r--src/stats/client-http.c233
1 files changed, 233 insertions, 0 deletions
diff --git a/src/stats/client-http.c b/src/stats/client-http.c
new file mode 100644
index 0000000..1811917
--- /dev/null
+++ b/src/stats/client-http.c
@@ -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 "stats-service.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 *res;
+ 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_elem(&stats_http_resources, res) {
+ 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_add_header(resp, "Allow", "GET");
+ http_server_response_submit(resp);
+ return;
+ }
+ if (strcmp(hreq->method, "GET") != 0) {
+ http_server_request_fail_bad_method(req, "GET");
+ 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(const struct stats_settings *set)
+{
+ struct http_server_settings http_set = {
+ .rawlog_dir = set->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);
+}