diff options
Diffstat (limited to 'src/supplemental/http/http_conn.c')
| -rw-r--r-- | src/supplemental/http/http_conn.c | 251 |
1 files changed, 199 insertions, 52 deletions
diff --git a/src/supplemental/http/http_conn.c b/src/supplemental/http/http_conn.c index 20ae2f05..f3e02314 100644 --- a/src/supplemental/http/http_conn.c +++ b/src/supplemental/http/http_conn.c @@ -9,6 +9,7 @@ // found online at https://opensource.org/licenses/MIT. // +#include <complex.h> #include <ctype.h> #include <stdbool.h> #include <stdio.h> @@ -24,7 +25,9 @@ // We insist that individual headers fit in 8K. // If you need more than that, you need something we can't do. -#define HTTP_BUFSIZE 8192 +// We leave some room for allocator overhead (32 bytes should +// be more than enough), to avoid possibly wasting an extra page. +#define HTTP_BUFSIZE (8192 - 32) // types of reads enum read_flavor { @@ -33,6 +36,7 @@ enum read_flavor { HTTP_RD_REQ, HTTP_RD_RES, HTTP_RD_CHUNK, + HTTP_RD_DISCARD, }; enum write_flavor { @@ -45,7 +49,6 @@ enum write_flavor { struct nng_http_conn { nng_stream *sock; void *ctx; - bool closed; nni_list rdq; // high level http read requests nni_list wrq; // high level http write requests @@ -59,16 +62,23 @@ struct nng_http_conn { nng_http_req req; nng_http_res res; - enum read_flavor rd_flavor; - uint8_t *rd_buf; - size_t rd_get; - size_t rd_put; - size_t rd_bufsz; - bool rd_buffered; - bool client; // true if this is a client's connection - bool res_sent; - + char meth[32]; + char host[260]; // 253 per IETF, plus 6 for :port plus null + char ubuf[200]; // Most URIs are smaller than this + const char *vers; + char *uri; + uint8_t *buf; + size_t bufsz; + size_t rd_get; + size_t rd_put; + size_t rd_discard; + + enum read_flavor rd_flavor; enum write_flavor wr_flavor; + bool rd_buffered; + bool client; // true if a client's connection + bool res_sent; + bool closed; }; nng_http_req * @@ -141,14 +151,14 @@ 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 +// http_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) +http_buf_pull_up(nni_http_conn *conn) { if (conn->rd_get != 0) { - memmove(conn->rd_buf, conn->rd_buf + conn->rd_get, + memmove(conn->buf, conn->buf + conn->rd_get, conn->rd_put - conn->rd_get); conn->rd_put -= conn->rd_get; conn->rd_get = 0; @@ -161,7 +171,7 @@ http_rd_buf(nni_http_conn *conn, nni_aio *aio) { size_t cnt = conn->rd_put - conn->rd_get; size_t n; - uint8_t *rbuf = conn->rd_buf; + uint8_t *rbuf = conn->buf; int rv; bool raw = false; nni_iov *iov; @@ -212,6 +222,25 @@ http_rd_buf(nni_http_conn *conn, nni_aio *aio) nng_stream_recv(conn->sock, &conn->rd_aio); return (NNG_EAGAIN); + case HTTP_RD_DISCARD: + n = conn->rd_put - conn->rd_get; + if (n > conn->rd_discard) { + n = conn->rd_discard; + } + conn->rd_get += n; + conn->rd_discard -= n; + http_buf_pull_up(conn); + if (conn->rd_discard > 0) { + nni_iov iov1; + iov1.iov_buf = conn->buf + conn->rd_put; + iov1.iov_len = conn->bufsz - conn->rd_put; + conn->rd_buffered = true; + nni_aio_set_iov(&conn->rd_aio, 1, &iov1); + nng_stream_recv(conn->sock, &conn->rd_aio); + return (NNG_EAGAIN); + } + return (0); + case HTTP_RD_REQ: conn->client = true; rv = nni_http_req_parse(conn, rbuf, cnt, &n); @@ -222,9 +251,9 @@ http_rd_buf(nni_http_conn *conn, nni_aio *aio) } 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; + http_buf_pull_up(conn); + iov1.iov_buf = conn->buf + conn->rd_put; + iov1.iov_len = conn->bufsz - conn->rd_put; conn->rd_buffered = true; if (iov1.iov_len == 0) { return (NNG_EMSGSIZE); @@ -244,9 +273,9 @@ http_rd_buf(nni_http_conn *conn, nni_aio *aio) } 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; + http_buf_pull_up(conn); + iov1.iov_buf = conn->buf + conn->rd_put; + iov1.iov_len = conn->bufsz - conn->rd_put; conn->rd_buffered = true; if (iov1.iov_len == 0) { return (NNG_EMSGSIZE); @@ -265,8 +294,8 @@ http_rd_buf(nni_http_conn *conn, nni_aio *aio) } if (rv == NNG_EAGAIN) { nni_iov iov1; - iov1.iov_buf = conn->rd_buf + conn->rd_put; - iov1.iov_len = conn->rd_bufsz - conn->rd_put; + iov1.iov_buf = conn->buf + conn->rd_put; + iov1.iov_len = conn->bufsz - conn->rd_put; conn->rd_buffered = true; nni_aio_set_iov(&conn->rd_aio, 1, &iov1); nng_stream_recv(conn->sock, &conn->rd_aio); @@ -341,7 +370,7 @@ http_rd_cb(void *arg) // If we were reading into the buffer, then advance location(s). if (conn->rd_buffered) { conn->rd_put += cnt; - NNI_ASSERT(conn->rd_put <= conn->rd_bufsz); + NNI_ASSERT(conn->rd_put <= conn->bufsz); http_rd_start(conn); nni_mtx_unlock(&conn->mtx); return; @@ -542,9 +571,15 @@ nni_http_conn_reset(nng_http *conn) { 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) snprintf(conn->meth, sizeof(conn->meth), "GET"); + if (strlen(conn->host)) { + nni_http_set_host(conn, conn->host); } + if (conn->uri != NULL && conn->uri != conn->ubuf) { + nni_strfree(conn->uri); + } + conn->uri = NULL; + nni_http_set_version(conn, NNG_HTTP_VERSION_1_1); } void @@ -572,6 +607,7 @@ nni_http_read_chunks(nni_http_conn *conn, nni_http_chunks *cl, nni_aio *aio) nni_aio_set_prov_data(aio, cl); nni_mtx_lock(&conn->mtx); + conn->rd_discard = 0; http_rd_submit(conn, aio, HTTP_RD_CHUNK); nni_mtx_unlock(&conn->mtx); } @@ -582,11 +618,21 @@ nni_http_read_full(nni_http_conn *conn, nni_aio *aio) nni_aio_set_prov_data(aio, NULL); nni_mtx_lock(&conn->mtx); + conn->rd_discard = 0; http_rd_submit(conn, aio, HTTP_RD_FULL); nni_mtx_unlock(&conn->mtx); } void +nni_http_read_discard(nng_http *conn, size_t discard, nng_aio *aio) +{ + nni_mtx_lock(&conn->mtx); + conn->rd_discard = discard; + http_rd_submit(conn, aio, HTTP_RD_DISCARD); + nni_mtx_unlock(&conn->mtx); +} + +void nni_http_read(nni_http_conn *conn, nni_aio *aio) { nni_aio_set_prov_data(aio, NULL); @@ -596,8 +642,97 @@ nni_http_read(nni_http_conn *conn, nni_aio *aio) nni_mtx_unlock(&conn->mtx); } +static size_t +http_sprintf_headers(char *buf, size_t sz, nni_list *list) +{ + size_t rv = 0; + http_header *h; + + if (buf == NULL) { + sz = 0; + } + + NNI_LIST_FOREACH (list, h) { + size_t l; + l = snprintf(buf, sz, "%s: %s\r\n", h->name, h->value); + if (buf != NULL) { + buf += l; + } + sz = (sz > l) ? sz - l : 0; + rv += l; + } + return (rv); +} + +static int +http_snprintf(nng_http *conn, char *buf, size_t sz) +{ + size_t len; + size_t n; + nni_list *hdrs; + + if (conn->client) { + len = snprintf(buf, sz, "%s %s %s\r\n", + nni_http_get_method(conn), nni_http_get_uri(conn), + nni_http_get_version(conn)); + hdrs = &conn->req.data.hdrs; + } else { + len = snprintf(buf, sz, "%s %d %s\r\n", + nni_http_get_version(conn), nni_http_get_status(conn), + nni_http_get_reason(conn)); + hdrs = &conn->res.data.hdrs; + } + + if (len < sz) { + sz -= len; + buf += len; + } else { + sz = 0; + buf = NULL; + } + + n = http_sprintf_headers(buf, sz, hdrs); + len += n; + if (len < sz) { + sz -= n; + buf += n; + } else { + sz = 0; + buf = NULL; + } + + len += snprintf(buf, sz, "\r\n"); + return (len); +} + +static int +http_prepare(nng_http *conn, void **data, size_t *szp) +{ + size_t len; + + // get length needed first + len = http_snprintf(conn, NULL, 0); + + // If it fits in the fixed buffer, use it. It should cover + // like 99% or more cases, as this buffer is 8KB. + if (len < conn->bufsz) { + http_snprintf(conn, (char *) conn->buf, conn->bufsz); + *data = conn->buf; + *szp = len; + return (0); + } + + // we have to allocate. + if ((*data = nni_alloc(len + 1)) == NULL) { + return (NNG_ENOMEM); + } + http_snprintf(conn, *data, len + 1); + *szp = len; // this does not include the terminating null + return (0); +} + void -nni_http_write_req(nni_http_conn *conn, nni_aio *aio) +nni_http_write_req(nng_http *conn, nni_aio *aio) { int rv; void *buf; @@ -605,10 +740,15 @@ nni_http_write_req(nni_http_conn *conn, nni_aio *aio) nni_iov iov[2]; int niov; - if ((rv = nni_http_req_get_buf(&conn->req, &buf, &bufsz)) != 0) { + if ((rv = http_prepare(conn, &buf, &bufsz)) != 0) { nni_aio_finish_error(aio, rv); return; } + if (buf != conn->buf) { + nni_free(conn->req.data.buf, conn->req.data.bufsz); + conn->req.data.buf = buf; + conn->req.data.bufsz = bufsz + 1; // including \0 + } niov = 1; iov[0].iov_len = bufsz; iov[0].iov_buf = buf; @@ -625,7 +765,7 @@ nni_http_write_req(nni_http_conn *conn, nni_aio *aio) } void -nni_http_write_res(nni_http_conn *conn, nni_aio *aio) +nni_http_write_res(nng_http *conn, nni_aio *aio) { int rv; void *buf; @@ -633,11 +773,17 @@ nni_http_write_res(nni_http_conn *conn, nni_aio *aio) nni_iov iov[2]; int nio; - conn->res_sent = true; - if ((rv = nni_http_res_get_buf(conn, &buf, &bufsz)) != 0) { + if ((rv = http_prepare(conn, &buf, &bufsz)) != 0) { nni_aio_finish_error(aio, rv); return; } + if (buf != conn->buf) { + nni_free(conn->res.data.buf, conn->res.data.bufsz); + conn->res.data.buf = buf; + conn->res.data.bufsz = bufsz + 1; // including \0 + } + + conn->res_sent = true; nio = 1; iov[0].iov_len = bufsz; iov[0].iov_buf = buf; @@ -672,7 +818,7 @@ nni_http_write_full(nni_http_conn *conn, nni_aio *aio) const char * nni_http_get_version(nng_http *conn) { - return (conn->req.vers); + return (conn->vers); } int @@ -691,8 +837,7 @@ nni_http_set_version(nng_http *conn, 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) { - conn->req.vers = http_versions[i]; - conn->res.vers = http_versions[i]; + conn->vers = http_versions[i]; return (0); } } @@ -707,18 +852,21 @@ nni_http_set_method(nng_http *conn, const char *method) } // this may truncate the method, but nobody should be sending // methods so long. - (void) snprintf(conn->req.meth, sizeof(conn->req.meth), "%s", method); + (void) snprintf(conn->meth, sizeof(conn->meth), "%s", method); } const char * nni_http_get_method(nng_http *conn) { - return (conn->req.meth); + return (conn->meth); } uint16_t nni_http_get_status(nng_http *conn) { + if (conn->res.code == 0) { + return (NNG_HTTP_STATUS_OK); + } return (conn->res.code); } @@ -944,12 +1092,12 @@ nni_http_set_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); + if (host != conn->host) { + snprintf(conn->host, sizeof(conn->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.value = conn->host; conn->req.host_header.static_name = true; conn->req.host_header.static_value = true; conn->req.host_header.alloc_header = false; @@ -980,7 +1128,7 @@ nni_http_set_content_type(nng_http *conn, const char *ctype) const char * nni_http_get_uri(nng_http *conn) { - return (conn->req.uri); + return (conn->uri); } int @@ -998,25 +1146,24 @@ nni_http_set_uri(nng_http *conn, const char *uri, const char *query) needed = strlen(uri); } - if (conn->req.uri != NULL && (strcmp(uri, conn->req.uri) == 0) && + if (conn->uri != NULL && (strcmp(uri, conn->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); + if (conn->uri != NULL && conn->uri != conn->ubuf) { + nni_strfree(conn->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; + if (needed < sizeof(conn->ubuf)) { + snprintf(conn->ubuf, sizeof(conn->ubuf), fmt, uri, query); + conn->uri = conn->ubuf; return (0); } // too big, we have to allocate it (slow path) - if (nni_asprintf(&conn->req.uri, fmt, uri, query) != 0) { + if (nni_asprintf(&conn->uri, fmt, uri, query) != 0) { return (NNG_ENOMEM); } return (0); @@ -1327,7 +1474,7 @@ nni_http_conn_fini(nni_http_conn *conn) nni_aio_fini(&conn->wr_aio); nni_aio_fini(&conn->rd_aio); nni_http_conn_reset(conn); - nni_free(conn->rd_buf, conn->rd_bufsz); + nni_free(conn->buf, conn->bufsz); nni_mtx_fini(&conn->mtx); NNI_FREE_STRUCT(conn); } @@ -1347,13 +1494,13 @@ http_init(nni_http_conn **connp, nng_stream *data, bool client) 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); + nni_http_set_method(conn, "GET"); - if ((conn->rd_buf = nni_alloc(HTTP_BUFSIZE)) == NULL) { + if ((conn->buf = nni_alloc(HTTP_BUFSIZE)) == NULL) { nni_http_conn_fini(conn); return (NNG_ENOMEM); } - conn->rd_bufsz = HTTP_BUFSIZE; + conn->bufsz = HTTP_BUFSIZE; nni_aio_init(&conn->wr_aio, http_wr_cb, conn); nni_aio_init(&conn->rd_aio, http_rd_cb, conn); |
