add support for request bodies with a fixed content length for the reverse proxy

Mon, 16 Feb 2026 17:43:14 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 16 Feb 2026 17:43:14 +0100
changeset 675
edacba8beedb
parent 674
6a031133a498
child 676
d43f1dd8b18e

add support for request bodies with a fixed content length for the reverse proxy

src/server/proxy/httpclient.c file | annotate | diff | comparison | revisions
src/server/proxy/httpclient.h file | annotate | diff | comparison | revisions
src/server/safs/proxy.c file | annotate | diff | comparison | revisions
--- 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;

mercurial