--- a/src/server/proxy/httpclient.c Sun Feb 15 13:30:29 2026 +0100 +++ b/src/server/proxy/httpclient.c Mon Feb 16 17:43:14 2026 +0100 @@ -142,6 +142,13 @@ } int http_client_add_request_header_copy(HttpClient *client, cxstring name, cxstring value) { + if(!client->mp) { + client->mp = cxMempoolCreate(64, CX_MEMPOOL_TYPE_PURE); + if(!client->mp) { + return 1; + } + } + cxmutstr n = cx_strdup_a(client->mp->allocator, name); cxmutstr v = cx_strdup_a(client->mp->allocator, value); @@ -156,6 +163,18 @@ return err; } +int http_client_set_content_length(HttpClient *client, int64_t contentlength) { + client->req_content_length = contentlength; + char ctlen_buf[32]; + size_t len = snprintf(ctlen_buf, 32, "%" PRId64, contentlength); + return http_client_add_request_header_copy(client, cx_str("content-length"), cx_strn(ctlen_buf, len)); +} + +int http_client_enable_chunked_transfer_encoding(HttpClient *client) { + client->req_content_length = -1; + return http_client_add_request_header(client, cx_mutstr("transfer-encoding"), cx_mutstr("chunked")); +} + int http_client_start(HttpClient *client) { int socketfd = socket(AF_INET, SOCK_STREAM, 0); if(socketfd < 0) { @@ -192,7 +211,7 @@ static int create_req_buffer(HttpClient *client) { CxBuffer buf; - if(cxBufferInit(&buf, cxDefaultAllocator, NULL, 1024, CX_BUFFER_AUTO_EXTEND)) { + if(cxBufferInit(&buf, cxDefaultAllocator, NULL, HTTP_CLIENT_BUFFER_SIZE, CX_BUFFER_AUTO_EXTEND)) { return 1; } @@ -216,6 +235,7 @@ } cxBufferPutString(&buf, "\r\n"); client->req_buffer = buf.space; + client->req_buffer_alloc = buf.capacity; client->req_buffer_len = buf.size; return 0; @@ -236,13 +256,44 @@ HttpClient *client = event->cookie; if(client->req_buffer_pos < client->req_buffer_len) { if(client_send_request(client)) { - if(client->error) { - return 0; // TODO: set error + return client->error == 0; + } + } + + // do we need to send a request body? + if(client->req_content_length != 0) { + while(!client->request_body_complete) { + ssize_t r = client->request_body_read(client, client->req_buffer, client->req_buffer_alloc, client->request_body_read_userdata); + if(r <= 0) { + if(r == HTTP_CLIENT_CALLBACK_WOULD_BLOCK) { + return 1; + } else if(r == 0) { + // EOF + client->request_body_complete = 1; + break; + } else { + // error + client->error = 1; + return 0; + } } - return 1; - } + client->req_contentlength_pos += r; + client->req_buffer_pos = 0; + client->req_buffer_len = r; + if(client_send_request(client)) { + return client->error == 0; + } + } + + if(client->req_content_length > 0 && client->req_content_length != client->req_contentlength_pos) { + // incomplete request body + client->error = 1; + return 0; + } } + + // writing complete, switch to read events event->events = EVENT_POLLIN; @@ -338,7 +389,15 @@ static int client_send_request(HttpClient *client) { size_t nbytes = client->req_buffer_len - client->req_buffer_pos; - ssize_t w = write(client->socketfd, client->req_buffer + client->req_buffer_pos, nbytes); + ssize_t w; + while((w = write(client->socketfd, client->req_buffer + client->req_buffer_pos, nbytes)) > 0) { + client->req_buffer_pos += w; + nbytes = client->req_buffer_len - client->req_buffer_pos; + if(nbytes == 0) { + break; + } + } + if(w <= 0) { if(errno != EAGAIN) { // TODO: log correct host @@ -348,8 +407,6 @@ return 1; } - client->req_buffer_pos += w; - return client->req_buffer_pos < client->req_buffer_len; }