summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2018-10-07 13:04:00 -0700
committerGarrett D'Amore <garrett@damore.org>2018-10-07 13:14:13 -0700
commit617bb5112834eee40d7eaf00bfc7e98e0ae1ff01 (patch)
tree686a5566b64d1cb79b495e00f5c106145f58b74b
parent6c334f30cccaa9ddae81ee0865621b6695fb7e3a (diff)
downloadnng-617bb5112834eee40d7eaf00bfc7e98e0ae1ff01.tar.gz
nng-617bb5112834eee40d7eaf00bfc7e98e0ae1ff01.tar.bz2
nng-617bb5112834eee40d7eaf00bfc7e98e0ae1ff01.zip
fixes #745 HTTP server redirect handler
-rw-r--r--docs/man/nng_http_handler_alloc.3http.adoc30
-rw-r--r--src/supplemental/http/http.h6
-rw-r--r--src/supplemental/http/http_api.h13
-rw-r--r--src/supplemental/http/http_msg.c65
-rw-r--r--src/supplemental/http/http_public.c15
-rw-r--r--src/supplemental/http/http_server.c179
-rw-r--r--tests/httpserver.c116
7 files changed, 358 insertions, 66 deletions
diff --git a/docs/man/nng_http_handler_alloc.3http.adoc b/docs/man/nng_http_handler_alloc.3http.adoc
index d5861d14..c01cd51b 100644
--- a/docs/man/nng_http_handler_alloc.3http.adoc
+++ b/docs/man/nng_http_handler_alloc.3http.adoc
@@ -31,6 +31,9 @@ int nng_http_handler_alloc_directory(nng_http_handler **hp, const char *path,
int nng_http_handler_alloc_file(nng_http_handler **hp, const char *path,
const char *filename);
+int nng_http_handler_alloc_redirect(nng_http_handler **hp, const char *path,
+ uint16_t status, const char *location);
+
int nng_http_handler_alloc_static(nng_http_handler **hp, const char *path,
const void *data, size_t size, const char *content_type);
----
@@ -48,7 +51,7 @@ Only the path component of the Request URI is
considered when determining whether the handler should be called.
Additionally each handler has a method it is registered to handle
-(the default is "GET", see
+(the default is `GET`, see
`<<nng_http_handler_set_method.3http#,nng_http_handler_set_method()>>`), and
optionally a 'Host' header it can be matched against (see
`<<nng_http_handler_set_host.3http#,nng_http_handler_set_host()>>`).
@@ -123,9 +126,30 @@ of the requested file name.
If a content type cannot be determined from
the extension, then `application/octet-stream` is used.
+=== Redirect Handler
+
+The fourth member is used to arrange for a server redirect from one
+URL to another.
+The reply will be with status code __status__, which should be a 3XX
+code such as 301, and a `Location:` header will contain the URL
+referenced by __location__, with any residual suffix from the request
+URI appended.
+
+TIP: Use `<<nng_http_handler_set_tree#,nng_http_handler_set_tree()>>`
+to redirect an entire tree.
+For example, it is possible to redirect an entire HTTP site to another
+HTTPS site by specifying `/` as the path and then using the base
+of the new site, such as `https://newsite.example.com` as the
+new location.
+
+TIP: Be sure to use the appropriate value for __status__.
+Permanent redirection should use 301 and temporary redirections should use 307.
+In REST APIs, using a redirection to supply the new location of an object
+created with `POST` should use 303.
+
=== Static Handler
-The fourth member of this family, `nng_http_handler_alloc_static()`, creates
+The fifth member of this family, `nng_http_handler_alloc_static()`, creates
a handler to serve up fixed content located in program data.
The client is
sent the _data_, with `Content-Length` of _size_ bytes, and `Content-Type` of
@@ -133,7 +157,7 @@ __content_type__.
== RETURN VALUES
-This function returns 0 on success, and non-zero otherwise.
+These functions return 0 on success, and non-zero otherwise.
== ERRORS
diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h
index d9edd5c8..d0854981 100644
--- a/src/supplemental/http/http.h
+++ b/src/supplemental/http/http.h
@@ -328,6 +328,12 @@ NNG_DECL int nng_http_handler_alloc_file(
NNG_DECL int nng_http_handler_alloc_static(
nng_http_handler **, const char *, const void *, size_t, const char *);
+// nng_http_handler_alloc_redirect creates an HTTP redirect handler.
+// The status is given, along with the new URL. If the status is 0,
+// then 301 will be used instead.
+NNG_DECL int nng_http_handler_alloc_redirect(
+ nng_http_handler **, const char *, uint16_t, const char *);
+
// nng_http_handler_alloc_file creates a "directory" based handler, that
// serves up static content from the given directory tree. Directories
// that contain an index.html or index.htm file use that file for the
diff --git a/src/supplemental/http/http_api.h b/src/supplemental/http/http_api.h
index a30399d2..14e842be 100644
--- a/src/supplemental/http/http_api.h
+++ b/src/supplemental/http/http_api.h
@@ -121,6 +121,13 @@ extern int nni_http_res_set_reason(nni_http_res *, const char *);
// the HTML body with customized content if it exists.
extern bool nni_http_res_is_error(nni_http_res *);
+// nni_http_alloc_html_error allocates a string corresponding to an
+// HTML error. This can be set as the body of the res. The status
+// will be looked up using HTTP status code lookups, but the details
+// will be added if present as further body text. The result can
+// be freed with nni_strfree() later.
+extern int nni_http_alloc_html_error(char **, uint16_t, const char *);
+
extern void nni_http_read(nni_http_conn *, nni_aio *);
extern void nni_http_read_full(nni_http_conn *, nni_aio *);
extern void nni_http_write(nni_http_conn *, nni_aio *);
@@ -214,7 +221,7 @@ extern int nni_http_hijack(nni_http_conn *);
//
// The callback function will receive the following arguments (via
// nng_aio_get_input(): nni_http_request *, nni_http_handler *, and
-// nni_http_context_t *. The first is a request object, for convenience.
+// nni_http_conn_t *. The first is a request object, for convenience.
// The second is the handler, from which the callback can obtain any other
// data it has set. The final is the http context, from which its possible
// to hijack the session.
@@ -244,6 +251,10 @@ extern int nni_http_handler_init_directory(
extern int nni_http_handler_init_static(
nni_http_handler **, const char *, const void *, size_t, const char *);
+// nni_http_handler_init_redirect creates a handler that redirects the request.
+extern int nni_http_handler_init_redirect(
+ nni_http_handler **, const char *, uint16_t, const char *);
+
// nni_http_handler_fini destroys a handler. This should only be done before
// the handler is added, or after it is deleted. The server automatically
// calls this for any handlers still registered with it if it is destroyed.
diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c
index 3b78a2a9..da60c746 100644
--- a/src/supplemental/http/http_msg.c
+++ b/src/supplemental/http/http_msg.c
@@ -128,15 +128,19 @@ nni_http_res_reset(nni_http_res *res)
void
nni_http_req_free(nni_http_req *req)
{
- nni_http_req_reset(req);
- NNI_FREE_STRUCT(req);
+ if (req != NULL) {
+ nni_http_req_reset(req);
+ NNI_FREE_STRUCT(req);
+ }
}
void
nni_http_res_free(nni_http_res *res)
{
- nni_http_res_reset(res);
- NNI_FREE_STRUCT(res);
+ if (res != NULL) {
+ nni_http_res_reset(res);
+ NNI_FREE_STRUCT(res);
+ }
}
static int
@@ -1012,36 +1016,49 @@ nni_http_res_set_reason(nni_http_res *res, const char *reason)
}
int
+nni_http_alloc_html_error(char **html, uint16_t code, const char *details)
+{
+ const char *rsn = nni_http_reason(code);
+
+ return (nni_asprintf(html,
+ "<!DOCTYPE html>\n"
+ "<html><head><title>%d %s</title>\n"
+ "<style>"
+ "body { font-family: Arial, sans serif; text-align: center }\n"
+ "h1 { font-size: 36px; }"
+ "span { background-color: gray; color: white; padding: 7px; "
+ "border-radius: 5px }"
+ "h2 { font-size: 24px; }"
+ "p { font-size: 20px; }"
+ "</style></head>"
+ "<body><p>&nbsp;</p>"
+ "<h1><span>%d</span></h1>"
+ "<h2>%s</h2>"
+ "<p>%s</p>"
+ "</body></html>",
+ code, rsn, code, rsn, details != NULL ? details : ""));
+}
+
+int
nni_http_res_alloc_error(nni_http_res **resp, uint16_t err)
{
- char html[512];
+ char * html = NULL;
+ nni_http_res *res = NULL;
int rv;
- nni_http_res *res;
- if ((rv = nni_http_res_alloc(&res)) != 0) {
- return (rv);
- }
-
- // very simple builtin error page
- (void) snprintf(html, sizeof(html),
- "<head><title>%d %s</title></head>"
- "<body><p/><h1 align=\"center\">"
- "<span style=\"font-size: 36px; border-radius: 5px; "
- "background-color: black; color: white; padding: 7px; "
- "font-family: Arial, sans serif;\">%d</span></h1>"
- "<p align=\"center\">"
- "<span style=\"font-size: 24px; font-family: Arial, sans serif;\">"
- "%s</span></p></body>",
- err, nni_http_reason(err), err, nni_http_reason(err));
-
- res->code = err;
- if (((rv = nni_http_res_set_header(
+ if (((rv = nni_http_res_alloc(&res)) != 0) ||
+ ((rv = nni_http_alloc_html_error(&html, err, NULL)) != 0) ||
+ ((rv = nni_http_res_set_header(
res, "Content-Type", "text/html; charset=UTF-8")) != 0) ||
((rv = nni_http_res_copy_data(res, html, strlen(html))) != 0)) {
+ nni_strfree(html);
nni_http_res_free(res);
} else {
+ nni_strfree(html);
+ res->code = err;
res->iserr = true;
*resp = res;
}
+
return (rv);
}
diff --git a/src/supplemental/http/http_public.c b/src/supplemental/http/http_public.c
index b86521b0..50ef03fa 100644
--- a/src/supplemental/http/http_public.c
+++ b/src/supplemental/http/http_public.c
@@ -559,6 +559,21 @@ nng_http_handler_alloc_directory(
}
int
+nng_http_handler_alloc_redirect(
+ nng_http_handler **hp, const char *uri, uint16_t status, const char *where)
+{
+#ifdef NNG_SUPP_HTTP
+ return (nni_http_handler_init_redirect(hp, uri, status, where));
+#else
+ NNI_ARG_UNUSED(hp);
+ NNI_ARG_UNUSED(uri);
+ NNI_ARG_UNUSED(status);
+ NNI_ARG_UNUSED(where);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+int
nng_http_handler_alloc_static(nng_http_handler **hp, const char *uri,
const void *data, size_t size, const char *ctype)
{
diff --git a/src/supplemental/http/http_server.c b/src/supplemental/http/http_server.c
index 1a7f2c25..44c338f4 100644
--- a/src/supplemental/http/http_server.c
+++ b/src/supplemental/http/http_server.c
@@ -229,12 +229,8 @@ http_sconn_reap(void *arg)
if (sc->conn != NULL) {
nni_http_conn_fini(sc->conn);
}
- if (sc->req != NULL) {
- nni_http_req_free(sc->req);
- }
- if (sc->res != NULL) {
- nni_http_res_free(sc->res);
- }
+ nni_http_req_free(sc->req);
+ nni_http_res_free(sc->res);
nni_aio_fini(sc->rxaio);
nni_aio_fini(sc->txaio);
nni_aio_fini(sc->txdataio);
@@ -294,10 +290,8 @@ http_sconn_txdatdone(void *arg)
return;
}
- if (sc->res != NULL) {
- nni_http_res_free(sc->res);
- sc->res = NULL;
- }
+ nni_http_res_free(sc->res);
+ sc->res = NULL;
if (sc->close) {
http_sconn_close(sc);
@@ -325,10 +319,8 @@ http_sconn_txdone(void *arg)
return;
}
- if (sc->res != NULL) {
- nni_http_res_free(sc->res);
- sc->res = NULL;
- }
+ 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);
@@ -1089,10 +1081,10 @@ nni_http_server_res_error(nni_http_server *s, nni_http_res *res)
{
http_error *epage;
char * body = NULL;
+ char * html = NULL;
size_t len;
uint16_t code = nni_http_res_get_status(res);
int rv;
- char html[512];
nni_mtx_lock(&s->errors_mtx);
NNI_LIST_FOREACH (&s->errors, epage) {
@@ -1102,23 +1094,16 @@ nni_http_server_res_error(nni_http_server *s, nni_http_res *res)
break;
}
}
+ nni_mtx_unlock(&s->errors_mtx);
+
if (body == NULL) {
- const char *reason = nni_http_reason(code);
- // very simple builtin error page
- (void) snprintf(html, sizeof(html),
- "<head><title>%d %s</title></head>"
- "<body><p/><h1 align=\"center\">"
- "<span style=\"font-size: 36px; border-radius: 5px; "
- "background-color: black; color: white; padding: 7px; "
- "font-family: Arial, sans serif;\">%d</span></h1>"
- "<p align=\"center\">"
- "<span style=\"font-size: 24px; font-family: Arial, sans "
- "serif;\">"
- "%s</span></p></body>",
- code, reason, code, reason);
+ if ((rv = nni_http_alloc_html_error(&html, code, NULL)) != 0) {
+ return (rv);
+ }
body = html;
len = strlen(body);
}
+
// NB: The server lock has to be held here to guard against the
// error page being tossed or changed.
if (((rv = nni_http_res_copy_data(res, body, len)) == 0) &&
@@ -1126,8 +1111,8 @@ nni_http_server_res_error(nni_http_server *s, nni_http_res *res)
res, "Content-Type", "text/html; charset=UTF-8")) == 0)) {
nni_http_res_set_status(res, code);
}
+ nni_strfree(html);
- nni_mtx_unlock(&s->errors_mtx);
return (rv);
}
@@ -1335,9 +1320,7 @@ http_handle_file(nni_aio *aio)
((rv = nni_http_res_set_header(res, "Content-Type", ctype)) !=
0) ||
((rv = nni_http_res_copy_data(res, data, size)) != 0)) {
- if (res != NULL) {
- nni_http_res_free(res);
- }
+ nni_http_res_free(res);
nni_free(data, size);
nni_aio_finish_error(aio, rv);
return;
@@ -1526,9 +1509,7 @@ http_handle_dir(nni_aio *aio)
((rv = nni_http_res_set_header(res, "Content-Type", ctype)) !=
0) ||
((rv = nni_http_res_copy_data(res, data, size)) != 0)) {
- if (res != NULL) {
- nni_http_res_free(res);
- }
+ nni_http_res_free(res);
nni_free(data, size);
nni_aio_finish_error(aio, rv);
return;
@@ -1573,6 +1554,130 @@ nni_http_handler_init_directory(
return (0);
}
+typedef struct http_redirect {
+ uint16_t code;
+ char * where;
+} http_redirect;
+
+static void
+http_handle_redirect(nni_aio *aio)
+{
+ nni_http_res * r = NULL;
+ char * html = NULL;
+ char * msg = NULL;
+ char * loc = NULL;
+ http_redirect * hr;
+ nni_http_handler *h;
+ int rv;
+ nni_http_req * req;
+ const char * base;
+ const char * uri;
+
+ req = nni_aio_get_input(aio, 0);
+ h = nni_aio_get_input(aio, 1);
+ base = nni_http_handler_get_uri(h); // base uri
+ uri = nni_http_req_get_uri(req);
+
+ hr = nni_http_handler_get_data(h);
+
+ // If we are doing a full tree, then include the entire suffix.
+ if (strncmp(uri, base, strlen(base)) == 0) {
+ rv = nni_asprintf(&loc, "%s%s", hr->where, uri + strlen(base));
+ if (rv != 0) {
+ nni_aio_finish_error(aio, rv);
+ return;
+ }
+ } else {
+ loc = hr->where;
+ }
+
+ // Builtin redirect page
+ rv = nni_asprintf(&msg,
+ "You should be automatically redirected to <a href=\"%s\">%s</a>.",
+ loc, loc);
+
+ // Build a response. We always close the connection for redirects,
+ // because it is probably going to another server. This also
+ // keeps us from having to consume the entity body, we can just
+ // discard it.
+ if ((rv != 0) || ((rv = nni_http_res_alloc(&r)) != 0) ||
+ ((rv = nni_http_alloc_html_error(&html, hr->code, msg)) != 0) ||
+ ((rv = nni_http_res_set_status(r, hr->code)) != 0) ||
+ ((rv = nni_http_res_set_header(r, "Connection", "close")) != 0) ||
+ ((rv = nni_http_res_set_header(
+ r, "Content-Type", "text/html; charset=UTF-8")) != 0) ||
+ ((rv = nni_http_res_set_header(r, "Location", loc)) != 0) ||
+ ((rv = nni_http_res_copy_data(r, html, strlen(html))) != 0)) {
+ if (loc != hr->where) {
+ nni_strfree(loc);
+ }
+ nni_strfree(msg);
+ nni_strfree(html);
+ nni_http_res_free(r);
+ nni_aio_finish_error(aio, rv);
+ return;
+ }
+ if (loc != hr->where) {
+ nni_strfree(loc);
+ }
+ nni_strfree(msg);
+ nni_strfree(html);
+ nni_aio_set_output(aio, 0, r);
+ nni_aio_finish(aio, 0, 0);
+}
+
+static void
+http_redirect_free(void *arg)
+{
+ http_redirect *hr;
+
+ if ((hr = arg) != NULL) {
+ nni_strfree(hr->where);
+ NNI_FREE_STRUCT(hr);
+ }
+}
+
+int
+nni_http_handler_init_redirect(nni_http_handler **hpp, const char *uri,
+ uint16_t status, const char *where)
+{
+ nni_http_handler *h;
+ int rv;
+ http_redirect * hr;
+
+ if ((hr = NNI_ALLOC_STRUCT(hr)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ if ((hr->where = nni_strdup(where)) == NULL) {
+ NNI_FREE_STRUCT(hr);
+ return (NNG_ENOMEM);
+ }
+ if (status == 0) {
+ status = NNG_HTTP_STATUS_STATUS_MOVED_PERMANENTLY;
+ }
+ hr->code = status;
+
+ if ((rv = nni_http_handler_init(&h, uri, http_handle_redirect)) != 0) {
+ http_redirect_free(hr);
+ return (rv);
+ }
+
+ if (((rv = nni_http_handler_set_method(h, NULL)) != 0) ||
+ ((rv = nni_http_handler_set_data(h, hr, http_redirect_free)) !=
+ 0)) {
+ http_redirect_free(hr);
+ nni_http_handler_fini(h);
+ return (rv);
+ }
+
+ // We don't need to collect the body at all, because the handler
+ // just discards the content and closes the connection.
+ nni_http_handler_collect_body(h, false, 0);
+
+ *hpp = h;
+ return (0);
+}
+
typedef struct http_static {
void * data;
size_t size;
@@ -1599,9 +1704,7 @@ http_handle_static(nni_aio *aio)
((rv = nni_http_res_set_header(r, "Content-Type", ctype)) != 0) ||
((rv = nni_http_res_set_status(r, NNG_HTTP_STATUS_OK)) != 0) ||
((rv = nni_http_res_set_data(r, hs->data, hs->size)) != 0)) {
- if (r != NULL) {
- nni_http_res_free(r);
- }
+ nni_http_res_free(r);
nni_aio_finish_error(aio, rv);
return;
}
diff --git a/tests/httpserver.c b/tests/httpserver.c
index 21b961b8..1bf88f12 100644
--- a/tests/httpserver.c
+++ b/tests/httpserver.c
@@ -550,4 +550,120 @@ TestMain("HTTP Server", {
nng_url_free(curl);
});
});
+
+ Convey("Redirect 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);
+ });
+
+ Convey("GET redirect works", {
+ char fullurl[256];
+ nng_http_req *req;
+ nng_http_res *res;
+ nng_url * curl;
+ const char * dest;
+ void * data;
+ size_t size;
+
+ So(nng_http_handler_alloc_redirect(&h, "/here", 301,
+ "http://127.0.0.1/there") == 0);
+ So(nng_http_server_add_handler(s, h) == 0);
+ So(nng_http_server_start(s) == 0);
+ nng_msleep(100);
+
+ So(nng_http_res_alloc(&res) == 0);
+ snprintf(fullurl, sizeof(fullurl), "%s/here", 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) == 301);
+ So((dest = nng_http_res_get_header(res, "Location")) !=
+ NULL);
+ So(strcmp(dest, "http://127.0.0.1/there") == 0);
+ So(data != NULL);
+ So(size > 0);
+ nng_http_req_free(req);
+ nng_http_res_free(res);
+ nng_url_free(curl);
+ });
+
+ Convey("Tree redirect works", {
+ char fullurl[256];
+ nng_http_req *req;
+ nng_http_res *res;
+ nng_url * curl;
+ const char * dest;
+ void * data;
+ size_t size;
+
+ // We'll use a 303 to ensure codes carry thru
+ So(nng_http_handler_alloc_redirect(&h, "/here", 303,
+ "http://127.0.0.1/there") == 0);
+ So(nng_http_handler_set_tree(h) == 0);
+ So(nng_http_server_add_handler(s, h) == 0);
+ So(nng_http_server_start(s) == 0);
+ nng_msleep(100);
+
+ So(nng_http_res_alloc(&res) == 0);
+ snprintf(fullurl, sizeof(fullurl),
+ "%s/here/i/go/again", 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) == 303);
+ So((dest = nng_http_res_get_header(res, "Location")) !=
+ NULL);
+ So(strcmp(dest, "http://127.0.0.1/there/i/go/again") ==
+ 0);
+ nng_http_req_free(req);
+ nng_http_res_free(res);
+ nng_url_free(curl);
+ });
+
+ Convey("POST Redirect works", {
+ char fullurl[256];
+ size_t size;
+ nng_http_req *req;
+ nng_http_res *res;
+ nng_url * curl;
+ char txdata[5];
+ const char * dest;
+ void * data;
+
+ So(nng_http_handler_alloc_redirect(&h, "/here", 301,
+ "http://127.0.0.1/there") == 0);
+ So(nng_http_server_add_handler(s, h) == 0);
+ So(nng_http_server_start(s) == 0);
+ nng_msleep(100);
+
+ snprintf(txdata, sizeof(txdata), "1234");
+ So(nng_http_res_alloc(&res) == 0);
+ snprintf(fullurl, sizeof(fullurl), "%s/here", 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 **) &data, &size) ==
+ 0);
+ So(nng_http_res_get_status(res) == 301);
+ So((dest = nng_http_res_get_header(res, "Location")) !=
+ NULL);
+ So(strcmp(dest, "http://127.0.0.1/there") == 0);
+ nng_http_req_free(req);
+ nng_http_res_free(res);
+ nng_url_free(curl);
+ });
+ });
})