add httpclient response write error tests default tip

Sat, 21 Feb 2026 14:40:03 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 21 Feb 2026 14:40:03 +0100
changeset 692
32faa1d6a744
parent 691
4d8a55a7618b

add httpclient response write error tests

src/server/proxy/httpclient.c file | annotate | diff | comparison | revisions
src/server/util/io.c file | annotate | diff | comparison | revisions
--- a/src/server/proxy/httpclient.c	Sat Feb 21 13:12:39 2026 +0100
+++ b/src/server/proxy/httpclient.c	Sat Feb 21 14:40:03 2026 +0100
@@ -274,13 +274,14 @@
                 return client->error == 0;
             }
         }
+        
+        client->transfer_buffer_pos = 0;
+        client->transfer_buffer_len = 0;
     }
     
     // writing complete, switch to read events
     event->events = EVENT_POLLIN;
     client->stage = 1;
-    client->transfer_buffer_pos = 0;
-    client->transfer_buffer_len = 0;
     
     if(client_read_response_header(client)) {
         return client->error == 0;
@@ -657,6 +658,8 @@
     int status;
     char *msg;
     CxBuffer *response;
+    int error_interval;
+    int error_test;
 } TestResponse;
 
 static int test_response_start(HttpClient *client, int status, char *msg, void *userdata) {
@@ -668,6 +671,12 @@
 
 static ssize_t test_response_body_write(HttpClient *client, void *buf, size_t size, void *userdata) {
     TestResponse *test = userdata;
+    if(test->error_interval > 0 && test->error_test >= test->error_interval) {
+        test->error_test = 0;
+        return HTTP_CLIENT_CALLBACK_WOULD_BLOCK;
+    }
+    test->error_test++;
+    
     cxBufferWrite(buf, 1, size, test->response);
     return size;
 }
@@ -942,7 +951,7 @@
     }
 }
 
-static CX_TEST_SUBROUTINE(test_http_client_io, cxstring response, int status_code, const char *msg, CxBuffer *out_buf, size_t write_blocksz) {
+static CX_TEST_SUBROUTINE(test_http_client_io, cxstring response, int status_code, const char *msg, CxBuffer *out_buf, size_t write_blocksz, int error_interval) {
     EventHandler dummy;
     HttpClient *client = http_client_new(&dummy);
 
@@ -965,6 +974,7 @@
 
     TestResponse testr = { 0 };
     testr.response = out_buf;
+    testr.error_interval = error_interval;
     client->response_start = test_response_start;
     client->response_start_userdata = &testr;
     client->response_body_write = test_response_body_write;
@@ -998,19 +1008,25 @@
     size_t response_str_pos = 0;
 
     // send response and do IO
-    while(response_str_pos < response_str_len) {
+    int in_progress = 1;
+    while(in_progress) {
         size_t len = response_str_len - response_str_pos;
-        if(len > write_blocksz) {
-            len = write_blocksz;
+        if(len > 0) {
+            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;
         }
-        ssize_t w = write(sock, response_str + response_str_pos, len);
-        if(w == 0) {
-            break;
+        
+        ret = client_io(&dummy, &event);
+        if(ret == 0) {
+            in_progress = 0;
         }
-        CX_TEST_ASSERT(w > 0);
-        response_str_pos += w;
-
-        ret = client_io(&dummy, &event);
 
         CX_TEST_ASSERT(!client->error);
     }
@@ -1026,7 +1042,7 @@
     http_client_free(client);
 }
 
-static CX_TEST_SUBROUTINE(test_http_client_io_simple, size_t blocksz) {
+static CX_TEST_SUBROUTINE(test_http_client_io_simple, size_t blocksz, int error_interval) {
     cxstring response_str = cx_str(
             "HTTP/1.1 200 OK\r\n"
             "Host: localhost\r\n"
@@ -1035,7 +1051,7 @@
             "Hello World!\n");
     CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
     
-    CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str, 200, "OK", buf, blocksz);
+    CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str, 200, "OK", buf, blocksz, error_interval);
     CX_TEST_ASSERT(buf->size == 13);
     CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), "Hello World!\n"));
     
@@ -1044,31 +1060,31 @@
 
 static CX_TEST(test_http_client_io_simple_1b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 1);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 1, 0);
     }
 }
 
 static CX_TEST(test_http_client_io_simple_2b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 2);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 2, 0);
     }
 }
 
 static CX_TEST(test_http_client_io_simple_3b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 3);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 3, 0);
     }
 }
 
 static CX_TEST(test_http_client_io_simple_16b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 16);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 16, 0);
     }
 }
 
 static CX_TEST(test_http_client_io_simple_512b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 512);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 512, 0);
     }
 }
 
@@ -1089,7 +1105,7 @@
     
     CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
     
-    CX_TEST_CALL_SUBROUTINE(test_http_client_io, cx_strn(resp->space, resp->size), 200, "OK", buf, blocksz);
+    CX_TEST_CALL_SUBROUTINE(test_http_client_io, cx_strn(resp->space, resp->size), 200, "OK", buf, blocksz, 0);
     CX_TEST_ASSERT(buf->size == ctlen);
     CX_TEST_ASSERT(!memcmp(buf->space, resp->space + content_start, ctlen));
     
