diff options
| author | Garrett D'Amore <garrett@damore.org> | 2025-01-06 15:20:09 -0800 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2025-01-09 23:22:56 -0800 |
| commit | 73f50e2679525e7df8734c875a3c12732565f953 (patch) | |
| tree | 23bd167dfcd95305b58a29c142b51879011f63b2 /src/supplemental/http/http_server.c | |
| parent | a381af4f5ca79576a4a9b461529a0f22fcf1e088 (diff) | |
| download | nng-73f50e2679525e7df8734c875a3c12732565f953.tar.gz nng-73f50e2679525e7df8734c875a3c12732565f953.tar.bz2 nng-73f50e2679525e7df8734c875a3c12732565f953.zip | |
http: The big HTTP API refactoring of January 2025.v2.0.0-alpha.3http-client-trans
This represents a major change in the HTTP code base, consisting
of a complete revamp of the HTTP API. The changes here are too
numerous to mention, but the end result should be a vastly
simpler API for both server and client applications.
Many needless allocations were removed by providing fixed buffers
for various parameters and headers when possible.
A few bugs were fixed. Most especially we have fixed some bugs
around very large URIs and headers, and we have also addressed
conformance bugs to more closely conform to RFCs 9110 and 9112.
As part of this work, the APIs for WebSockets changed slightly
as well. In particular the properties available for accessing
headers have changed.
There is still documentation conversion work to do, and additional
functionality (such as proper support for chunked transfers), but
this is a big step in the right direction.
Diffstat (limited to 'src/supplemental/http/http_server.c')
| -rw-r--r-- | src/supplemental/http/http_server.c | 260 |
1 files changed, 94 insertions, 166 deletions
diff --git a/src/supplemental/http/http_server.c b/src/supplemental/http/http_server.c index 87285417..7fbd7a56 100644 --- a/src/supplemental/http/http_server.c +++ b/src/supplemental/http/http_server.c @@ -20,7 +20,9 @@ #include "core/nng_impl.h" #include "http_api.h" -#include "nng/supplemental/http/http.h" +#include "http_msg.h" +#include "nng/http.h" +#include "nng/nng.h" #ifndef NNG_HTTP_MAX_URI #define NNG_HTTP_MAX_URI 1024 @@ -59,13 +61,13 @@ typedef struct http_sconn { nni_aio txdataio; nni_reap_node reap; nni_atomic_flag closed; + nni_http_header close_header; } http_sconn; typedef struct http_error { nni_list_node node; uint16_t code; - void *body; - size_t len; + char *body; } http_error; struct nng_http_server { @@ -414,20 +416,15 @@ http_uri_canonify(char *path) static void http_sconn_error(http_sconn *sc, uint16_t err) { - nni_http_res *res; - - res = nng_http_conn_res(sc->conn); - nni_http_res_set_status(res, err); - if (nni_http_server_res_error(sc->server, res) != 0) { + nng_http_set_status(sc->conn, err, NULL); + if (nni_http_server_error(sc->server, sc->conn) != 0) { http_sconn_close(sc); return; } if (sc->close) { - if (nni_http_res_set_header(res, "Connection", "close") != 0) { - http_sconn_close(sc); - return; - } + nni_http_set_static_header( + sc->conn, &sc->close_header, "Connection", "close"); } nni_http_write_res(sc->conn, &sc->txaio); } @@ -515,7 +512,7 @@ http_sconn_rxdone(void *arg) nni_http_handler *h = NULL; nni_http_handler *head = NULL; const char *val; - nni_http_req *req = nng_http_conn_req(sc->conn); + nni_http_req *req = nni_http_conn_req(sc->conn); char *uri; size_t urisz; char *path; @@ -525,7 +522,15 @@ http_sconn_rxdone(void *arg) const char *cls; if ((rv = nni_aio_result(aio)) != 0) { - http_sconn_close(sc); + if (rv == NNG_EMSGSIZE) { + sc->close = true; + http_sconn_error(sc, + nni_http_parsed(sc->conn) + ? NNG_HTTP_STATUS_HEADERS_TOO_LARGE + : NNG_HTTP_STATUS_URI_TOO_LONG); + } else { + http_sconn_close(sc); + } return; } @@ -537,7 +542,7 @@ http_sconn_rxdone(void *arg) // 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. - if ((val = nni_http_req_get_version(req)) == NULL) { + if ((val = nni_http_get_version(sc->conn)) == NULL) { sc->close = true; http_sconn_error(sc, NNG_HTTP_STATUS_BAD_REQUEST); return; @@ -557,7 +562,7 @@ http_sconn_rxdone(void *arg) // If the connection was 1.0, or a connection: close was // requested, then mark this close on our end. - if ((val = nni_http_req_get_header(req, "Connection")) != NULL) { + if ((val = nni_http_get_header(sc->conn, "Connection")) != NULL) { // HTTP 1.1 says these have to be case insensitive if (nni_strcasestr(val, "close") != NULL) { // In theory this could falsely match some other weird @@ -569,7 +574,7 @@ http_sconn_rxdone(void *arg) } } - val = nni_http_req_get_uri(req); + val = nni_http_get_uri(sc->conn); urisz = strlen(val) + 1; if ((uri = nni_alloc(urisz)) == NULL) { http_sconn_close(sc); // out of memory @@ -578,7 +583,7 @@ http_sconn_rxdone(void *arg) strncpy(uri, val, urisz); path = http_uri_canonify(uri); - host = nni_http_req_get_header(req, "Host"); + host = nni_http_get_header(sc->conn, "Host"); if ((host == NULL) && (needhost)) { // Per RFC 2616 14.23 we have to send 400 status here. http_sconn_error(sc, NNG_HTTP_STATUS_BAD_REQUEST); @@ -616,7 +621,7 @@ http_sconn_rxdone(void *arg) break; } // So, what about the method? - val = nni_http_req_get_method(req); + val = nni_http_get_method(sc->conn); if (strcmp(val, h->method) == 0) { break; } @@ -646,7 +651,8 @@ http_sconn_rxdone(void *arg) } if ((h->getbody) && - ((cls = nni_http_req_get_header(req, "Content-Length")) != NULL)) { + ((cls = nni_http_get_header(sc->conn, "Content-Length")) != + NULL)) { uint64_t len; char *end; @@ -665,7 +671,8 @@ http_sconn_rxdone(void *arg) sc, NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR); return; } - nng_http_req_get_data(req, &iov.iov_buf, &iov.iov_len); + iov.iov_buf = req->data.data; + iov.iov_len = req->data.size; sc->handler = h; nni_mtx_unlock(&s->mtx); nni_aio_set_iov(&sc->rxaio, 1, &iov); @@ -725,28 +732,25 @@ http_sconn_cbdone(void *arg) return; } res = nni_http_conn_res(sc->conn); - if (!nni_http_conn_res_sent(sc->conn)) { + if (!nni_http_res_sent(sc->conn)) { const char *val; - val = nni_http_res_get_header(res, "Connection"); + const char *method; + uint16_t status; + val = nni_http_get_header(sc->conn, "Connection"); + status = nni_http_get_status(sc->conn); + method = nni_http_get_method(sc->conn); if ((val != NULL) && (strstr(val, "close") != NULL)) { sc->close = true; } if (sc->close) { - nni_http_res_set_header(res, "Connection", "close"); + nni_http_set_header(sc->conn, "Connection", "close"); } - if (strcmp( - nni_http_req_get_method(nng_http_conn_req(sc->conn)), - "HEAD") == 0) { - void *data; - size_t size; - // prune off the data, but preserve the content-length - // header. By passing NULL here, we leave off the old - // data, but the non-zero size means we don't clobber - // the HTTP header. - nni_http_res_get_data(res, &data, &size); - nni_http_res_set_data(res, NULL, size); + if ((strcmp(method, "HEAD") == 0) && status >= 200 && + status <= 299) { + // prune off data, preserving content-length header. + nni_http_prune_body(sc->conn); } else if (nni_http_res_is_error(res)) { - (void) nni_http_server_res_error(s, res); + (void) nni_http_server_error(s, sc->conn); } nni_http_write_res(sc->conn, &sc->txaio); } else if (sc->close) { @@ -775,7 +779,7 @@ http_sconn_init(http_sconn **scp, nng_stream *stream) nni_aio_init(&sc->txdataio, http_sconn_txdatdone, sc); nni_aio_init(&sc->cbaio, http_sconn_cbdone, sc); - if ((rv = nni_http_conn_init(&sc->conn, stream)) != 0) { + if ((rv = nni_http_conn_init(&sc->conn, stream, false)) != 0) { // Can't even accept the incoming request. Hard close. http_sconn_close(sc); return (rv); @@ -847,7 +851,7 @@ http_server_fini(nni_http_server *s) 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_strfree(epage->body); NNI_FREE_STRUCT(epage); } nni_mtx_unlock(&s->errors_mtx); @@ -1026,7 +1030,7 @@ nni_http_server_close(nni_http_server *s) } static int -http_server_set_err(nni_http_server *s, uint16_t code, void *body, size_t len) +http_server_set_err(nni_http_server *s, uint16_t code, char *body) { http_error *epage; @@ -1044,11 +1048,8 @@ http_server_set_err(nni_http_server *s, uint16_t code, void *body, size_t len) epage->code = code; nni_list_append(&s->errors, epage); } - if (epage->len != 0) { - nni_free(epage->body, epage->len); - } + nni_strfree(epage->body); epage->body = body; - epage->len = len; nni_mtx_unlock(&s->errors_mtx); return (0); } @@ -1057,75 +1058,36 @@ int nni_http_server_set_error_page( nni_http_server *s, uint16_t code, const char *html) { - char *body; - int rv; - size_t len; + char *body; + int rv; // We copy the content, without the trailing NUL. - len = strlen(html); - if ((body = nni_alloc(len)) == NULL) { + if ((body = nni_strdup(html)) == 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); + if ((rv = http_server_set_err(s, code, body)) != 0) { + nni_strfree(body); } return (rv); } int -nni_http_server_res_error(nni_http_server *s, nni_http_res *res) +nni_http_server_error(nni_http_server *s, nng_http *conn) { http_error *epage; char *body = NULL; - char *html = NULL; - size_t len = 0; - uint16_t code = nni_http_res_get_status(res); + uint16_t code = nni_http_get_status(conn); int rv; nni_mtx_lock(&s->errors_mtx); NNI_LIST_FOREACH (&s->errors, epage) { if (epage->code == code) { body = epage->body; - len = epage->len; break; } } + rv = nni_http_set_error(conn, code, NULL, body); nni_mtx_unlock(&s->errors_mtx); - - if (body == NULL) { - 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) && - ((rv = nni_http_res_set_header( - res, "Content-Type", "text/html; charset=UTF-8")) == 0)) { - nni_http_res_set_status(res, code); - } - nni_strfree(html); - return (rv); } @@ -1322,14 +1284,13 @@ typedef struct http_file { } http_file; static void -http_handle_file(nni_http_conn *conn, void *arg, nni_aio *aio) +http_handle_file(nng_http *conn, void *arg, nni_aio *aio) { - nni_http_res *res = nng_http_conn_res(conn); - void *data; - size_t size; - int rv; - http_file *hf = arg; - const char *ctype; + void *data; + size_t size; + int rv; + http_file *hf = arg; + const char *ctype; if ((ctype = hf->ctype) == NULL) { ctype = "application/octet-stream"; @@ -1356,26 +1317,23 @@ http_handle_file(nni_http_conn *conn, void *arg, nni_aio *aio) status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR; break; } - if ((rv = nni_http_res_set_error(res, status)) != 0) { + if ((rv = nni_http_set_error(conn, status, NULL, NULL)) != 0) { nni_aio_finish_error(aio, rv); return; } - nni_aio_set_output(aio, 0, res); nni_aio_finish(aio, 0, 0); return; } - if (((rv = nni_http_res_set_header(res, "Content-Type", ctype)) != - 0) || - ((rv = nni_http_res_copy_data(res, data, size)) != 0)) { + if (((rv = nni_http_set_header(conn, "Content-Type", ctype)) != 0) || + ((rv = nni_http_copy_body(conn, data, size)) != 0)) { nni_free(data, size); nni_aio_finish_error(aio, rv); return; } - nni_http_res_set_status(res, NNG_HTTP_STATUS_OK); + nng_http_set_status(conn, NNG_HTTP_STATUS_OK, NULL); nni_free(data, size); - nni_aio_set_output(aio, 0, res); nni_aio_finish(aio, 0, 0); } @@ -1439,22 +1397,20 @@ nni_http_handler_init_file( } static void -http_handle_dir(nng_http_conn *conn, void *arg, nng_aio *aio) +http_handle_dir(nng_http *conn, void *arg, nng_aio *aio) { - nni_http_req *req = nni_http_conn_req(conn); - nni_http_res *res = nni_http_conn_res(conn); - void *data; - size_t size; - int rv; - http_file *hf = arg; - const char *path = hf->path; - const char *base = hf->base; - const char *uri = nni_http_req_get_uri(req); - const char *ctype; - char *dst; - size_t len; - size_t pnsz; - char *pn; + void *data; + size_t size; + int rv; + http_file *hf = arg; + const char *path = hf->path; + const char *base = hf->base; + const char *uri = nni_http_get_uri(conn); + const char *ctype; + char *dst; + size_t len; + size_t pnsz; + char *pn; len = strlen(base); if (base[1] != '\0' && // Allows "/" as base @@ -1544,27 +1500,24 @@ http_handle_dir(nng_http_conn *conn, void *arg, nng_aio *aio) status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR; break; } - if ((rv = nni_http_res_set_error(res, status)) != 0) { + if ((rv = nni_http_set_error(conn, status, NULL, NULL)) != 0) { nni_aio_finish_error(aio, rv); return; } - nni_aio_set_output(aio, 0, res); nni_aio_finish(aio, 0, 0); return; } - if (((rv = nni_http_res_set_header(res, "Content-Type", ctype)) != - 0) || - ((rv = nni_http_res_copy_data(res, data, size)) != 0)) { + if (((rv = nng_http_set_header(conn, "Content-Type", ctype)) != 0) || + ((rv = nng_http_copy_body(conn, data, size)) != 0)) { nni_free(data, size); nni_aio_finish_error(aio, rv); return; } - nni_http_res_set_status(res, NNG_HTTP_STATUS_OK); + nng_http_set_status(conn, NNG_HTTP_STATUS_OK, NULL); nni_free(data, size); - nni_aio_set_output(aio, 0, res); nni_aio_finish(aio, 0, 0); } @@ -1605,20 +1558,17 @@ typedef struct http_redirect { } http_redirect; static void -http_handle_redirect(nng_http_conn *conn, void *data, nng_aio *aio) +http_handle_redirect(nng_http *conn, void *data, nng_aio *aio) { - nni_http_res *res = nng_http_conn_res(conn); - nni_http_req *req = nng_http_conn_req(conn); - char *html = NULL; - char *msg = NULL; - char *loc = NULL; - http_redirect *hr = data; + nni_http_res *res = nni_http_conn_res(conn); + char *loc = NULL; + http_redirect *hr = data; int rv; const char *base; const char *uri; base = hr->from; // base uri - uri = nni_http_req_get_uri(req); + uri = nni_http_get_uri(conn); // If we are doing a full tree, then include the entire suffix. if (strncmp(uri, base, strlen(base)) == 0) { @@ -1631,39 +1581,24 @@ http_handle_redirect(nng_http_conn *conn, void *data, nng_aio *aio) 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_alloc_html_error(&html, hr->code, msg)) != 0) || - ((rv = nni_http_res_set_header(res, "Connection", "close")) != - 0) || - ((rv = nni_http_res_set_header( - res, "Content-Type", "text/html; charset=UTF-8")) != 0) || - ((rv = nni_http_res_set_header(res, "Location", loc)) != 0) || - ((rv = nni_http_res_copy_data(res, html, strlen(html))) != 0)) { + if (((rv = nni_http_set_redirect(conn, hr->code, NULL, loc)) != 0) || + ((rv = nni_http_set_header(conn, "Connection", "close")) != 0)) { if (loc != hr->where) { nni_strfree(loc); } - nni_strfree(msg); - nni_strfree(html); nni_aio_finish_error(aio, rv); return; } - nni_http_res_set_status(res, hr->code); + nng_http_set_status(conn, hr->code, NULL); if (loc != hr->where) { nni_strfree(loc); } - nni_strfree(msg); - nni_strfree(html); nni_aio_set_output(aio, 0, res); nni_aio_finish(aio, 0, 0); } @@ -1725,28 +1660,21 @@ typedef struct http_static { } http_static; static void -http_handle_static(nng_http_conn *conn, void *data, nni_aio *aio) +http_handle_static(nng_http *conn, void *data, nni_aio *aio) { - http_static *hs = data; - const char *ctype; - nni_http_res *r = NULL; - int rv; + http_static *hs = data; + const char *ctype; if ((ctype = hs->ctype) == NULL) { ctype = "application/octet-stream"; } - r = nng_http_conn_res(conn); - nng_http_res_reset(r); - if (((rv = nni_http_res_set_header(r, "Content-Type", ctype)) != 0) || - ((rv = nni_http_res_set_data(r, hs->data, hs->size)) != 0)) { - nni_aio_finish_error(aio, rv); - return; - } + // this cannot fail (no dynamic allocation) + (void) nni_http_set_header(conn, "Content-Type", ctype); + nni_http_set_body(conn, hs->data, hs->size); - nni_http_res_set_status(r, NNG_HTTP_STATUS_OK); + nng_http_set_status(conn, NNG_HTTP_STATUS_OK, NULL); - nni_aio_set_output(aio, 0, r); nni_aio_finish(aio, 0, 0); } |
