aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Bielik <robert.bielik@dirac.com>2020-01-28 07:43:09 +0100
committerGarrett D'Amore <garrett@damore.org>2020-01-27 22:43:09 -0800
commit2545add6240145b419357b9260ae5e8c0d95ba6c (patch)
tree155ae2ccfc4c720df7206c7fc81f68531624bc35
parent9414a69a9575564d04022aef9bd898028e52bf09 (diff)
downloadnng-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.adoc1
-rw-r--r--docs/man/nng_http_handler_alloc.3http.adoc10
-rw-r--r--docs/man/nng_http_handler_set_tree.3http.adoc10
-rw-r--r--include/nng/supplemental/http/http.h9
-rw-r--r--src/supplemental/http/http_api.h7
-rw-r--r--src/supplemental/http/http_public.c11
-rw-r--r--src/supplemental/http/http_server.c81
-rw-r--r--tests/httpserver.c96
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;