diff options
| author | Robert Bielik <robert.bielik@dirac.com> | 2020-01-28 07:43:09 +0100 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2020-01-27 22:43:09 -0800 |
| commit | 2545add6240145b419357b9260ae5e8c0d95ba6c (patch) | |
| tree | 155ae2ccfc4c720df7206c7fc81f68531624bc35 | |
| parent | 9414a69a9575564d04022aef9bd898028e52bf09 (diff) | |
| download | nng-2545add6240145b419357b9260ae5e8c0d95ba6c.tar.gz nng-2545add6240145b419357b9260ae5e8c0d95ba6c.tar.bz2 nng-2545add6240145b419357b9260ae5e8c0d95ba6c.zip | |
Add possibility to explicitly set a tree handler as exclusive (#1158)
- Default tree handler behavior is now non-exclusive
- Add 'longest uri first' ordering for http handlers
| -rw-r--r-- | docs/man/libnng.3.adoc | 1 | ||||
| -rw-r--r-- | docs/man/nng_http_handler_alloc.3http.adoc | 10 | ||||
| -rw-r--r-- | docs/man/nng_http_handler_set_tree.3http.adoc | 10 | ||||
| -rw-r--r-- | include/nng/supplemental/http/http.h | 9 | ||||
| -rw-r--r-- | src/supplemental/http/http_api.h | 7 | ||||
| -rw-r--r-- | src/supplemental/http/http_public.c | 11 | ||||
| -rw-r--r-- | src/supplemental/http/http_server.c | 81 | ||||
| -rw-r--r-- | tests/httpserver.c | 96 |
8 files changed, 203 insertions, 22 deletions
diff --git a/docs/man/libnng.3.adoc b/docs/man/libnng.3.adoc index 4e59890f..84c653b7 100644 --- a/docs/man/libnng.3.adoc +++ b/docs/man/libnng.3.adoc @@ -3,6 +3,7 @@ // Copyright 2019 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> // Copyright 2019 Devolutions <info@devolutions.net> +// Copyright 2020 Dirac Research <robert.bielik@dirac.com> // // This document is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this diff --git a/docs/man/nng_http_handler_alloc.3http.adoc b/docs/man/nng_http_handler_alloc.3http.adoc index 6504212f..506cdcf9 100644 --- a/docs/man/nng_http_handler_alloc.3http.adoc +++ b/docs/man/nng_http_handler_alloc.3http.adoc @@ -2,6 +2,7 @@ // // Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> +// Copyright 2020 Dirac Research <robert.bielik@dirac.com> // // This document is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -111,10 +112,14 @@ If no such index file exists, then an `NNG_HTTP_STATUS_NOT_FOUND` (404) error is sent back to the client. The `Content-Type` will be set automatically based upon the extension -of the requested file name. -If a content type cannot be determined from +of the requested file name. If a content type cannot be determined from the extension, then `application/octet-stream` is used. +The directory handler is created as a tree handler initially in exclusive mode (see +xref:nng_http_handler_set_tree.3http.adoc[nng_http_handler_set_tree_exclusive +]). This can be changed by calling xref:nng_http_handler_set_tree.3http.adoc +[nng_http_handler_set_tree(3http)] after creating the directory handler. + === File Handler The third member of this family, `nng_http_handler_alloc_file()`, creates @@ -178,6 +183,7 @@ xref:nng_http_handler_free.3http.adoc[nng_http_handler_free(3http)], xref:nng_http_handler_set_host.3http.adoc[nng_http_handler_set_host(3http)], xref:nng_http_handler_set_method.3http.adoc[nng_http_handler_set_method(3http)], xref:nng_http_handler_set_tree.3http.adoc[nng_http_handler_set_tree(3http)], +xref:nng_http_handler_set_tree.3http.adoc[nng_http_handler_set_tree_exclusive(3http)], xref:nng_http_res_alloc.3http.adoc[nng_http_res_alloc(3http)], xref:nng_http_res_alloc_error.3http.adoc[nng_http_res_alloc_error(3http)], xref:nng_http_server_add_handler.3http.adoc[nng_http_server_add_handler(3http)], diff --git a/docs/man/nng_http_handler_set_tree.3http.adoc b/docs/man/nng_http_handler_set_tree.3http.adoc index fc26ec70..dd2731f8 100644 --- a/docs/man/nng_http_handler_set_tree.3http.adoc +++ b/docs/man/nng_http_handler_set_tree.3http.adoc @@ -2,6 +2,7 @@ // // Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> +// Copyright 2020 Dirac Research <robert.bielik@dirac.com> // // This document is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -21,6 +22,8 @@ nng_http_handler_set_tree - set HTTP handler to match trees #include <nng/supplemental/http/http.h> int nng_http_handler_set_tree(nng_http_handler *handler); + +int nng_http_handler_set_tree_exclusive(nng_http_handler *handler); ---- == DESCRIPTION @@ -29,7 +32,12 @@ The `nng_http_handler_set_tree()` function causes the _handler_ to be matched if the Request URI sent by the client is a logical child of the path for _handler_. -TIP: This method is useful when constructing API handlers where a single +The `nng_http_handler_set_tree_exclusive()` function is similar to `nng_http_server_set_tree()` +with the distinction that the _handler_ will be considered to *exclusively* handling its +Request URI. Other handlers will be tested against _handler_ when being added to a server, +possibly resulting in a URI conflict error. + +TIP: These methods are useful when constructing API handlers where a single service address (path) supports dynamically generated children. == RETURN VALUES diff --git a/include/nng/supplemental/http/http.h b/include/nng/supplemental/http/http.h index cdd9b25f..a2d67be4 100644 --- a/include/nng/supplemental/http/http.h +++ b/include/nng/supplemental/http/http.h @@ -1,6 +1,7 @@ // // Copyright 2020 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> +// Copyright 2020 Dirac Research <robert.bielik@dirac.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -372,6 +373,14 @@ NNG_DECL int nng_http_handler_collect_body(nng_http_handler *, bool, size_t); // called for an exact path match. NNG_DECL int nng_http_handler_set_tree(nng_http_handler *); +// nng_http_handler_set_tree_exclusive indicates that the handler is being +// registered for a heirarchical tree *exclusively*, rather than just a single +// path, so it will be called for all child paths supplied. By default the +// handler is only called for an exact path match. Exclusive means that any +// other handler on a conflicting path will induce an address conflict error +// when added to a server. +NNG_DECL int nng_http_handler_set_tree_exclusive(nng_http_handler *); + // nng_http_handler_set_data is used to store additional data, along with // a possible clean up routine. (The clean up is a custom de-allocator and // will be called with the supplied data as an argument, when the handler diff --git a/src/supplemental/http/http_api.h b/src/supplemental/http/http_api.h index 45738318..6be681a9 100644 --- a/src/supplemental/http/http_api.h +++ b/src/supplemental/http/http_api.h @@ -301,6 +301,13 @@ extern void nni_http_handler_collect_body(nni_http_handler *, bool, size_t); // will probably need to inspect the URL of the request. extern int nni_http_handler_set_tree(nni_http_handler *); +// nni_http_handler_set_tree_exclusive marks the handler as servicing the +// entire tree (e.g. a directory) exclusively, rather than just a leaf node. +// When servicing a tree exclusively, other handlers sharing parts of the uri +// will induce an address conflict when adding them to a server. The handler +// will probably need to inspect the URL of the request. +extern int nni_http_handler_set_tree_exclusive(nni_http_handler *); + // nni_http_handler_set_host limits the handler to only being called for // the given Host: field. This can be used to set up multiple virtual // hosts. Note that host names must match exactly. If NULL or an empty diff --git a/src/supplemental/http/http_public.c b/src/supplemental/http/http_public.c index d2e876ab..60ca2693 100644 --- a/src/supplemental/http/http_public.c +++ b/src/supplemental/http/http_public.c @@ -636,6 +636,17 @@ nng_http_handler_set_tree(nng_http_handler *h) } int +nng_http_handler_set_tree_exclusive(nng_http_handler *h) +{ +#ifdef NNG_SUPP_HTTP + return (nni_http_handler_set_tree_exclusive(h)); +#else + NNI_ARG_UNUSED(h); + return (NNG_ENOTSUP); +#endif +} + +int nng_http_handler_set_data(nng_http_handler *h, void *dat, void (*dtor)(void *)) { #ifdef NNG_SUPP_HTTP diff --git a/src/supplemental/http/http_server.c b/src/supplemental/http/http_server.c index da12c20d..5eadf6d5 100644 --- a/src/supplemental/http/http_server.c +++ b/src/supplemental/http/http_server.c @@ -3,6 +3,7 @@ // Copyright 2018 Capitar IT Group BV <info@capitar.com> // Copyright 2018 QXSoftware <lh563566994@126.com> // Copyright 2019 Devolutions <info@devolutions.net> +// Copyright 2020 Dirac Research <robert.bielik@dirac.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -37,6 +38,7 @@ struct nng_http_handler { char * method; char * host; bool tree; + bool tree_exclusive; nni_atomic_u64 ref; nni_atomic_bool busy; size_t maxbody; @@ -110,11 +112,12 @@ nni_http_handler_init( return (NNG_ENOMEM); } NNI_LIST_NODE_INIT(&h->node); - h->cb = cb; - h->data = NULL; - h->dtor = NULL; - h->host = NULL; - h->tree = false; + h->cb = cb; + h->data = NULL; + h->dtor = NULL; + h->host = NULL; + h->tree = false; + h->tree_exclusive = false; h->maxbody = 1024 * 1024; // By default we accept up to 1MB of body h->getbody = true; *hp = h; @@ -177,7 +180,19 @@ nni_http_handler_set_tree(nni_http_handler *h) if (nni_atomic_get_bool(&h->busy) != 0) { return (NNG_EBUSY); } - h->tree = true; + h->tree = true; + h->tree_exclusive = false; + return (0); +} + +int +nni_http_handler_set_tree_exclusive(nni_http_handler *h) +{ + if (nni_atomic_get_bool(&h->busy) != 0) { + return (NNG_EBUSY); + } + h->tree = true; + h->tree_exclusive = true; return (0); } @@ -1115,8 +1130,8 @@ nni_http_server_add_handler(nni_http_server *s, nni_http_handler *h) } nni_mtx_lock(&s->mtx); - // General rule for finding a conflict is that if either string - // is a strict substring of the other, then we have a + // General rule for finding a conflict is that if either uri + // string is an exact duplicate of the other, then we have a // collision. (But only if the methods match, and the host // matches.) Note that a wild card host matches both. NNI_LIST_FOREACH (&s->handlers, h2) { @@ -1146,26 +1161,54 @@ nni_http_server_add_handler(nni_http_server *s, nni_http_handler *h) while ((len2 > 0) && (h2->uri[len2 - 1] == '/')) { len2--; // ignore trailing '/' } - if (strncmp(h->uri, h2->uri, len > len2 ? len2 : len) != 0) { - continue; // prefixes don't match. - } - if (len2 > len) { - if ((h2->uri[len] == '/') && (h->tree)) { - nni_mtx_unlock(&s->mtx); - return (NNG_EADDRINUSE); + if ((h2->tree && h2->tree_exclusive) || + (h->tree && h->tree_exclusive)) { + // Old behavior + if (strncmp(h->uri, h2->uri, + len > len2 ? len2 : len) != 0) { + continue; // prefixes don't match. } - } else if (len > len2) { - if ((h->uri[len2] == '/') && (h2->tree)) { + + if (len2 > len) { + if ((h2->uri[len] == '/') && (h->tree)) { + nni_mtx_unlock(&s->mtx); + return (NNG_EADDRINUSE); + } + } else if (len > len2) { + if ((h->uri[len2] == '/') && (h2->tree)) { + nni_mtx_unlock(&s->mtx); + return (NNG_EADDRINUSE); + } + } else { nni_mtx_unlock(&s->mtx); return (NNG_EADDRINUSE); } } else { + if (len != len2) { + continue; // length mismatch + } + + if (strcmp(h->uri, h2->uri) != 0) { + continue; // not a duplicate + } + nni_mtx_unlock(&s->mtx); return (NNG_EADDRINUSE); } } - nni_list_append(&s->handlers, h); + + // Maintain list of handlers in longest uri first order + NNI_LIST_FOREACH (&s->handlers, h2) { + size_t len2 = strlen(h2->uri); + if (len > len2) { + nni_list_insert_before(&s->handlers, h, h2); + break; + } + } + if (h2 == NULL) { + nni_list_append(&s->handlers, h); + } // Note that we have borrowed the reference count on the handler. // Thus we own it, and if the server is destroyed while we have it, @@ -1533,7 +1576,7 @@ nni_http_handler_init_directory( // We don't permit a body for getting a file. nni_http_handler_collect_body(h, true, 0); - if (((rv = nni_http_handler_set_tree(h)) != 0) || + if (((rv = nni_http_handler_set_tree_exclusive(h)) != 0) || ((rv = nni_http_handler_set_data(h, hf, http_file_free)) != 0)) { http_file_free(hf); nni_http_handler_fini(h); diff --git a/tests/httpserver.c b/tests/httpserver.c index 9b7ed0e3..a509638e 100644 --- a/tests/httpserver.c +++ b/tests/httpserver.c @@ -1,6 +1,7 @@ // // Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> +// Copyright 2020 Dirac Research <robert.bielik@dirac.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -695,6 +696,101 @@ TestMain("HTTP Server", { }); }); + Convey("Multiple tree handlers works", { + char urlstr[32]; + nng_url *url; + char * tmpdir; + char * workdir; + char * workdir2; + char * file1; + char * file2; + + trantest_next_address(urlstr, "http://127.0.0.1:%u"); + So(nng_url_parse(&url, urlstr) == 0); + So(nng_http_server_hold(&s, url) == 0); + So((tmpdir = nni_plat_temp_dir()) != NULL); + So((workdir = nni_file_join(tmpdir, "httptest")) != NULL); + So((workdir2 = nni_file_join(tmpdir, "httptest2")) != NULL); + So((file1 = nni_file_join(workdir, "file1.txt")) != NULL); + So((file2 = nni_file_join(workdir2, "file2.txt")) != NULL); + + So(nni_file_put(file1, doc1, strlen(doc1)) == 0); + So(nni_file_put(file2, doc2, strlen(doc2)) == 0); + + Reset({ + nng_http_server_release(s); + free(tmpdir); + nni_file_delete(file1); + nni_file_delete(file2); + nni_file_delete(workdir); + nni_file_delete(workdir2); + free(workdir2); + free(workdir); + free(file1); + free(file2); + nng_url_free(url); + }); + + So(nng_http_handler_alloc_directory(&h, "/", workdir) == 0); + So(nng_http_handler_set_tree(h) == 0); + So(nng_http_server_add_handler(s, h) == 0); + + So(nng_http_handler_alloc_directory(&h, "/", workdir) == 0); + So(nng_http_handler_set_tree(h) == 0); + So(nng_http_server_add_handler(s, h) == NNG_EADDRINUSE); + nng_http_handler_free(h); + + So(nng_http_handler_alloc_directory(&h, "/subdir", workdir2) == + 0); + So(nng_http_handler_set_tree(h) == 0); + So(nng_http_server_add_handler(s, h) == 0); + + So(nng_http_handler_alloc_directory(&h, "/subdir", workdir2) == + 0); + So(nng_http_handler_set_tree(h) == 0); + So(nng_http_server_add_handler(s, h) == NNG_EADDRINUSE); + nng_http_handler_free(h); + + So(nng_http_server_start(s) == 0); + nng_msleep(100); + + Convey("Named file works (1)", { + char fullurl[256]; + void * data; + size_t size; + uint16_t stat; + char * ctype; + + snprintf( + fullurl, sizeof(fullurl), "%s/file1.txt", urlstr); + So(httpget(fullurl, &data, &size, &stat, &ctype) == 0); + So(stat == NNG_HTTP_STATUS_OK); + So(size == strlen(doc1)); + So(memcmp(data, doc1, size) == 0); + So(strcmp(ctype, "text/plain") == 0); + nni_strfree(ctype); + nng_free(data, size); + }); + + Convey("Named file works (2)", { + char fullurl[256]; + void * data; + size_t size; + uint16_t stat; + char * ctype; + + snprintf(fullurl, sizeof(fullurl), + "%s/subdir/file2.txt", urlstr); + So(httpget(fullurl, &data, &size, &stat, &ctype) == 0); + So(stat == NNG_HTTP_STATUS_OK); + So(size == strlen(doc2)); + So(memcmp(data, doc2, size) == 0); + So(strcmp(ctype, "text/plain") == 0); + nni_strfree(ctype); + nng_free(data, size); + }); + }); + Convey("Custom POST handler works", { char urlstr[32]; nng_url *url; |
