Mon, 16 Feb 2026 17:43:14 +0100
add support for request bodies with a fixed content length for the reverse proxy
--- a/src/server/proxy/httpclient.c Sun Feb 15 13:30:29 2026 +0100 +++ b/src/server/proxy/httpclient.c Mon Feb 16 17:43:14 2026 +0100 @@ -142,6 +142,13 @@ } int http_client_add_request_header_copy(HttpClient *client, cxstring name, cxstring value) { + if(!client->mp) { + client->mp = cxMempoolCreate(64, CX_MEMPOOL_TYPE_PURE); + if(!client->mp) { + return 1; + } + } + cxmutstr n = cx_strdup_a(client->mp->allocator, name); cxmutstr v = cx_strdup_a(client->mp->allocator, value); @@ -156,6 +163,18 @@ return err; } +int http_client_set_content_length(HttpClient *client, int64_t contentlength) { + client->req_content_length = contentlength; + char ctlen_buf[32]; + size_t len = snprintf(ctlen_buf, 32, "%" PRId64, contentlength); + return http_client_add_request_header_copy(client, cx_str("content-length"), cx_strn(ctlen_buf, len)); +} + +int http_client_enable_chunked_transfer_encoding(HttpClient *client) { + client->req_content_length = -1; + return http_client_add_request_header(client, cx_mutstr("transfer-encoding"), cx_mutstr("chunked")); +} + int http_client_start(HttpClient *client) { int socketfd = socket(AF_INET, SOCK_STREAM, 0); if(socketfd < 0) { @@ -192,7 +211,7 @@ static int create_req_buffer(HttpClient *client) { CxBuffer buf; - if(cxBufferInit(&buf, cxDefaultAllocator, NULL, 1024, CX_BUFFER_AUTO_EXTEND)) { + if(cxBufferInit(&buf, cxDefaultAllocator, NULL, HTTP_CLIENT_BUFFER_SIZE, CX_BUFFER_AUTO_EXTEND)) { return 1; } @@ -216,6 +235,7 @@ } cxBufferPutString(&buf, "\r\n"); client->req_buffer = buf.space; + client->req_buffer_alloc = buf.capacity; client->req_buffer_len = buf.size; return 0; @@ -236,13 +256,44 @@ HttpClient *client = event->cookie; if(client->req_buffer_pos < client->req_buffer_len) { if(client_send_request(client)) { - if(client->error) { - return 0; // TODO: set error + return client->error == 0; + } + } + + // do we need to send a request body? + if(client->req_content_length != 0) { + while(!client->request_body_complete) { + ssize_t r = client->request_body_read(client, client->req_buffer, client->req_buffer_alloc, client->request_body_read_userdata); + if(r <= 0) { + if(r == HTTP_CLIENT_CALLBACK_WOULD_BLOCK) { + return 1; + } else if(r == 0) { + // EOF + client->request_body_complete = 1; + break; + } else { + // error + client->error = 1; + return 0; + } } - return 1; - } + client->req_contentlength_pos += r; + client->req_buffer_pos = 0; + client->req_buffer_len = r; + if(client_send_request(client)) { + return client->error == 0; + } + } + + if(client->req_content_length > 0 && client->req_content_length != client->req_contentlength_pos) { + // incomplete request body + client->error = 1; + return 0; + } } + + // writing complete, switch to read events event->events = EVENT_POLLIN; @@ -338,7 +389,15 @@ static int client_send_request(HttpClient *client) { size_t nbytes = client->req_buffer_len - client->req_buffer_pos; - ssize_t w = write(client->socketfd, client->req_buffer + client->req_buffer_pos, nbytes); + ssize_t w; + while((w = write(client->socketfd, client->req_buffer + client->req_buffer_pos, nbytes)) > 0) { + client->req_buffer_pos += w; + nbytes = client->req_buffer_len - client->req_buffer_pos; + if(nbytes == 0) { + break; + } + } + if(w <= 0) { if(errno != EAGAIN) { // TODO: log correct host @@ -348,8 +407,6 @@ return 1; } - client->req_buffer_pos += w; - return client->req_buffer_pos < client->req_buffer_len; }
--- a/src/server/proxy/httpclient.h Sun Feb 15 13:30:29 2026 +0100 +++ b/src/server/proxy/httpclient.h Mon Feb 16 17:43:14 2026 +0100 @@ -37,6 +37,8 @@ #include <cx/mempool.h> #include <cx/test.h> +#include <inttypes.h> + #ifdef __cplusplus extern "C" { #endif @@ -61,17 +63,27 @@ HeaderArray *request_headers; HeaderArray *response_headers; + /* + * request content length + * 0: no request body + * > 0: request body with static length + * -1: request body with chunked transfer encoding + */ + int64_t req_content_length; + int error; int statuscode; /* * Request body callback function * - * size_t request_body_read(HttpClient *client, void *buf, size_t size, void *userdata) + * ssize_t request_body_read(HttpClient *client, void *buf, size_t size, void *userdata) + * + * Return: number of processed bytes, + * HTTP_CLIENT_CALLBACK_WOULD_BLOCK or HTTP_CLIENT_CALLBACK_ERROR. */ - // TODO: fix, doesn't work this way - //size_t (*request_body_read)(HttpClient *, void *, size_t, void *); - //void *request_body_read_userdata; + ssize_t (*request_body_read)(HttpClient *, void *, size_t, void *); + void *request_body_read_userdata; /* * Response start callback function @@ -109,9 +121,12 @@ netbuf buffer; char *req_buffer; + size_t req_buffer_alloc; size_t req_buffer_len; size_t req_buffer_pos; + size_t req_contentlength_pos; + int request_body_complete; int header_complete; Event readev; @@ -148,6 +163,16 @@ */ int http_client_add_request_header_copy(HttpClient *client, cxstring name, cxstring value); +/* + * Sets the content length for the request body + */ +int http_client_set_content_length(HttpClient *client, int64_t contentlength); + +/* + * Enables a request body with a chunked transfer encoding + */ +int http_client_enable_chunked_transfer_encoding(HttpClient *client); + int http_client_start(HttpClient *client);
--- a/src/server/safs/proxy.c Sun Feb 15 13:30:29 2026 +0100 +++ b/src/server/safs/proxy.c Mon Feb 16 17:43:14 2026 +0100 @@ -151,6 +151,16 @@ nsapi_function_return(proxy->sn, proxy->rq, ret); } +static ssize_t proxy_request_read(HttpClient *client, void *buf, size_t nbytes, void *userdata) { + ProxyRequest *proxy = userdata; + int ret = netbuf_getbytes(proxy->sn->inbuf, buf, nbytes); + if(ret == NETBUF_EOF) { + ret = 0; + } + // TODO: handle errors + return ret; +} + static ssize_t proxy_response_write(HttpClient *client, void *buf, size_t nbytes, void *userdata) { ProxyRequest *proxy = userdata; ssize_t ret = net_write(proxy->sn->csd, buf, nbytes); @@ -179,10 +189,12 @@ proxy->response_header_rewrite = pblock_create_pool(sn->pool, 16); proxy->response_started = 0; - // some request/response headers should be removed or altered - // an empty string means, the header should be removed + // Some request/response headers should be removed or altered + // An empty string means, the header should be removed pblock_nvinsert("host", "", proxy->request_header_rewrite); pblock_nvinsert("connection", "", proxy->request_header_rewrite); + pblock_nvinsert("transfer-encoding", "", proxy->request_header_rewrite); + pblock_nvinsert("content-length", "", proxy->request_header_rewrite); pblock_nvinsert("server", "", proxy->response_header_rewrite); pblock_nvinsert("connection", "", proxy->response_header_rewrite); @@ -216,9 +228,35 @@ cxmutstr header_value; char *rewrite_header = pblock_findval(entry->param->name, proxy->request_header_rewrite); if(rewrite_header) { - header_value = cx_mutstr(rewrite_header); - if(header_value.length == 0) { + if(!strcmp(entry->param->name, "transfer-encoding")) { + if(!strcmp(entry->param->value, "chunked")) { + // enable chunked transfer encoding + if(http_client_enable_chunked_transfer_encoding(client)) { + http_client_free(client); + return REQ_ABORTED; + } + continue; + } + } else if(!strcmp(entry->param->name, "content-length")) { + long long contentlength; + if(!cx_strtoll(cx_str(entry->param->value), &contentlength, 10)) { + if(http_client_set_content_length(client, contentlength)) { + http_client_free(client); + return REQ_ABORTED; + } + } else { + // illegal content-length + protocol_status(sn, rq, 400, NULL); + http_client_free(client); + return REQ_ABORTED; + } continue; + } else { + // static header rewrite or remove header if it is empty + header_value = cx_mutstr(rewrite_header); + if(header_value.length == 0) { + continue; + } } } else { header_value = cx_mutstr(entry->param->value); @@ -230,6 +268,8 @@ } } + client->request_body_read = proxy_request_read; + client->request_body_read_userdata = proxy; client->response_start = proxy_response_start; client->response_start_userdata = proxy; client->response_body_write = proxy_response_write;