From: Stephan Bosch Date: Fri, 8 Nov 2019 16:11:36 +0000 (+0100) Subject: lib-http: http-server - Implement support for dynamically adding resources. X-Git-Tag: 2.3.11.2~323 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fd8e4fae4310ec720a4e327d412870bbf0769734;p=thirdparty%2Fdovecot%2Fcore.git lib-http: http-server - Implement support for dynamically adding resources. 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. --- diff --git a/src/lib-http/Makefile.am b/src/lib-http/Makefile.am index 2dace1afe6..268ed04088 100644 --- a/src/lib-http/Makefile.am +++ b/src/lib-http/Makefile.am @@ -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 = \ diff --git a/src/lib-http/http-server-private.h b/src/lib-http/http-server-private.h index 9f2cb515ff..afc25e5064 100644 --- a/src/lib-http/http-server-private.h +++ b/src/lib-http/http-server-private.h @@ -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 */ diff --git a/src/lib-http/http-server-request.c b/src/lib-http/http-server-request.c index 0134541faf..6328d02067 100644 --- a/src/lib-http/http-server-request.c +++ b/src/lib-http/http-server-request.c @@ -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 index 0000000000..c5e081f944 --- /dev/null +++ b/src/lib-http/http-server-resource.c @@ -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; +} diff --git a/src/lib-http/http-server.c b/src/lib-http/http-server.c index 4fef8f42c4..1970ae76f9 100644 --- a/src/lib-http/http-server.c +++ b/src/lib-http/http-server.c @@ -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); diff --git a/src/lib-http/http-server.h b/src/lib-http/http-server.h index 8b9fcb34dc..0eea4afc51 100644 --- a/src/lib-http/http-server.h +++ b/src/lib-http/http-server.h @@ -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 */