diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/supplemental/http/http.h | 23 | ||||
| -rw-r--r-- | src/supplemental/http/http_api.h | 21 | ||||
| -rw-r--r-- | src/supplemental/http/http_msg.c | 20 | ||||
| -rw-r--r-- | src/supplemental/http/http_public.c | 41 | ||||
| -rw-r--r-- | src/supplemental/http/http_server.c | 148 |
5 files changed, 246 insertions, 7 deletions
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) { |
