Thu, 19 Feb 2026 17:05:46 +0100
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); }