enable buffered reader for request bodies with fixed content-length

Wed, 18 Feb 2026 12:31:19 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 18 Feb 2026 12:31:19 +0100
changeset 683
db37761a8494
parent 682
f4c593a99266
child 684
48da20bde908

enable buffered reader for request bodies with fixed content-length

src/server/daemon/httprequest.c file | annotate | diff | comparison | revisions
src/server/proxy/httpclient.c file | annotate | diff | comparison | revisions
src/server/proxy/httpclient.h file | annotate | diff | comparison | revisions
src/server/util/io.c file | annotate | diff | comparison | revisions
src/server/util/io.h file | annotate | diff | comparison | revisions
--- a/src/server/daemon/httprequest.c	Wed Feb 18 10:53:55 2026 +0100
+++ b/src/server/daemon/httprequest.c	Wed Feb 18 12:31:19 2026 +0100
@@ -399,16 +399,41 @@
        
     // check for request body and prepare input buffer
     char *ctlen_str = pblock_findkeyval(pb_key_content_length, rq->rq.headers);
-    if(ctlen_str) {
+    char *transfer_encoding = pblock_findkeyval(pb_key_transfer_encoding, rq->rq.headers);
+    if(ctlen_str || transfer_encoding) {
+        netbuf *nb = sn->netbuf;
+        HttpStream *net_io = (HttpStream*)sn->sn.csd;
+        
+        // a separate buffer is required for reading chunked transfer enc
+        sn->buffer = pool_malloc(pool, nb->maxsize);
+        if(!sn->buffer) {
+            request->status = 503;
+            return 1;
+        }
+
+        // copy remaining bytes from inbuf to the additional buffer
+        if(nb->cursize - nb->pos > 0) {
+            memcpy(sn->buffer, nb->inbuf, nb->cursize);
+        }
+
+        sn->pos = nb->pos;
+        sn->cursize = nb->cursize;
+
+        // clear inbuf
+        nb->pos = 0;
+        nb->cursize = 0;
+        
         int64_t ctlen;
-        if(util_strtoint(ctlen_str, &ctlen)) {
-            netbuf *nb = sn->netbuf;
-            HttpStream *net_io = (HttpStream*)sn->sn.csd;
+        if(ctlen_str && util_strtoint(ctlen_str, &ctlen)) {
             net_io->read_eof = WS_FALSE;
-
+            if(httpstream_enable_buffered_read(sn->sn.csd, sn->buffer, nb->maxsize, &sn->cursize, &sn->pos)) {
+                request->status = 500; // should not happen
+                return 1;
+            }
+            
             // how many bytes are already read and in the buffer
             int cur_input_available = nb->cursize - nb->pos;
-
+            
             if(cur_input_available >= ctlen) {
                 // we have the whole request body in the buffer and
                 // maybe even more
@@ -419,37 +444,12 @@
                 // read still required to get the complete request body
                 net_io->max_read = ctlen - cur_input_available;
             }
-            //printf("request body length: %d\n", ctlen);
-        } // else: should we abort?
-    }
-    char *transfer_encoding = pblock_findkeyval(pb_key_transfer_encoding, rq->rq.headers);
-    if(transfer_encoding) {
-        if(!strcmp(transfer_encoding, "chunked")) {
-            netbuf *nb = sn->netbuf;
-            // a separate buffer is required for reading chunked transfer enc
-            sn->buffer = pool_malloc(pool, nb->maxsize);
-            if(!sn->buffer) {
-                request->status = 503;
-                return 1;
-            }
-            
-            // copy remaining bytes from inbuf to the additional buffer
-            if(nb->cursize - nb->pos > 0) {
-                memcpy(sn->buffer, nb->inbuf, nb->cursize);
-            }
-            
-            sn->pos = nb->pos;
-            sn->cursize = nb->cursize;
-            
-            // clear inbuf
-            nb->pos = 0;
-            nb->cursize = 0;
-            
+        } else if (transfer_encoding) {
             if(httpstream_enable_chunked_read(sn->sn.csd, sn->buffer, nb->maxsize, &sn->cursize, &sn->pos)) {
                 request->status = 500; // should not happen
                 return 1;
             }
-        } // else: TODO: unknown transfer encoding error
+        }
     }
     
     //
--- a/src/server/proxy/httpclient.c	Wed Feb 18 10:53:55 2026 +0100
+++ b/src/server/proxy/httpclient.c	Wed Feb 18 12:31:19 2026 +0100
@@ -42,6 +42,7 @@
 
 static int client_send_request(HttpClient *client);
 static int client_send_request_body(HttpClient *client);
+static int client_read_response_header(HttpClient *client);
 
 HttpClient* http_client_new(EventHandler *ev) {
     CxMempool *mp = cxMempoolCreate(32, CX_MEMPOOL_TYPE_PURE);
@@ -274,7 +275,7 @@
     
     char *buffer;
     size_t nbytes;
-    if(client->header_complete) {
+    if(client->response_header_complete) {
         buffer = client->buffer.inbuf;
         nbytes = client->buffer.maxsize;
     } else {
@@ -286,7 +287,7 @@
     ssize_t r;
     while((r = read(client->socketfd, buffer, nbytes)) > 0) {
         client->buffer.cursize += r;
-        if(!client->header_complete) {
+        if(!client->response_header_complete) {
             switch(http_parser_process(client->parser)) {
                 case 0: { // finish
                     if(!http_parser_validate(client->parser)) {
@@ -295,7 +296,7 @@
                     }
                     client->statuscode = client->parser->status;
                     
-                    client->header_complete = 1;
+                    client->response_header_complete = 1;
                     if(client->response_start) {
                         cxmutstr msg = client->parser->msg;
                         char t = msg.ptr[msg.length];
@@ -464,6 +465,55 @@
     return 0;
 }
 
+/*
+static int client_read_response_header(HttpClient *client) {
+    if(client->response_header_complete) {
+        return 0;
+    }
+    
+    char *buffer = client->buffer.inbuf + client->buffer.pos;
+    size_t nbytes = client->buffer.maxsize - client->buffer.cursize;
+    
+    ssize_t r;
+    while((r = read(client->socketfd, buffer, nbytes)) > 0) {
+        client->buffer.cursize += r;
+        if(!client->response_header_complete) {
+            switch(http_parser_process(client->parser)) {
+                case 0: { // finish
+                    if(!http_parser_validate(client->parser)) {
+                        client->error = 1;
+                        return 0;
+                    }
+                    client->statuscode = client->parser->status;
+                    
+                    client->response_header_complete = 1;
+                    if(client->response_start) {
+                        cxmutstr msg = client->parser->msg;
+                        char t = msg.ptr[msg.length];
+                        msg.ptr[msg.length] = 0;
+                        int ret = client->response_start(client, client->statuscode, msg.ptr, client->response_start_userdata);
+                        msg.ptr[msg.length] = t;
+                        
+                        // TODO: check ret
+                    }
+                    break;
+                }
+                case 1: { // need more data
+                    continue;
+                }
+                case 2: { // error
+                    client->error = 1;
+                    return 0;
+                }
+            }
+        }
+        
+        // header complete
+
+    }
+}
+*/
+
 /* --------------------------------- Tests --------------------------------- */
 
 static CX_TEST(test_http_client_send_request) {
@@ -755,7 +805,6 @@
             }
         }
         CX_TEST_ASSERT(req.cur_reads < req.max_reads);
-        //CX_TEST_ASSERT(buf.size == 1084 + 5);
         
         // verify chunks
         char test_request_body[1024];
@@ -776,6 +825,7 @@
             }
             
             char *data = str + 4;
+            CX_TEST_ASSERT(data + chunklen < buf.space + buf.size);
             memcpy(test_request_body + pos, data, chunklen);
             pos += chunklen;
             str = data + chunklen;
--- a/src/server/proxy/httpclient.h	Wed Feb 18 10:53:55 2026 +0100
+++ b/src/server/proxy/httpclient.h	Wed Feb 18 12:31:19 2026 +0100
@@ -31,6 +31,7 @@
 
 #include "../public/nsapi.h"
 #include "../daemon/httpparser.h"
+#include "../util/io.h"
 
 #include <sys/socket.h>
 #include <cx/string.h>
@@ -59,6 +60,7 @@
     size_t addrlen;
     
     int socketfd;
+    HttpStream *stream;
     
     HeaderArray *request_headers;
     HeaderArray *response_headers;
@@ -128,7 +130,7 @@
     
     int request_body_complete;
     int request_body_terminated;
-    int header_complete;
+    int response_header_complete;
     
     Event readev;
     Event writeev;
--- a/src/server/util/io.c	Wed Feb 18 10:53:55 2026 +0100
+++ b/src/server/util/io.c	Wed Feb 18 12:31:19 2026 +0100
@@ -306,6 +306,20 @@
     return 0;
 }
 
+int httpstream_enable_buffered_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos) {
+    if(st->read != (io_read_f)net_http_read) {
+        log_ereport(LOG_FAILURE, "%s", "httpstream_enable_chunked_read: IOStream is not an HttpStream");
+        return 1;
+    }
+    st->read = (io_read_f)net_http_read_buffered;
+    HttpStream *http = (HttpStream*)st;
+    http->readbuf = buffer;
+    http->bufsize = bufsize;
+    http->buflen = cursize;
+    http->bufpos = pos;
+    return 0;
+}
+
 int httpstream_enable_chunked_write(IOStream *st) {
     if(st->type != IO_STREAM_TYPE_HTTP) {
         log_ereport(LOG_FAILURE, "%s", "httpstream_enable_chunked_write: IOStream is not an HttpStream");
@@ -525,9 +539,9 @@
 
 #define BUF_UNNEEDED_DIFF 64
 /*
- * read from st->chunk_buf first, read from st->fd if perform_io is true
+ * read from st->readbuf first, read from st->fd if perform_io is true
  */
-static ssize_t net_http_read_buffered(HttpStream *st, char *buf, size_t nbytes, WSBool read_data, WSBool *perform_io) {
+static ssize_t http_read_buffered(HttpStream *st, char *buf, size_t nbytes, WSBool read_data, WSBool *perform_io) {
     ssize_t r = 0;
     
     //memset(buf, 'x', nbytes);
@@ -574,13 +588,18 @@
         
         if(rlen > 0) {
             // call func again to get data from buffer (no IO will be performed)
-            r += net_http_read_buffered(st, buf, nbytes, read_data, perform_io);
+            r += http_read_buffered(st, buf, nbytes, read_data, perform_io);
         }
     }
     
     return r;
 }
 
+ssize_t net_http_read_buffered(HttpStream *st, void *buf, size_t nbytes) {
+    WSBool perform_io = TRUE;
+    return http_read_buffered(st, buf, nbytes, TRUE, &perform_io);
+}
+
 
 /*
  * parses a chunk header
@@ -687,7 +706,7 @@
         // how many bytes are available in the current chunk
         size_t chunk_available = st->max_read - st->read;
         if(chunk_available > 0) {
-            ssize_t r = net_http_read_buffered(st, rbuf, rbuflen, TRUE, &perform_io);
+            ssize_t r = http_read_buffered(st, rbuf, rbuflen, TRUE, &perform_io);
             if(r == 0) {
                 break;
             }
@@ -705,7 +724,7 @@
                 return -1;
             }
             // fill st->chunk_buf
-            ssize_t r = net_http_read_buffered(st, &st->chunk_buf[st->chunk_buf_pos], chunkbuf_avail, FALSE, &perform_io);
+            ssize_t r = http_read_buffered(st, &st->chunk_buf[st->chunk_buf_pos], chunkbuf_avail, FALSE, &perform_io);
             if(r == 0) {
                 break;
             }
--- a/src/server/util/io.h	Wed Feb 18 10:53:55 2026 +0100
+++ b/src/server/util/io.h	Wed Feb 18 12:31:19 2026 +0100
@@ -201,6 +201,7 @@
 IOStream* httpstream_new(pool_handle_t *pool, IOStream *fd);
 
 int httpstream_enable_chunked_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos);
+int httpstream_enable_buffered_read(IOStream *st, char *buffer, size_t bufsize, int *cursize, int *pos);
 int httpstream_enable_chunked_write(IOStream *st);
 int httpstream_set_max_read(IOStream *st, int64_t maxread);
 WSBool httpstream_eof(IOStream *st);
@@ -209,6 +210,7 @@
 ssize_t net_http_write(HttpStream *st, const void *buf, size_t nbytes);
 ssize_t net_http_writev(HttpStream *st, struct iovec *iovec, int iovcnt);
 ssize_t net_http_read(HttpStream *st, void *buf, size_t nbytes);
+ssize_t net_http_read_buffered(HttpStream *st, void *buf, size_t nbytes);
 ssize_t net_http_read_chunked(HttpStream *st, void *buf, size_t nbytes);
 ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd);
 void    net_http_close(HttpStream *st);

mercurial