aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/man/libnng.3.adoc1
-rw-r--r--docs/man/nng_http_handler_alloc.3http.adoc1
-rw-r--r--docs/man/nng_http_handler_collect_body.3http.adoc78
-rw-r--r--src/supplemental/http/http.h10
-rw-r--r--src/supplemental/http/http_api.h6
-rw-r--r--src/supplemental/http/http_msg.c11
-rw-r--r--src/supplemental/http/http_public.c11
-rw-r--r--src/supplemental/http/http_server.c99
-rw-r--r--tests/httpserver.c92
9 files changed, 289 insertions, 20 deletions
diff --git a/docs/man/libnng.3.adoc b/docs/man/libnng.3.adoc
index 0a62f931..25e8b08d 100644
--- a/docs/man/libnng.3.adoc
+++ b/docs/man/libnng.3.adoc
@@ -356,6 +356,7 @@ These functions are intended for use with HTTP server applications.
|===
|<<nng_http_handler_alloc.3http#,nng_http_handler_alloc()>>|allocate HTTP server handler
+|<<nng_http_handler_collect_body.3http#,nng_http_handler_collect_body()>>|set HTTP handler to collect request body
|<<nng_http_handler_free.3http#,nng_http_handler_free()>>|free HTTP server handler
|<<nng_http_handler_get_data.3http#,nng_http_handler_get_data()>>|return extra data for HTTP handler
|<<nng_http_handler_set_data.3http#,nng_http_handler_set_data()>>|set extra data for HTTP handler
diff --git a/docs/man/nng_http_handler_alloc.3http.adoc b/docs/man/nng_http_handler_alloc.3http.adoc
index 523b5328..d5861d14 100644
--- a/docs/man/nng_http_handler_alloc.3http.adoc
+++ b/docs/man/nng_http_handler_alloc.3http.adoc
@@ -149,6 +149,7 @@ This function returns 0 on success, and non-zero otherwise.
<<nng_aio_finish.3#,nng_aio_finish(3)>>,
<<nng_aio_get_input.3#,nng_aio_get_input(3)>>,
<<nng_aio_set_output.3#,nng_aio_set_output(3)>>,
+<<nng_http_handler_collect_body.3http#,nng_http_handler_collect_body(3http)>>,
<<nng_http_handler_free.3http#,nng_http_handler_free(3http)>>,
<<nng_http_handler_set_host.3http#,nng_http_handler_set_host(3http)>>,
<<nng_http_handler_set_method.3http#,nng_http_handler_set_method(3http)>>,
diff --git a/docs/man/nng_http_handler_collect_body.3http.adoc b/docs/man/nng_http_handler_collect_body.3http.adoc
new file mode 100644
index 00000000..875f32c4
--- /dev/null
+++ b/docs/man/nng_http_handler_collect_body.3http.adoc
@@ -0,0 +1,78 @@
+= nng_http_handler_collect_body(3http)
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This document is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+== NAME
+
+nng_http_handler_collect_body - set HTTP handler to collect request body
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/http/http.h>
+
+int nng_http_handler_collect_body(nng_http_handler *handler, bool want, size_t maxsz);
+----
+
+== DESCRIPTION
+
+The `nng_http_handler_collect_data()` function causes the _handler_ to
+collect any request body that was submitted with the request, and attach
+it to the `nng_http_req` before the handler is called.
+
+Subsequently the data can be retrieved by the handler from the request with the
+`<<nng_http_req_get_data.3http#,nng_http_req_get_data()>>` function.
+
+The collection is enabled if _want_ is true.
+Furthermore, the data that the client may sent is limited by the
+value of _maxsz_.
+If the client attempts to send more data than _maxsz_, then the
+request will be terminated with a 400 "`Bad Request`" status.
+
+TIP: Limiting the size of incoming request data can provide protection
+against denial of service attacks, as a buffer of the client-supplied
+size must be allocated to receive the data.
+
+In order to provide an unlimited size, use `(size_t)-1` for _maxsz_.
+The value `0` for _maxsz_ can be used to prevent any data from being passed
+by the client.
+
+The built-in handlers for files, directories, and static data limit the
+_maxsz_ to zero by default.
+Otherwise the default setting is to enable this capability with a default
+value of _maxsz_ of 1 megabyte.
+
+NOTE: The handler looks for data indicated by the `Content-Length:` HTTP
+header.
+If this header is absent, the request is assumed not to contain any data.
+
+NOTE: This specifically does not support the `Chunked` transfer-encoding.
+This is considered a bug, and is a deficiency for full HTTP/1.1 compliance.
+However, few clients send data in this format, so in practice this should
+not create few limitations.
+
+== RETURN VALUES
+
+This function returns 0 on success, and non-zero otherwise.
+
+== ERRORS
+
+[horizontal]
+`NNG_ENOTSUP`:: No support for HTTP in the library.
+
+== SEE ALSO
+
+[.text-left]
+<<nng_http_handler_alloc.3http#,nng_http_handler_alloc(3http)>>,
+<<nng_http_server_add_handler.3http#,nng_http_server_add_handler(3http)>>,
+<<nng_http_req_get_data.3http#,nng_http_req_get_data(3http)>>,
+<<nng.7#,nng(7)>>
diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h
index cffd1a8b..d9edd5c8 100644
--- a/src/supplemental/http/http.h
+++ b/src/supplemental/http/http.h
@@ -350,6 +350,16 @@ NNG_DECL int nng_http_handler_set_method(nng_http_handler *, const char *);
// that case is not considered.)
NNG_DECL int nng_http_handler_set_host(nng_http_handler *, const char *);
+// nng_http_handler_collect_body is used to indicate the server should
+// check for, and process, data sent by the client, which will be attached
+// to the request. If this is false, then the handler will need to check
+// for and process any content data. By default the server will accept
+// up to 1MB. If the client attempts to send more data than requested,
+// then a 400 Bad Request will be sent back to the client. To set an
+// unlimited value, use (size_t)-1. To preclude the client from sending
+// *any* data, use 0. (The static and file handlers use 0 by default.)
+NNG_DECL int nng_http_handler_collect_body(nng_http_handler *, bool, size_t);
+
// nng_http_handler_set_tree indicates that the handler is being registered
// for a heirarchical tree, rather than just a single path, so it will be
// called for all child paths supplied. By default the handler is only
diff --git a/src/supplemental/http/http_api.h b/src/supplemental/http/http_api.h
index 71b24f54..a30399d2 100644
--- a/src/supplemental/http/http_api.h
+++ b/src/supplemental/http/http_api.h
@@ -101,6 +101,7 @@ extern int nni_http_req_copy_data(nni_http_req *, const void *, size_t);
extern int nni_http_res_copy_data(nni_http_res *, const void *, size_t);
extern int nni_http_req_set_data(nni_http_req *, const void *, size_t);
extern int nni_http_res_set_data(nni_http_res *, const void *, size_t);
+extern int nni_http_req_alloc_data(nni_http_req *, size_t);
extern int nni_http_res_alloc_data(nni_http_res *, size_t);
extern const char *nni_http_req_get_method(nni_http_req *);
extern const char *nni_http_req_get_version(nni_http_req *);
@@ -248,6 +249,11 @@ extern int nni_http_handler_init_static(
// calls this for any handlers still registered with it if it is destroyed.
extern void nni_http_handler_fini(nni_http_handler *);
+// nni_http_handler_collect_body informs the server that it should collect
+// the entitty data associated with the client request, and sets the maximum
+// size to accept.
+extern void nni_http_handler_collect_body(nni_http_handler *, bool, size_t);
+
// nni_http_handler_set_tree marks the handler as servicing the entire
// tree (e.g. a directory), rather than just a leaf node. The handler
// will probably need to inspect the URL of the request.
diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c
index 6d7e9f8a..3b78a2a9 100644
--- a/src/supplemental/http/http_msg.c
+++ b/src/supplemental/http/http_msg.c
@@ -382,6 +382,17 @@ nni_http_req_copy_data(nni_http_req *req, const void *data, size_t size)
}
int
+nni_http_req_alloc_data(nni_http_req *req, size_t size)
+{
+ int rv;
+
+ if ((rv = http_entity_alloc_data(&req->data, size)) != 0) {
+ return (rv);
+ }
+ return (0);
+}
+
+int
nni_http_res_copy_data(nni_http_res *res, const void *data, size_t size)
{
int rv;
diff --git a/src/supplemental/http/http_public.c b/src/supplemental/http/http_public.c
index 389b74c8..b86521b0 100644
--- a/src/supplemental/http/http_public.c
+++ b/src/supplemental/http/http_public.c
@@ -587,6 +587,17 @@ nng_http_handler_set_method(nng_http_handler *h, const char *meth)
}
int
+nng_http_handler_collect_body(nng_http_handler *h, bool want, size_t len)
+{
+#ifdef NNG_SUPP_HTTP
+ nni_http_handler_collect_body(h, want, len);
+ return (0);
+#else
+ return (NNG_ENOTSUP);
+#endif
+}
+
+int
nng_http_handler_set_host(nng_http_handler *h, const char *host)
{
#ifdef NNG_SUPP_HTTP
diff --git a/src/supplemental/http/http_server.c b/src/supplemental/http/http_server.c
index f36c941d..1a7f2c25 100644
--- a/src/supplemental/http/http_server.c
+++ b/src/supplemental/http/http_server.c
@@ -35,25 +35,28 @@ struct nng_http_handler {
char * host;
bool tree;
int refcnt;
+ size_t maxbody;
+ bool getbody;
void * data;
nni_cb dtor;
void (*cb)(nni_aio *);
};
typedef struct http_sconn {
- nni_list_node node;
- nni_http_conn * conn;
- nni_http_server *server;
- nni_http_req * req;
- nni_http_res * res;
- bool close;
- bool closed;
- bool finished;
- nni_aio * cbaio;
- nni_aio * rxaio;
- nni_aio * txaio;
- nni_aio * txdataio;
- nni_reap_item reap;
+ nni_list_node node;
+ nni_http_conn * conn;
+ nni_http_server * server;
+ nni_http_req * req;
+ nni_http_res * res;
+ nni_http_handler *handler; // set if we deferred to read body
+ bool close;
+ bool closed;
+ bool finished;
+ nni_aio * cbaio;
+ nni_aio * rxaio;
+ nni_aio * txaio;
+ nni_aio * txdataio;
+ nni_reap_item reap;
} http_sconn;
typedef struct http_error {
@@ -101,13 +104,15 @@ 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->refcnt = 0;
- *hp = h;
+ h->cb = cb;
+ h->data = NULL;
+ h->dtor = NULL;
+ h->host = NULL;
+ h->tree = false;
+ h->refcnt = 0;
+ h->maxbody = 1024 * 1024; // By default we accept up to 1MB of body
+ h->getbody = true;
+ *hp = h;
return (0);
}
@@ -126,6 +131,13 @@ nni_http_handler_fini(nni_http_handler *h)
NNI_FREE_STRUCT(h);
}
+void
+nni_http_handler_collect_body(nni_http_handler *h, bool want, size_t maxbody)
+{
+ h->getbody = want;
+ h->maxbody = maxbody;
+}
+
int
nni_http_handler_set_data(nni_http_handler *h, void *data, nni_cb dtor)
{
@@ -292,6 +304,7 @@ http_sconn_txdatdone(void *arg)
return;
}
+ sc->handler = NULL;
nni_http_req_reset(sc->req);
nni_http_read_req(sc->conn, sc->req, sc->rxaio);
}
@@ -316,6 +329,7 @@ http_sconn_txdone(void *arg)
nni_http_res_free(sc->res);
sc->res = NULL;
}
+ sc->handler = NULL;
nni_http_req_reset(sc->req);
nni_http_read_req(sc->conn, sc->req, sc->rxaio);
}
@@ -457,12 +471,18 @@ http_sconn_rxdone(void *arg)
bool badmeth = false;
bool needhost = false;
const char * host;
+ const char * cls;
if ((rv = nni_aio_result(aio)) != 0) {
http_sconn_close(sc);
return;
}
+ if ((h = sc->handler) != NULL) {
+ nni_mtx_lock(&s->mtx);
+ goto finish;
+ }
+
// Validate the request -- it has to at least look like HTTP
// 1.x. We flatly refuse to deal with HTTP 0.9, and we can't
// cope with HTTP/2.
@@ -594,6 +614,35 @@ http_sconn_rxdone(void *arg)
return;
}
+ if ((h->getbody) &&
+ ((cls = nni_http_req_get_header(req, "Content-Length")) != NULL)) {
+ uint64_t len;
+
+ if ((nni_strtou64(cls, &len) != 0) || (len > h->maxbody)) {
+ nni_mtx_unlock(&s->mtx);
+ http_sconn_error(sc, NNG_HTTP_STATUS_BAD_REQUEST);
+ return;
+ }
+ if (len > 0) {
+ nng_iov iov;
+ if ((nni_http_req_alloc_data(req, (size_t) len)) !=
+ 0) {
+ nni_mtx_unlock(&s->mtx);
+ http_sconn_error(
+ sc, NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ nng_http_req_get_data(req, &iov.iov_buf, &iov.iov_len);
+ sc->handler = h;
+ nni_mtx_unlock(&s->mtx);
+ nni_aio_set_iov(sc->rxaio, 1, &iov);
+ nni_http_read_full(sc->conn, aio);
+ return;
+ }
+ }
+
+finish:
+ sc->handler = NULL;
nni_aio_set_input(sc->cbaio, 0, sc->req);
nni_aio_set_input(sc->cbaio, 1, h);
nni_aio_set_input(sc->cbaio, 2, sc->conn);
@@ -671,6 +720,7 @@ http_sconn_cbdone(void *arg)
} else {
// Presumably client already sent a response.
// Wait for another request.
+ sc->handler = NULL;
nni_http_req_reset(sc->req);
nni_http_read_req(sc->conn, sc->req, sc->rxaio);
}
@@ -747,6 +797,7 @@ http_server_acccb(void *arg)
sc->server = s;
nni_list_append(&s->conns, sc);
+ sc->handler = NULL;
nni_http_read_req(sc->conn, sc->req, sc->rxaio);
nni_tcp_listener_accept(s->listener, s->accaio);
nni_mtx_unlock(&s->mtx);
@@ -1345,6 +1396,9 @@ nni_http_handler_init_file_ctype(nni_http_handler **hpp, const char *uri,
return (rv);
}
+ // We don't permit a body for getting a file.
+ nni_http_handler_collect_body(h, true, 0);
+
*hpp = h;
return (0);
}
@@ -1505,6 +1559,8 @@ nni_http_handler_init_directory(
http_file_free(hf);
return (rv);
}
+ // 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) ||
((rv = nni_http_handler_set_data(h, hf, http_file_free)) != 0)) {
@@ -1596,6 +1652,9 @@ nni_http_handler_init_static(nni_http_handler **hpp, const char *uri,
return (rv);
}
+ // We don't permit a body for getting static data.
+ nni_http_handler_collect_body(h, true, 0);
+
*hpp = h;
return (0);
}
diff --git a/tests/httpserver.c b/tests/httpserver.c
index a36901f5..21b961b8 100644
--- a/tests/httpserver.c
+++ b/tests/httpserver.c
@@ -158,6 +158,30 @@ fail:
return (rv);
}
+static void
+httpecho(nng_aio *aio)
+{
+ nng_http_req *req = nng_aio_get_input(aio, 0);
+ nng_http_res *res;
+ int rv;
+ void * body;
+ size_t len;
+
+ nng_http_req_get_data(req, &body, &len);
+
+ if (((rv = nng_http_res_alloc(&res)) != 0) ||
+ ((rv = nng_http_res_copy_data(res, body, len)) != 0) ||
+ ((rv = nng_http_res_set_header(
+ res, "Content-type", "text/plain")) != 0) ||
+ ((rv = nng_http_res_set_status(res, NNG_HTTP_STATUS_OK)) != 0)) {
+ nng_http_res_free(res);
+ nng_aio_finish(aio, rv);
+ return;
+ }
+ nng_aio_set_output(aio, 0, res);
+ nng_aio_finish(aio, 0);
+}
+
TestMain("HTTP Server", {
nng_http_server * s;
nng_http_handler *h;
@@ -458,4 +482,72 @@ TestMain("HTTP Server", {
nng_url_free(curl);
});
});
+ Convey("Custom POST handler works", {
+ char urlstr[32];
+ nng_url *url;
+
+ 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);
+
+ Reset({
+ nng_http_server_release(s);
+ nng_url_free(url);
+ });
+
+ So(nng_http_handler_alloc(&h, "/post", httpecho) == 0);
+ So(nng_http_handler_set_method(h, "POST") == 0);
+ So(nng_http_server_add_handler(s, h) == 0);
+ So(nng_http_server_start(s) == 0);
+
+ nng_msleep(100);
+
+ Convey("Echo POST works", {
+ char fullurl[256];
+ size_t size;
+ nng_http_req *req;
+ nng_http_res *res;
+ nng_url * curl;
+ char txdata[5];
+ char * rxdata;
+
+ snprintf(txdata, sizeof(txdata), "1234");
+ So(nng_http_res_alloc(&res) == 0);
+ snprintf(fullurl, sizeof(fullurl), "%s/post", urlstr);
+ So(nng_url_parse(&curl, fullurl) == 0);
+ So(nng_http_req_alloc(&req, curl) == 0);
+ nng_http_req_set_data(req, txdata, strlen(txdata));
+ So(nng_http_req_set_method(req, "POST") == 0);
+ So(httpdo(curl, req, res, (void **) &rxdata, &size) ==
+ 0);
+ So(nng_http_res_get_status(res) == NNG_HTTP_STATUS_OK);
+ So(size == strlen(txdata));
+ So(strncmp(txdata, rxdata, size) == 0);
+ nng_http_req_free(req);
+ nng_http_res_free(res);
+ nng_url_free(curl);
+ });
+
+ Convey("Get method gives 405", {
+ char fullurl[256];
+ void * data;
+ size_t size;
+ nng_http_req *req;
+ nng_http_res *res;
+ nng_url * curl;
+
+ So(nng_http_res_alloc(&res) == 0);
+ snprintf(fullurl, sizeof(fullurl), "%s/post", urlstr);
+ So(nng_url_parse(&curl, fullurl) == 0);
+ So(nng_http_req_alloc(&req, curl) == 0);
+ So(nng_http_req_set_method(req, "GET") == 0);
+
+ So(httpdo(curl, req, res, &data, &size) == 0);
+ So(nng_http_res_get_status(res) ==
+ NNG_HTTP_STATUS_METHOD_NOT_ALLOWED);
+ nng_http_req_free(req);
+ nng_http_res_free(res);
+ nng_url_free(curl);
+ });
+ });
})