fix header iteration in client_read_response_header and add more tests

Thu, 19 Feb 2026 17:05:46 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 19 Feb 2026 17:05:46 +0100
changeset 686
9adf57ddcd0f
parent 685
349d62bfae29
child 687
4bded456b4a7

fix header iteration in client_read_response_header and add more tests

src/server/proxy/httpclient.c file | annotate | diff | comparison | revisions
--- a/src/server/proxy/httpclient.c	Thu Feb 19 17:05:13 2026 +0100
+++ b/src/server/proxy/httpclient.c	Thu Feb 19 17:05:46 2026 +0100
@@ -484,6 +484,10 @@
                 }
             }
         }
+        
+        if(headers) {
+            headers = headers->next;
+        }
     }
     
     if(contentlength > 0 || chunkedtransferenc) {
@@ -624,104 +628,6 @@
     return size;
 }
 
-static CX_TEST(test_http_client_io_simple) {
-    CX_TEST_DO {
-        EventHandler dummy;
-        HttpClient *client = http_client_new(&dummy);
-        
-        int fds[2];
-        util_socketpair(fds);
-        util_socket_setnonblock(fds[0], 1);
-        util_socket_setnonblock(fds[1], 1);
-        client->socketfd = fds[0];
-        int sock = fds[1];
-        
-        // setup client
-        http_client_set_uri(client, "/test/uri/");
-        http_client_set_method(client, "GET");
-        http_client_add_request_header(client, cx_mutstr("Host"), cx_mutstr("localhost"));
-        http_client_add_request_header(client, cx_mutstr("Test1"), cx_mutstr("value1"));
-        http_client_add_request_header(client, cx_mutstr("Test2"), cx_mutstr("value2"));
-        create_req_buffer(client);
-        
-        size_t req_header_len = client->transfer_buffer_len;
-        
-        // response buffer
-        CxBuffer buf;
-        cxBufferInit(&buf, cxDefaultAllocator, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
-        
-        TestResponse testr = { 0 };
-        testr.response = &buf;
-        client->response_start = test_response_start;
-        client->response_start_userdata = &testr;
-        client->response_body_write = test_response_body_write;
-        client->response_body_write_userdata = &testr;
-        
-        // test IO
-        Event event;
-        event.cookie = client;
-        int ret = client_io(&dummy, &event);
-        CX_TEST_ASSERT(!client->error);
-        CX_TEST_ASSERT(ret == 1);
-        
-        // do IO and read request until the header is processed
-        size_t req_header_pos = 0;
-        char req_buf[4];
-        while(req_header_pos < req_header_len) {
-            ssize_t r = read(sock, req_buf, 4);
-            if(r == 0) {
-                break;
-            }
-            CX_TEST_ASSERT(r > 0);
-            req_header_pos += r;
-            ret = client_io(&dummy, &event);
-            CX_TEST_ASSERT(!client->error);
-            CX_TEST_ASSERT(ret == 1);
-        }
-        CX_TEST_ASSERT(req_header_pos == req_header_len);
-        
-        char *response_str = 
-                "HTTP/1.1 200 OK\r\n"
-                "Host: localhost\r\n"
-                "Content-length: 13\r\n"
-                "\r\n"
-                "Hello World!\n";
-        size_t response_str_len = strlen(response_str);
-        size_t response_str_pos = 0;
-        
-        // send response and do IO
-        while(response_str_pos < response_str_len) {
-            size_t len = response_str_len - response_str_pos;
-            if(len > 3) {
-                //len = 3;
-            }
-            ssize_t w = write(sock, response_str + response_str_pos, len);
-            if(w == 0) {
-                break;
-            }
-            CX_TEST_ASSERT(w > 0);
-            response_str_pos += w;
-            
-            ret = client_io(&dummy, &event);
-            
-            CX_TEST_ASSERT(!client->error);
-        }
-        CX_TEST_ASSERT(response_str_pos == response_str_len);
-        CX_TEST_ASSERT(testr.status == 200);
-        CX_TEST_ASSERT(testr.msg);
-        CX_TEST_ASSERT(!strcmp(testr.msg, "OK"));
-        CX_TEST_ASSERT(testr.response->size == 13);
-        CX_TEST_ASSERT(!cx_strcmp(cx_strn(testr.response->space, testr.response->size), "Hello World!\n"));
-        
-        // cleanup
-        free(testr.msg);
-        close(fds[0]);
-        close(fds[1]);
-        http_client_free(client);
-        cxBufferDestroy(&buf);
-    }
-}
-
 
 typedef struct TestRequestBody {
     char   *content;
@@ -859,59 +765,243 @@
     }
 }
 
