aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/man/libnng.3.adoc3
-rw-r--r--docs/man/nng_http_server_res_error.3http.adoc63
-rw-r--r--docs/man/nng_http_server_set_error_file.3http.adoc73
-rw-r--r--docs/man/nng_http_server_set_error_page.3http.adoc69
-rw-r--r--src/supplemental/http/http.h23
-rw-r--r--src/supplemental/http/http_api.h21
-rw-r--r--src/supplemental/http/http_msg.c20
-rw-r--r--src/supplemental/http/http_public.c41
-rw-r--r--src/supplemental/http/http_server.c148
-rw-r--r--tests/httpserver.c23
10 files changed, 471 insertions, 13 deletions
diff --git a/docs/man/libnng.3.adoc b/docs/man/libnng.3.adoc
index 21a34a6a..70fd3be2 100644
--- a/docs/man/libnng.3.adoc
+++ b/docs/man/libnng.3.adoc
@@ -341,7 +341,10 @@ These functions are intended for use with HTTP server applications.
|<<nng_http_server_get_tls.3http#,nng_http_server_get_tls()>>|get HTTP server TLS configuration
|<<nng_http_server_hold.3http#,nng_http_server_get_tls()>>|get and hold HTTP server instance
|<<nng_http_server_release.3http#,nng_http_server_get_tls()>>|release HTTP server instance
+|<<nng_http_server_set_error_file.3http#,nng_http_server_set_error_file()>>|set custom HTTP error file
+|<<nng_http_server_set_error_page.3http#,nng_http_server_set_error_page()>>|set custom HTTP error page
|<<nng_http_server_set_tls.3http#,nng_http_server_set_tls()>>|set HTTP server TLS configuration
+|<<nng_http_server_res_error.3http#,nng_http_server_res_error()>>|use HTTP server error page
|<<nng_http_server_start.3http#,nng_http_server_start()>>|start HTTP server
|<<nng_http_server_stop.3http#,nng_http_server_stop()>>|stop HTTP server
|===
diff --git a/docs/man/nng_http_server_res_error.3http.adoc b/docs/man/nng_http_server_res_error.3http.adoc
new file mode 100644
index 00000000..a62628f7
--- /dev/null
+++ b/docs/man/nng_http_server_res_error.3http.adoc
@@ -0,0 +1,63 @@
+= nng_http_server_res_error(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_server_res_error - use HTTP server error page
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/http/http.h>
+
+int nng_http_server_set_error_file(nng_http_server *server,
+ nng_http_res *response);
+----
+
+== DESCRIPTION
+
+The `nng_http_server_res_error()` sets the body of _response_
+to _server_'s error page, which may have been customized using the
+`<<nng_http_server_set_error_file.3http#,nng_http_server_error_file()>>`
+or
+`<<nng_http_server_set_error_page.3http#,nng_http_server_error_page()>>`
+functions.
+
+The status code of the _response_ should have already been set, either
+implicitly by allocating it with
+`<<nng_http_res_alloc_error.3http#,nng_http_res_alloc_error()>>`,
+or by calling
+`<<nng_http_res_set_status.3http#,nng_http_res_set_status()>>`.
+
+Any content body previously set for _response_ will be overridden by
+this function.
+
+== RETURN VALUES
+
+This function returns 0 on success, and non-zero otherwise.
+
+== ERRORS
+
+[horizontal]
+`NNG_ENOMEM`:: Insufficient free memory exists.
+`NNG_ENOTSUP`:: HTTP not supported.
+
+== SEE ALSO
+
+[.text-left]
+<<nng_http_res_alloc_error.3http#,nng_http_res_alloc_error(3http)>>,
+<<nng_http_server_hold.3http#,nng_http_server_hold(3http)>>,
+<<nng_http_server_set_error_file.3http#,nng_http_server_set_error_file(3http)>>,
+<<nng_http_server_set_error_page.3http#,nng_http_server_set_error_page(3http)>>,
+<<nng_strerror.3#,nng_strerror(3)>>,
+<<nng.7#,nng(7)>>
diff --git a/docs/man/nng_http_server_set_error_file.3http.adoc b/docs/man/nng_http_server_set_error_file.3http.adoc
new file mode 100644
index 00000000..dde8d980
--- /dev/null
+++ b/docs/man/nng_http_server_set_error_file.3http.adoc
@@ -0,0 +1,73 @@
+= nng_http_server_set_error_file(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_server_set_error_file - set custom HTTP error file
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/http/http.h>
+
+int nng_http_server_set_error_file(nng_http_server *server,
+ uint16_t code, const char *path);
+----
+
+== DESCRIPTION
+
+The `nng_http_server_set_error_file()` sets an error page to be used
+for HTTP status _code_ on the server instance _server_.
+The body content of the HTTP responses will contain the file contents of
+the file located at _path_, which should be an HTML file.
+
+The custom HTML content will be used when the server is returning an
+internally generated error response, or is returning an error response
+that was allocated with the
+`<<nng_http_res_alloc_error.3http#,nng_http_res_alloc_error()>>`
+function.
+This HTML content will also be used if the application calls the
+`<<nng_http_server_res_error.3http#,nng_http_server_res_error()>>`.
+The last custom error page set for _code_ by either this function or
+`<<nng_http_server_set_error_page.3http#,nng_http_server_error_page()>>`
+will be used.
+
+NOTE: Error responses that have their body content changed after allocation,
+or that are written directly by the application, will not use the body
+content supplied here.
+
+NOTE: The file contents of _path_ are read when this function is called.
+Therefore, if the file contents are changed, then this function should
+be called again to update the error page.
+
+== RETURN VALUES
+
+This function returns 0 on success, and non-zero otherwise.
+
+== ERRORS
+
+[horizontal]
+`NNG_ENOENT`:: The file named by _path_ does not exist.
+`NNG_EPERM`:: No permission to read the file named by _path_.
+`NNG_ENOMEM`:: Insufficient free memory exists.
+`NNG_ENOTSUP`:: HTTP not supported.
+
+== SEE ALSO
+
+[.text-left]
+<<nng_http_res_alloc_error.3http#,nng_http_res_alloc_error(3http)>>,
+<<nng_http_server_hold.3http#,nng_http_server_hold(3http)>>,
+<<nng_http_server_res_error.3http#,nng_http_server_res_error(3http)>>,
+<<nng_http_server_set_error_page.3http#,nng_http_server_set_error_page(3http)>>,
+<<nng_strerror.3#,nng_strerror(3)>>,
+<<nng.7#,nng(7)>>
diff --git a/docs/man/nng_http_server_set_error_page.3http.adoc b/docs/man/nng_http_server_set_error_page.3http.adoc
new file mode 100644
index 00000000..271b9d99
--- /dev/null
+++ b/docs/man/nng_http_server_set_error_page.3http.adoc
@@ -0,0 +1,69 @@
+= nng_http_server_set_error_page(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_server_set_error_page - set custom HTTP error page
+
+== SYNOPSIS
+
+[source, c]
+----
+#include <nng/nng.h>
+#include <nng/supplemental/http/http.h>
+
+int nng_http_server_set_error_page(nng_http_server *server,
+ uint16_t code, const char *html);
+----
+
+== DESCRIPTION
+
+The `nng_http_server_set_error_page()` sets an error page to be used
+for HTTP status _code_ on the server instance _server_.
+The body content of the HTTP responses will contain _html_.
+
+The custom HTML content will be used when the server is returning an
+internally generated error response, or is returning an error response
+that was allocated with the
+`<<nng_http_res_alloc_error.3http#,nng_http_res_alloc_error()>>`
+function.
+This HTML content will also be used if the application calls the
+`<<nng_http_server_res_error.3http#,nng_http_server_res_error()>>`.
+The last custom error page set for _code_ by either this function or
+`<<nng_http_server_set_error_file.3http#,nng_http_server_error_file()>>`
+will be used.
+
+NOTE: Error responses that have their body content changed after allocation,
+or that are written directly by the application, will not use the body
+content supplied here.
+
+The supplied HTML content is copied by this function, and may be reused
+after this function returns.
+
+== RETURN VALUES
+
+This function returns 0 on success, and non-zero otherwise.
+
+== ERRORS
+
+[horizontal]
+`NNG_ENOMEM`:: Insufficient free memory exists.
+`NNG_ENOTSUP`:: HTTP not supported.
+
+== SEE ALSO
+
+[.text-left]
+<<nng_http_res_alloc_error.3http#,nng_http_res_alloc_error(3http)>>,
+<<nng_http_server_hold.3http#,nng_http_server_hold(3http)>>,
+<<nng_http_server_res_error.3http#,nng_http_server_res_error(3http)>>,
+<<nng_http_server_set_error_file.3http#,nng_http_server_set_error_file(3http)>>,
+<<nng_strerror.3#,nng_strerror(3)>>,
+<<nng.7#,nng(7)>>
diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h
index f6a45df7..21e6b5de 100644
--- a/src/supplemental/http/http.h
+++ b/src/supplemental/http/http.h
@@ -407,6 +407,29 @@ NNG_DECL int nng_http_server_set_tls(
NNG_DECL int nng_http_server_get_tls(
nng_http_server *, struct nng_tls_config **);
+// nng_http_server_set_error_page sets a custom error page (HTML) content
+// to be sent for the given error code. This is used when the error is
+// generated internally by the framework, or when the application returns
+// the response back to the server via the handler's aio, and the response
+// was allocated with nng_http_res_alloc_error. If the response was not
+// allocated this way, or the application writes the response itself instead
+// of letting the server do so, then this setting will be ignored.
+NNG_DECL int nng_http_server_set_error_page(
+ nng_http_server *, uint16_t, const char *);
+
+// nng_http_server_set_error_file works like nng_http_server_error_page,
+// except that the content is loaded from the named file path. The contents
+// are loaded at the time this function is called, so this function should be
+// called anytime the contents of the named file have changed.
+NNG_DECL int nng_http_server_set_error_file(
+ nng_http_server *, uint16_t, const char *);
+
+// nng_http_server_res_error takes replaces the body of the response with
+// a custom error page previously set for the server, using the status
+// of the response. The response must have the status set first using
+// nng_http_res_set_status or implicitly via nng_http_res_alloc_error.
+NNG_DECL int nng_http_server_res_error(nng_http_server *, nng_http_res *);
+
// nng_http_hijack is intended to be called by a handler that wishes to
// take over the processing of the HTTP session -- usually to change protocols
// (such as in the case of websocket). The caller is responsible for the
diff --git a/src/supplemental/http/http_api.h b/src/supplemental/http/http_api.h
index f1a5c0fa..4b515ca5 100644
--- a/src/supplemental/http/http_api.h
+++ b/src/supplemental/http/http_api.h
@@ -30,6 +30,9 @@ typedef struct nng_http_client nni_http_client;
// These functions are private to the internal framework, and really should
// not be used elsewhere.
+
+extern const char *nni_http_reason(uint16_t);
+
extern int nni_http_req_init(nni_http_req **);
extern void nni_http_req_reset(nni_http_req *);
extern int nni_http_req_get_buf(nni_http_req *, void **, size_t *);
@@ -111,6 +114,11 @@ extern int nni_http_res_set_version(nni_http_res *, const char *);
extern const char *nni_http_res_get_reason(nni_http_res *);
extern int nni_http_res_set_reason(nni_http_res *, const char *);
+// nni_http_res_is_error is true if the status was allocated as part of
+// nni_http_res_alloc_error(). This is a hint to the server to replace
+// the HTML body with customized content if it exists.
+extern bool nni_http_res_is_error(nni_http_res *);
+
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 *);
@@ -170,6 +178,19 @@ extern int nni_http_server_start(nni_http_server *);
// associated with a callback will complete their callback, and then close.
extern void nni_http_server_stop(nni_http_server *);
+// nni_http_server_set_error_page sets an error page for the named status.
+extern int nni_http_server_set_error_page(
+ nni_http_server *, uint16_t, const char *);
+
+// nni_http_server_set_error_page sets an error file for the named status.
+extern int nni_http_server_set_error_file(
+ nni_http_server *, uint16_t, const char *);
+
+// nni_http_server_res_error takes replaces the body of the res with
+// a custom error page previously set for the server, using the status
+// of the res. The res must have the status set first.
+extern int nni_http_server_res_error(nni_http_server *, nni_http_res *);
+
// nni_http_hijack is intended to be called by a handler that wishes to
// take over the processing of the HTTP session -- usually to change protocols
// (such as in the case of websocket). The caller is responsible for obtaining
diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c
index bc51e408..ff9764bf 100644
--- a/src/supplemental/http/http_msg.c
+++ b/src/supplemental/http/http_msg.c
@@ -54,6 +54,7 @@ struct nng_http_res {
char * buf;
size_t bufsz;
bool parsed;
+ bool iserr;
};
static int
@@ -375,6 +376,7 @@ nni_http_res_set_data(nni_http_res *res, const void *data, size_t size)
if ((rv = http_set_content_length(&res->data, &res->hdrs)) != 0) {
http_entity_set_data(&res->data, NULL, 0);
}
+ res->iserr = false;
return (rv);
}
@@ -401,9 +403,16 @@ nni_http_res_copy_data(nni_http_res *res, const void *data, size_t size)
http_entity_set_data(&res->data, NULL, 0);
return (rv);
}
+ res->iserr = false;
return (0);
}
+bool
+nni_http_res_is_error(nni_http_res *res)
+{
+ return (res->iserr);
+}
+
static int
http_parse_header(nni_list *hdrs, void *line)
{
@@ -962,7 +971,7 @@ static struct {
};
const char *
-http_reason(uint16_t code)
+nni_http_reason(uint16_t code)
{
for (int i = 0; http_status[i].code != 0; i++) {
if (http_status[i].code == code) {
@@ -975,14 +984,14 @@ http_reason(uint16_t code)
const char *
nni_http_res_get_reason(nni_http_res *res)
{
- return (res->rsn ? res->rsn : http_reason(res->code));
+ return (res->rsn ? res->rsn : nni_http_reason(res->code));
}
int
nni_http_res_set_reason(nni_http_res *res, const char *reason)
{
if ((reason != NULL) &&
- (strcmp(reason, http_reason(res->code)) == 0)) {
+ (strcmp(reason, nni_http_reason(res->code)) == 0)) {
reason = NULL;
}
return (http_set_string(&res->rsn, reason));
@@ -1009,7 +1018,7 @@ nni_http_res_alloc_error(nni_http_res **resp, uint16_t err)
"<p align=\"center\">"
"<span style=\"font-size: 24px; font-family: Arial, sans serif;\">"
"%s</span></p></body>",
- err, http_reason(err), err, http_reason(err));
+ err, nni_http_reason(err), err, nni_http_reason(err));
res->code = err;
if (((rv = nni_http_res_set_header(
@@ -1017,7 +1026,8 @@ nni_http_res_alloc_error(nni_http_res **resp, uint16_t err)
((rv = nni_http_res_copy_data(res, html, strlen(html))) != 0)) {
nni_http_res_free(res);
} else {
- *resp = res;
+ res->iserr = true;
+ *resp = res;
}
return (rv);
}
diff --git a/src/supplemental/http/http_public.c b/src/supplemental/http/http_public.c
index 40d4a19c..94e51ed0 100644
--- a/src/supplemental/http/http_public.c
+++ b/src/supplemental/http/http_public.c
@@ -676,6 +676,34 @@ nng_http_server_del_handler(nng_http_server *srv, nng_http_handler *h)
}
int
+nng_http_server_set_error_page(
+ nng_http_server *srv, uint16_t code, const char *body)
+{
+#ifdef NNG_SUPP_HTTP
+ return (nni_http_server_set_error_page(srv, code, body));
+#else
+ NNI_ARG_UNUSED(srv);
+ NNI_ARG_UNUSED(code);
+ NNI_ARG_UNUSED(body);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+int
+nng_http_server_set_error_file(
+ nng_http_server *srv, uint16_t code, const char *path)
+{
+#ifdef NNG_SUPP_HTTP
+ return (nni_http_server_set_error_file(srv, code, path));
+#else
+ NNI_ARG_UNUSED(srv);
+ NNI_ARG_UNUSED(code);
+ NNI_ARG_UNUSED(path);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+int
nng_http_server_set_tls(nng_http_server *srv, struct nng_tls_config *cfg)
{
#if defined(NNG_SUPP_HTTP) && defined(NNG_SUPP_TLS)
@@ -700,6 +728,19 @@ nng_http_server_get_tls(nng_http_server *srv, struct nng_tls_config **cfgp)
}
int
+nng_http_server_res_error(nng_http_server *srv, nng_http_res *res)
+{
+#ifdef NNG_SUPP_HTTP
+ return (nni_http_server_res_error(srv, res));
+#else
+ NNI_ARG_UNUSED(srv);
+ NNI_ARG_UNUSED(res);
+ NNI_ARG_UNUSED(code);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+int
nng_http_hijack(nng_http_conn *conn)
{
#ifdef NNG_SUPP_HTTP
diff --git a/src/supplemental/http/http_server.c b/src/supplemental/http/http_server.c
index 5c6d1dcc..4a07d544 100644
--- a/src/supplemental/http/http_server.c
+++ b/src/supplemental/http/http_server.c
@@ -40,7 +40,7 @@ struct nng_http_handler {
void (*cb)(nni_aio *);
};
-typedef struct nni_http_ctx {
+typedef struct http_sconn {
nni_list_node node;
nni_http_conn * conn;
nni_http_server *server;
@@ -56,6 +56,13 @@ typedef struct nni_http_ctx {
nni_reap_item reap;
} http_sconn;
+typedef struct http_error {
+ nni_list_node node;
+ uint16_t code;
+ void * body;
+ size_t len;
+} http_error;
+
struct nng_http_server {
nng_sockaddr addr;
nni_list_node node;
@@ -71,6 +78,8 @@ struct nng_http_server {
nni_plat_tcp_ep *tep;
char * port;
char * hostname;
+ nni_list errors;
+ nni_mtx errors_mtx;
};
int
@@ -395,13 +404,20 @@ http_sconn_error(http_sconn *sc, uint16_t err)
{
nni_http_res *res;
- if (nni_http_res_alloc_error(&res, err) != 0) {
+ if (nni_http_res_alloc(&res) != 0) {
+ http_sconn_close(sc);
+ return;
+ }
+ nni_http_res_set_status(res, err);
+ if (nni_http_server_res_error(sc->server, res) != 0) {
+ nni_http_res_free(res);
http_sconn_close(sc);
return;
}
if (sc->close) {
if (nni_http_res_set_header(res, "Connection", "close") != 0) {
+ nni_http_res_free(res);
http_sconn_close(sc);
}
}
@@ -651,6 +667,8 @@ http_sconn_cbdone(void *arg)
// the HTTP header.
nni_http_res_get_data(res, &data, &size);
nni_http_res_set_data(res, NULL, size);
+ } else if (nni_http_res_is_error(res)) {
+ (void) nni_http_server_res_error(s, res);
}
nni_http_write_res(sc->conn, res, sc->txaio);
} else if (sc->close) {
@@ -743,6 +761,7 @@ static void
http_server_fini(nni_http_server *s)
{
nni_http_handler *h;
+ http_error * epage;
nni_aio_stop(s->accaio);
@@ -764,6 +783,15 @@ http_server_fini(nni_http_server *s)
nni_tls_config_fini(s->tls);
}
#endif
+ nni_mtx_lock(&s->errors_mtx);
+ while ((epage = nni_list_first(&s->errors)) != NULL) {
+ nni_list_remove(&s->errors, epage);
+ nni_free(epage->body, epage->len);
+ NNI_FREE_STRUCT(epage);
+ }
+ nni_mtx_unlock(&s->errors_mtx);
+ nni_mtx_fini(&s->errors_mtx);
+
nni_aio_fini(s->accaio);
nni_cv_fini(&s->cv);
nni_mtx_fini(&s->mtx);
@@ -804,8 +832,13 @@ http_server_init(nni_http_server **serverp, const nni_url *url)
}
nni_mtx_init(&s->mtx);
nni_cv_init(&s->cv, &s->mtx);
+ nni_mtx_init(&s->errors_mtx);
NNI_LIST_INIT(&s->handlers, nni_http_handler, node);
NNI_LIST_INIT(&s->conns, http_sconn, node);
+
+ nni_mtx_init(&s->errors_mtx);
+ NNI_LIST_INIT(&s->errors, http_error, node);
+
if ((rv = nni_aio_init(&s->accaio, http_server_acccb, s)) != 0) {
http_server_fini(s);
return (rv);
@@ -951,6 +984,117 @@ nni_http_server_stop(nni_http_server *s)
nni_mtx_unlock(&s->mtx);
}
+static int
+http_server_set_err(nni_http_server *s, uint16_t code, void *body, size_t len)
+{
+ http_error *epage;
+
+ nni_mtx_lock(&s->errors_mtx);
+ NNI_LIST_FOREACH (&s->errors, epage) {
+ if (epage->code == code) {
+ break;
+ }
+ }
+ if (epage == NULL) {
+ if ((epage = NNI_ALLOC_STRUCT(epage)) == NULL) {
+ nni_mtx_unlock(&s->mtx);
+ return (NNG_ENOMEM);
+ }
+ epage->code = code;
+ nni_list_append(&s->errors, epage);
+ }
+ if (epage->len != 0) {
+ nni_free(epage->body, epage->len);
+ }
+ epage->body = body;
+ epage->len = len;
+ nni_mtx_unlock(&s->errors_mtx);
+ return (0);
+}
+
+int
+nni_http_server_set_error_page(
+ nni_http_server *s, uint16_t code, const char *html)
+{
+ char * body;
+ int rv;
+ size_t len;
+
+ // We copy the content, without the trailing NUL.
+ len = strlen(html);
+ if ((body = nni_alloc(len)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ memcpy(body, html, len);
+ if ((rv = http_server_set_err(s, code, body, len)) != 0) {
+ nni_free(body, len);
+ }
+ return (rv);
+}
+
+int
+nni_http_server_set_error_file(
+ nni_http_server *s, uint16_t code, const char *path)
+{
+ void * body;
+ size_t len;
+ int rv;
+ if ((rv = nni_file_get(path, &body, &len)) != 0) {
+ return (rv);
+ }
+ if ((rv = http_server_set_err(s, code, body, len)) != 0) {
+ nni_free(body, len);
+ }
+ return (rv);
+}
+
+int
+nni_http_server_res_error(nni_http_server *s, nni_http_res *res)
+{
+ http_error *epage;
+ char * body = 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) {
+ if (epage->code == code) {
+ body = epage->body;
+ len = epage->len;
+ break;
+ }
+ }
+ 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);
+ 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) &&
+ ((rv = nni_http_res_set_header(
+ res, "Content-Type", "text/html; charset=UTF-8")) == 0)) {
+ nni_http_res_set_status(res, code);
+ }
+
+ nni_mtx_unlock(&s->errors_mtx);
+ return (rv);
+}
+
int
nni_http_server_add_handler(nni_http_server *s, nni_http_handler *h)
{
diff --git a/tests/httpserver.c b/tests/httpserver.c
index 9fefe1f7..a36901f5 100644
--- a/tests/httpserver.c
+++ b/tests/httpserver.c
@@ -25,6 +25,7 @@
const char *doc1 = "<html><body>Someone <b>is</b> home!</body</html>";
const char *doc2 = "This is a text file.";
const char *doc3 = "<html><body>This is doc number 3.</body></html>";
+const char *doc4 = "<html><body>Whoops, Errored!</body></html>";
void
cleanup(void)
@@ -68,8 +69,7 @@ httpdo(nng_url *url, nng_http_req *req, nng_http_res *res, void **datap,
}
clen = 0;
- if ((nng_http_res_get_status(res) == NNG_HTTP_STATUS_OK) &&
- ((ptr = nng_http_res_get_header(res, "Content-Length")) != NULL)) {
+ if ((ptr = nng_http_res_get_header(res, "Content-Length")) != NULL) {
clen = atoi(ptr);
}
@@ -377,7 +377,21 @@ TestMain("HTTP Server", {
snprintf(fullurl, sizeof(fullurl), "%s/docs/", urlstr);
So(httpget(fullurl, &data, &size, &stat, &ctype) == 0);
So(stat == NNG_HTTP_STATUS_NOT_FOUND);
- So(size == 0);
+ });
+
+ Convey("Custom error page works", {
+ char fullurl[256];
+ void * data;
+ size_t size;
+ uint16_t stat;
+ char * ctype;
+
+ So(nng_http_server_set_error_page(s, 404, doc4) == 0);
+ snprintf(fullurl, sizeof(fullurl), "%s/docs/", urlstr);
+ So(httpget(fullurl, &data, &size, &stat, &ctype) == 0);
+ So(stat == NNG_HTTP_STATUS_NOT_FOUND);
+ So(size == strlen(doc4));
+ So(memcmp(data, doc4, size) == 0);
});
Convey("Bad method gives 405", {
@@ -397,7 +411,6 @@ TestMain("HTTP Server", {
So(httpdo(curl, req, res, &data, &size) == 0);
So(nng_http_res_get_status(res) ==
NNG_HTTP_STATUS_METHOD_NOT_ALLOWED);
- So(size == 0);
nng_http_req_free(req);
nng_http_res_free(res);
nng_url_free(curl);
@@ -419,7 +432,6 @@ TestMain("HTTP Server", {
So(httpdo(curl, req, res, &data, &size) == 0);
So(nng_http_res_get_status(res) ==
NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP);
- So(size == 0);
nng_http_req_free(req);
nng_http_res_free(res);
nng_url_free(curl);
@@ -441,7 +453,6 @@ TestMain("HTTP Server", {
So(httpdo(curl, req, res, &data, &size) == 0);
So(nng_http_res_get_status(res) ==
NNG_HTTP_STATUS_BAD_REQUEST);
- So(size == 0);
nng_http_req_free(req);
nng_http_res_free(res);
nng_url_free(curl);