]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: http-server - Implement support for dynamically adding resources.
authorStephan Bosch <stephan.bosch@open-xchange.com>
Fri, 8 Nov 2019 16:11:36 +0000 (17:11 +0100)
committermartti.rannanjarvi <martti.rannanjarvi@open-xchange.com>
Sat, 18 Apr 2020 14:55:11 +0000 (14:55 +0000)
Before, the application would get all requests through a single main callback
and the application was always responsible for mapping request targets to a
particular application-defined HTTP resource. Now, an application can receive
requests for a certain HTTP resource through a separately-registered resource
object, which allows dynamic configuration of server resources. This way, e.g.
a generic HTTP service with several separate 'drivers' or plugin support for an
HTTP service can be implemented more easily.

src/lib-http/Makefile.am
src/lib-http/http-server-private.h
src/lib-http/http-server-request.c
src/lib-http/http-server-resource.c [new file with mode: 0644]
src/lib-http/http-server.c
src/lib-http/http-server.h

index 2dace1afe66e622c105390b1598a535f16347685..268ed0408829b953ba45b7bc4d57546d6019454a 100644 (file)
@@ -30,6 +30,7 @@ libhttp_la_SOURCES = \
        http-server-response.c \
        http-server-request.c \
        http-server-connection.c \
+       http-server-resource.c \
        http-server.c
 
 headers = \
index 9f2cb515ffce197acf10e0b6a281a17aaf46aa10..afc25e5064f6c6c89876c30145d016e10e8d6b20 100644 (file)
@@ -157,6 +157,26 @@ struct http_server_connection {
        bool switching_ioloop:1; /* in the middle of switching ioloop */
 };
 