+static CX_TEST_SUBROUTINE(test_read_response, cxstring response_str, CxBuffer *response_body) {
+    EventHandler dummy;
+    HttpClient *client = http_client_new(&dummy);
+    create_req_buffer(client);
+    client->req_content_length = -1;
+
+    int fds[2];
+    util_socketpair(fds);
+    util_socket_setnonblock(fds[0], 1);
+    util_socket_setnonblock(fds[1], 1);
+    client->socketfd = fds[0];
+    int sock = fds[1];
+    
+    TestResponse testr = { 0 };
+    testr.response = response_body;
+    client->response_body_write = test_response_body_write;
+    client->response_body_write_userdata = &testr;
+
+    // test
+
+    size_t response_pos = 0;
+    while(response_pos < response_str.length) {
+        size_t nbytes = response_str.length - response_pos;
+        ssize_t w = write(sock, response_str.ptr + response_pos, nbytes);
+        if(w > 0) {
+            response_pos += w;
+        }
+
+        if(!client->response_header_complete) {
+            int ret = client_read_response_header(client);
+            CX_TEST_ASSERT(client->error == 0);
+            if(ret == 1) {
+                continue;
+            }
+        }
+        
+        if(response_body != NULL) {
+            CX_TEST_ASSERT(client->stream != NULL);
+            
+            int ret = client_read_response_body(client);
+            CX_TEST_ASSERT(client->error == 0);
+            if(ret == 1) {
+                continue;
+            }
+        } else {
+            CX_TEST_ASSERT(client->stream == NULL);
+        }
+
+        break;
+    }
+
+    // cleanup
+    close(fds[0]);
+    close(fds[1]);
+    http_client_free(client);
+}
+
 static CX_TEST(test_http_client_read_response_head) {
     CX_TEST_DO {
-        EventHandler dummy;
-        HttpClient *client = http_client_new(&dummy);
-        create_req_buffer(client);
-        client->req_content_length = -1;
-        
-        int fds[2];
-        util_socketpair(fds);
-        util_socket_setnonblock(fds[0], 1);
-        util_socket_setnonblock(fds[1], 1);
-        client->socketfd = fds[0];
-        int sock = fds[1];
-        
-        // test
         char *response_str = 
                 "HTTP/1.1 204 OK\r\n"
                 "Host: localhost\r\n"
                 "Content-length: 0\r\n"
                 "\r\n";
         
-        size_t response_len = strlen(response_str);
-        size_t response_pos = 0;
-        while(response_pos < response_len) {
-            size_t nbytes = response_len - response_pos;
-            ssize_t w = write(sock, response_str + response_pos, nbytes);
-            if(w > 0) {
-                response_pos += w;
-            }
-            
-            if(!client->response_header_complete) {
-                int ret = client_read_response_header(client);
-                CX_TEST_ASSERT(client->error == 0);
-                if(ret == 1) {
-                    continue;
-                }
-                
-                CX_TEST_ASSERT(client->stream == NULL);
-            }
-            
+        CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str), NULL);
+        
+        response_str = 
+                "HTTP/1.1 204 OK\r\n"
+                "Host: localhost\r\n"
+                "\r\n";
+        
+        CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str), NULL);
+    }
+}
+
+static CX_TEST(test_http_client_read_response_ctlen) {
+    CX_TEST_DO {
+        char *response_str = 
+                "HTTP/1.1 200 OK\r\n"
+                "Host: localhost\r\n"
+                "Content-length: 13\r\n"
+                "\r\n"
+                "Hello World!\n";
+        CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+        
+        CX_TEST_CALL_SUBROUTINE(test_read_response, cx_str(response_str), buf);
+        CX_TEST_ASSERT(buf->size == 13);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), "Hello World!\n"));
+        
+        cxBufferFree(buf);
+    }
+}
+
+static CX_TEST(test_http_client_read_response_ctlen_big) {
+    CX_TEST_DO {
+        // create response body
+        size_t len = 1024*1024*32;
+        char *response_str = malloc(len);
+        char *str = response_str;
+        for(size_t i=0;i<len;i+=sizeof(int)) {
+            int *p = (int*)(str+i);
+            *p = rand();
+        }
+        cxstring body = cx_strn(response_str, len);
+        
+        // create request string
+        CxBuffer *req = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+        cxBufferPutString(req,
+                "HTTP/1.1 200 OK\r\n"
+                "Host: localhost\r\n"
+                "Content-length: ");
+        char ctlen[32];
+        snprintf(ctlen, 32, "%d\r\n\r\n", (int)len);
+        cxBufferPutString(req, ctlen);
+        cxBufferPutString(req, body);
+        cxBufferTerminate(req);
+        
+        // response buffer
+        CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+        
+        // test
+        CX_TEST_CALL_SUBROUTINE(test_read_response, cx_strn(req->space, req->size), buf);
+        CX_TEST_ASSERT(buf->size == len);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), body));
+        
+        cxBufferFree(req);
+        cxBufferFree(buf);
+    }
+}
+
+static CX_TEST_SUBROUTINE(test_http_client_io, const char *response_str, int status_code, const char *msg, CxBuffer *out_buf, size_t write_blocksz) {
+    EventHandler dummy;
+    HttpClient *client = http_client_new(&dummy);
+
+    int fds[2];
+    util_socketpair(fds);
+    util_socket_setnonblock(fds[0], 1);
+    util_socket_setnonblock(fds[1], 1);
+    client->socketfd = fds[0];
+    int sock = fds[1];
+
+    // setup client
+    http_client_set_uri(client, "/test/uri/");
+    http_client_set_method(client, "GET");
+    http_client_add_request_header(client, cx_mutstr("Host"), cx_mutstr("localhost"));
+    http_client_add_request_header(client, cx_mutstr("Test1"), cx_mutstr("value1"));
+    http_client_add_request_header(client, cx_mutstr("Test2"), cx_mutstr("value2"));
+    create_req_buffer(client);
+
+    size_t req_header_len = client->transfer_buffer_len;
+
+    TestResponse testr = { 0 };
+    testr.response = out_buf;
+    client->response_start = test_response_start;
+    client->response_start_userdata = &testr;
+    client->response_body_write = test_response_body_write;
+    client->response_body_write_userdata = &testr;
+
+    // test IO
+    Event event;
+    event.cookie = client;
+    int ret = client_io(&dummy, &event);
+    CX_TEST_ASSERT(!client->error);
+    CX_TEST_ASSERT(ret == 1);
+
+    // do IO and read request until the header is processed
+    size_t req_header_pos = 0;
+    char req_buf[4];
+    while(req_header_pos < req_header_len) {
+        ssize_t r = read(sock, req_buf, 4);
+        if(r == 0) {
             break;
         }
-        
-        // cleanup
-        close(fds[0]);
-        close(fds[1]);
-        http_client_free(client);
+        CX_TEST_ASSERT(r > 0);
+        req_header_pos += r;
+        ret = client_io(&dummy, &event);
+        CX_TEST_ASSERT(!client->error);
+        CX_TEST_ASSERT(ret == 1);
+    }
+    CX_TEST_ASSERT(req_header_pos == req_header_len);
+
+    size_t response_str_len = strlen(response_str);
+    size_t response_str_pos = 0;
+
+    // send response and do IO
+    while(response_str_pos < response_str_len) {
+        size_t len = response_str_len - response_str_pos;
+        if(len > write_blocksz) {
+            len = write_blocksz;
+        }
+        ssize_t w = write(sock, response_str + response_str_pos, len);
+        if(w == 0) {
+            break;
+        }
+        CX_TEST_ASSERT(w > 0);
+        response_str_pos += w;
+
+        ret = client_io(&dummy, &event);
+
+        CX_TEST_ASSERT(!client->error);
     }
+    CX_TEST_ASSERT(response_str_pos == response_str_len);
+    CX_TEST_ASSERT(testr.status == status_code);
+    CX_TEST_ASSERT(testr.msg);
+    CX_TEST_ASSERT(!strcmp(testr.msg, msg));
+
+    // cleanup
+    free(testr.msg);
+    close(fds[0]);
+    close(fds[1]);
+    http_client_free(client);
+}
+
+static CX_TEST(test_http_client_io_simple) {
+    char *response_str = 
+            "HTTP/1.1 200 OK\r\n"
+            "Host: localhost\r\n"
+            "Content-length: 13\r\n"
+            "\r\n"
+            "Hello World!\n";
+    CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+    
+    CX_TEST_DO {
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str, 200, "OK", buf, 1024);
+        CX_TEST_ASSERT(buf->size == 13);
+        CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), "Hello World!\n"));
+    }
+    
+    cxBufferFree(buf);
 }
 
 void http_client_add_tests(CxTestSuite *suite) {
     cx_test_register(suite, test_http_client_send_request);
     cx_test_register(suite, test_http_client_send_request_body_chunked);
     cx_test_register(suite, test_http_client_read_response_head);
+    cx_test_register(suite, test_http_client_read_response_ctlen);
+    cx_test_register(suite, test_http_client_read_response_ctlen_big);
     cx_test_register(suite, test_http_client_io_simple);
 }

mercurial