# HG changeset patch # User Olaf Wintermann # Date 1771681203 -3600 # Node ID 32faa1d6a74453924176d09fa6e4c53a6d11768e # Parent 4d8a55a7618b1daba1308f95ccbaf1311025da35 add httpclient response write error tests diff -r 4d8a55a7618b -r 32faa1d6a744 src/server/proxy/httpclient.c --- 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); } diff -r 4d8a55a7618b -r 32faa1d6a744 src/server/util/io.c --- 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;