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_conn.c | |
| parent | a381af4f5ca79576a4a9b461529a0f22fcf1e088 (diff) | |
| download | nng-2.0.0-alpha.3.tar.gz nng-2.0.0-alpha.3.tar.bz2 nng-2.0.0-alpha.3.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_conn.c')
| -rw-r--r-- | src/supplemental/http/http_conn.c | 744 |
1 files changed, 702 insertions, 42 deletions
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); +} |