+struct http_server_location {
+       const char *path;
+
+       struct http_server_resource *resource;
+};
+
+struct http_server_resource {
+       pool_t pool;
+       struct http_server *server;
+       struct event *event;
+
+       http_server_resource_callback_t *callback;
+       void *context;
+
+       void (*destroy_callback)(void *);
+       void *destroy_context;
+
+       ARRAY(struct http_server_location *) locations;
+};
+
 struct http_server {
        pool_t pool;
 
@@ -168,6 +188,9 @@ struct http_server {
 
        struct connection_list *conn_list;
 
+       ARRAY(struct http_server_resource *) resources;
+       ARRAY(struct http_server_location *) locations;
+
        bool shutting_down:1;    /* shutting down server */
 };
 
@@ -270,6 +293,16 @@ int http_server_connection_discard_payload(
 bool http_server_connection_pending_payload(
        struct http_server_connection *conn);
 
+/*
+ * Resource
+ */
+
+int http_server_resource_find(struct http_server *server, const char *path,
+                             struct http_server_resource **res_r,
+                             const char **sub_path_r) ATTR_NULL(2);
+
+bool http_server_resource_callback(struct http_server_request *req);
+
 /*
  * Server
  */
index 0134541faf4cd9935a44563d6e81c4da1668c489..6328d0206748243391fdef0bab07d3370da6ecd1 100644 (file)
@@ -1,6 +1,7 @@
 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "array.h"
 #include "ioloop.h"
 #include "ostream.h"
 #include "istream-private.h"
@@ -306,6 +307,12 @@ void http_server_request_callback(struct http_server_request *req)
                return;
        }
 
+       if (http_server_resource_callback(req))
+               return;
+
+       if (array_count(&req->server->resources) > 0)
+               e_debug(req->event, "No matching resource found");
+
        if (conn->callbacks->handle_request == NULL) {
                http_server_request_default_handler(req);
                return;
diff --git a/src/lib-http/http-server-resource.c b/src/lib-http/http-server-resource.c
new file mode 100644 (file)
index 0000000..c5e081f
--- /dev/null
@@ -0,0 +1,282 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "bsearch-insert-pos.h"
+
+#include "http-url.h"
+#include "http-server-private.h"
+
+static struct event_category event_category_http_server_resource = {
+       .name = "http-server-resource"
+};
+
+/*
+ * Location
+ */
+
+static int
+http_server_location_cmp(struct http_server_location *const *loc1,
+                        struct http_server_location *const *loc2)
+{
+       return strcmp((*loc1)->path, (*loc2)->path);
+}
+
+static struct http_server_location *
+http_server_location_add(struct http_server *server, pool_t pool,
+                        const char *path)
+{
+       struct http_server_location qloc, *loc;
+       unsigned int insert_idx;
+
+       i_zero(&qloc);
+       qloc.path = path;
+       loc = &qloc;
+
+       if (array_bsearch_insert_pos(&server->locations, &loc,
+                                    http_server_location_cmp, &insert_idx)) {
+               struct http_server_location *const *loc_p;
+
+               loc_p = array_idx(&server->locations, insert_idx);
+               return *loc_p;
+       }
+
+       loc = p_new(pool, struct http_server_location, 1);
+       loc->path = p_strdup(pool, path);
+       array_insert(&server->locations, insert_idx, &loc, 1);
+       return loc;
+}
+
+static int
+http_server_location_find(struct http_server *server, const char *path,
+                         struct http_server_location **loc_r,
+                         const char **sub_path_r)
+{
+       struct http_server_location qloc, *loc;
+       struct http_server_location *const *loc_p;
+       size_t loc_len;
+       unsigned int insert_idx;
+
+       *sub_path_r = NULL;
+       *loc_r = NULL;
+
+       i_zero(&qloc);
+       qloc.path = path;
+       loc = &qloc;
+
+       if (array_bsearch_insert_pos(&server->locations, &loc,
+                                    http_server_location_cmp, &insert_idx)) {
+               /* Exact match */
+               loc_p = array_idx(&server->locations, insert_idx);
+               *sub_path_r = "";
+               *loc_r = *loc_p;
+               return 1;
+       }
+       if (insert_idx == 0) {
+               /* Not found at all */
+               return -1;
+       }
+       loc_p = array_idx(&server->locations, insert_idx-1);
+       loc = *loc_p;
+
+       loc_len = strlen(loc->path);
+       if (!str_begins(path, loc->path)) {
+               /* Location isn't a prefix of path */
+               return -1;
+       } else if (path[loc_len] != '/') {
+               /* Match doesn't end at '/' */
+               return -1;
+       }
+
+       *sub_path_r = &path[loc_len + 1];
+       *loc_r = loc;
+       return 0;
+}
+
+static void
+http_server_location_remove(struct http_server *server,
+                           struct http_server_location *loc)
+{
+       struct http_server_location *const *locp;
+
+       array_foreach(&server->locations, locp) {
+               if (*locp == loc) {
+                       array_delete(
+                               &server->locations,
+                               array_foreach_idx(&server->locations, locp), 1);
+                       return;
+               }
+       }
+}
+
+/*
+ * Resource
+ */
+
+static void http_server_resource_update_event(struct http_server_resource *res)
+{
+       struct http_server_location *const *locs;
+       unsigned int locs_count;
+
+       locs = array_get(&res->locations, &locs_count);
+       if (locs_count == 0) {
+               event_set_append_log_prefix(res->event, "resource: ");
+               return;
+       }
+
+       event_add_str(res->event, "path", locs[0]->path);
+       event_set_append_log_prefix(
+               res->event, t_strdup_printf("resource %s: ", locs[0]->path));
+}
+
+#undef http_server_resource_create
+struct http_server_resource *
+http_server_resource_create(struct http_server *server, pool_t pool,
+                           http_server_resource_callback_t *callback,
+                           void *context)
+{
+       struct http_server_resource *res;
+
+       pool_ref(pool);
+
+       pool = pool_alloconly_create("http server resource", 1024);
+       res = p_new(pool, struct http_server_resource, 1);
+       res->pool = pool;
+       res->server = server;
+
+       res->callback = callback;
+       res->context = context;
+
+       p_array_init(&res->locations, pool, 4);
+
+       res->event = event_create(server->event);
+       event_add_category(res->event, &event_category_http_server_resource);
+       http_server_resource_update_event(res);
+
+       array_append(&server->resources, &res, 1);
+
+       return res;
+}
+
+void http_server_resource_free(struct http_server_resource **_res)
+{
+       struct http_server_resource *res = *_res;
+       struct http_server_location *const *locp;
+
+       if (res == NULL)
+               return;
+
+       *_res = NULL;
+
+       e_debug(res->event, "Free");
+
+       if (res->destroy_callback != NULL) {
+               res->destroy_callback(res->destroy_context);
+               res->destroy_callback = NULL;
+       }
+
+       array_foreach(&res->locations, locp)
+               http_server_location_remove(res->server, *locp);
+
+       event_unref(&res->event);
+       pool_unref(&res->pool);
+}
+
+pool_t http_server_resource_get_pool(struct http_server_resource *res)
+{
+       return res->pool;
+}
+
+const char *http_server_resource_get_path(struct http_server_resource *res)
+{
+       struct http_server_location *const *locs;
+       unsigned int locs_count;
+
+       locs = array_get(&res->locations, &locs_count);
+       i_assert(locs_count > 0);
+
+       return locs[0]->path;
+}
+
+struct event *http_server_resource_get_event(struct http_server_resource *res)
+{
+       return res->event;
+}
+
+void http_server_resource_add_location(struct http_server_resource *res,
+                                      const char *path)
+{
+       struct http_server_location *loc;
+
+       i_assert(*path == '\0' || *path == '/');
+
+       loc = http_server_location_add(res->server, res->pool, path);
+       i_assert(loc->resource == NULL);
+
+       loc->resource = res;
+       array_append(&res->locations, &loc, 1);
+
+       if (array_count(&res->locations) == 1)
+               http_server_resource_update_event(res);
+}
+
+int http_server_resource_find(struct http_server *server, const char *path,
+                             struct http_server_resource **res_r,
+                             const char **sub_path_r)
+{
+       struct http_server_location *loc;
+       int ret;
+
+       if (path == NULL)
+               return -1;
+
+       *res_r = NULL;
+       *sub_path_r = NULL;
+
+       ret = http_server_location_find(server, path, &loc, sub_path_r);
+       if (ret < 0)
+               return -1;
+
+       i_assert(loc->resource != NULL);
+       *res_r = loc->resource;
+       return ret;
+}
+
+bool http_server_resource_callback(struct http_server_request *req)
+{
+       struct http_server *server = req->server;
+       struct http_server_resource *res;
+       const char *sub_path;
+
+       switch (req->req.target.format) {
+       case HTTP_REQUEST_TARGET_FORMAT_ORIGIN:
+               /* According to RFC 7240, Section 5.3.1 only the origin form is
+                  applicable to local resources on an origin server.
+               */
+               break;
+       case HTTP_REQUEST_TARGET_FORMAT_ABSOLUTE:
+       case HTTP_REQUEST_TARGET_FORMAT_AUTHORITY:
+       case HTTP_REQUEST_TARGET_FORMAT_ASTERISK:
+               /* Not applicable for a local resource. */
+               return FALSE;
+       }
+
+       if (http_server_resource_find(server, req->req.target.url->path,
+                                     &res, &sub_path) < 0)
+               return FALSE;
+
+       e_debug(res->event, "Got request: %s", http_server_request_label(req));
+
+       i_assert(res->callback != NULL);
+       res->callback(res->context, req, sub_path);
+       return TRUE;
+}
+
+#undef http_server_resource_set_destroy_callback
+void http_server_resource_set_destroy_callback(struct http_server_resource *res,
+                                              void (*callback)(void *),
+                                              void *context)
+{
+       res->destroy_callback = callback;
+       res->destroy_context = context;
+}
index 4fef8f42c4358c9e889596abb2c943b16e5a14e2..1970ae76f9ffeb8b4a83afc41a666dece5314070 100644 (file)
@@ -58,17 +58,25 @@ struct http_server *http_server_init(const struct http_server_settings *set)
 
        server->conn_list = http_server_connection_list_init();
 
+       p_array_init(&server->resources, pool, 4);
+       p_array_init(&server->locations, pool, 4);
+
        return server;
 }
 
 void http_server_deinit(struct http_server **_server)
 {
        struct http_server *server = *_server;
+       struct http_server_resource **resp;
 
        *_server = NULL;
 
        connection_list_deinit(&server->conn_list);
 
+       array_foreach_modifiable(&server->resources, resp)
+               http_server_resource_free(resp);
+       i_assert(array_count(&server->locations) == 0);
+
        if (server->ssl_ctx != NULL)
                ssl_iostream_context_unref(&server->ssl_ctx);
        event_unref(&server->event);
index 8b9fcb34dc0e01daa295112ae3fa87828cd45e8d..0eea4afc51b7a4d9ec22bf1ab29a5de420fc7916 100644 (file)
@@ -11,6 +11,7 @@ struct ostream;
 struct http_request;
 
 struct http_server;
+struct http_server_resource;
 struct http_server_request;
 struct http_server_response;
 
@@ -344,6 +345,50 @@ void http_server_connection_close(struct http_server_connection **_conn,
 const struct http_server_stats *
 http_server_connection_get_stats(struct http_server_connection *conn);
 
+/*
+ * Resource
+ */
+
+typedef void
+(http_server_resource_callback_t)(void *context,
+                                 struct http_server_request *req,
+                                 const char *sub_path);
+
+struct http_server_resource *
+http_server_resource_create(struct http_server *server, pool_t pool,
+                           http_server_resource_callback_t *callback,
+                           void *context);
+#define http_server_resource_create(server, pool, callback, context) \
+       http_server_resource_create(server, pool, \
+               (http_server_resource_callback_t *)callback, \
+               (TRUE ? context : \
+                CALLBACK_TYPECHECK(callback, void (*)( \
+                       typeof(context), struct http_server_request *req, \
+                       const char *sub_path))))
+/* Resources are freed upon http_server_deinit(), so calling
+   http_server_resource_free() is only necessary when the resource needs to
+   disappear somewhere in the middle of the server lifetime. */
+void http_server_resource_free(struct http_server_resource **_res);
+
+pool_t http_server_resource_get_pool(struct http_server_resource *res)
+                                    ATTR_PURE;
+const char *
+http_server_resource_get_path(struct http_server_resource *res) ATTR_PURE;
+struct event *
+http_server_resource_get_event(struct http_server_resource *res) ATTR_PURE;
+
+void http_server_resource_add_location(struct http_server_resource *res,
+                                      const char *path);
+
+/* Call the specified callback when HTTP resource is destroyed. */
+void http_server_resource_set_destroy_callback(struct http_server_resource *res,
+                                              void (*callback)(void *),
+                                              void *context);
+#define http_server_resource_set_destroy_callback(req, callback, context) \
+       http_server_resource_set_destroy_callback(req, \
+               (void(*)(void*))callback, context - \
+               CALLBACK_TYPECHECK(callback, void (*)(typeof(context))))
+
 /*
  * Server
  */