@@ -1109,7 +1125,7 @@
     }
 }
 
-static CX_TEST_SUBROUTINE(test_http_client_io_chunked_transfer, size_t blocksz) {
+static CX_TEST_SUBROUTINE(test_http_client_io_chunked_transfer, size_t blocksz, int error_interval) {
     cxstring response_str = cx_str( 
             "HTTP/1.1 200 OK\r\n"
             "Host: localhost\r\n"
@@ -1121,7 +1137,7 @@
             "0\r\n\r\n");
     CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
     
-    CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str, 200, "OK", buf, blocksz);
+    CX_TEST_CALL_SUBROUTINE(test_http_client_io, response_str, 200, "OK", buf, blocksz, error_interval);
     CX_TEST_ASSERT(buf->size == 13);
     CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf->space, buf->size), "Hello World!\n"));
     
@@ -1130,29 +1146,29 @@
 
 static CX_TEST(test_http_client_io_chunked_transfer_1b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 1);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 1, 0);
     }
 }
 
 static CX_TEST(test_http_client_io_chunked_transfer_2b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 2);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 2, 0);
     }
 }
 
 static CX_TEST(test_http_client_io_chunked_transfer_8b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 16);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 16, 0);
     }
 }
 
 static CX_TEST(test_http_client_io_chunked_transfer_64b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 64);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_chunked_transfer, 64, 0);
     }
 }
 
-static CX_TEST_SUBROUTINE(test_http_client_io_large_chunked_transfer, size_t blocksz) {
+static CX_TEST_SUBROUTINE(test_http_client_io_large_chunked_transfer, size_t blocksz, int error_interval) {
     int chunk1 = 1024*1024*2;
     int chunk2 = 1024*128;
     int chunk3 = 123;
@@ -1204,7 +1220,7 @@
     
     CxBuffer *buf = cxBufferCreate(NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
     
-    CX_TEST_CALL_SUBROUTINE(test_http_client_io, cx_strn(resp->space, resp->size), 200, "OK", buf, blocksz);
+    CX_TEST_CALL_SUBROUTINE(test_http_client_io, cx_strn(resp->space, resp->size), 200, "OK", buf, blocksz, error_interval);
     CX_TEST_ASSERT(buf->size == ctlen);
     CX_TEST_ASSERT(!memcmp(buf->space, resp->space + chunk1_pos, chunk1));
     CX_TEST_ASSERT(!memcmp(buf->space + chunk1, resp->space + chunk2_pos, chunk2));
@@ -1218,7 +1234,43 @@
 
 static CX_TEST(test_http_client_io_large_chunked_transfer_1024b) {
     CX_TEST_DO {
-        CX_TEST_CALL_SUBROUTINE(test_http_client_io_large_chunked_transfer, 1024);
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_large_chunked_transfer, 1024, 0);
+    }
+}
+
+static CX_TEST(test_http_client_io_write_error1) {
+    CX_TEST_DO {
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 1, 1);
+    }
+}
+
+static CX_TEST(test_http_client_io_write_error2) {
+    CX_TEST_DO {
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 1, 2);
+    }
+}
+
+static CX_TEST(test_http_client_io_write_error3) {
+    CX_TEST_DO {
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 1, 3);
+    }
+}
+
+static CX_TEST(test_http_client_io_write_blsz8_error1) {
+    CX_TEST_DO {
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 8, 1);
+    }
+}
+
+static CX_TEST(test_http_client_io_write_blsz8_error2) {
+    CX_TEST_DO {
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 8, 2);
+    }
+}
+
+static CX_TEST(test_http_client_io_write_blsz8_error3) {
+    CX_TEST_DO {
+        CX_TEST_CALL_SUBROUTINE(test_http_client_io_simple, 8, 3);
     }
 }
 
@@ -1240,4 +1292,10 @@
     cx_test_register(suite, test_http_client_io_chunked_transfer_8b);
     cx_test_register(suite, test_http_client_io_chunked_transfer_64b);
     cx_test_register(suite, test_http_client_io_large_chunked_transfer_1024b);
+    cx_test_register(suite, test_http_client_io_write_error1);
+    cx_test_register(suite, test_http_client_io_write_error2);
+    cx_test_register(suite, test_http_client_io_write_error3);
+    cx_test_register(suite, test_http_client_io_write_blsz8_error1);
+    cx_test_register(suite, test_http_client_io_write_blsz8_error2);
+    cx_test_register(suite, test_http_client_io_write_blsz8_error3);
 }
--- a/src/server/util/io.c	Sat Feb 21 13:12:39 2026 +0100
+++ b/src/server/util/io.c	Sat Feb 21 14:40:03 2026 +0100
@@ -730,7 +730,10 @@
         size_t chunk_available = st->max_read - st->read;
         if(chunk_available > 0) {
             ssize_t r = http_read_buffered(st, rbuf, rbuflen, TRUE, &perform_io);
-            if(r == 0) {
+            if(r <= 0) {
+                if(rd == 0) {
+                    rd = r;
+                }
                 break;
             }
             rd += r;

mercurial