diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/sp/transport/ws/ws_test.c | 11 | ||||
| -rw-r--r-- | src/supplemental/http/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/supplemental/http/http_api.h | 131 | ||||
| -rw-r--r-- | src/supplemental/http/http_client.c | 139 | ||||
| -rw-r--r-- | src/supplemental/http/http_conn.c | 744 | ||||
| -rw-r--r-- | src/supplemental/http/http_msg.c | 778 | ||||
| -rw-r--r-- | src/supplemental/http/http_msg.h | 33 | ||||
| -rw-r--r-- | src/supplemental/http/http_public.c | 411 | ||||
| -rw-r--r-- | src/supplemental/http/http_server.c | 260 | ||||
| -rw-r--r-- | src/supplemental/http/http_server_test.c | 284 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket.c | 348 | ||||
| -rw-r--r-- | src/supplemental/websocket/websocket_test.c | 16 |
13 files changed, 1373 insertions, 1786 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index be3d3605..3cf64f94 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ nng_sources(nng.c nng_legacy.c) nng_headers(nng/args.h) nng_headers(nng/nng.h) +nng_headers(nng/http.h) target_include_directories(nng PRIVATE ${PROJECT_SOURCE_DIR}/src) target_include_directories(nng_testing PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/sp/transport/ws/ws_test.c b/src/sp/transport/ws/ws_test.c index 5d6215e6..98856d4f 100644 --- a/src/sp/transport/ws/ws_test.c +++ b/src/sp/transport/ws/ws_test.c @@ -231,17 +231,6 @@ check_props_v4(nng_msg *msg) NUTS_PASS(nng_pipe_get_bool(p, NNG_OPT_TCP_NODELAY, &b)); NUTS_TRUE(b); // default - - // Request Header - char *buf = NULL; - NUTS_PASS(nng_pipe_get_string(p, NNG_OPT_WS_REQUEST_HEADERS, &buf)); - NUTS_TRUE(strstr(buf, "Sec-WebSocket-Key") != NULL); - nng_strfree(buf); - - // Response Header - NUTS_PASS(nng_pipe_get_string(p, NNG_OPT_WS_RESPONSE_HEADERS, &buf)); - NUTS_TRUE(strstr(buf, "Sec-WebSocket-Accept") != NULL); - nng_strfree(buf); } void diff --git a/src/supplemental/http/CMakeLists.txt b/src/supplemental/http/CMakeLists.txt index 0e4cc808..31a544a9 100644 --- a/src/supplemental/http/CMakeLists.txt +++ b/src/supplemental/http/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2020 Staysail Systems, Inc. <info@staysail.tech> +# Copyright 2025 Staysail Systems, Inc. <info@staysail.tech> # Copyright 2018 Capitar IT Group BV <info@capitar.com> # # This software is supplied under the terms of the MIT License, a @@ -15,7 +15,6 @@ endif() mark_as_advanced(NNG_ENABLE_HTTP) nng_sources(http_public.c http_api.h) -nng_headers(nng/supplemental/http/http.h) nng_defines_if(NNG_SUPP_HTTP NNG_SUPP_HTTP) nng_sources_if(NNG_SUPP_HTTP diff --git a/src/supplemental/http/http_api.h b/src/supplemental/http/http_api.h index 1630198d..b2b07b99 100644 --- a/src/supplemental/http/http_api.h +++ b/src/supplemental/http/http_api.h @@ -13,7 +13,9 @@ #define NNG_SUPPLEMENTAL_HTTP_HTTP_API_H #include "core/nng_impl.h" -#include <nng/supplemental/http/http.h> +#include "nng/http.h" + +#include "http_msg.h" // This represents the "internal" HTTP API. It should not be used // or exposed to applications directly. @@ -32,21 +34,15 @@ typedef struct nng_http_chunks nni_http_chunks; // These functions are private to the internal framework, and really should // not be used elsewhere. -extern const char *nni_http_reason(uint16_t); - -extern void 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 *); -extern int nni_http_req_parse(nni_http_req *, void *, size_t, size_t *); -extern char *nni_http_req_headers(nni_http_req *); -extern void nni_http_req_get_data(nni_http_req *, void **, size_t *); +extern void 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 *); +extern int nni_http_req_parse(nng_http *, void *, size_t, size_t *); -extern void nni_http_res_init(nni_http_res *); -extern void nni_http_res_reset(nni_http_res *); -extern int nni_http_res_get_buf(nni_http_res *, void **, size_t *); -extern int nni_http_res_parse(nni_http_res *, void *, size_t, size_t *); -extern void nni_http_res_get_data(nni_http_res *, void **, size_t *); -extern char *nni_http_res_headers(nni_http_res *); +extern void nni_http_res_init(nni_http_res *); +extern void nni_http_res_reset(nni_http_res *); +extern int nni_http_res_get_buf(nni_http_conn *, void **, size_t *); +extern int nni_http_res_parse(nng_http *, void *, size_t, size_t *); // Chunked transfer encoding. For the moment this is not part of our public // API. We can change that later. @@ -99,12 +95,12 @@ extern void *nni_http_conn_get_ctx(nni_http_conn *); // These initialization functions create stream for HTTP transactions. // They should only be used by the server or client HTTP implementations, // and are not for use by other code. -extern int nni_http_conn_init(nni_http_conn **, nng_stream *); +extern int nni_http_conn_init(nng_http **, nng_stream *, bool); -extern void nni_http_conn_close(nni_http_conn *); +extern void nni_http_conn_close(nng_http *); extern void nni_http_conn_fini(nni_http_conn *); extern int nni_http_conn_getopt( - nni_http_conn *, const char *, void *, size_t *, nni_type); + nng_http *, const char *, void *, size_t *, nni_type); // Reading messages -- the caller must supply a preinitialized (but otherwise // idle) message. We recommend the caller store this in the aio's user data. @@ -112,61 +108,40 @@ extern int nni_http_conn_getopt( // must not use them for any other purpose. extern int nni_http_req_alloc(nni_http_req **, const nng_url *); -extern int nni_http_res_alloc(nni_http_res **); -extern int nni_http_res_alloc_error(nni_http_res **, uint16_t); extern int nni_http_res_set_error(nni_http_res *, uint16_t); extern void nni_http_req_free(nni_http_req *); extern void nni_http_res_free(nni_http_res *); -extern void nni_http_write_req(nni_http_conn *, nni_http_req *, nni_aio *); -extern void nni_http_read_res(nni_http_conn *, nni_http_res *, nni_aio *); +extern void nni_http_write_req(nni_http_conn *, nni_aio *); +extern void nni_http_read_res(nni_http_conn *, nni_aio *); extern void nni_http_read_req(nni_http_conn *, nni_aio *); extern void nni_http_write_res(nni_http_conn *, nni_aio *); -extern const char *nni_http_req_get_header(const nni_http_req *, const char *); -extern const char *nni_http_res_get_header(const nni_http_res *, const char *); extern int nni_http_req_add_header(nni_http_req *, const char *, const char *); -extern int nni_http_res_add_header(nni_http_res *, const char *, const char *); -extern int nni_http_req_set_header(nni_http_req *, const char *, const char *); -extern int nni_http_res_set_header(nni_http_res *, const char *, const char *); extern int nni_http_req_del_header(nni_http_req *, const char *); extern int nni_http_res_del_header(nni_http_res *, const char *); -extern int nni_http_req_copy_data(nni_http_req *, const void *, size_t); -extern int nni_http_res_copy_data(nni_http_res *, const void *, size_t); -extern int nni_http_req_set_data(nni_http_req *, const void *, size_t); -extern int nni_http_res_set_data(nni_http_res *, const void *, size_t); extern int nni_http_req_alloc_data(nni_http_req *, size_t); extern int nni_http_res_alloc_data(nni_http_res *, size_t); -extern const char *nni_http_req_get_method(const nni_http_req *); -extern const char *nni_http_req_get_version(const nni_http_req *); -extern const char *nni_http_req_get_uri(const nni_http_req *); -extern void nni_http_req_set_method(nni_http_req *, const char *); -extern int nni_http_req_set_version(nni_http_req *, const char *); -extern int nni_http_req_set_uri(nni_http_req *, const char *); -extern int nni_http_req_set_url(nni_http_req *, const nng_url *); -extern uint16_t nni_http_res_get_status(const nni_http_res *); -extern void nni_http_res_set_status(nni_http_res *, uint16_t); -extern const char *nni_http_res_get_version(const nni_http_res *); -extern int nni_http_res_set_version(nni_http_res *, const char *); -extern const char *nni_http_res_get_reason(const 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 *); -// 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 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 *); extern void nni_http_write_full(nni_http_conn *, nni_aio *); +extern int nni_http_add_header(nng_http *, const char *, const char *); +extern int nni_http_set_header(nng_http *, const char *, const char *); +extern void nni_http_del_header(nng_http *, const char *); +extern const char *nni_http_get_header(nng_http *, const char *); + +extern void nni_http_get_body(nng_http *, void **, size_t *); +extern void nni_http_set_body(nng_http *, void *, size_t); +extern int nni_http_copy_body(nng_http *, const void *, size_t); + +// prune body clears the outgoing body (0 bytes), but leaves content-length +// intact if present for the benefit of HEAD. +extern void nni_http_prune_body(nng_http *); + // nni_http_server will look for an existing server with the same // name and port, or create one if one does not exist. The servers // are reference counted to permit sharing the server object across @@ -232,14 +207,10 @@ extern void nni_http_server_close(nni_http_server *); 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 *); +extern int nni_http_server_error(nni_http_server *, nng_http *); // 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 @@ -390,6 +361,44 @@ extern void nni_http_transact( extern const char *nni_http_stream_scheme(const char *); // Private method used for the server. -extern bool nni_http_conn_res_sent(nni_http_conn *conn); +extern bool nni_http_res_sent(nni_http_conn *conn); + +extern const char *nni_http_get_version(nng_http *conn); +extern int nni_http_set_version(nng_http *conn, const char *vers); + +extern void nni_http_set_method(nng_http *conn, const char *method); +extern const char *nni_http_get_method(nng_http *conn); + +extern int nni_http_set_status( + nng_http *conn, uint16_t status, const char *reason); + +extern uint16_t nni_http_get_status(nng_http *); +extern const char *nni_http_get_reason(nng_http *); + +// nni_http_set_error flags an error using the built in HTML page. +// unless body is not NULL. To pass no content, pass an empty string for body. +extern int nni_http_set_error( + nng_http *conn, uint16_t status, const char *reason, const char *body); + +// nni_http_set_redirect is used to set the redirection. +// It uses a built-in error page, with a message about the redirection, and +// sets the response Location: header accordingly. +extern int nni_http_set_redirect( + nng_http *conn, uint16_t status, const char *reason, const char *dest); + +extern int nni_http_set_uri( + nng_http *conn, const char *uri, const char *query); +extern const char *nni_http_get_uri(nng_http *conn); + +extern void nni_http_set_host(nng_http *conn, const char *); +extern void nni_http_set_content_type(nng_http *conn, const char *); +extern void nni_http_conn_reset(nng_http *conn); +extern int nni_http_add_header( + nng_http *conn, const char *key, const char *val); + +extern void nni_http_set_static_header( + nng_http *conn, nni_http_header *header, const char *key, const char *val); + +extern bool nni_http_parsed(nng_http *conn); #endif // NNG_SUPPLEMENTAL_HTTP_HTTP_API_H diff --git a/src/supplemental/http/http_client.c b/src/supplemental/http/http_client.c index 8701a96b..7062ae3c 100644 --- a/src/supplemental/http/http_client.c +++ b/src/supplemental/http/http_client.c @@ -10,12 +10,14 @@ // #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include "core/nng_impl.h" #include "http_api.h" +#include "http_msg.h" static nni_mtx http_txn_lk = NNI_MTX_INITIALIZER; @@ -24,6 +26,7 @@ struct nng_http_client { nni_mtx mtx; bool closed; nni_aio aio; + char host[260]; nng_stream_dialer *dialer; }; @@ -70,7 +73,9 @@ http_dial_cb(void *arg) stream = nni_aio_get_output(&c->aio, 0); NNI_ASSERT(stream != NULL); - rv = nni_http_conn_init(&conn, stream); + rv = nni_http_conn_init(&conn, stream, true); + + // set up the host header http_dial_start(c); nni_mtx_unlock(&c->mtx); @@ -79,7 +84,7 @@ http_dial_cb(void *arg) nni_aio_finish_error(aio, rv); return; } - + nni_http_set_host(conn, c->host); nni_aio_set_output(aio, 0, conn); nni_aio_finish(aio, 0, 0); } @@ -110,7 +115,8 @@ nni_http_client_init(nni_http_client **cp, const nng_url *url) memcpy(&my_url, url, sizeof(my_url)); my_url.u_scheme = (char *) scheme; - if (strlen(url->u_hostname) == 0) { + if ((strlen(url->u_hostname) == 0) || + (strlen(url->u_hostname) > 253)) { // We require a valid hostname. return (NNG_EADDRINVAL); } @@ -122,6 +128,16 @@ nni_http_client_init(nni_http_client **cp, const nng_url *url) nni_aio_list_init(&c->aios); nni_aio_init(&c->aio, http_dial_cb, c); + if (nni_url_default_port(url->u_scheme) == url->u_port) { + snprintf(c->host, sizeof(c->host), "%s", url->u_hostname); + } else if (strchr(url->u_hostname, ':') != NULL) { + // IPv6 address, needs [wrapping] + snprintf(c->host, sizeof(c->host), "[%s]:%d", url->u_hostname, + url->u_port); + } else { + snprintf(c->host, sizeof(c->host), "%s:%d", url->u_hostname, + url->u_port); + } if ((rv = nng_stream_dialer_alloc_url(&c->dialer, &my_url)) != 0) { nni_http_client_fini(c); return (rv); @@ -190,7 +206,6 @@ nni_http_client_connect(nni_http_client *c, nni_aio *aio) } typedef enum http_txn_state { - HTTP_CONNECTING, HTTP_SENDING, HTTP_RECVING, HTTP_RECVING_BODY, @@ -198,7 +213,7 @@ typedef enum http_txn_state { } http_txn_state; typedef struct http_txn { - nni_aio *aio; // lower level aio + nni_aio aio; // lower level aio nni_list aios; // upper level aio(s) -- maximum one nni_http_client *client; nni_http_conn *conn; @@ -206,12 +221,15 @@ typedef struct http_txn { nni_http_res *res; nni_http_chunks *chunks; http_txn_state state; + nni_reap_node reap; } http_txn; static void -http_txn_fini(void *arg) +http_txn_reap(void *arg) { http_txn *txn = arg; + + nni_aio_stop(&txn->aio); if (txn->client != NULL) { // We only close the connection if we created it. if (txn->conn != NULL) { @@ -220,10 +238,21 @@ http_txn_fini(void *arg) } } nni_http_chunks_free(txn->chunks); - nni_aio_reap(txn->aio); + nni_aio_fini(&txn->aio); NNI_FREE_STRUCT(txn); } +static nni_reap_list http_txn_reaplist = { + .rl_offset = offsetof(http_txn, reap), + .rl_func = (nni_cb) http_txn_reap, +}; + +static void +http_txn_fini(http_txn *txn) +{ + nni_reap(&http_txn_reaplist, txn); +} + static void http_txn_finish_aios(http_txn *txn, int rv) { @@ -248,31 +277,26 @@ http_txn_cb(void *arg) nni_http_chunk *chunk = NULL; nni_mtx_lock(&http_txn_lk); - if ((rv = nni_aio_result(txn->aio)) != 0) { + if ((rv = nni_aio_result(&txn->aio)) != 0) { http_txn_finish_aios(txn, rv); nni_mtx_unlock(&http_txn_lk); http_txn_fini(txn); return; } switch (txn->state) { - case HTTP_CONNECTING: - txn->conn = nni_aio_get_output(txn->aio, 0); - txn->state = HTTP_SENDING; - nni_http_write_req(txn->conn, txn->req, txn->aio); - nni_mtx_unlock(&http_txn_lk); - return; - case HTTP_SENDING: txn->state = HTTP_RECVING; - nni_http_read_res(txn->conn, txn->res, txn->aio); + nni_http_read_res(txn->conn, &txn->aio); nni_mtx_unlock(&http_txn_lk); return; case HTTP_RECVING: - // Detect chunked encoding. You poor bastard. - if (((str = nni_http_res_get_header( - txn->res, "Transfer-Encoding")) != NULL) && + // Detect chunked encoding. You poor bastard. (Only if not + // HEAD.) + if ((strcmp(nni_http_get_method(txn->conn), "HEAD") != 0) && + ((str = nni_http_get_header( + txn->conn, "Transfer-Encoding")) != NULL) && (strstr(str, "chunked") != NULL)) { if ((rv = nni_http_chunks_init(&txn->chunks, 0)) != @@ -280,15 +304,15 @@ http_txn_cb(void *arg) goto error; } txn->state = HTTP_RECVING_CHUNKS; - nni_http_read_chunks(txn->conn, txn->chunks, txn->aio); + nni_http_read_chunks( + txn->conn, txn->chunks, &txn->aio); nni_mtx_unlock(&http_txn_lk); return; } - str = nni_http_req_get_method(txn->req); - if ((nni_strcasecmp(str, "HEAD") == 0) || - ((str = nni_http_res_get_header( - txn->res, "Content-Length")) == NULL) || + if ((strcmp(nni_http_get_method(txn->conn), "HEAD") == 0) || + ((str = nni_http_get_header( + txn->conn, "Content-Length")) == NULL) || ((len = (uint64_t) strtoull(str, &end, 10)) == 0) || (end == NULL) || (*end != '\0')) { // If no content-length, or HEAD (which per RFC @@ -303,10 +327,10 @@ http_txn_cb(void *arg) 0) { goto error; } - nni_http_res_get_data(txn->res, &iov.iov_buf, &iov.iov_len); - nni_aio_set_iov(txn->aio, 1, &iov); + nni_http_get_body(txn->conn, &iov.iov_buf, &iov.iov_len); + nni_aio_set_iov(&txn->aio, 1, &iov); txn->state = HTTP_RECVING_BODY; - nni_http_read_full(txn->conn, txn->aio); + nni_http_read_full(txn->conn, &txn->aio); nni_mtx_unlock(&http_txn_lk); return; @@ -324,7 +348,7 @@ http_txn_cb(void *arg) if ((rv = nni_http_res_alloc_data(txn->res, sz)) != 0) { goto error; } - nni_http_res_get_data(txn->res, (void **) &dst, &sz); + nni_http_get_body(txn->conn, (void **) &dst, &sz); while ((chunk = nni_http_chunks_iter(txn->chunks, chunk)) != NULL) { memcpy(dst, nni_http_chunk_data(chunk), @@ -350,7 +374,7 @@ http_txn_cancel(nni_aio *aio, void *arg, int rv) http_txn *txn = arg; nni_mtx_lock(&http_txn_lk); if (nni_aio_list_active(aio)) { - nni_aio_abort(txn->aio, rv); + nni_aio_abort(&txn->aio, rv); } nni_mtx_unlock(&http_txn_lk); } @@ -364,18 +388,13 @@ void nni_http_transact_conn(nni_http_conn *conn, nni_aio *aio) { http_txn *txn; - int rv; nni_aio_reset(aio); if ((txn = NNI_ALLOC_STRUCT(txn)) == NULL) { nni_aio_finish_error(aio, NNG_ENOMEM); return; } - if ((rv = nni_aio_alloc(&txn->aio, http_txn_cb, txn)) != 0) { - NNI_FREE_STRUCT(txn); - nni_aio_finish_error(aio, rv); - return; - } + nni_aio_init(&txn->aio, http_txn_cb, txn); nni_aio_list_init(&txn->aios); txn->client = NULL; txn->conn = conn; @@ -391,55 +410,7 @@ nni_http_transact_conn(nni_http_conn *conn, nni_aio *aio) http_txn_fini(txn); return; } - nni_http_res_reset(txn->res); - nni_list_append(&txn->aios, aio); - nni_http_write_req(conn, txn->req, txn->aio); - nni_mtx_unlock(&http_txn_lk); -} - -// nni_http_transact_simple does a single transaction, creating a connection -// just for the purpose, and closing it when done. (No connection caching.) -// The reason we require a client to be created first is to deal with TLS -// settings. A single global client (per server) may be used. -void -nni_http_transact(nni_http_client *client, nni_http_req *req, - nni_http_res *res, nni_aio *aio) -{ - http_txn *txn; - int rv; - - nni_aio_reset(aio); - if ((txn = NNI_ALLOC_STRUCT(txn)) == NULL) { - nni_aio_finish_error(aio, NNG_ENOMEM); - return; - } - if ((rv = nni_aio_alloc(&txn->aio, http_txn_cb, txn)) != 0) { - NNI_FREE_STRUCT(txn); - nni_aio_finish_error(aio, rv); - return; - } - - if ((rv = nni_http_req_set_header(req, "Connection", "close")) != 0) { - nni_aio_finish_error(aio, rv); - http_txn_fini(txn); - return; - } - - nni_aio_list_init(&txn->aios); - txn->client = client; - txn->conn = NULL; - txn->req = req; - txn->res = res; - txn->state = HTTP_CONNECTING; - - nni_mtx_lock(&http_txn_lk); - if (!nni_aio_start(aio, http_txn_cancel, txn)) { - nni_mtx_unlock(&http_txn_lk); - http_txn_fini(txn); - return; - } - nni_http_res_reset(txn->res); nni_list_append(&txn->aios, aio); - nni_http_client_connect(client, txn->aio); + nni_http_write_req(conn, &txn->aio); nni_mtx_unlock(&http_txn_lk); } diff --git a/src/supplemental/http/http_conn.c b/src/supplemental/http/http_conn.c index 9ed882f1..20ae2f05 100644 --- a/src/supplemental/http/http_conn.c +++ b/src/supplemental/http/http_conn.c @@ -1,5 +1,5 @@ // -// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2025 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> // Copyright 2019 Devolutions <info@devolutions.net> // @@ -11,9 +11,12 @@ #include <ctype.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> +#include "core/list.h" #include "core/nng_impl.h" +#include "nng/http.h" #include "supplemental/tls/tls_api.h" #include "http_api.h" @@ -62,20 +65,20 @@ struct nng_http_conn { size_t rd_put; size_t rd_bufsz; bool rd_buffered; - - bool res_sent; + bool client; // true if this is a client's connection + bool res_sent; enum write_flavor wr_flavor; }; nng_http_req * -nni_http_conn_req(nng_http_conn *conn) +nni_http_conn_req(nng_http *conn) { return (&conn->req); } nng_http_res * -nni_http_conn_res(nng_http_conn *conn) +nni_http_conn_res(nng_http *conn) { return (&conn->res); } @@ -138,6 +141,20 @@ nni_http_conn_close(nni_http_conn *conn) nni_mtx_unlock(&conn->mtx); } +// http_rd_buf_pull_up pulls the content of the read buffer back to the +// beginning, so that the next read can go at the end. This avoids the problem +// of dealing with a read that might wrap. +static void +http_rd_buf_pull_up(nni_http_conn *conn) +{ + if (conn->rd_get != 0) { + memmove(conn->rd_buf, conn->rd_buf + conn->rd_get, + conn->rd_put - conn->rd_get); + conn->rd_put -= conn->rd_get; + conn->rd_get = 0; + } +} + // http_rd_buf attempts to satisfy the read from data in the buffer. static int http_rd_buf(nni_http_conn *conn, nni_aio *aio) @@ -196,34 +213,44 @@ http_rd_buf(nni_http_conn *conn, nni_aio *aio) return (NNG_EAGAIN); case HTTP_RD_REQ: - rv = nni_http_req_parse( - nni_aio_get_prov_data(aio), rbuf, cnt, &n); + conn->client = true; + rv = nni_http_req_parse(conn, rbuf, cnt, &n); + conn->client = false; conn->rd_get += n; if (conn->rd_get == conn->rd_put) { conn->rd_get = conn->rd_put = 0; } if (rv == NNG_EAGAIN) { nni_iov iov1; + http_rd_buf_pull_up(conn); iov1.iov_buf = conn->rd_buf + conn->rd_put; iov1.iov_len = conn->rd_bufsz - conn->rd_put; conn->rd_buffered = true; + if (iov1.iov_len == 0) { + return (NNG_EMSGSIZE); + } nni_aio_set_iov(&conn->rd_aio, 1, &iov1); nng_stream_recv(conn->sock, &conn->rd_aio); } return (rv); case HTTP_RD_RES: - rv = nni_http_res_parse( - nni_aio_get_prov_data(aio), rbuf, cnt, &n); + conn->client = false; + rv = nni_http_res_parse(conn, rbuf, cnt, &n); + conn->client = true; conn->rd_get += n; if (conn->rd_get == conn->rd_put) { conn->rd_get = conn->rd_put = 0; } if (rv == NNG_EAGAIN) { nni_iov iov1; + http_rd_buf_pull_up(conn); iov1.iov_buf = conn->rd_buf + conn->rd_put; iov1.iov_len = conn->rd_bufsz - conn->rd_put; conn->rd_buffered = true; + if (iov1.iov_len == 0) { + return (NNG_EMSGSIZE); + } nni_aio_set_iov(&conn->rd_aio, 1, &iov1); nng_stream_recv(conn->sock, &conn->rd_aio); } @@ -511,23 +538,29 @@ http_wr_submit(nni_http_conn *conn, nni_aio *aio, enum write_flavor flavor) } void -nni_http_read_req(nni_http_conn *conn, nni_aio *aio) +nni_http_conn_reset(nng_http *conn) { - nni_aio_set_prov_data(aio, &conn->req); + nni_http_req_reset(&conn->req); + nni_http_res_reset(&conn->res); + if (strlen(conn->req.host)) { + nni_http_set_host(conn, conn->req.host); + } +} +void +nni_http_read_req(nni_http_conn *conn, nni_aio *aio) +{ // clear the sent flag (used for the server) conn->res_sent = false; - nni_http_req_reset(&conn->req); + nni_http_conn_reset(conn); nni_mtx_lock(&conn->mtx); http_rd_submit(conn, aio, HTTP_RD_REQ); nni_mtx_unlock(&conn->mtx); } void -nni_http_read_res(nni_http_conn *conn, nni_http_res *res, nni_aio *aio) +nni_http_read_res(nni_http_conn *conn, nni_aio *aio) { - nni_aio_set_prov_data(aio, res); - nni_mtx_lock(&conn->mtx); http_rd_submit(conn, aio, HTTP_RD_RES); nni_mtx_unlock(&conn->mtx); @@ -564,28 +597,25 @@ nni_http_read(nni_http_conn *conn, nni_aio *aio) } void -nni_http_write_req(nni_http_conn *conn, nni_http_req *req, nni_aio *aio) +nni_http_write_req(nni_http_conn *conn, nni_aio *aio) { int rv; void *buf; size_t bufsz; - void *data; - size_t size; nni_iov iov[2]; int niov; - if ((rv = nni_http_req_get_buf(req, &buf, &bufsz)) != 0) { + if ((rv = nni_http_req_get_buf(&conn->req, &buf, &bufsz)) != 0) { nni_aio_finish_error(aio, rv); return; } - nni_http_req_get_data(req, &data, &size); niov = 1; iov[0].iov_len = bufsz; iov[0].iov_buf = buf; - if ((size > 0) && (data != NULL)) { + iov[1].iov_len = conn->req.data.size; + iov[1].iov_buf = conn->req.data.data; + if ((iov[1].iov_len > 0) && (iov[1].iov_buf != NULL)) { niov++; - iov[1].iov_len = size; - iov[1].iov_buf = data; } nni_aio_set_iov(aio, niov, iov); @@ -597,28 +627,24 @@ nni_http_write_req(nni_http_conn *conn, nni_http_req *req, nni_aio *aio) void nni_http_write_res(nni_http_conn *conn, nni_aio *aio) { - int rv; - void *buf; - size_t bufsz; - void *data; - size_t size; - nni_iov iov[2]; - int nio; - nng_http_res *res = nng_http_conn_res(conn); + int rv; + void *buf; + size_t bufsz; + nni_iov iov[2]; + int nio; conn->res_sent = true; - if ((rv = nni_http_res_get_buf(res, &buf, &bufsz)) != 0) { + if ((rv = nni_http_res_get_buf(conn, &buf, &bufsz)) != 0) { nni_aio_finish_error(aio, rv); return; } - nni_http_res_get_data(res, &data, &size); nio = 1; iov[0].iov_len = bufsz; iov[0].iov_buf = buf; - if ((size > 0) && (data != NULL)) { + iov[1].iov_len = conn->res.data.size; + iov[1].iov_buf = conn->res.data.data; + if ((iov[1].iov_len > 0) && (iov[1].iov_buf != NULL)) { nio++; - iov[1].iov_len = size; - iov[1].iov_buf = data; } nni_aio_set_iov(aio, nio, iov); @@ -643,6 +669,632 @@ nni_http_write_full(nni_http_conn *conn, nni_aio *aio) nni_mtx_unlock(&conn->mtx); } +const char * +nni_http_get_version(nng_http *conn) +{ + return (conn->req.vers); +} + +int +nni_http_set_version(nng_http *conn, const char *vers) +{ + static const char *http_versions[] = { + // for efficiency, we order in most likely first + "HTTP/1.1", + "HTTP/2", + "HTTP/3", + "HTTP/1.0", + "HTTP/0.9", + NULL, + }; + + vers = vers != NULL ? vers : NNG_HTTP_VERSION_1_1; + for (int i = 0; http_versions[i] != NULL; i++) { + if (strcmp(vers, http_versions[i]) == 0) { + conn->req.vers = http_versions[i]; + conn->res.vers = http_versions[i]; + return (0); + } + } + return (NNG_ENOTSUP); +} + +void +nni_http_set_method(nng_http *conn, const char *method) +{ + if (method == NULL) { + method = "GET"; + } + // this may truncate the method, but nobody should be sending + // methods so long. + (void) snprintf(conn->req.meth, sizeof(conn->req.meth), "%s", method); +} + +const char * +nni_http_get_method(nng_http *conn) +{ + return (conn->req.meth); +} + +uint16_t +nni_http_get_status(nng_http *conn) +{ + return (conn->res.code); +} + +const char * +nni_http_reason(uint16_t code) +{ + static struct { + uint16_t code; + const char *mesg; + } http_status[] = { + // 200, listed first because most likely + { NNG_HTTP_STATUS_OK, "OK" }, + + // 100 series -- informational + { NNG_HTTP_STATUS_CONTINUE, "Continue" }, + { NNG_HTTP_STATUS_SWITCHING, "Switching Protocols" }, + { NNG_HTTP_STATUS_PROCESSING, "Processing" }, + + // 200 series -- successful + { NNG_HTTP_STATUS_CREATED, "Created" }, + { NNG_HTTP_STATUS_ACCEPTED, "Accepted" }, + { NNG_HTTP_STATUS_NOT_AUTHORITATIVE, "Not Authoritative" }, + { NNG_HTTP_STATUS_NO_CONTENT, "No Content" }, + { NNG_HTTP_STATUS_RESET_CONTENT, "Reset Content" }, + { NNG_HTTP_STATUS_PARTIAL_CONTENT, "Partial Content" }, + { NNG_HTTP_STATUS_MULTI_STATUS, "Multi-Status" }, + { NNG_HTTP_STATUS_ALREADY_REPORTED, "Already Reported" }, + { NNG_HTTP_STATUS_IM_USED, "IM Used" }, + + // 300 series -- redirection + { NNG_HTTP_STATUS_MULTIPLE_CHOICES, "Multiple Choices" }, + { NNG_HTTP_STATUS_STATUS_MOVED_PERMANENTLY, + "Moved Permanently" }, + { NNG_HTTP_STATUS_FOUND, "Found" }, + { NNG_HTTP_STATUS_SEE_OTHER, "See Other" }, + { NNG_HTTP_STATUS_NOT_MODIFIED, "Not Modified" }, + { NNG_HTTP_STATUS_USE_PROXY, "Use Proxy" }, + { NNG_HTTP_STATUS_TEMPORARY_REDIRECT, "Temporary Redirect" }, + { NNG_HTTP_STATUS_PERMANENT_REDIRECT, "Permanent Redirect" }, + + // 400 series -- client errors + { NNG_HTTP_STATUS_BAD_REQUEST, "Bad Request" }, + { NNG_HTTP_STATUS_UNAUTHORIZED, "Unauthorized" }, + { NNG_HTTP_STATUS_PAYMENT_REQUIRED, "Payment Required" }, + { NNG_HTTP_STATUS_FORBIDDEN, "Forbidden" }, + { NNG_HTTP_STATUS_NOT_FOUND, "Not Found" }, + { NNG_HTTP_STATUS_METHOD_NOT_ALLOWED, "Method Not Allowed" }, + { NNG_HTTP_STATUS_NOT_ACCEPTABLE, "Not Acceptable" }, + { NNG_HTTP_STATUS_PROXY_AUTH_REQUIRED, + "Proxy Authentication Required" }, + { NNG_HTTP_STATUS_REQUEST_TIMEOUT, "Request Timeout" }, + { NNG_HTTP_STATUS_CONFLICT, "Conflict" }, + { NNG_HTTP_STATUS_GONE, "Gone" }, + { NNG_HTTP_STATUS_LENGTH_REQUIRED, "Length Required" }, + { NNG_HTTP_STATUS_PRECONDITION_FAILED, "Precondition Failed" }, + { NNG_HTTP_STATUS_CONTENT_TOO_LARGE, "Content Too Large" }, + { NNG_HTTP_STATUS_URI_TOO_LONG, "URI Too Long" }, + { NNG_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, + "Unsupported Media Type" }, + { NNG_HTTP_STATUS_RANGE_NOT_SATISFIABLE, + "Range Not Satisfiable" }, + { NNG_HTTP_STATUS_EXPECTATION_FAILED, "Expectation Failed" }, + { NNG_HTTP_STATUS_TEAPOT, "I Am A Teapot" }, + { NNG_HTTP_STATUS_LOCKED, "Locked" }, + { NNG_HTTP_STATUS_FAILED_DEPENDENCY, "Failed Dependency" }, + { NNG_HTTP_STATUS_UPGRADE_REQUIRED, "Upgrade Required" }, + { NNG_HTTP_STATUS_PRECONDITION_REQUIRED, + "Precondition Required" }, + { NNG_HTTP_STATUS_TOO_MANY_REQUESTS, "Too Many Requests" }, + { NNG_HTTP_STATUS_HEADERS_TOO_LARGE, "Headers Too Large" }, + { NNG_HTTP_STATUS_UNAVAIL_LEGAL_REASONS, + "Unavailable For Legal Reasons" }, + + // 500 series -- server errors + { NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR, + "Internal Server Error" }, + { NNG_HTTP_STATUS_NOT_IMPLEMENTED, "Not Implemented" }, + { NNG_HTTP_STATUS_BAD_REQUEST, "Bad Gateway" }, + { NNG_HTTP_STATUS_SERVICE_UNAVAILABLE, "Service Unavailable" }, + { NNG_HTTP_STATUS_GATEWAY_TIMEOUT, "Gateway Timeout" }, + { NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP, + "HTTP Version Not Supported" }, + { NNG_HTTP_STATUS_VARIANT_ALSO_NEGOTIATES, + "Variant Also Negotiates" }, + { NNG_HTTP_STATUS_INSUFFICIENT_STORAGE, + "Insufficient Storage" }, + { NNG_HTTP_STATUS_LOOP_DETECTED, "Loop Detected" }, + { NNG_HTTP_STATUS_NOT_EXTENDED, "Not Extended" }, + { NNG_HTTP_STATUS_NETWORK_AUTH_REQUIRED, + "Network Authentication Required" }, + + // Terminator + { 0, NULL }, + }; + + for (int i = 0; http_status[i].code != 0; i++) { + if (http_status[i].code == code) { + return (http_status[i].mesg); + } + } + return ("Unknown HTTP Status"); +} + +const char * +nni_http_get_reason(nng_http *conn) +{ +#ifdef NNG_SUPP_HTTP + return ( + conn->res.rsn ? conn->res.rsn : nni_http_reason(conn->res.code)); +#else + return (NULL); +#endif +} + +int +nni_http_set_status(nng_http *conn, uint16_t status, const char *reason) +{ + conn->res.code = status; + char *dup = NULL; + if ((reason != NULL) && + (strcmp(reason, nni_http_reason(conn->res.code)) == 0)) { + reason = NULL; + return (0); + } + if ((reason != NULL) && (dup = nni_strdup(reason)) == NULL) { + return (NNG_ENOMEM); + } + if (conn->res.rsn != NULL) { + nni_strfree(conn->res.rsn); + } + conn->res.rsn = dup; + return (0); +} + +static int +http_conn_set_error(nng_http *conn, uint16_t status, const char *reason, + const char *body, const char *redirect) +{ + int rv; + char content[1024]; + const char *prefix = "<!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> </p>" + "<h1><span>%d</span></h1>" + "<h2>%s</h2><p>"; + const char *suffix = "</p></body></html>"; + + conn->res.iserr = true; + + if ((rv = nni_http_set_status(conn, status, reason)) != 0) { + return (rv); + } + reason = nni_http_get_reason(conn); + + if (body == NULL) { + snprintf(content, sizeof(content), prefix, status, reason, + status, reason); + size_t avail = sizeof(content) - strlen(content); + + if (redirect != NULL && strlen(redirect) > 200 && + strlen(reason) < 40) { + // URL is too long for buffer and unlikely to be useful + // to humans anyway. 600 bytes will fit in the 1K + // buffer without issue. (Our prelude and trailer are + // less than 400 bytes.) + snprintf(content + strlen(content), avail, + "You should be automatically redirected."); + avail = sizeof(content) - strlen(content); + } else if (redirect != NULL) { + // TODO: redirect should be URL encoded. + snprintf(content + strlen(content), avail, + "You should be automatically redirected to <a " + "href=\"%s\">%s</a>.", + redirect, redirect); + avail = sizeof(content) - strlen(content); + } + snprintf(content + strlen(content), avail, "%s", suffix); + body = content; + } + if (strlen(body) > 0) { + nni_http_set_content_type(conn, "text/html; charset=UTF-8"); + return (nni_http_copy_body(conn, body, strlen(body))); + } + return (0); +} + +int +nni_http_set_error( + nng_http *conn, uint16_t status, const char *reason, const char *body) +{ + return (http_conn_set_error(conn, status, reason, body, NULL)); +} + +int +nni_http_set_redirect( + nng_http *conn, uint16_t status, const char *reason, const char *redirect) +{ + char *loc; + if ((loc = nni_strdup(redirect)) == NULL) { + return (NNG_ENOMEM); + } + (void) nni_http_del_header(conn, "Location"); + nni_list_node_remove(&conn->res.location.node); + nni_http_free_header(&conn->res.location); + conn->res.location.name = "Location"; + conn->res.location.value = loc; + conn->res.location.static_name = true; + conn->res.location.static_value = false; + nni_list_prepend(&conn->res.data.hdrs, &conn->res.location); + return (http_conn_set_error(conn, status, reason, NULL, redirect)); +} + +void +nni_http_set_host(nng_http *conn, const char *host) +{ + if (host != conn->req.host) { + snprintf(conn->req.host, sizeof(conn->req.host), "%s", host); + } + nni_list_node_remove(&conn->req.host_header.node); + conn->req.host_header.name = "Host"; + conn->req.host_header.value = conn->req.host; + conn->req.host_header.static_name = true; + conn->req.host_header.static_value = true; + conn->req.host_header.alloc_header = false; + nni_list_prepend(&conn->req.data.hdrs, &conn->req.host_header); +} + +void +nni_http_set_content_length(nng_http *conn, size_t size) +{ + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + + snprintf(data->clen, sizeof(data->clen), "%lu", (unsigned long) size); + nni_http_set_static_header( + conn, &data->content_length, "Content-Length", data->clen); +} + +void +nni_http_set_content_type(nng_http *conn, const char *ctype) +{ + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + snprintf(data->ctype, sizeof(data->ctype), "%s", ctype); + nni_http_set_static_header( + conn, &data->content_type, "Content-Type", data->ctype); +} + +const char * +nni_http_get_uri(nng_http *conn) +{ + return (conn->req.uri); +} + +int +nni_http_set_uri(nng_http *conn, const char *uri, const char *query) +{ + size_t needed; + const char *fmt; + + if (query != NULL) { + fmt = strchr(uri, '?') != NULL ? "%s&%s" : "%s?%s"; + needed = strlen(uri) + strlen(query) + 1; + } else { + fmt = "%s%s"; + query = ""; + needed = strlen(uri); + } + + if (conn->req.uri != NULL && (strcmp(uri, conn->req.uri) == 0) && + strlen(query) == 0) { + // no change, do nothing + return (0); + } + if (conn->req.uri != NULL && conn->req.uri != conn->req.ubuf) { + nni_strfree(conn->req.uri); + } + + // fast path, small size URI fits in our buffer + if (needed < sizeof(conn->req.ubuf)) { + snprintf( + conn->req.ubuf, sizeof(conn->req.ubuf), fmt, uri, query); + conn->req.uri = conn->req.ubuf; + return (0); + } + + // too big, we have to allocate it (slow path) + if (nni_asprintf(&conn->req.uri, fmt, uri, query) != 0) { + return (NNG_ENOMEM); + } + return (0); +} + +static int +http_set_header(nng_http *conn, const char *key, const char *val) +{ + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + http_header *h; + + NNI_LIST_FOREACH (&data->hdrs, h) { + if (nni_strcasecmp(key, h->name) == 0) { + char *news; + if ((news = nni_strdup(val)) == NULL) { + return (NNG_ENOMEM); + } + if (!h->static_value) { + nni_strfree(h->value); + h->value = NULL; + } + h->value = news; + return (0); + } + } + + if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { + return (NNG_ENOMEM); + } + h->alloc_header = true; + if ((h->name = nni_strdup(key)) == NULL) { + NNI_FREE_STRUCT(h); + return (NNG_ENOMEM); + } + if ((h->value = nni_strdup(val)) == NULL) { + nni_strfree(h->name); + NNI_FREE_STRUCT(h); + return (NNG_ENOMEM); + } + nni_list_append(&data->hdrs, h); + return (0); +} + +static int +http_add_header(nng_http *conn, const char *key, const char *val) +{ + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + http_header *h; + NNI_LIST_FOREACH (&data->hdrs, h) { + if (nni_strcasecmp(key, h->name) == 0) { + char *news; + int rv; + rv = nni_asprintf(&news, "%s, %s", h->value, val); + if (rv != 0) { + return (rv); + } + if (!h->static_value) { + nni_strfree(h->value); + } + h->value = news; + return (0); + } + } + + if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { + return (NNG_ENOMEM); + } + h->alloc_header = true; + if ((h->name = nni_strdup(key)) == NULL) { + NNI_FREE_STRUCT(h); + return (NNG_ENOMEM); + } + if ((h->value = nni_strdup(val)) == NULL) { + nni_strfree(h->name); + NNI_FREE_STRUCT(h); + return (NNG_ENOMEM); + } + nni_list_append(&data->hdrs, h); + return (0); +} + +static bool +http_set_known_header(nng_http *conn, const char *key, const char *val) +{ + if (nni_strcasecmp(key, "Content-Type") == 0) { + nni_http_set_content_type(conn, val); + return (true); + } + if (nni_strcasecmp(key, "Content-Length") == 0) { + nni_http_entity *data = + conn->client ? &conn->req.data : &conn->res.data; + snprintf(data->clen, sizeof(data->clen), "%s", val); + nni_http_set_static_header( + conn, &data->content_length, "Content-Length", data->clen); + return (true); + } + + if (conn->client) { + if (nni_strcasecmp(key, "Host") == 0) { + nni_http_set_host(conn, val); + return (true); + } + } + return (false); +} + +int +nni_http_add_header(nng_http *conn, const char *key, const char *val) +{ + if (http_set_known_header(conn, key, val)) { + return (0); + } + + return (http_add_header(conn, key, val)); +} + +void +nni_http_set_static_header( + nng_http *conn, nni_http_header *h, const char *key, const char *val) +{ + nni_list *headers; + if (conn->client) { + headers = &conn->req.data.hdrs; + } else { + headers = &conn->res.data.hdrs; + } + + nni_http_del_header(conn, key); + nni_list_node_remove(&h->node); + h->alloc_header = false; + h->static_name = true; + h->static_value = true; + h->name = (char *) key; + h->value = (char *) val; + nni_list_append(headers, h); +} + +int +nni_http_set_header(nng_http *conn, const char *key, const char *val) +{ + if (http_set_known_header(conn, key, val)) { + return (0); + } + return (http_set_header(conn, key, val)); +} + +static bool +http_del_header_one(nni_list *hdrs, const char *key) +{ + http_header *h; + NNI_LIST_FOREACH (hdrs, h) { + if (nni_strcasecmp(key, h->name) == 0) { + nni_http_free_header(h); + return (true); + } + } + return (false); +} + +void +nni_http_del_header(nng_http *conn, const char *key) +{ + nni_list *hdrs = + conn->client ? &conn->req.data.hdrs : &conn->res.data.hdrs; + while (http_del_header_one(hdrs, key)) { + continue; + } +} + +static const char * +http_get_header(const nni_list *hdrs, const char *key) +{ + http_header *h; + NNI_LIST_FOREACH (hdrs, h) { + if (nni_strcasecmp(h->name, key) == 0) { + return (h->value); + } + } + return (NULL); +} + +const char * +nni_http_get_header(nng_http *conn, const char *key) +{ + if (conn->client) { + return (http_get_header(&conn->res.data.hdrs, key)); + } else { + return (http_get_header(&conn->req.data.hdrs, key)); + } +} + +void +nni_http_get_body(nng_http *conn, void **datap, size_t *sizep) +{ + if (conn->client) { + *datap = conn->res.data.data; + *sizep = conn->res.data.size; + } else { + *datap = conn->req.data.data; + *sizep = conn->req.data.size; + } +} + +static void +http_set_data(nni_http_entity *entity, const void *data, size_t size) +{ + if (entity->own) { + nni_free(entity->data, entity->size); + } + entity->data = (void *) data; + entity->size = size; + entity->own = false; +} + +static int +http_alloc_data(nni_http_entity *entity, size_t size) +{ + void *newdata; + if (size != 0) { + if ((newdata = nni_zalloc(size)) == NULL) { + return (NNG_ENOMEM); + } + } + http_set_data(entity, newdata, size); + entity->own = true; + return (0); +} + +static int +http_copy_data(nni_http_entity *entity, const void *data, size_t size) +{ + int rv; + if ((rv = http_alloc_data(entity, size)) == 0) { + memcpy(entity->data, data, size); + } + return (rv); +} + +void +nni_http_set_body(nng_http *conn, void *data, size_t size) +{ + if (conn->client) { + http_set_data(&conn->req.data, data, size); + } else { + http_set_data(&conn->res.data, data, size); + } + nni_http_set_content_length(conn, size); +} + +void +nni_http_prune_body(nng_http *conn) +{ + // prune body but leave content-length header intact. + // This is for HEAD. + if (conn->client) { + http_set_data(&conn->req.data, NULL, 0); + } else { + http_set_data(&conn->res.data, NULL, 0); + } +} + +int +nni_http_copy_body(nng_http *conn, const void *data, size_t size) +{ + int rv; + if (conn->client) { + rv = http_copy_data(&conn->req.data, data, size); + } else { + rv = http_copy_data(&conn->res.data, data, size); + } + if (rv == 0) { + nni_http_set_content_length(conn, size); + } + return (rv); +} + int nni_http_conn_getopt( nni_http_conn *conn, const char *name, void *buf, size_t *szp, nni_type t) @@ -674,26 +1326,28 @@ nni_http_conn_fini(nni_http_conn *conn) nni_aio_fini(&conn->wr_aio); nni_aio_fini(&conn->rd_aio); - nni_http_req_reset(&conn->req); - nni_http_res_reset(&conn->res); + nni_http_conn_reset(conn); nni_free(conn->rd_buf, conn->rd_bufsz); nni_mtx_fini(&conn->mtx); NNI_FREE_STRUCT(conn); } static int -http_init(nni_http_conn **connp, nng_stream *data) +http_init(nni_http_conn **connp, nng_stream *data, bool client) { nni_http_conn *conn; if ((conn = NNI_ALLOC_STRUCT(conn)) == NULL) { return (NNG_ENOMEM); } + conn->client = client; nni_mtx_init(&conn->mtx); nni_aio_list_init(&conn->rdq); nni_aio_list_init(&conn->wrq); nni_http_req_init(&conn->req); nni_http_res_init(&conn->res); + nni_http_set_version(conn, NNG_HTTP_VERSION_1_1); + nni_http_set_method(conn, NULL); if ((conn->rd_buf = nni_alloc(HTTP_BUFSIZE)) == NULL) { nni_http_conn_fini(conn); @@ -712,10 +1366,10 @@ http_init(nni_http_conn **connp, nng_stream *data) } int -nni_http_conn_init(nni_http_conn **connp, nng_stream *stream) +nni_http_conn_init(nni_http_conn **connp, nng_stream *stream, bool client) { int rv; - if ((rv = http_init(connp, stream)) != 0) { + if ((rv = http_init(connp, stream, client)) != 0) { nng_stream_free(stream); } return (rv); @@ -723,7 +1377,13 @@ nni_http_conn_init(nni_http_conn **connp, nng_stream *stream) // private to the HTTP framework, used on the server bool -nni_http_conn_res_sent(nni_http_conn *conn) +nni_http_res_sent(nni_http_conn *conn) { return (conn->res_sent); } + +bool +nni_http_parsed(nng_http *conn) +{ + return (conn->client ? conn->res.data.parsed : conn->req.data.parsed); +} diff --git a/src/supplemental/http/http_msg.c b/src/supplemental/http/http_msg.c index 6b12496c..988bb37e 100644 --- a/src/supplemental/http/http_msg.c +++ b/src/supplemental/http/http_msg.c @@ -14,23 +14,27 @@ #include <stdlib.h> #include <string.h> +#include "core/list.h" #include "core/nng_impl.h" #include "http_api.h" #include "http_msg.h" -#include "nng/supplemental/http/http.h" +#include "nng/http.h" -static int -http_set_string(char **strp, const char *val) +void +nni_http_free_header(http_header *h) { - char *news; - if (val == NULL) { - news = NULL; - } else if ((news = nni_strdup(val)) == NULL) { - return (NNG_ENOMEM); + nni_list_node_remove(&h->node); + if (!h->static_name) { + nni_strfree(h->name); + h->name = NULL; + } + if (!h->static_value) { + nni_strfree(h->value); + h->value = NULL; + } + if (h->alloc_header) { + NNI_FREE_STRUCT(h); } - nni_strfree(*strp); - *strp = news; - return (0); } static void @@ -38,10 +42,7 @@ http_headers_reset(nni_list *hdrs) { http_header *h; while ((h = nni_list_first(hdrs)) != NULL) { - nni_list_remove(hdrs, h); - nni_strfree(h->name); - nni_strfree(h->value); - NNI_FREE_STRUCT(h); + nni_http_free_header(h); } } @@ -51,57 +52,35 @@ http_entity_reset(nni_http_entity *entity) if (entity->own && entity->size) { nni_free(entity->data, entity->size); } - entity->data = NULL; - entity->size = 0; - entity->own = false; + http_headers_reset(&entity->hdrs); + nni_free(entity->buf, entity->bufsz); + entity->data = NULL; + entity->size = 0; + entity->own = false; + entity->parsed = false; + entity->buf = NULL; + entity->bufsz = 0; } void nni_http_req_reset(nni_http_req *req) { - http_headers_reset(&req->hdrs); http_entity_reset(&req->data); - nni_strfree(req->uri); + if (req->uri != req->ubuf) { + nni_strfree(req->uri); + } req->uri = NULL; - nni_free(req->buf, req->bufsz); - nni_http_req_set_method(req, NULL); - nni_http_req_set_version(req, NNG_HTTP_VERSION_1_1); - req->bufsz = 0; - req->buf = NULL; - req->parsed = false; + (void) snprintf(req->meth, sizeof(req->meth), "GET"); } void nni_http_res_reset(nni_http_res *res) { - http_headers_reset(&res->hdrs); http_entity_reset(&res->data); nni_strfree(res->rsn); - res->vers = NNG_HTTP_VERSION_1_1; - res->rsn = NULL; - res->code = 0; - res->parsed = false; - nni_free(res->buf, res->bufsz); - res->buf = NULL; - res->bufsz = 0; -} - -void -nni_http_req_free(nni_http_req *req) -{ - if (req != NULL) { - nni_http_req_reset(req); - NNI_FREE_STRUCT(req); - } -} - -void -nni_http_res_free(nni_http_res *res) -{ - if (res != NULL) { - nni_http_res_reset(res); - NNI_FREE_STRUCT(res); - } + res->vers = NNG_HTTP_VERSION_1_1; + res->rsn = NULL; + res->code = 0; } static int @@ -110,10 +89,7 @@ http_del_header(nni_list *hdrs, const char *key) http_header *h; NNI_LIST_FOREACH (hdrs, h) { if (nni_strcasecmp(key, h->name) == 0) { - nni_list_remove(hdrs, h); - nni_strfree(h->name); - nni_free(h->value, strlen(h->value) + 1); - NNI_FREE_STRUCT(h); + nni_http_free_header(h); return (0); } } @@ -123,127 +99,21 @@ http_del_header(nni_list *hdrs, const char *key) int nni_http_req_del_header(nni_http_req *req, const char *key) { - return (http_del_header(&req->hdrs, key)); -} - -int -nni_http_res_del_header(nni_http_res *res, const char *key) -{ - return (http_del_header(&res->hdrs, key)); -} - -static int -http_set_header(nni_list *hdrs, const char *key, const char *val) -{ - http_header *h; - NNI_LIST_FOREACH (hdrs, h) { - if (nni_strcasecmp(key, h->name) == 0) { - char *news; - if ((news = nni_strdup(val)) == NULL) { - return (NNG_ENOMEM); - } - nni_strfree(h->value); - h->value = news; - return (0); - } - } - - if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { - return (NNG_ENOMEM); - } - if ((h->name = nni_strdup(key)) == NULL) { - NNI_FREE_STRUCT(h); - return (NNG_ENOMEM); - } - if ((h->value = nni_strdup(val)) == NULL) { - nni_strfree(h->name); - NNI_FREE_STRUCT(h); - return (NNG_ENOMEM); - } - nni_list_append(hdrs, h); - return (0); -} - -int -nni_http_req_set_header(nni_http_req *req, const char *key, const char *val) -{ - return (http_set_header(&req->hdrs, key, val)); -} - -int -nni_http_res_set_header(nni_http_res *res, const char *key, const char *val) -{ - return (http_set_header(&res->hdrs, key, val)); -} - -static int -http_add_header(nni_list *hdrs, const char *key, const char *val) -{ - http_header *h; - NNI_LIST_FOREACH (hdrs, h) { - if (nni_strcasecmp(key, h->name) == 0) { - char *news; - int rv; - rv = nni_asprintf(&news, "%s, %s", h->value, val); - if (rv != 0) { - return (rv); - } - nni_strfree(h->value); - h->value = news; - return (0); - } - } - - if ((h = NNI_ALLOC_STRUCT(h)) == NULL) { - return (NNG_ENOMEM); - } - if ((h->name = nni_strdup(key)) == NULL) { - NNI_FREE_STRUCT(h); - return (NNG_ENOMEM); + int rv = NNG_ENOENT; + while (http_del_header(&req->data.hdrs, key) == 0) { + rv = 0; } - if ((h->value = nni_strdup(val)) == NULL) { - nni_strfree(h->name); - NNI_FREE_STRUCT(h); - return (NNG_ENOMEM); - } - nni_list_append(hdrs, h); - return (0); -} - -int -nni_http_req_add_header(nni_http_req *req, const char *key, const char *val) -{ - return (http_add_header(&req->hdrs, key, val)); + return (rv); } int -nni_http_res_add_header(nni_http_res *res, const char *key, const char *val) -{ - return (http_add_header(&res->hdrs, key, val)); -} - -static const char * -http_get_header(const nni_list *hdrs, const char *key) +nni_http_res_del_header(nni_http_res *res, const char *key) { - http_header *h; - NNI_LIST_FOREACH (hdrs, h) { - if (nni_strcasecmp(h->name, key) == 0) { - return (h->value); - } + int rv = NNG_ENOENT; + while (http_del_header(&res->data.hdrs, key) == 0) { + rv = 0; } - return (NULL); -} - -const char * -nni_http_req_get_header(const nni_http_req *req, const char *key) -{ - return (http_get_header(&req->hdrs, key)); -} - -const char * -nni_http_res_get_header(const nni_http_res *res, const char *key) -{ - return (http_get_header(&res->hdrs, key)); + return (rv); } // http_entity_set_data sets the entity, but does not update the @@ -273,81 +143,6 @@ http_entity_alloc_data(nni_http_entity *entity, size_t size) return (0); } -static int -http_entity_copy_data(nni_http_entity *entity, const void *data, size_t size) -{ - int rv; - if ((rv = http_entity_alloc_data(entity, size)) == 0) { - memcpy(entity->data, data, size); - } - return (rv); -} - -static int -http_set_content_length(nni_http_entity *entity, nni_list *hdrs) -{ - char buf[16]; - (void) snprintf(buf, sizeof(buf), "%u", (unsigned) entity->size); - return (http_set_header(hdrs, "Content-Length", buf)); -} - -static void -http_entity_get_data(nni_http_entity *entity, void **datap, size_t *sizep) -{ - *datap = entity->data; - *sizep = entity->size; -} - -void -nni_http_req_get_data(nni_http_req *req, void **datap, size_t *sizep) -{ - http_entity_get_data(&req->data, datap, sizep); -} - -void -nni_http_res_get_data(nni_http_res *res, void **datap, size_t *sizep) -{ - http_entity_get_data(&res->data, datap, sizep); -} - -int -nni_http_req_set_data(nni_http_req *req, const void *data, size_t size) -{ - int rv; - - http_entity_set_data(&req->data, data, size); - if ((rv = http_set_content_length(&req->data, &req->hdrs)) != 0) { - http_entity_set_data(&req->data, NULL, 0); - } - return (rv); -} - -int -nni_http_res_set_data(nni_http_res *res, const void *data, size_t size) -{ - int rv; - - http_entity_set_data(&res->data, data, 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); -} - -int -nni_http_req_copy_data(nni_http_req *req, const void *data, size_t size) -{ - int rv; - - if (((rv = http_entity_copy_data(&req->data, data, size)) != 0) || - ((rv = http_set_content_length(&req->data, &req->hdrs)) != 0)) { - http_entity_set_data(&req->data, NULL, 0); - return (rv); - } - return (0); -} - int nni_http_req_alloc_data(nni_http_req *req, size_t size) { @@ -359,20 +154,6 @@ nni_http_req_alloc_data(nni_http_req *req, size_t size) return (0); } -int -nni_http_res_copy_data(nni_http_res *res, const void *data, size_t size) -{ - int rv; - - if (((rv = http_entity_copy_data(&res->data, data, size)) != 0) || - ((rv = http_set_content_length(&res->data, &res->hdrs)) != 0)) { - http_entity_set_data(&res->data, NULL, 0); - return (rv); - } - res->iserr = false; - return (0); -} - // nni_http_res_alloc_data allocates the data region, but does not update any // headers. The intended use is for client implementations that want to // allocate a buffer to receive the entity into. @@ -394,7 +175,7 @@ nni_http_res_is_error(nni_http_res *res) } static int -http_parse_header(nni_list *hdrs, void *line) +http_parse_header(nng_http *conn, void *line) { char *key = line; char *val; @@ -418,7 +199,7 @@ http_parse_header(nni_list *hdrs, void *line) end--; } - return (http_add_header(hdrs, key, val)); + return (nni_http_add_header(conn, key, val)); } // http_sprintf_headers makes headers for an HTTP request or an HTTP response @@ -494,258 +275,77 @@ http_req_prepare(nni_http_req *req) if (req->uri == NULL) { return (NNG_EINVAL); } - rv = http_asprintf(&req->buf, &req->bufsz, &req->hdrs, "%s %s %s\r\n", - req->meth, req->uri, req->vers); + rv = http_asprintf(&req->data.buf, &req->data.bufsz, &req->data.hdrs, + "%s %s %s\r\n", req->meth, req->uri, req->vers); return (rv); } static int -http_res_prepare(nni_http_res *res) +http_res_prepare(nng_http *conn) { - int rv; + int rv; + nng_http_res *res = nni_http_conn_res(conn); + if (res->code == 0) { res->code = NNG_HTTP_STATUS_OK; } - rv = http_asprintf(&res->buf, &res->bufsz, &res->hdrs, "%s %d %s\r\n", - res->vers, nni_http_res_get_status(res), - nni_http_res_get_reason(res)); + rv = http_asprintf(&res->data.buf, &res->data.bufsz, &res->data.hdrs, + "%s %d %s\r\n", res->vers, res->code, nni_http_get_reason(conn)); return (rv); } -char * -nni_http_req_headers(nni_http_req *req) -{ - char *s; - size_t len; - - len = http_sprintf_headers(NULL, 0, &req->hdrs) + 1; - if ((s = nni_alloc(len)) != NULL) { - http_sprintf_headers(s, len, &req->hdrs); - } - return (s); -} - -char * -nni_http_res_headers(nni_http_res *res) -{ - char *s; - size_t len; - - len = http_sprintf_headers(NULL, 0, &res->hdrs) + 1; - if ((s = nni_alloc(len)) != NULL) { - http_sprintf_headers(s, len, &res->hdrs); - } - return (s); -} - int nni_http_req_get_buf(nni_http_req *req, void **data, size_t *szp) { int rv; - if ((req->buf == NULL) && (rv = http_req_prepare(req)) != 0) { + if ((req->data.buf == NULL) && (rv = http_req_prepare(req)) != 0) { return (rv); } - *data = req->buf; - *szp = req->bufsz - 1; // exclude terminating NUL + *data = req->data.buf; + *szp = req->data.bufsz - 1; // exclude terminating NUL return (0); } int -nni_http_res_get_buf(nni_http_res *res, void **data, size_t *szp) +nni_http_res_get_buf(nng_http *conn, void **data, size_t *szp) { - int rv; + int rv; + nni_http_res *res = nni_http_conn_res(conn); - if ((res->buf == NULL) && (rv = http_res_prepare(res)) != 0) { + if ((res->data.buf == NULL) && (rv = http_res_prepare(conn)) != 0) { return (rv); } - *data = res->buf; - *szp = res->bufsz - 1; // exclude terminating NUL + *data = res->data.buf; + *szp = res->data.bufsz - 1; // exclude terminating NUL return (0); } void nni_http_req_init(nni_http_req *req) { - NNI_LIST_INIT(&req->hdrs, http_header, node); - req->buf = NULL; - req->bufsz = 0; - req->data.data = NULL; - req->data.size = 0; - req->data.own = false; - req->uri = NULL; - nni_http_req_set_version(req, NNG_HTTP_VERSION_1_1); - nni_http_req_set_method(req, "GET"); -} - -int -nni_http_req_set_url(nni_http_req *req, const nng_url *url) -{ - if (url == NULL) { - return (0); - } - const char *host; - char host_buf[264]; // 256 + 8 for port - int rv; - rv = nni_asprintf(&req->uri, "%s%s%s%s%s", url->u_path, - url->u_query ? "?" : "", url->u_query ? url->u_query : "", - url->u_fragment ? "#" : "", - url->u_fragment ? url->u_fragment : ""); - if (rv != 0) { - return (NNG_ENOMEM); - } - - // Add a Host: header since we know that from the URL. Also, - // only include the :port portion if it isn't the default port. - if (nni_url_default_port(url->u_scheme) == url->u_port) { - host = url->u_hostname; - } else { - if (strchr(url->u_hostname, ':')) { - snprintf(host_buf, sizeof(host_buf), "[%s]:%u", - url->u_hostname, url->u_port); - } else { - snprintf(host_buf, sizeof(host_buf), "%s:%u", - url->u_hostname, url->u_port); - } - host = host_buf; - } - if ((rv = nni_http_req_set_header(req, "Host", host)) != 0) { - return (rv); - } - return (0); -} - -int -nni_http_req_alloc(nni_http_req **reqp, const nng_url *url) -{ - nni_http_req *req; - if ((req = NNI_ALLOC_STRUCT(req)) == NULL) { - return (NNG_ENOMEM); - } - nni_http_req_init(req); - if (url != NULL) { - int rv; - if ((rv = nni_http_req_set_url(req, url)) != 0) { - nni_http_req_free(req); - return (rv); - } - } - *reqp = req; - return (0); + NNI_LIST_INIT(&req->data.hdrs, http_header, node); + req->data.buf = NULL; + req->data.bufsz = 0; + req->data.data = NULL; + req->data.size = 0; + req->data.own = false; + req->uri = NULL; + (void) snprintf(req->meth, sizeof(req->meth), "GET"); } void nni_http_res_init(nni_http_res *res) { - NNI_LIST_INIT(&res->hdrs, http_header, node); - res->buf = NULL; - res->bufsz = 0; - res->data.data = NULL; - res->data.size = 0; - res->data.own = false; - res->vers = NNG_HTTP_VERSION_1_1; - res->rsn = NULL; - res->code = 0; -} - -int -nni_http_res_alloc(nni_http_res **resp) -{ - nni_http_res *res; - if ((res = NNI_ALLOC_STRUCT(res)) == NULL) { - return (NNG_ENOMEM); - } - nni_http_res_init(res); - *resp = res; - return (0); -} - -const char * -nni_http_req_get_method(const nni_http_req *req) -{ - return (req->meth); -} - -const char * -nni_http_req_get_uri(const nni_http_req *req) -{ - return (req->uri != NULL ? req->uri : ""); -} - -const char * -nni_http_req_get_version(const nni_http_req *req) -{ - return (req->vers); -} - -const char * -nni_http_res_get_version(const nni_http_res *res) -{ - return (res->vers); -} - -static const char *http_versions[] = { - // for efficiency, we order in most likely first - "HTTP/1.1", - "HTTP/2", - "HTTP/3", - "HTTP/1.0", - "HTTP/0.9", - NULL, -}; - -static int -http_set_version(const char **ptr, const char *vers) -{ - vers = vers != NULL ? vers : NNG_HTTP_VERSION_1_1; - for (int i = 0; http_versions[i] != NULL; i++) { - if (strcmp(vers, http_versions[i]) == 0) { - *ptr = http_versions[i]; - return (0); - } - } - return (NNG_ENOTSUP); -} - -int -nni_http_req_set_version(nni_http_req *req, const char *vers) -{ - return (http_set_version(&req->vers, vers)); -} - -int -nni_http_res_set_version(nni_http_res *res, const char *vers) -{ - return (http_set_version(&res->vers, vers)); -} - -int -nni_http_req_set_uri(nni_http_req *req, const char *uri) -{ - return (http_set_string(&req->uri, uri)); -} - -void -nni_http_req_set_method(nni_http_req *req, const char *meth) -{ - if (meth == NULL) { - meth = "GET"; - } - // this may truncate the method, but nobody should be sending - // methods so long. - (void) snprintf(req->meth, sizeof(req->meth), "%s", meth); -} - -void -nni_http_res_set_status(nni_http_res *res, uint16_t status) -{ - res->code = status; -} - -uint16_t -nni_http_res_get_status(const nni_http_res *res) -{ - return (res->code); + NNI_LIST_INIT(&res->data.hdrs, http_header, node); + res->data.buf = NULL; + res->data.bufsz = 0; + res->data.data = NULL; + res->data.size = 0; + res->data.own = false; + res->vers = NNG_HTTP_VERSION_1_1; + res->rsn = NULL; + res->code = 0; } static int @@ -782,7 +382,7 @@ http_scan_line(void *vbuf, size_t n, size_t *lenp) } static int -http_req_parse_line(nni_http_req *req, void *line) +http_req_parse_line(nng_http *conn, void *line) { int rv; char *method; @@ -802,17 +402,16 @@ http_req_parse_line(nni_http_req *req, void *line) *version = '\0'; version++; - nni_http_req_set_method(req, method); - if (((rv = nni_http_req_set_uri(req, uri)) != 0) || - ((rv = nni_http_req_set_version(req, version)) != 0)) { + nni_http_set_method(conn, method); + if (((rv = nni_http_set_uri(conn, uri, NULL)) != 0) || + ((rv = nni_http_set_version(conn, version)) != 0)) { return (rv); } - req->parsed = true; return (0); } static int -http_res_parse_line(nni_http_res *res, uint8_t *line) +http_res_parse_line(nng_http *conn, uint8_t *line) { int rv; char *reason; @@ -838,13 +437,13 @@ http_res_parse_line(nni_http_res *res, uint8_t *line) return (NNG_EPROTO); } - nni_http_res_set_status(res, (uint16_t) status); + if ((rv = nni_http_set_status(conn, (uint16_t) status, reason)) != 0) { + return (rv); + } - if (((rv = nni_http_res_set_version(res, version)) != 0) || - ((rv = nni_http_res_set_reason(res, reason)) != 0)) { + if ((rv = nni_http_set_version(conn, version)) != 0) { return (rv); } - res->parsed = true; return (0); } @@ -855,12 +454,13 @@ http_res_parse_line(nni_http_res *res, uint8_t *line) // be updated even in the face of errors (esp. NNG_EAGAIN, which is // not an error so much as a request for more data.) int -nni_http_req_parse(nni_http_req *req, void *buf, size_t n, size_t *lenp) +nni_http_req_parse(nng_http *conn, void *buf, size_t n, size_t *lenp) { - size_t len = 0; - size_t cnt; - int rv = 0; + size_t len = 0; + size_t cnt; + int rv = 0; + nni_http_req *req = nni_http_conn_req(conn); for (;;) { uint8_t *line; @@ -877,10 +477,10 @@ nni_http_req_parse(nni_http_req *req, void *buf, size_t n, size_t *lenp) break; } - if (req->parsed) { - rv = http_parse_header(&req->hdrs, line); - } else { - rv = http_req_parse_line(req, line); + if (req->data.parsed) { + rv = http_parse_header(conn, line); + } else if ((rv = http_req_parse_line(conn, line)) == 0) { + req->data.parsed = true; } if (rv != 0) { @@ -888,17 +488,21 @@ nni_http_req_parse(nni_http_req *req, void *buf, size_t n, size_t *lenp) } } + if (rv == 0) { + req->data.parsed = false; + } *lenp = len; return (rv); } int -nni_http_res_parse(nni_http_res *res, void *buf, size_t n, size_t *lenp) +nni_http_res_parse(nng_http *conn, void *buf, size_t n, size_t *lenp) { - size_t len = 0; - size_t cnt; - int rv = 0; + size_t len = 0; + size_t cnt; + int rv = 0; + nng_http_res *res = nni_http_conn_res(conn); for (;;) { uint8_t *line; if ((rv = http_scan_line(buf, n, &cnt)) != 0) { @@ -914,10 +518,10 @@ nni_http_res_parse(nni_http_res *res, void *buf, size_t n, size_t *lenp) break; } - if (res->parsed) { - rv = http_parse_header(&res->hdrs, line); - } else { - rv = http_res_parse_line(res, line); + if (res->data.parsed) { + rv = http_parse_header(conn, line); + } else if ((rv = http_res_parse_line(conn, line)) == 0) { + res->data.parsed = true; } if (rv != 0) { @@ -925,171 +529,9 @@ nni_http_res_parse(nni_http_res *res, void *buf, size_t n, size_t *lenp) } } + if (rv == 0) { + res->data.parsed = false; + } *lenp = len; return (rv); } - -static struct { - uint16_t code; - const char *mesg; -} http_status[] = { - // 200, listed first because most likely - { NNG_HTTP_STATUS_OK, "OK" }, - - // 100 series -- informational - { NNG_HTTP_STATUS_CONTINUE, "Continue" }, - { NNG_HTTP_STATUS_SWITCHING, "Switching Protocols" }, - { NNG_HTTP_STATUS_PROCESSING, "Processing" }, - - // 200 series -- successful - { NNG_HTTP_STATUS_CREATED, "Created" }, - { NNG_HTTP_STATUS_ACCEPTED, "Accepted" }, - { NNG_HTTP_STATUS_NOT_AUTHORITATIVE, "Not Authoritative" }, - { NNG_HTTP_STATUS_NO_CONTENT, "No Content" }, - { NNG_HTTP_STATUS_RESET_CONTENT, "Reset Content" }, - { NNG_HTTP_STATUS_PARTIAL_CONTENT, "Partial Content" }, - - // 300 series -- redirection - { NNG_HTTP_STATUS_MULTIPLE_CHOICES, "Multiple Choices" }, - { NNG_HTTP_STATUS_STATUS_MOVED_PERMANENTLY, "Moved Permanently" }, - { NNG_HTTP_STATUS_FOUND, "Found" }, - { NNG_HTTP_STATUS_SEE_OTHER, "See Other" }, - { NNG_HTTP_STATUS_NOT_MODIFIED, "Not Modified" }, - { NNG_HTTP_STATUS_USE_PROXY, "Use Proxy" }, - { NNG_HTTP_STATUS_TEMPORARY_REDIRECT, "Temporary Redirect" }, - - // 400 series -- client errors - { NNG_HTTP_STATUS_BAD_REQUEST, "Bad Request" }, - { NNG_HTTP_STATUS_UNAUTHORIZED, "Unauthorized" }, - { NNG_HTTP_STATUS_PAYMENT_REQUIRED, "Payment Required" }, - { NNG_HTTP_STATUS_FORBIDDEN, "Forbidden" }, - { NNG_HTTP_STATUS_NOT_FOUND, "Not Found" }, - { NNG_HTTP_STATUS_METHOD_NOT_ALLOWED, "Method Not Allowed" }, - { NNG_HTTP_STATUS_NOT_ACCEPTABLE, "Not Acceptable" }, - { NNG_HTTP_STATUS_PROXY_AUTH_REQUIRED, - "Proxy Authentication Required" }, - { NNG_HTTP_STATUS_REQUEST_TIMEOUT, "Request Timeout" }, - { NNG_HTTP_STATUS_CONFLICT, "Conflict" }, - { NNG_HTTP_STATUS_GONE, "Gone" }, - { NNG_HTTP_STATUS_LENGTH_REQUIRED, "Length Required" }, - { NNG_HTTP_STATUS_PRECONDITION_FAILED, "Precondition Failed" }, - { NNG_HTTP_STATUS_ENTITY_TOO_LONG, "Request Entity Too Long" }, - { NNG_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type" }, - { NNG_HTTP_STATUS_RANGE_NOT_SATISFIABLE, - "Requested Range Not Satisfiable" }, - { NNG_HTTP_STATUS_EXPECTATION_FAILED, "Expectation Failed" }, - { NNG_HTTP_STATUS_TEAPOT, "I Am A Teapot" }, - { NNG_HTTP_STATUS_LOCKED, "Locked" }, - { NNG_HTTP_STATUS_FAILED_DEPENDENCY, "Failed Dependency" }, - { NNG_HTTP_STATUS_UPGRADE_REQUIRED, "Upgrade Required" }, - { NNG_HTTP_STATUS_PRECONDITION_REQUIRED, "Precondition Required" }, - { NNG_HTTP_STATUS_TOO_MANY_REQUESTS, "Too Many Requests" }, - { NNG_HTTP_STATUS_HEADERS_TOO_LARGE, "Headers Too Large" }, - { NNG_HTTP_STATUS_UNAVAIL_LEGAL_REASONS, - "Unavailable For Legal Reasons" }, - - // 500 series -- server errors - { NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR, "Internal Server Error" }, - { NNG_HTTP_STATUS_NOT_IMPLEMENTED, "Not Implemented" }, - { NNG_HTTP_STATUS_BAD_REQUEST, "Bad Gateway" }, - { NNG_HTTP_STATUS_SERVICE_UNAVAILABLE, "Service Unavailable" }, - { NNG_HTTP_STATUS_GATEWAY_TIMEOUT, "Gateway Timeout" }, - { NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP, - "HTTP Version Not Supported" }, - { NNG_HTTP_STATUS_VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates" }, - { NNG_HTTP_STATUS_INSUFFICIENT_STORAGE, "Insufficient Storage" }, - { NNG_HTTP_STATUS_LOOP_DETECTED, "Loop Detected" }, - { NNG_HTTP_STATUS_NOT_EXTENDED, "Not Extended" }, - { NNG_HTTP_STATUS_NETWORK_AUTH_REQUIRED, - "Network Authentication Required" }, - - // Terminator - { 0, NULL }, -}; - -const char * -nni_http_reason(uint16_t code) -{ - for (int i = 0; http_status[i].code != 0; i++) { - if (http_status[i].code == code) { - return (http_status[i].mesg); - } - } - return ("Unknown HTTP Status"); -} - -const char * -nni_http_res_get_reason(const nni_http_res *res) -{ - 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, nni_http_reason(res->code)) == 0)) { - reason = NULL; - } - 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, - "<!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> </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_set_error(nni_http_res *res, uint16_t err) -{ - int rv; - char *html = NULL; - if (((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); - return (rv); - } - nni_strfree(html); - res->code = err; - res->iserr = true; - return (0); -} - -int -nni_http_res_alloc_error(nni_http_res **resp, uint16_t err) -{ - nni_http_res *res; - int rv; - - if ((rv = nni_http_res_alloc(&res)) != 0) { - return (rv); - } - rv = nni_http_res_set_error(res, err); - if (rv != 0) { - nni_http_res_free(res); - return (rv); - } - *resp = res; - return (0); -} diff --git a/src/supplemental/http/http_msg.h b/src/supplemental/http/http_msg.h index 7d9e7dcf..e08dab8a 100644 --- a/src/supplemental/http/http_msg.h +++ b/src/supplemental/http/http_msg.h @@ -22,36 +22,45 @@ typedef struct http_header { char *name; char *value; nni_list_node node; + bool static_name : 1; // name is static, do not free it + bool static_value : 1; // value is static, do not free it + bool alloc_header : 1; // header is heap allocated } http_header; +typedef struct http_header nni_http_header; typedef struct nni_http_entity { - char *data; - size_t size; // allocated/expected size - size_t len; // current length - bool own; // if true, data is "ours", and should be freed + char *data; + size_t size; + bool own; // if true, data is "ours", and should be freed + char clen[24]; // 64-bit lengths, in decimal + char ctype[128]; // 63+63+; per RFC 6838 + http_header content_type; + http_header content_length; + nni_list hdrs; + char *buf; + size_t bufsz; + bool parsed; } nni_http_entity; struct nng_http_req { - nni_list hdrs; nni_http_entity data; char meth[32]; + char host[260]; // 253 per IETF, plus 6 for :port plus null + char ubuf[200]; // Most URIs are smaller than this char *uri; const char *vers; - char *buf; - size_t bufsz; - bool parsed; + http_header host_header; }; struct nng_http_res { - nni_list hdrs; nni_http_entity data; uint16_t code; char *rsn; const char *vers; - char *buf; - size_t bufsz; - bool parsed; bool iserr; + http_header location; }; +extern void nni_http_free_header(http_header *); + #endif diff --git a/src/supplemental/http/http_public.c b/src/supplemental/http/http_public.c index dd35151a..ef000802 100644 --- a/src/supplemental/http/http_public.c +++ b/src/supplemental/http/http_public.c @@ -1,5 +1,5 @@ // -// Copyright 2024 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2025 Staysail Systems, Inc. <info@staysail.tech> // Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a @@ -10,123 +10,30 @@ #include "core/nng_impl.h" #include "http_api.h" -#include "nng/supplemental/http/http.h" +#include "nng/http.h" // Symbols in this file are "public" versions of the HTTP API. // These are suitable for exposure to applications. -int -nng_http_req_alloc(nng_http_req **reqp, const nng_url *url) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_req_alloc(reqp, url)); -#else - NNI_ARG_UNUSED(reqp); - NNI_ARG_UNUSED(url); - return (NNG_ENOTSUP); -#endif -} - -void -nng_http_req_free(nng_http_req *req) -{ -#ifdef NNG_SUPP_HTTP - nni_http_req_free(req); -#else - NNI_ARG_UNUSED(req); -#endif -} - -void -nng_http_res_free(nng_http_res *res) -{ -#ifdef NNG_SUPP_HTTP - nni_http_res_free(res); -#else - NNI_ARG_UNUSED(res); -#endif -} - -int -nng_http_res_alloc(nng_http_res **resp) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_res_alloc(resp)); -#else - NNI_ARG_UNUSED(resp); - return (NNG_ENOTSUP); -#endif -} - -int -nng_http_res_alloc_error(nng_http_res **resp, uint16_t code) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_res_alloc_error(resp, code)); -#else - NNI_ARG_UNUSED(resp); - NNI_ARG_UNUSED(code); - return (NNG_ENOTSUP); -#endif -} - const char * -nng_http_req_get_header(const nng_http_req *req, const char *key) +nng_http_get_header(nng_http *conn, const char *key) { #ifdef NNG_SUPP_HTTP - return (nni_http_req_get_header(req, key)); + return (nni_http_get_header(conn, key)); #else - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(key); - return (NULL); -#endif -} - -const char * -nng_http_res_get_header(const nng_http_res *res, const char *key) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_res_get_header(res, key)); -#else - NNI_ARG_UNUSED(res); + NNI_ARG_UNUSED(conn); NNI_ARG_UNUSED(key); return (NULL); #endif } int -nng_http_req_add_header(nng_http_req *req, const char *key, const char *val) +nng_http_set_header(nng_http *conn, const char *key, const char *val) { #ifdef NNG_SUPP_HTTP - return (nni_http_req_add_header(req, key, val)); + return (nni_http_set_header(conn, key, val)); #else - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(key); - NNI_ARG_UNUSED(val); - return (NNG_ENOTSUP); -#endif -} - -int -nng_http_res_add_header(nng_http_res *res, const char *key, const char *val) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_res_add_header(res, key, val)); -#else - NNI_ARG_UNUSED(res); - NNI_ARG_UNUSED(key); - NNI_ARG_UNUSED(val); - return (NNG_ENOTSUP); -#endif -} - -int -nng_http_req_set_header(nng_http_req *req, const char *key, const char *val) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_req_set_header(req, key, val)); -#else - NNI_ARG_UNUSED(req); + NNI_ARG_UNUSED(conn); NNI_ARG_UNUSED(key); NNI_ARG_UNUSED(val); return (NNG_ENOTSUP); @@ -134,75 +41,37 @@ nng_http_req_set_header(nng_http_req *req, const char *key, const char *val) } int -nng_http_res_set_header(nng_http_res *res, const char *key, const char *val) +nng_http_add_header(nng_http *conn, const char *key, const char *val) { #ifdef NNG_SUPP_HTTP - return (nni_http_res_set_header(res, key, val)); + return (nni_http_add_header(conn, key, val)); #else - NNI_ARG_UNUSED(res); + NNI_ARG_UNUSED(conn); NNI_ARG_UNUSED(key); NNI_ARG_UNUSED(val); return (NNG_ENOTSUP); #endif } -int -nng_http_req_del_header(nng_http_req *req, const char *key) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_req_del_header(req, key)); -#else - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(key); - return (NNG_ENOTSUP); -#endif -} - -int -nng_http_res_del_header(nng_http_res *res, const char *key) +void +nng_http_del_header(nng_http *conn, const char *key) { #ifdef NNG_SUPP_HTTP - return (nni_http_res_del_header(res, key)); + nni_http_del_header(conn, key); #else - NNI_ARG_UNUSED(res); + NNI_ARG_UNUSED(conn); NNI_ARG_UNUSED(key); return (NNG_ENOTSUP); #endif } -int -nng_http_req_copy_data(nng_http_req *req, const void *data, size_t sz) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_req_copy_data(req, data, sz)); -#else - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(data); - NNI_ARG_UNUSED(sz); - return (NNG_ENOTSUP); -#endif -} - -int -nng_http_res_copy_data(nng_http_res *res, const void *data, size_t sz) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_res_copy_data(res, data, sz)); -#else - NNI_ARG_UNUSED(res); - NNI_ARG_UNUSED(data); - NNI_ARG_UNUSED(sz); - return (NNG_ENOTSUP); -#endif -} - -int -nng_http_req_set_data(nng_http_req *req, const void *data, size_t sz) +void +nng_http_set_body(nng_http *conn, void *data, size_t sz) { #ifdef NNG_SUPP_HTTP - return (nni_http_req_set_data(req, data, sz)); + nni_http_set_body(conn, data, sz); #else - NNI_ARG_UNUSED(req); + NNI_ARG_UNUSED(conn); NNI_ARG_UNUSED(data); NNI_ARG_UNUSED(sz); return (NNG_ENOTSUP); @@ -210,212 +79,146 @@ nng_http_req_set_data(nng_http_req *req, const void *data, size_t sz) } int -nng_http_res_set_data(nng_http_res *res, const void *data, size_t sz) +nng_http_copy_body(nng_http *conn, const void *data, size_t len) { #ifdef NNG_SUPP_HTTP - return (nni_http_res_set_data(res, data, sz)); + return (nni_http_copy_body(conn, data, len)); #else - NNI_ARG_UNUSED(res); + NNI_ARG_UNUSED(conn); NNI_ARG_UNUSED(data); - NNI_ARG_UNUSED(sz); + NNI_ARG_UNUSED(len); return (NNG_ENOTSUP); #endif } void -nng_http_req_get_data(nng_http_req *req, void **datap, size_t *lenp) +nng_http_get_body(nng_http *conn, void **datap, size_t *lenp) { #ifdef NNG_SUPP_HTTP - nni_http_req_get_data(req, datap, lenp); + nni_http_get_body(conn, datap, lenp); #else - NNI_ARG_UNUSED(req); - *datap = NULL; - *lenp = 0; -#endif -} - -void -nng_http_res_get_data(nng_http_res *res, void **datap, size_t *lenp) -{ -#ifdef NNG_SUPP_HTTP - nni_http_res_get_data(res, datap, lenp); -#else - NNI_ARG_UNUSED(res); - *datap = NULL; - *lenp = 0; + NNI_ARG_UNUSED(conn); + NNI_ARG_UNUSED(datap); + NNI_ARG_UNUSED(lenp); #endif } const char * -nng_http_req_get_method(const nng_http_req *req) +nng_http_get_uri(nng_http *conn) { #ifdef NNG_SUPP_HTTP - return (nni_http_req_get_method(req)); + return (nni_http_get_uri(conn)); #else NNI_ARG_UNUSED(req); return (NULL); #endif } -const char * -nng_http_req_get_version(const nng_http_req *req) +int +nng_http_set_uri(nng_http *conn, const char *uri, const char *query) { #ifdef NNG_SUPP_HTTP - return (nni_http_req_get_version(req)); + return (nni_http_set_uri(conn, uri, query)); #else - NNI_ARG_UNUSED(req); - return (NULL); + NNI_ARG_UNUSED(conn); + NNI_ARG_UNUSED(uri); + NNI_ARG_UNUSED(query); + return (NNG_ENOTSUP); #endif } const char * -nng_http_req_get_uri(const nng_http_req *req) +nng_http_get_version(nng_http *conn) { #ifdef NNG_SUPP_HTTP - return (nni_http_req_get_uri(req)); + return (nni_http_get_version(conn)); #else - NNI_ARG_UNUSED(req); + NNI_ARG_UNUSED(res); return (NULL); #endif } -void -nng_http_req_set_method(nng_http_req *req, const char *meth) -{ -#ifdef NNG_SUPP_HTTP - nni_http_req_set_method(req, meth); -#else - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(meth); -#endif -} - -int -nng_http_req_set_version(nng_http_req *req, const char *vers) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_req_set_version(req, vers)); -#else - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(vers); - return (NNG_ENOTSUP); -#endif -} - -int -nng_http_req_set_url(nng_http_req *req, const nng_url *url) +nng_http_res * +nng_http_conn_res(nng_http *conn) { #ifdef NNG_SUPP_HTTP - return (nni_http_req_set_url(req, url)); + return (nni_http_conn_res(conn)); #else - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(url); - return (NNG_ENOTSUP); + return (NULL); #endif } int -nng_http_req_set_uri(nng_http_req *req, const char *uri) +nng_http_set_status(nng_http *conn, uint16_t status, const char *reason) { #ifdef NNG_SUPP_HTTP - return (nni_http_req_set_uri(req, uri)); + return (nni_http_set_status(conn, status, reason)); #else - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(uri); + NNI_ARG_UNUSED(res); + NNI_ARG_UNUSED(status); + NNI_ARG_UNUSED(reason); return (NNG_ENOTSUP); #endif } uint16_t -nng_http_res_get_status(const nng_http_res *res) +nng_http_get_status(nng_http *conn) { #ifdef NNG_SUPP_HTTP - return (nni_http_res_get_status(res)); + return (nni_http_get_status(conn)); #else NNI_ARG_UNUSED(res); + NNI_ARG_UNUSED(status); return (0); #endif } const char * -nng_http_res_get_version(const nng_http_res *res) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_res_get_version(res)); -#else - NNI_ARG_UNUSED(res); - return (NULL); -#endif -} - -const char * -nng_http_res_get_reason(const nng_http_res *res) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_res_get_reason(res)); -#else - NNI_ARG_UNUSED(res); - return (NULL); -#endif -} - -void -nng_http_res_set_status(nng_http_res *res, uint16_t status) +nng_http_get_reason(nng_http *conn) { #ifdef NNG_SUPP_HTTP - nni_http_res_set_status(res, status); + return (nni_http_get_reason(conn)); #else NNI_ARG_UNUSED(res); NNI_ARG_UNUSED(status); + return (0); #endif } int -nng_http_res_set_version(nng_http_res *res, const char *vers) -{ -#ifdef NNG_SUPP_HTTP - return (nni_http_res_set_version(res, vers)); -#else - NNI_ARG_UNUSED(res); - NNI_ARG_UNUSED(vers); - return (NNG_ENOTSUP); -#endif -} - -int -nng_http_res_set_reason(nng_http_res *res, const char *rsn) +nng_http_set_version(nng_http *conn, const char *version) { #ifdef NNG_SUPP_HTTP - return (nni_http_res_set_reason(res, rsn)); + return (nni_http_set_version(conn, version)); #else - NNI_ARG_UNUSED(res); - NNI_ARG_UNUSED(rsn); return (NNG_ENOTSUP); #endif } -nng_http_req * -nng_http_conn_req(nng_http_conn *conn) +void +nng_http_set_method(nng_http *conn, const char *method) { #ifdef NNG_SUPP_HTTP - return (nni_http_conn_req(conn)); + nni_http_set_method(conn, method); #else - return (NULL); + NNI_ARG_UNUSED(conn); + NNI_ARG_UNUSED(method); #endif } -nng_http_res * -nng_http_conn_res(nng_http_conn *conn) +const char * +nng_http_get_method(nng_http *conn) { #ifdef NNG_SUPP_HTTP - return (nni_http_conn_res(conn)); + return (nni_http_get_method(conn)); #else + NNI_ARG_UNUSED(conn); return (NULL); #endif } void -nng_http_conn_close(nng_http_conn *conn) +nng_http_close(nng_http *conn) { #ifdef NNG_SUPP_HTTP // API version of this closes *and* frees the structure. @@ -426,7 +229,7 @@ nng_http_conn_close(nng_http_conn *conn) } void -nng_http_conn_read(nng_http_conn *conn, nng_aio *aio) +nng_http_read(nng_http *conn, nng_aio *aio) { #ifdef NNG_SUPP_HTTP nni_http_read(conn, aio); @@ -437,7 +240,7 @@ nng_http_conn_read(nng_http_conn *conn, nng_aio *aio) } void -nng_http_conn_read_all(nng_http_conn *conn, nng_aio *aio) +nng_http_read_all(nng_http *conn, nng_aio *aio) { #ifdef NNG_SUPP_HTTP nni_http_read_full(conn, aio); @@ -448,7 +251,7 @@ nng_http_conn_read_all(nng_http_conn *conn, nng_aio *aio) } void -nng_http_conn_write(nng_http_conn *conn, nng_aio *aio) +nng_http_write(nng_http *conn, nng_aio *aio) { #ifdef NNG_SUPP_HTTP nni_http_write(conn, aio); @@ -459,7 +262,7 @@ nng_http_conn_write(nng_http_conn *conn, nng_aio *aio) } void -nng_http_conn_write_all(nng_http_conn *conn, nng_aio *aio) +nng_http_write_all(nng_http *conn, nng_aio *aio) { #ifdef NNG_SUPP_HTTP nni_http_write_full(conn, aio); @@ -470,19 +273,18 @@ nng_http_conn_write_all(nng_http_conn *conn, nng_aio *aio) } void -nng_http_conn_write_req(nng_http_conn *conn, nng_http_req *req, nng_aio *aio) +nng_http_write_request(nng_http *conn, nng_aio *aio) { #ifdef NNG_SUPP_HTTP - nni_http_write_req(conn, req, aio); + nni_http_write_req(conn, aio); #else NNI_ARG_UNUSED(conn); - NNI_ARG_UNUSED(req); nni_aio_finish_error(aio, NNG_ENOTSUP); #endif } void -nng_http_conn_write_res(nng_http_conn *conn, nng_aio *aio) +nng_http_write_response(nng_http *conn, nng_aio *aio) { #ifdef NNG_SUPP_HTTP nni_http_write_res(conn, aio); @@ -493,21 +295,10 @@ nng_http_conn_write_res(nng_http_conn *conn, nng_aio *aio) } void -nng_http_conn_read_req(nng_http_conn *conn, nng_aio *aio) -{ -#ifdef NNG_SUPP_HTTP - nni_http_read_req(conn, aio); -#else - NNI_ARG_UNUSED(conn); - nni_aio_finish_error(aio, NNG_ENOTSUP); -#endif -} - -void -nng_http_conn_read_res(nng_http_conn *conn, nng_http_res *res, nng_aio *aio) +nng_http_read_response(nng_http *conn, nng_aio *aio) { #ifdef NNG_SUPP_HTTP - nni_http_read_res(conn, res, aio); + nni_http_read_res(conn, aio); #else NNI_ARG_UNUSED(conn); NNI_ARG_UNUSED(res); @@ -757,20 +548,6 @@ nng_http_server_set_error_page( } 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, nng_tls_config *cfg) { #if defined(NNG_SUPP_HTTP) && defined(NNG_SUPP_TLS) @@ -811,10 +588,10 @@ nng_http_server_get_addr(nng_http_server *srv, nng_sockaddr *addr) } int -nng_http_server_res_error(nng_http_server *srv, nng_http_res *res) +nng_http_server_error(nng_http_server *srv, nng_http *conn) { #ifdef NNG_SUPP_HTTP - return (nni_http_server_res_error(srv, res)); + return (nni_http_server_error(srv, conn)); #else NNI_ARG_UNUSED(srv); NNI_ARG_UNUSED(res); @@ -823,7 +600,7 @@ nng_http_server_res_error(nng_http_server *srv, nng_http_res *res) } int -nng_http_hijack(nng_http_conn *conn) +nng_http_hijack(nng_http *conn) { #ifdef NNG_SUPP_HTTP return (nni_http_hijack(conn)); @@ -891,21 +668,7 @@ nng_http_client_connect(nng_http_client *cli, nng_aio *aio) } void -nng_http_client_transact( - nng_http_client *cli, nng_http_req *req, nng_http_res *res, nng_aio *aio) -{ -#ifdef NNG_SUPP_HTTP - nni_http_transact(cli, req, res, aio); -#else - NNI_ARG_UNUSED(cli); - NNI_ARG_UNUSED(req); - NNI_ARG_UNUSED(res); - nni_aio_finish_error(aio, NNG_ENOTSUP); -#endif -} - -void -nng_http_conn_transact(nng_http_conn *conn, nng_aio *aio) +nng_http_transact(nng_http *conn, nng_aio *aio) { #ifdef NNG_SUPP_HTTP nni_http_transact_conn(conn, aio); @@ -916,21 +679,11 @@ nng_http_conn_transact(nng_http_conn *conn, nng_aio *aio) } void -nng_http_req_reset(nng_http_req *req) +nng_http_reset(nng_http *conn) { #ifdef NNG_SUPP_HTTP - nni_http_req_reset(req); + nni_http_conn_reset(conn); #else NNI_ARG_UNUSED(req); #endif } - -void -nng_http_res_reset(nng_http_res *res) -{ -#ifdef NNG_SUPP_HTTP - nni_http_res_reset(res); -#else - NNI_ARG_UNUSED(res); -#endif -} 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); } diff --git a/src/supplemental/http/http_server_test.c b/src/supplemental/http/http_server_test.c index 91411499..7a38a396 100644 --- a/src/supplemental/http/http_server_test.c +++ b/src/supplemental/http/http_server_test.c @@ -11,8 +11,9 @@ // Basic HTTP server tests. #include "core/defs.h" +#include <complex.h> +#include <nng/http.h> #include <nng/nng.h> -#include <nng/supplemental/http/http.h> #include <nuts.h> @@ -27,49 +28,31 @@ struct server_test { nng_http_server *s; nng_http_handler *h; nng_http_client *cli; - nng_http_conn *conn; - nng_http_req *req; - nng_http_res *res; + nng_http *conn; char urlstr[2048]; }; static int -httpdo(nng_url *url, nng_http_req *req, nng_http_res *res, void **datap, - size_t *sizep) +httpdo(struct server_test *st, void **datap, size_t *sizep) { - int rv; - nng_aio *aio = NULL; - nng_http_client *cli = NULL; - nng_http_conn *h = NULL; - size_t clen = 0; - void *data = NULL; - const char *ptr; - - if (((rv = nng_aio_alloc(&aio, NULL, NULL)) != 0) || - ((rv = nng_http_client_alloc(&cli, url)) != 0)) { - goto fail; - } - nng_http_client_connect(cli, aio); - nng_aio_wait(aio); - if ((rv = nng_aio_result(aio)) != 0) { - goto fail; - } - - h = nng_aio_get_output(aio, 0); + int rv; + size_t clen = 0; + void *data = NULL; + const char *ptr; - nng_http_conn_write_req(h, req, aio); - nng_aio_wait(aio); - if ((rv = nng_aio_result(aio)) != 0) { - goto fail; + nng_http_write_request(st->conn, st->aio); + nng_aio_wait(st->aio); + if ((rv = nng_aio_result(st->aio)) != 0) { + return (rv); } - nng_http_conn_read_res(h, res, aio); - nng_aio_wait(aio); - if ((rv = nng_aio_result(aio)) != 0) { - goto fail; + nng_http_read_response(st->conn, st->aio); + nng_aio_wait(st->aio); + if ((rv = nng_aio_result(st->aio)) != 0) { + return (rv); } clen = 0; - if ((ptr = nng_http_res_get_header(res, "Content-Length")) != NULL) { + if ((ptr = nng_http_get_header(st->conn, "Content-Length")) != NULL) { clen = atoi(ptr); } @@ -78,28 +61,17 @@ httpdo(nng_url *url, nng_http_req *req, nng_http_res *res, void **datap, data = nng_alloc(clen); iov.iov_buf = data; iov.iov_len = clen; - nng_aio_set_iov(aio, 1, &iov); - nng_http_conn_read_all(h, aio); - nng_aio_wait(aio); - if ((rv = nng_aio_result(aio)) != 0) { - goto fail; + nng_aio_set_iov(st->aio, 1, &iov); + nng_http_read_all(st->conn, st->aio); + nng_aio_wait(st->aio); + if ((rv = nng_aio_result(st->aio)) != 0) { + return (rv); } } *datap = data; *sizep = clen; -fail: - if (aio != NULL) { - nng_aio_free(aio); - } - if (h != NULL) { - nng_http_conn_close(h); - } - if (cli != NULL) { - nng_http_client_free(cli); - } - return (rv); } @@ -111,16 +83,17 @@ httpget(struct server_test *st, void **datap, size_t *sizep, uint16_t *statp, size_t clen = 0; void *data = NULL; char *ctype = NULL; + nng_http *conn = st->conn; const char *ptr; - if ((rv = httpdo(st->url, st->req, st->res, &data, &clen)) != 0) { + if ((rv = httpdo(st, &data, &clen)) != 0) { goto fail; } - *statp = nng_http_res_get_status(st->res); + *statp = nng_http_get_status(conn); if (clen > 0) { - if ((ptr = nng_http_res_get_header(st->res, "Content-Type")) != + if ((ptr = nng_http_get_header(conn, "Content-Type")) != NULL) { ctype = nng_strdup(ptr); } @@ -142,25 +115,22 @@ fail: } static void -httpecho(nng_http_conn *conn, void *arg, nng_aio *aio) +httpecho(nng_http *conn, void *arg, nng_aio *aio) { - nng_http_req *req = nng_http_conn_req(conn); - nng_http_res *res = nng_http_conn_res(conn); - int rv; - void *body; - size_t len; + int rv; + void *body; + size_t len; NNI_ARG_UNUSED(arg); - nng_http_req_get_data(req, &body, &len); + nng_http_get_body(conn, &body, &len); - if (((rv = nng_http_res_copy_data(res, body, len)) != 0) || - ((rv = nng_http_res_set_header( - res, "Content-type", "text/plain")) != 0)) { + if (((rv = nng_http_copy_body(conn, body, len)) != 0) || + ((rv = nng_http_set_header(conn, "Content-type", "text/plain")) != + 0)) { nng_aio_finish(aio, rv); return; } - nng_http_res_set_status(res, NNG_HTTP_STATUS_OK); - nng_aio_set_output(aio, 0, res); + nng_http_set_status(conn, NNG_HTTP_STATUS_OK, NULL); nng_aio_finish(aio, 0); } @@ -188,17 +158,20 @@ server_setup(struct server_test *st, nng_http_handler *h) NUTS_PASS(nng_aio_result(st->aio)); st->conn = nng_aio_get_output(st->aio, 0); NUTS_TRUE(st->conn != NULL); - NUTS_PASS(nng_http_req_alloc(&st->req, st->url)); - NUTS_PASS(nng_http_res_alloc(&st->res)); + NUTS_PASS(nng_http_set_uri(st->conn, "/", NULL)); } static void server_reset(struct server_test *st) { - nng_http_req_free(st->req); - nng_http_res_free(st->res); - nng_http_req_alloc(&st->req, st->url); - nng_http_res_alloc(&st->res); + if (st->conn) { + nng_http_close(st->conn); + } + nng_http_client_connect(st->cli, st->aio); + nng_aio_wait(st->aio); + NUTS_PASS(nng_aio_result(st->aio)); + st->conn = nng_aio_get_output(st->aio, 0); + NUTS_PASS(nng_http_set_uri(st->conn, "/", NULL)); } static void @@ -211,7 +184,7 @@ server_free(struct server_test *st) nng_http_client_free(st->cli); } if (st->conn != NULL) { - nng_http_conn_close(st->conn); + nng_http_close(st->conn); } if (st->s != NULL) { nng_http_server_release(st->s); @@ -219,12 +192,6 @@ server_free(struct server_test *st) if (st->url != NULL) { nng_url_free(st->url); } - if (st->req != NULL) { - nng_http_req_free(st->req); - } - if (st->res != NULL) { - nng_http_res_free(st->res); - } } static void @@ -241,26 +208,26 @@ test_server_basic(void) server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/home.html")); - nng_http_conn_write_req(st.conn, st.req, st.aio); + NUTS_PASS(nng_http_set_uri(st.conn, "/home.html", NULL)); + nng_http_write_request(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - nng_http_conn_read_res(st.conn, st.res, st.aio); + nng_http_read_response(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - NUTS_TRUE(nng_http_res_get_status(st.res) == NNG_HTTP_STATUS_OK); + NUTS_TRUE(nng_http_get_status(st.conn) == NNG_HTTP_STATUS_OK); - ptr = nng_http_res_get_header(st.res, "Content-Length"); + ptr = nng_http_get_header(st.conn, "Content-Length"); NUTS_TRUE(ptr != NULL); NUTS_TRUE(atoi(ptr) == (int) strlen(doc1)); iov.iov_len = strlen(doc1); iov.iov_buf = chunk; NUTS_PASS(nng_aio_set_iov(st.aio, 1, &iov)); - nng_http_conn_read_all(st.conn, st.aio); + nng_http_read_all(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); NUTS_TRUE(nng_aio_count(st.aio) == strlen(doc1)); @@ -276,18 +243,17 @@ test_server_404(void) server_setup(&st, NULL); - NUTS_PASS(nng_http_req_set_uri(st.req, "/bogus")); - nng_http_conn_write_req(st.conn, st.req, st.aio); + NUTS_PASS(nng_http_set_uri(st.conn, "/bogus", NULL)); + nng_http_write_request(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - nng_http_conn_read_res(st.conn, st.res, st.aio); + nng_http_read_response(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - NUTS_TRUE( - nng_http_res_get_status(st.res) == NNG_HTTP_STATUS_NOT_FOUND); + NUTS_TRUE(nng_http_get_status(st.conn) == NNG_HTTP_STATUS_NOT_FOUND); server_free(&st); } @@ -299,18 +265,19 @@ test_server_bad_version(void) server_setup(&st, NULL); - NUTS_PASS(nng_http_req_set_version(st.req, "HTTP/0.9")); - NUTS_PASS(nng_http_req_set_uri(st.req, "/bogus")); - nng_http_conn_write_req(st.conn, st.req, st.aio); + NUTS_PASS(nng_http_set_version(st.conn, "HTTP/0.9")); + NUTS_PASS(nng_http_set_uri(st.conn, "/bogus", NULL)); + nng_http_write_request(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - nng_http_conn_read_res(st.conn, st.res, st.aio); + nng_http_read_response(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - NUTS_TRUE(nng_http_res_get_status(st.res) == 505); + NUTS_TRUE(nng_http_get_status(st.conn) == + NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP); server_free(&st); } @@ -321,18 +288,18 @@ test_server_missing_host(void) struct server_test st; server_setup(&st, NULL); - nng_http_req_del_header(st.req, "Host"); - NUTS_PASS(nng_http_req_set_uri(st.req, "/bogus")); - nng_http_conn_write_req(st.conn, st.req, st.aio); + nng_http_del_header(st.conn, "Host"); + NUTS_PASS(nng_http_set_uri(st.conn, "/bogus", NULL)); + nng_http_write_request(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - nng_http_conn_read_res(st.conn, st.res, st.aio); + nng_http_read_response(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - NUTS_TRUE(nng_http_res_get_status(st.res) == 400); + NUTS_TRUE(nng_http_get_status(st.conn) == 400); server_free(&st); } @@ -363,21 +330,21 @@ test_server_wrong_method(void) server_setup(&st, h); - nng_http_req_set_method(st.req, "POST"); - NUTS_PASS(nng_http_req_set_uri(st.req, "/home.html")); - nng_http_conn_write_req(st.conn, st.req, st.aio); + nng_http_set_method(st.conn, "POST"); + NUTS_PASS(nng_http_set_uri(st.conn, "/home.html", NULL)); + nng_http_write_request(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - nng_http_conn_read_res(st.conn, st.res, st.aio); + nng_http_read_response(st.conn, st.aio); nng_aio_wait(st.aio); NUTS_PASS(nng_aio_result(st.aio)); - NUTS_TRUE(nng_http_res_get_status(st.res) == + NUTS_TRUE(nng_http_get_status(st.conn) == NNG_HTTP_STATUS_METHOD_NOT_ALLOWED); - NUTS_MSG("Got result %d: %s", nng_http_res_get_status(st.res), - nng_http_res_get_reason(st.res)); + NUTS_MSG("Got result %d: %s", nng_http_get_status(st.conn), + nng_http_get_reason(st.conn)); server_free(&st); } @@ -398,25 +365,26 @@ test_server_post_handler(void) server_setup(&st, h); snprintf(txdata, sizeof(txdata), "1234"); - nng_http_req_set_uri(st.req, "/post"); - nng_http_req_set_data(st.req, txdata, strlen(txdata)); - nng_http_req_set_method(st.req, "POST"); - NUTS_PASS(httpdo(st.url, st.req, st.res, (void **) &rxdata, &size)); - NUTS_TRUE(nng_http_res_get_status(st.res) == NNG_HTTP_STATUS_OK); + + NUTS_PASS(nng_http_set_uri(st.conn, "/post", NULL)); + nng_http_set_body(st.conn, txdata, strlen(txdata)); + nng_http_set_method(st.conn, "POST"); + NUTS_PASS(httpdo(&st, (void **) &rxdata, &size)); + NUTS_TRUE(nng_http_get_status(st.conn) == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(txdata)); NUTS_TRUE(strncmp(txdata, rxdata, size) == 0); nng_free(rxdata, size); server_reset(&st); - NUTS_PASS(nng_http_req_set_uri(st.req, "/post")); - nng_http_req_set_method(st.req, "GET"); - NUTS_PASS(nng_http_req_set_data(st.req, txdata, strlen(txdata))); + NUTS_PASS(nng_http_set_uri(st.conn, "/post", NULL)); + nng_http_set_method(st.conn, "GET"); + nng_http_set_body(st.conn, txdata, strlen(txdata)); - NUTS_PASS(httpdo(st.url, st.req, st.res, &data, &size)); - NUTS_TRUE(nng_http_res_get_status(st.res) == + NUTS_PASS(httpdo(&st, &data, &size)); + NUTS_TRUE(nng_http_get_status(st.conn) == NNG_HTTP_STATUS_METHOD_NOT_ALLOWED); - NUTS_MSG("HTTP status was %u", nng_http_res_get_status(st.res)); + NUTS_MSG("HTTP status was %u", nng_http_get_status(st.conn)); nng_free(data, size); server_free(&st); @@ -437,15 +405,14 @@ test_server_get_redirect(void) &h, "/here", 303, "http://127.0.0.1/there")); server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/here")); - nng_http_req_set_method(st.req, "GET"); + NUTS_PASS(nng_http_set_uri(st.conn, "/here", NULL)); + nng_http_set_method(st.conn, "GET"); - NUTS_PASS(httpdo(st.url, st.req, st.res, &data, &size)); - NUTS_TRUE(nng_http_res_get_status(st.res) == 303); + NUTS_PASS(httpdo(&st, &data, &size)); + NUTS_TRUE(nng_http_get_status(st.conn) == 303); NUTS_MSG("HTTP status got %d, expected %d (url %s)", - nng_http_res_get_status(st.res), 303, fullurl); - NUTS_TRUE( - (dest = nng_http_res_get_header(st.res, "Location")) != NULL); + nng_http_get_status(st.conn), 303, fullurl); + NUTS_TRUE((dest = nng_http_get_header(st.conn, "Location")) != NULL); NUTS_MATCH(dest, "http://127.0.0.1/there"); nng_free(data, size); @@ -468,15 +435,14 @@ test_server_tree_redirect(void) nng_http_handler_set_tree(h); server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/here/i/go/again")); - nng_http_req_set_method(st.req, "GET"); + NUTS_PASS(nng_http_set_uri(st.conn, "/here/i/go/again", NULL)); + nng_http_set_method(st.conn, "GET"); - NUTS_PASS(httpdo(st.url, st.req, st.res, &data, &size)); - NUTS_TRUE(nng_http_res_get_status(st.res) == 303); + NUTS_PASS(httpdo(&st, &data, &size)); + NUTS_TRUE(nng_http_get_status(st.conn) == 303); NUTS_MSG("HTTP status got %d, expected %d (url %s)", - nng_http_res_get_status(st.res), 303, fullurl); - NUTS_TRUE( - (dest = nng_http_res_get_header(st.res, "Location")) != NULL); + nng_http_get_status(st.conn), 303, fullurl); + NUTS_TRUE((dest = nng_http_get_header(st.conn, "Location")) != NULL); NUTS_MATCH(dest, "http://127.0.0.1/there/i/go/again"); nng_free(data, size); @@ -499,12 +465,12 @@ test_server_post_redirect(void) server_setup(&st, h); snprintf(txdata, sizeof(txdata), "1234"); - NUTS_PASS(nng_http_req_set_uri(st.req, "/here")); - nng_http_req_set_data(st.req, txdata, strlen(txdata)); - nng_http_req_set_method(st.req, "POST"); - NUTS_PASS(httpdo(st.url, st.req, st.res, (void **) &data, &size)); - NUTS_TRUE(nng_http_res_get_status(st.res) == 301); - dest = nng_http_res_get_header(st.res, "Location"); + NUTS_PASS(nng_http_set_uri(st.conn, "/here", NULL)); + nng_http_set_body(st.conn, txdata, strlen(txdata)); + nng_http_set_method(st.conn, "POST"); + NUTS_PASS(httpdo(&st, (void **) &data, &size)); + NUTS_TRUE(nng_http_get_status(st.conn) == 301); + dest = nng_http_get_header(st.conn, "Location"); NUTS_TRUE(dest != NULL); NUTS_MATCH(dest, "http://127.0.0.1/there"); nng_free(data, size); @@ -527,11 +493,11 @@ test_server_post_echo_tree(void) server_setup(&st, h); snprintf(txdata, sizeof(txdata), "1234"); - nng_http_req_set_data(st.req, txdata, strlen(txdata)); - nng_http_req_set_method(st.req, "POST"); - NUTS_PASS(nng_http_req_set_uri(st.req, "/some_sub/directory")); - NUTS_PASS(httpdo(st.url, st.req, st.res, (void **) &rxdata, &size)); - NUTS_TRUE(nng_http_res_get_status(st.res) == NNG_HTTP_STATUS_OK); + nng_http_set_body(st.conn, txdata, strlen(txdata)); + nng_http_set_method(st.conn, "POST"); + NUTS_PASS(nng_http_set_uri(st.conn, "/some_sub/directory", NULL)); + NUTS_PASS(httpdo(&st, (void **) &rxdata, &size)); + NUTS_TRUE(nng_http_get_status(st.conn) == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(txdata)); NUTS_TRUE(strncmp(txdata, rxdata, size) == 0); nng_free(rxdata, size); @@ -613,7 +579,7 @@ test_server_multiple_trees(void) char *ctype; NUTS_CASE("Directory 1"); - NUTS_PASS(nng_http_req_set_uri(st.req, "/file1.txt")); + NUTS_PASS(nng_http_set_uri(st.conn, "/file1.txt", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(doc1)); @@ -625,7 +591,7 @@ test_server_multiple_trees(void) server_reset(&st); NUTS_CASE("Directory 2"); - NUTS_PASS(nng_http_req_set_uri(st.req, "/subdir/file2.txt")); + NUTS_PASS(nng_http_set_uri(st.conn, "/subdir/file2.txt", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(doc2)); @@ -710,7 +676,7 @@ test_serve_directory(void) NUTS_PASS(nng_http_handler_alloc_directory(&h, "/", sd.workdir)); server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/subdir1/index.html")); + NUTS_PASS(nng_http_set_uri(st.conn, "/subdir1/index.html", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(doc1)); @@ -740,7 +706,7 @@ test_serve_directory_index(void) server_setup(&st, h); NUTS_CASE("Directory 1: index.html"); - NUTS_PASS(nng_http_req_set_uri(st.req, "/subdir1/")); + NUTS_PASS(nng_http_set_uri(st.conn, "/subdir1/", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(doc1)); @@ -752,7 +718,7 @@ test_serve_directory_index(void) server_reset(&st); NUTS_CASE("Directory 2: index.htm"); - NUTS_PASS(nng_http_req_set_uri(st.req, "/subdir2/")); + NUTS_PASS(nng_http_set_uri(st.conn, "/subdir2/", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(doc3)); @@ -781,7 +747,7 @@ test_serve_plain_text(void) NUTS_PASS(nng_http_handler_alloc_directory(&h, "/", sd.workdir)); server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/file.txt")); + NUTS_PASS(nng_http_set_uri(st.conn, "/file.txt", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(doc2)); @@ -810,12 +776,24 @@ test_serve_file_parameters(void) NUTS_PASS(nng_http_handler_alloc_directory(&h, "/", sd.workdir)); server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/file.txt?param=1234")); + NUTS_PASS(nng_http_set_uri(st.conn, "/file.txt?param=1234", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(doc2)); NUTS_TRUE(memcmp(data, doc2, size) == 0); NUTS_MATCH(ctype, "text/plain"); + nng_free(data, size); + nng_strfree(ctype); + + // again but this time pass parameter as arg + nng_http_reset(st.conn); + NUTS_PASS(nng_http_set_uri(st.conn, "/file.txt", "param=1234")); + NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); + NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); + NUTS_TRUE(size == strlen(doc2)); + NUTS_TRUE(memcmp(data, doc2, size) == 0); + NUTS_MATCH(ctype, "text/plain"); + nng_strfree(ctype); nng_free(data, size); @@ -839,7 +817,7 @@ test_serve_missing_index(void) NUTS_PASS(nng_http_handler_alloc_directory(&h, "/", sd.workdir)); server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/index.html")); + NUTS_PASS(nng_http_set_uri(st.conn, "/index.html", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_NOT_FOUND); nng_strfree(ctype); @@ -865,8 +843,8 @@ test_serve_index_not_post(void) NUTS_PASS(nng_http_handler_alloc_directory(&h, "/", sd.workdir)); server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/subdir2/index.html")); - nng_http_req_set_method(st.req, "POST"); + NUTS_PASS(nng_http_set_uri(st.conn, "/subdir2/index.html", NULL)); + nng_http_set_method(st.conn, "POST"); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_METHOD_NOT_ALLOWED); nng_strfree(ctype); @@ -892,7 +870,7 @@ test_serve_subdir_index(void) NUTS_PASS(nng_http_handler_alloc_directory(&h, "/docs", sd.workdir)); server_setup(&st, h); - NUTS_PASS(nng_http_req_set_uri(st.req, "/docs/subdir1/")); + NUTS_PASS(nng_http_set_uri(st.conn, "/docs/subdir1/", NULL)); NUTS_PASS(httpget(&st, &data, &size, &stat, &ctype)); NUTS_TRUE(stat == NNG_HTTP_STATUS_OK); NUTS_TRUE(size == strlen(doc1)); diff --git a/src/supplemental/websocket/websocket.c b/src/supplemental/websocket/websocket.c index 70323b0c..86edf123 100644 --- a/src/supplemental/websocket/websocket.c +++ b/src/supplemental/websocket/websocket.c @@ -14,6 +14,7 @@ #include <string.h> #include "core/nng_impl.h" +#include "nng/http.h" #include "supplemental/http/http_api.h" #include "base64.h" @@ -21,7 +22,7 @@ #include "websocket.h" // This should be removed or handled differently in the future. -typedef int (*nni_ws_listen_hook)(void *, nng_http_req *, nng_http_res *); +typedef int (*nni_ws_listen_hook)(void *, nng_http *); // We have chosen to be a bit more stringent in the size of the frames that // we send, while we more generously allow larger incoming frames. These @@ -55,6 +56,7 @@ struct nni_ws { bool inmsg; bool send_text; bool recv_text; + bool recv_res; nni_mtx mtx; nni_list sendq; nni_list recvq; @@ -68,16 +70,21 @@ struct nni_ws { nni_aio httpaio; nni_aio connaio; // connect aio nni_aio *useraio; // user aio, during HTTP negotiation - nni_http_conn *http; - nni_http_req *req; - nni_http_res *res; - char *reqhdrs; - char *reshdrs; + nng_http *http; size_t maxframe; size_t fragsize; size_t recvmax; // largest message size nni_ws_listener *listener; nni_ws_dialer *dialer; + char keybuf[29]; // key on client, accept on server + struct { + nni_http_header connection; + nni_http_header upgrade; + nni_http_header wsaccept; + nni_http_header wskey; + nni_http_header wsproto; + nni_http_header wsversion; + } hdrs; }; struct nni_ws_listener { @@ -235,64 +242,6 @@ ws_set_header(nni_list *l, const char *n, const char *v) return (ws_set_header_ext(l, n, v, true)); } -static int -ws_set_headers(nni_list *l, const char *str) -{ - char *dupstr; - size_t duplen; - char *n; - char *v; - char *nl; - int rv; - - if ((dupstr = nni_strdup(str)) == NULL) { - return (NNG_ENOMEM); - } - duplen = strlen(dupstr) + 1; // so we can free it later - - n = dupstr; - for (;;) { - if ((v = strchr(n, ':')) == NULL) { - // Note that this also means that if - // a bare word is present, we ignore it. - break; - } - *v = '\0'; - v++; - while (*v == ' ') { - // Skip leading whitespace. Not strictly - // necessary, but still a good idea. - v++; - } - nl = v; - // Find the end of the line -- should be CRLF, but can - // also be unterminated or just LF if user - while ((*nl != '\0') && (*nl != '\r') && (*nl != '\n')) { - nl++; - } - while ((*nl == '\r') || (*nl == '\n')) { - *nl = '\0'; - nl++; - } - - // Note that this can lead to a partial failure. As this - // is most likely ENOMEM, don't worry too much about it. - // This method does *not* eliminate duplicates. - if ((rv = ws_set_header_ext(l, n, v, false)) != 0) { - goto done; - } - - // Advance to the next name. - n = nl; - } - - rv = 0; - -done: - nni_free(dupstr, duplen); - return (rv); -} - // This looks, case independently for a word in a list, which is either // space or comma separated. static bool @@ -1244,8 +1193,6 @@ ws_fini(void *arg) nni_http_conn_fini(ws->http); } - nni_strfree(ws->reqhdrs); - nni_strfree(ws->reshdrs); nni_aio_fini(&ws->rxaio); nni_aio_fini(&ws->txaio); nni_aio_fini(&ws->closeaio); @@ -1325,14 +1272,14 @@ ws_http_cb_dialer(nni_ws *ws, nni_aio *aio) // If we have no response structure, then this was completion // of sending the request. Prepare an empty response, and read it. - if (ws->res == NULL) { - ws->res = nni_http_conn_res(ws->http); - nni_http_read_res(ws->http, ws->res, &ws->httpaio); + if (!ws->recv_res) { + ws->recv_res = true; + nng_http_read_response(ws->http, &ws->httpaio); nni_mtx_unlock(&d->mtx); return; } - status = nni_http_res_get_status(ws->res); + status = nni_http_get_status(ws->http); switch (status) { case NNG_HTTP_STATUS_SWITCHING: break; @@ -1342,6 +1289,7 @@ ws_http_cb_dialer(nni_ws *ws, nni_aio *aio) goto err; case NNG_HTTP_STATUS_NOT_FOUND: case NNG_HTTP_STATUS_METHOD_NOT_ALLOWED: + case NNG_HTTP_STATUS_NOT_IMPLEMENTED: rv = NNG_ECONNREFUSED; // Treat these as refusals. goto err; case NNG_HTTP_STATUS_BAD_REQUEST: @@ -1351,34 +1299,31 @@ ws_http_cb_dialer(nni_ws *ws, nni_aio *aio) goto err; } - // Check that the server gave us back the right key. - rv = ws_make_accept( - nni_http_req_get_header(ws->req, "Sec-WebSocket-Key"), wskey); + rv = ws_make_accept(ws->keybuf, wskey); if (rv != 0) { goto err; } -#define GETH(h) nni_http_res_get_header(ws->res, h) - - if (((ptr = GETH("Sec-WebSocket-Accept")) == NULL) || + if (((ptr = nng_http_get_header(ws->http, "Sec-WebSocket-Accept")) == + NULL) || (strcmp(ptr, wskey) != 0) || - ((ptr = GETH("Connection")) == NULL) || + ((ptr = nng_http_get_header(ws->http, "Connection")) == NULL) || (!ws_contains_word(ptr, "upgrade")) || - ((ptr = GETH("Upgrade")) == NULL) || + ((ptr = nng_http_get_header(ws->http, "Upgrade")) == NULL) || (strcmp(ptr, "websocket") != 0)) { ws_close_error(ws, WS_CLOSE_PROTOCOL_ERR); rv = NNG_EPROTO; goto err; } if (d->proto != NULL) { - if (((ptr = GETH("Sec-WebSocket-Protocol")) == NULL) || + if (((ptr = nng_http_get_header( + ws->http, "Sec-WebSocket-Protocol")) == NULL) || (!ws_contains_word(d->proto, ptr))) { ws_close_error(ws, WS_CLOSE_PROTOCOL_ERR); rv = NNG_EPROTO; goto err; } } -#undef GETH // At this point, we are in business! nni_list_remove(&d->wspend, ws); @@ -1506,19 +1451,16 @@ ws_listener_free(void *arg) } static void -ws_handler(nng_http_conn *conn, void *arg, nng_aio *aio) +ws_handler(nng_http *conn, void *arg, nng_aio *aio) { nni_ws_listener *l = arg; - ; - nni_http_req *req = nng_http_conn_req(conn); - nni_http_res *res = nng_http_conn_res(conn); - nni_ws *ws; - const char *ptr; - const char *proto; - uint16_t status; - int rv; - char key[29]; - ws_header *hdr; + nni_ws *ws; + const char *ptr; + const char *proto; + uint16_t status; + int rv; + char key[29]; + ws_header *hdr; nni_mtx_lock(&l->mtx); if (l->closed) { @@ -1527,39 +1469,39 @@ ws_handler(nng_http_conn *conn, void *arg, nng_aio *aio) } // Now check the headers, etc. - if (strcmp(nni_http_req_get_version(req), "HTTP/1.1") != 0) { + if (strcmp(nng_http_get_version(conn), "HTTP/1.1") != 0) { status = NNG_HTTP_STATUS_HTTP_VERSION_NOT_SUPP; goto err; } - if (strcmp(nni_http_req_get_method(req), "GET") != 0) { + if (strcmp(nng_http_get_method(conn), "GET") != 0) { // HEAD request. We can't really deal with it. status = NNG_HTTP_STATUS_BAD_REQUEST; goto err; } -#define GETH(h) nni_http_req_get_header(req, h) -#define SETH(h, v) nni_http_res_set_header(res, h, v) - - if ((((ptr = GETH("Content-Length")) != NULL) && (atoi(ptr) > 0)) || - (((ptr = GETH("Transfer-Encoding")) != NULL) && + if ((((ptr = nng_http_get_header(conn, "Content-Length")) != NULL) && + (atoi(ptr) > 0)) || + (((ptr = nng_http_get_header(conn, "Transfer-Encoding")) != + NULL) && (nni_strcasestr(ptr, "chunked") != NULL))) { - status = NNG_HTTP_STATUS_PAYLOAD_TOO_LARGE; + status = NNG_HTTP_STATUS_CONTENT_TOO_LARGE; goto err; } // These headers have to be present. - if (((ptr = GETH("Upgrade")) == NULL) || + if (((ptr = nng_http_get_header(conn, "Upgrade")) == NULL) || (!ws_contains_word(ptr, "websocket")) || - ((ptr = GETH("Connection")) == NULL) || + ((ptr = nng_http_get_header(conn, "Connection")) == NULL) || (!ws_contains_word(ptr, "upgrade")) || - ((ptr = GETH("Sec-WebSocket-Version")) == NULL) || + ((ptr = nng_http_get_header(conn, "Sec-WebSocket-Version")) == + NULL) || (strcmp(ptr, "13") != 0)) { status = NNG_HTTP_STATUS_BAD_REQUEST; goto err; } - if (((ptr = GETH("Sec-WebSocket-Key")) == NULL) || + if (((ptr = nng_http_get_header(conn, "Sec-WebSocket-Key")) == NULL) || (ws_make_accept(ptr, key) != 0)) { status = NNG_HTTP_STATUS_BAD_REQUEST; goto err; @@ -1569,7 +1511,7 @@ ws_handler(nng_http_conn *conn, void *arg, nng_aio *aio) // we need to try to match it to what the handler says we // support. (If no suitable option is found in the handler, we // fail the request.) - proto = GETH("Sec-WebSocket-Protocol"); + proto = nng_http_get_header(conn, "Sec-WebSocket-Protocol"); if (proto == NULL) { if (l->proto != NULL) { status = NNG_HTTP_STATUS_BAD_REQUEST; @@ -1581,23 +1523,13 @@ ws_handler(nng_http_conn *conn, void *arg, nng_aio *aio) goto err; } - nni_http_res_set_status(res, NNG_HTTP_STATUS_SWITCHING); - - if ((SETH("Connection", "Upgrade") != 0) || - (SETH("Upgrade", "websocket") != 0) || - (SETH("Sec-WebSocket-Accept", key) != 0)) { - status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR; - goto err; - } - if ((proto != NULL) && (SETH("Sec-WebSocket-Protocol", proto) != 0)) { - status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR; - goto err; - } + nng_http_set_status(conn, NNG_HTTP_STATUS_SWITCHING, NULL); // Set any user supplied headers. This is better than using a hook - // for most things, because it is loads easier. + // for most things, because it is loads easier. Note that websocket + // headers we care about will be overridden below! NNI_LIST_FOREACH (&l->headers, hdr) { - if (SETH(hdr->name, hdr->value) != 0) { + if (nng_http_set_header(conn, hdr->name, hdr->value) != 0) { status = NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR; goto err; } @@ -1608,33 +1540,25 @@ ws_handler(nng_http_conn *conn, void *arg, nng_aio *aio) // need to, because it's much more complex. But if you want to set // up an HTTP Authorization handler this might be the only choice. if (l->hookfn != NULL) { - rv = l->hookfn(l->hookarg, req, res); + rv = l->hookfn(l->hookarg, conn); if (rv != 0) { nni_aio_finish_error(aio, rv); nni_mtx_unlock(&l->mtx); return; } - if (nni_http_res_get_status(res) != - NNG_HTTP_STATUS_SWITCHING) { + if (nng_http_get_status(conn) != NNG_HTTP_STATUS_SWITCHING) { // The hook has decided to give back a // different reply and we are not upgrading // anymore. For example the Origin might not // be permitted, or another level of - // authentication may be required. (Note that - // the hook can also give back various other - // headers, but it would be bad for it to alter - // the websocket mandated headers.) - nni_aio_set_output(aio, 0, res); + // authentication may be required. nni_aio_finish(aio, 0, 0); nni_mtx_unlock(&l->mtx); return; } } -#undef GETH -#undef SETH - // We are good to go, provided we can get the websocket struct, // and send the reply. if ((rv = ws_init(&ws)) != 0) { @@ -1642,8 +1566,6 @@ ws_handler(nng_http_conn *conn, void *arg, nng_aio *aio) goto err; } ws->http = conn; - ws->req = req; - ws->res = res; ws->server = true; ws->maxframe = l->maxframe; ws->fragsize = l->fragsize; @@ -1652,9 +1574,23 @@ ws_handler(nng_http_conn *conn, void *arg, nng_aio *aio) ws->recv_text = l->recv_text; ws->send_text = l->send_text; ws->listener = l; + memcpy(ws->keybuf, key, sizeof(ws->keybuf)); + + nni_http_set_static_header( + conn, &ws->hdrs.connection, "Connection", "Upgrade"); + nni_http_set_static_header( + conn, &ws->hdrs.upgrade, "Upgrade", "websocket"); + nni_http_set_static_header( + conn, &ws->hdrs.wsaccept, "Sec-WebSocket-Accept", ws->keybuf); + if (proto != NULL) { + // NB: we still have the request protocol in the header, so + // that should be fine. + nni_http_set_static_header( + conn, &ws->hdrs.wsproto, "Sec-WebSocket-Protocol", proto); + } nni_list_append(&l->reply, ws); - nni_http_write_res(conn, &ws->httpaio); + nng_http_write_response(conn, &ws->httpaio); (void) nni_http_hijack(conn); nni_aio_set_output(aio, 0, NULL); nni_aio_finish(aio, 0, 0); @@ -1662,10 +1598,9 @@ ws_handler(nng_http_conn *conn, void *arg, nng_aio *aio) return; err: - 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); } else { - nni_aio_set_output(aio, 0, res); nni_aio_finish(aio, 0, 0); } nni_mtx_unlock(&l->mtx); @@ -1862,20 +1797,6 @@ ws_listener_get_recvmax(void *arg, void *buf, size_t *szp, nni_type t) } static int -ws_listener_set_res_headers(void *arg, const void *buf, size_t sz, nni_type t) -{ - nni_ws_listener *l = arg; - int rv; - - if ((rv = ws_check_string(buf, sz, t)) == 0) { - nni_mtx_lock(&l->mtx); - rv = ws_set_headers(&l->headers, buf); - nni_mtx_unlock(&l->mtx); - } - return (rv); -} - -static int ws_listener_set_proto(void *arg, const void *buf, size_t sz, nni_type t) { nni_ws_listener *l = arg; @@ -1996,11 +1917,6 @@ static const nni_option ws_listener_options[] = { .o_get = ws_listener_get_recvmax, }, { - .o_name = NNG_OPT_WS_RESPONSE_HEADERS, - .o_set = ws_listener_set_res_headers, - // XXX: Get not implemented yet; likely of marginal value. - }, - { .o_name = NNG_OPT_WS_PROTOCOL, .o_set = ws_listener_set_proto, .o_get = ws_listener_get_proto, @@ -2025,7 +1941,7 @@ ws_listener_set_header(nni_ws_listener *l, const char *name, const void *buf, size_t sz, nni_type t) { int rv; - name += strlen(NNG_OPT_WS_RESPONSE_HEADER); + name += strlen(NNG_OPT_WS_HEADER); if ((rv = ws_check_string(buf, sz, t)) == 0) { nni_mtx_lock(&l->mtx); rv = ws_set_header(&l->headers, name, buf); @@ -2047,7 +1963,7 @@ ws_listener_set( } if (rv == NNG_ENOTSUP) { - if (startswith(name, NNG_OPT_WS_RESPONSE_HEADER)) { + if (startswith(name, NNG_OPT_WS_HEADER)) { rv = ws_listener_set_header(l, name, buf, sz, t); } } @@ -2147,10 +2063,8 @@ ws_conn_cb(void *arg) nni_ws_dialer *d; nni_ws *ws; nni_aio *uaio; - nni_http_req *req = NULL; int rv; uint8_t raw[16]; - char wskey[25]; ws_header *hdr; ws = arg; @@ -2192,35 +2106,36 @@ ws_conn_cb(void *arg) for (int i = 0; i < 16; i++) { raw[i] = (uint8_t) nni_random(); } - nni_base64_encode(raw, 16, wskey, 24); - wskey[24] = '\0'; - - req = nni_http_conn_req(ws->http); + nni_base64_encode(raw, 16, ws->keybuf, 24); + ws->keybuf[24] = '\0'; -#define SETH(h, v) nni_http_req_set_header(req, h, v) - if ((rv != 0) || ((rv = nni_http_req_set_url(req, d->url)) != 0) || - ((rv = SETH("Upgrade", "websocket")) != 0) || - ((rv = SETH("Connection", "Upgrade")) != 0) || - ((rv = SETH("Sec-WebSocket-Key", wskey)) != 0) || - ((rv = SETH("Sec-WebSocket-Version", "13")) != 0)) { + if ((rv = nni_http_set_uri( + ws->http, d->url->u_path, d->url->u_query)) != 0) { goto err; } - if ((d->proto != NULL) && - ((rv = SETH("Sec-WebSocket-Protocol", d->proto)) != 0)) { - goto err; + nni_http_set_static_header( + ws->http, &ws->hdrs.connection, "Connection", "Upgrade"); + nni_http_set_static_header( + ws->http, &ws->hdrs.upgrade, "Upgrade", "websocket"); + nni_http_set_static_header( + ws->http, &ws->hdrs.wskey, "Sec-WebSocket-Key", ws->keybuf); + nni_http_set_static_header( + ws->http, &ws->hdrs.wsversion, "Sec-WebSocket-Version", "13"); + + if (d->proto != NULL) { + nni_http_set_static_header(ws->http, &ws->hdrs.wsproto, + "Sec-WebSocket-Protocol", d->proto); } NNI_LIST_FOREACH (&d->headers, hdr) { - if ((rv = SETH(hdr->name, hdr->value)) != 0) { + if ((rv = nni_http_set_header( + ws->http, hdr->name, hdr->value)) != 0) { goto err; } } -#undef SETH - - ws->req = req; - nni_http_write_req(ws->http, req, &ws->httpaio); + nni_http_write_req(ws->http, &ws->httpaio); nni_mtx_unlock(&ws->mtx); return; @@ -2455,20 +2370,6 @@ ws_dialer_get_recvmax(void *arg, void *buf, size_t *szp, nni_type t) } static int -ws_dialer_set_req_headers(void *arg, const void *buf, size_t sz, nni_type t) -{ - nni_ws_dialer *d = arg; - int rv; - - if ((rv = ws_check_string(buf, sz, t)) == 0) { - nni_mtx_lock(&d->mtx); - rv = ws_set_headers(&d->headers, buf); - nni_mtx_unlock(&d->mtx); - } - return (rv); -} - -static int ws_dialer_set_proto(void *arg, const void *buf, size_t sz, nni_type t) { nni_ws_dialer *d = arg; @@ -2544,11 +2445,6 @@ static const nni_option ws_dialer_options[] = { .o_get = ws_dialer_get_recvmax, }, { - .o_name = NNG_OPT_WS_REQUEST_HEADERS, - .o_set = ws_dialer_set_req_headers, - // XXX: Get not implemented yet; likely of marginal value. - }, - { .o_name = NNG_OPT_WS_PROTOCOL, .o_set = ws_dialer_set_proto, .o_get = ws_dialer_get_proto, @@ -2574,7 +2470,7 @@ ws_dialer_set_header( nni_ws_dialer *d, const char *name, const void *buf, size_t sz, nni_type t) { int rv; - name += strlen(NNG_OPT_WS_REQUEST_HEADER); + name += strlen(NNG_OPT_WS_HEADER); if ((rv = ws_check_string(buf, sz, t)) == 0) { nni_mtx_lock(&d->mtx); rv = ws_set_header(&d->headers, name, buf); @@ -2596,7 +2492,7 @@ ws_dialer_set( } if (rv == NNG_ENOTSUP) { - if (startswith(name, NNG_OPT_WS_REQUEST_HEADER)) { + if (startswith(name, NNG_OPT_WS_HEADER)) { rv = ws_dialer_set_header(d, name, buf, sz, t); } } @@ -2773,34 +2669,10 @@ ws_str_recv(void *arg, nng_aio *aio) } static int -ws_get_request_headers(void *arg, void *buf, size_t *szp, nni_type t) -{ - nni_ws *ws = arg; - nni_mtx_lock(&ws->mtx); - if (ws->reqhdrs == NULL) { - ws->reqhdrs = nni_http_req_headers(ws->req); - } - nni_mtx_unlock(&ws->mtx); - return (nni_copyout_str(ws->reqhdrs, buf, szp, t)); -} - -static int -ws_get_response_headers(void *arg, void *buf, size_t *szp, nni_type t) -{ - nni_ws *ws = arg; - nni_mtx_lock(&ws->mtx); - if (ws->reshdrs == NULL) { - ws->reshdrs = nni_http_res_headers(ws->res); - } - nni_mtx_unlock(&ws->mtx); - return (nni_copyout_str(ws->reshdrs, buf, szp, t)); -} - -static int ws_get_request_uri(void *arg, void *buf, size_t *szp, nni_type t) { nni_ws *ws = arg; - return (nni_copyout_str(nni_http_req_get_uri(ws->req), buf, szp, t)); + return (nni_copyout_str(nni_http_get_uri(ws->http), buf, szp, t)); } static int @@ -2829,14 +2701,6 @@ ws_get_send_text(void *arg, void *buf, size_t *szp, nni_type t) static const nni_option ws_options[] = { { - .o_name = NNG_OPT_WS_REQUEST_HEADERS, - .o_get = ws_get_request_headers, - }, - { - .o_name = NNG_OPT_WS_RESPONSE_HEADERS, - .o_get = ws_get_response_headers, - }, - { .o_name = NNG_OPT_WS_REQUEST_URI, .o_get = ws_get_request_uri, }, @@ -2854,25 +2718,11 @@ static const nni_option ws_options[] = { }; static int -ws_get_req_header( - nni_ws *ws, const char *nm, void *buf, size_t *szp, nni_type t) -{ - const char *s; - nm += strlen(NNG_OPT_WS_REQUEST_HEADER); - s = nni_http_req_get_header(ws->req, nm); - if (s == NULL) { - return (NNG_ENOENT); - } - return (nni_copyout_str(s, buf, szp, t)); -} - -static int -ws_get_res_header( - nni_ws *ws, const char *nm, void *buf, size_t *szp, nni_type t) +ws_get_header(nni_ws *ws, const char *nm, void *buf, size_t *szp, nni_type t) { const char *s; - nm += strlen(NNG_OPT_WS_RESPONSE_HEADER); - s = nni_http_res_get_header(ws->res, nm); + nm += strlen(NNG_OPT_WS_HEADER); + s = nni_http_get_header(ws->http, nm); if (s == NULL) { return (NNG_ENOENT); } @@ -2897,10 +2747,8 @@ ws_str_get(void *arg, const char *nm, void *buf, size_t *szp, nni_type t) } // Check for generic headers... if (rv == NNG_ENOTSUP) { - if (startswith(nm, NNG_OPT_WS_REQUEST_HEADER)) { - rv = ws_get_req_header(ws, nm, buf, szp, t); - } else if (startswith(nm, NNG_OPT_WS_RESPONSE_HEADER)) { - rv = ws_get_res_header(ws, nm, buf, szp, t); + if (startswith(nm, NNG_OPT_WS_HEADER)) { + rv = ws_get_header(ws, nm, buf, szp, t); } } return (rv); diff --git a/src/supplemental/websocket/websocket_test.c b/src/supplemental/websocket/websocket_test.c index 30ebb3f5..98bcf658 100644 --- a/src/supplemental/websocket/websocket_test.c +++ b/src/supplemental/websocket/websocket_test.c @@ -163,10 +163,10 @@ test_websocket_conn_props(void) NUTS_PASS(nng_stream_dialer_alloc(&d, uri)); NUTS_PASS(nng_stream_dialer_set_string( - d, NNG_OPT_WS_REQUEST_HEADER "NNG-Req", "True")); + d, NNG_OPT_WS_HEADER "NNG-Req", "True")); NUTS_PASS(nng_stream_listener_set_string( - l, NNG_OPT_WS_RESPONSE_HEADER "NNG-Rep", "True")); + l, NNG_OPT_WS_HEADER "NNG-Rep", "True")); nng_stream_dialer_dial(d, daio); nng_stream_listener_accept(l, laio); @@ -203,20 +203,20 @@ test_websocket_conn_props(void) nng_stream_get_size(c1, NNG_OPT_TCP_NODELAY, &sz), NNG_EBADTYPE); NUTS_FAIL(nng_stream_get_string( - c1, NNG_OPT_WS_REQUEST_HEADER "No-Such-Header", &str), + c1, NNG_OPT_WS_HEADER "No-Such-Header", &str), NNG_ENOENT); - NUTS_PASS(nng_stream_get_string( - c1, NNG_OPT_WS_REQUEST_HEADER "NNG-Req", &str)); + NUTS_PASS( + nng_stream_get_string(c1, NNG_OPT_WS_HEADER "NNG-Req", &str)); NUTS_MATCH(str, "True"); nng_strfree(str); - NUTS_PASS(nng_stream_get_string( - c2, NNG_OPT_WS_RESPONSE_HEADER "NNG-Rep", &str)); + NUTS_PASS( + nng_stream_get_string(c2, NNG_OPT_WS_HEADER "NNG-Rep", &str)); NUTS_MATCH(str, "True"); nng_strfree(str); NUTS_PASS(nng_stream_get_string( - c1, NNG_OPT_WS_REQUEST_HEADER "Sec-WebSocket-Version", &str)); + c1, NNG_OPT_WS_HEADER "Sec-WebSocket-Version", &str)); NUTS_TRUE(str != NULL); NUTS_TRUE(strcmp(str, "13") == 0); nng_strfree(str); |
