From 617bb5112834eee40d7eaf00bfc7e98e0ae1ff01 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Sun, 7 Oct 2018 13:04:00 -0700 Subject: fixes #745 HTTP server redirect handler --- docs/man/nng_http_handler_alloc.3http.adoc | 30 ++++- src/supplemental/http/http.h | 6 + src/supplemental/http/http_api.h | 13 ++- src/supplemental/http/http_msg.c | 65 +++++++---- src/supplemental/http/http_public.c | 15 +++ src/supplemental/http/http_server.c | 179 +++++++++++++++++++++++------ tests/httpserver.c | 116 +++++++++++++++++++ 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 `<>`), and optionally a 'Host' header it can be matched against (see `<>`). @@ -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 `<>` +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 @@ -1011,37 +1015,50 @@ nni_http_res_set_reason(nni_http_res *res, const char *reason) return (http_set_string(&res->rsn, 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, + "\n" + "%d %s\n" + "" + "

 

" + "

%d

" + "

%s

" + "

%s

" + "", + 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), - "%d %s" - "

" - "%d

" - "

" - "" - "%s

", - 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 @@ -558,6 +558,21 @@ nng_http_handler_alloc_directory( #endif } +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), - "%d %s" - "

" - "%d

" - "

" - "" - "%s

", - 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 %s.", + 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); + }); + }); }) -- cgit v1.2.3-70-g09d2