diff options
| author | Garrett D'Amore <garrett@damore.org> | 2025-01-06 15:20:09 -0800 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2025-01-09 23:22:56 -0800 |
| commit | 73f50e2679525e7df8734c875a3c12732565f953 (patch) | |
| tree | 23bd167dfcd95305b58a29c142b51879011f63b2 /src/supplemental/http/http_client.c | |
| parent | a381af4f5ca79576a4a9b461529a0f22fcf1e088 (diff) | |
| download | nng-http-client-trans.tar.gz nng-http-client-trans.tar.bz2 nng-http-client-trans.zip | |
http: The big HTTP API refactoring of January 2025.v2.0.0-alpha.3http-client-trans
This represents a major change in the HTTP code base, consisting
of a complete revamp of the HTTP API. The changes here are too
numerous to mention, but the end result should be a vastly
simpler API for both server and client applications.
Many needless allocations were removed by providing fixed buffers
for various parameters and headers when possible.
A few bugs were fixed. Most especially we have fixed some bugs
around very large URIs and headers, and we have also addressed
conformance bugs to more closely conform to RFCs 9110 and 9112.
As part of this work, the APIs for WebSockets changed slightly
as well. In particular the properties available for accessing
headers have changed.
There is still documentation conversion work to do, and additional
functionality (such as proper support for chunked transfers), but
this is a big step in the right direction.
Diffstat (limited to 'src/supplemental/http/http_client.c')
| -rw-r--r-- | src/supplemental/http/http_client.c | 139 |
1 files changed, 55 insertions, 84 deletions
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); } |
