Mon, 23 Feb 2026 22:28:44 +0100
add first full httpclient test
--- a/src/server/proxy/httpclient.c Sun Feb 22 13:39:39 2026 +0100 +++ b/src/server/proxy/httpclient.c Mon Feb 23 22:28:44 2026 +0100 @@ -114,6 +114,15 @@ return 0; } +int http_client_set_socket(HttpClient *client, int socketfd) { + client->socketfd = socketfd; + if(util_socket_setnonblock(socketfd, 1)) { + client->socketfd = -1; + return 1; + } + return 0; +} + int http_client_set_method(HttpClient *client, const char *method) { return http_client_set_method_len(client, method, method ? strlen(method) : 0); } @@ -185,28 +194,40 @@ return http_client_add_request_header(client, cx_mutstr("transfer-encoding"), cx_mutstr("chunked")); } +static int client_start_poll(HttpClient *client, int in) { + client->event.fn = client_connected; + client->event.finish = client_finished; + if(in) { + return ev_pollin(client->ev, client->socketfd, &client->event); + } else { + return ev_pollout(client->ev, client->socketfd, &client->event); + } +} + int http_client_start(HttpClient *client) { + client->event.cookie = client; + if(client->socketfd != -1) { + int ret = client_connected(client->ev, &client->event); + if(ret != 0) { + return client_start_poll(client, 1); // TODO: check event type + } + return 0; + } + int socketfd = socket(client->domain, SOCK_STREAM, 0); if(socketfd < 0) { return 1; } - if(util_socket_setnonblock(socketfd, 1)) { - close(socketfd); return 1; } - client->socketfd = socketfd; - client->event.cookie = client; - client->event.fn = client_connected; - client->event.finish = client_finished; - int ret = 1; if(connect(socketfd, client->addr, client->addrlen)) { int err = errno; if(err == EINPROGRESS) { - ret = ev_pollout(client->ev, socketfd, &client->event); + ret = client_start_poll(client, 1); } else { log_ereport(LOG_FAILURE, "http-client-start: connect failed: %s", strerror(err)); } @@ -216,6 +237,7 @@ if(ret) { close(socketfd); + client->socketfd = -1; } return ret; }
--- a/src/server/proxy/httpclient.h Sun Feb 22 13:39:39 2026 +0100 +++ b/src/server/proxy/httpclient.h Mon Feb 23 22:28:44 2026 +0100 @@ -175,6 +175,8 @@ int http_client_set_addr(HttpClient *client, int domain, const struct sockaddr *addr, socklen_t addrlen); +int http_client_set_socket(HttpClient *client, int socketfd); + int http_client_set_method(HttpClient *client, const char *method); int http_client_set_uri(HttpClient *client, const char *uri);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/httpclient.c Mon Feb 23 22:28:44 2026 +0100 @@ -0,0 +1,119 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2026 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "httpclient.h" + +#include "../util/socket.h" +#include "../daemon/event.h" +#include "../proxy/httpclient.h" + +#include <cx/buffer.h> +#include <cx/string.h> + +#include <pthread.h> + +static EVHandler *test_eventhandler; +static pthread_mutex_t test_mutex; +static pthread_cond_t test_cond; +static volatile int test_finished; + +static void test_response_finished(HttpClient *client, void *userdata) { + pthread_mutex_lock(&test_mutex); + pthread_cond_signal(&test_cond); + pthread_mutex_unlock(&test_mutex); + http_client_free(client); +} + +static ssize_t test_response_body_write(HttpClient *client, void *buf, size_t nbytes, void *userdata) { + char *str = buf; + return cxBufferWrite(buf, 1, nbytes, userdata); +} + +CX_TEST_SUBROUTINE(test_httpclient, cxstring response, CxBuffer *out, int response_blocksz, int error_interval) { + pthread_mutex_init(&test_mutex, NULL); + pthread_cond_init(&test_cond, NULL); + + if(!test_eventhandler) { + EventHandlerConfig config; + config.isdefault = 0; + config.name = cx_str(NULL); + config.nthreads = 1; + test_eventhandler = evhandler_create(&config); + } + + HttpClient *client = http_client_new(test_eventhandler->instances[0]); + client->response_body_write = test_response_body_write; + client->response_body_write_userdata = out; + client->response_finished = test_response_finished; + + int fds[2]; + util_socketpair(fds); + util_socket_setnonblock(fds[0], 1); + + http_client_set_socket(client, fds[0]); + http_client_set_method(client, "GET"); + http_client_set_uri(client, "/testx01"); + + int ret = http_client_start(client); + CX_TEST_ASSERT(ret == 0); + + size_t response_pos = 0; + while(response_pos < response.length) { + size_t nbytes = response.length - response_pos; + const char *str = response.ptr + response_pos; + ssize_t w = write(fds[1], str, nbytes); + CX_TEST_ASSERT(w >= 0); + response_pos += w; + } + close(fds[1]); + + pthread_mutex_lock(&test_mutex); + if(test_finished == 0) { + pthread_cond_wait(&test_cond, &test_mutex); + } + pthread_mutex_unlock(&test_mutex); + + +} + +CX_TEST(test_http_client_simple_get1) { + CX_TEST_DO { + cxstring response = cx_str( + "HTTP/1.1 200 OK\r\n" + "Content-length: 13\r\n" + "\r\n" + "Hello World!\n"); + CxBuffer *out = cxBufferCreate(NULL, NULL, 256, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); + + CX_TEST_CALL_SUBROUTINE(test_httpclient, response, out, 1024, 0); + CX_TEST_ASSERT(!cx_strcmp(cx_strn(out->space, out->size), "Hello World!\n")); + + cxBufferFree(out); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/test/httpclient.h Mon Feb 23 22:28:44 2026 +0100 @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2026 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEST_HTTPCLIENT_H +#define TEST_HTTPCLIENT_H + +#include "test.h" + +#ifdef __cplusplus +extern "C" { +#endif + +CX_TEST(test_http_client_simple_get1); + + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_HTTPCLIENT_H */ +
--- a/src/server/test/main.c Sun Feb 22 13:39:39 2026 +0100 +++ b/src/server/test/main.c Mon Feb 23 22:28:44 2026 +0100 @@ -56,6 +56,7 @@ #include "event.h" #include "strreplace.h" #include "rewrite.h" +#include "httpclient.h" void register_pg_tests(int argc, char **argv, CxTestSuite *suite); @@ -207,6 +208,7 @@ // http tests http_client_add_tests(suite); + cx_test_register(suite, test_http_client_simple_get1); // plugin tests #ifdef ENABLE_POSTGRESQL
--- a/src/server/test/objs.mk Sun Feb 22 13:39:39 2026 +0100 +++ b/src/server/test/objs.mk Mon Feb 23 22:28:44 2026 +0100 @@ -45,6 +45,7 @@ TESTOBJ += event.o TESTOBJ += strreplace.o TESTOBJ += rewrite.o +TESTOBJ += httpclient.o TESTOBJS = $(TESTOBJ:%=$(TEST_OBJPRE)%) TESTSOURCE = $(TESTOBJ:%.o=test/%.c)