feat: enable keep-alive for internal routes by resetting state and handling pending reads
Some checks failed
Build and Test / build (push) Failing after 40s
Build and Test / coverage (push) Failing after 48s

test: add test for keep-alive second request on internal route
This commit is contained in:
retoor 2025-12-29 02:22:20 +01:00
parent 967cf09beb
commit 6c91398c1c
3 changed files with 107 additions and 0 deletions

View File

@ -8,6 +8,14 @@
## Version 0.8.0 - 2025-12-29
Enables keep-alive connections for internal routes, allowing multiple requests over the same connection. Adds a test to verify handling of the second request on an internal route.
**Changes:** 2 files, 99 lines
**Languages:** C (99 lines)
## Version 0.7.0 - 2025-12-29 ## Version 0.7.0 - 2025-12-29
The dashboard now formats numbers and times in metrics for improved readability. The dashboard now formats numbers and times in metrics for improved readability.

View File

@ -1236,7 +1236,19 @@ static void handle_write_event(connection_t *conn) {
memset(&conn->request, 0, sizeof(http_request_t)); memset(&conn->request, 0, sizeof(http_request_t));
conn->request.keep_alive = 1; conn->request.keep_alive = 1;
conn->state = CLIENT_STATE_READING_HEADERS; conn->state = CLIENT_STATE_READING_HEADERS;
conn->content_type_checked = 0;
conn->is_textual_content = 0;
conn->response_headers_parsed = 0;
conn->original_content_length = 0;
conn->content_length_delta = 0;
conn->patch_blocked = 0;
connection_modify_epoll(conn->fd, EPOLLIN); connection_modify_epoll(conn->fd, EPOLLIN);
if (buffer_available_read(&conn->read_buf) > 0) {
handle_client_read(conn);
}
} else { } else {
connection_modify_epoll(conn->fd, EPOLLIN); connection_modify_epoll(conn->fd, EPOLLIN);
} }

View File

@ -4,6 +4,7 @@
#include "../src/buffer.h" #include "../src/buffer.h"
#include "../src/monitor.h" #include "../src/monitor.h"
#include "../src/config.h" #include "../src/config.h"
#include "../src/http.h"
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
@ -2136,6 +2137,91 @@ void test_connection_cleanup_active_conn(void) {
TEST_SUITE_END(); TEST_SUITE_END();
} }
void test_connection_keep_alive_internal_route_second_request(void) {
TEST_SUITE_BEGIN("Keep-Alive Internal Route Second Request");
connection_init_all();
connection_update_cached_time();
int old_epoll = epoll_fd;
epoll_fd = epoll_create1(0);
TEST_ASSERT(epoll_fd >= 0, "Epoll created");
int sockfd[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd);
connection_set_non_blocking(sockfd[0]);
connection_set_non_blocking(sockfd[1]);
connection_t *conn = &connections[sockfd[0]];
memset(conn, 0, sizeof(connection_t));
conn->fd = sockfd[0];
conn->type = CONN_TYPE_CLIENT;
conn->state = CLIENT_STATE_READING_HEADERS;
buffer_init(&conn->read_buf, 4096);
buffer_init(&conn->write_buf, 4096);
conn->splice_pipe[0] = -1;
conn->splice_pipe[1] = -1;
struct epoll_event ev = { .events = EPOLLIN | EPOLLOUT, .data.fd = sockfd[0] };
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd[0], &ev);
conn->epoll_events = EPOLLIN | EPOLLOUT;
const char *first_request = "GET /rproxy/dashboard HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n";
IGNORE_RESULT(write(sockfd[1], first_request, strlen(first_request)));
struct epoll_event event = { .events = EPOLLIN, .data.fd = sockfd[0] };
connection_handle_event(&event);
TEST_ASSERT(conn->state == CLIENT_STATE_SERVING_INTERNAL || conn->write_buf.tail > 0, "First dashboard request processed");
if (conn->state == CLIENT_STATE_SERVING_INTERNAL && conn->request.keep_alive) {
memset(&conn->request, 0, sizeof(http_request_t));
conn->request.keep_alive = 1;
conn->state = CLIENT_STATE_READING_HEADERS;
conn->content_type_checked = 0;
conn->is_textual_content = 0;
conn->response_headers_parsed = 0;
conn->original_content_length = 0;
conn->content_length_delta = 0;
conn->patch_blocked = 0;
conn->write_buf.head = 0;
conn->write_buf.tail = 0;
}
const char *second_request = "GET /rproxy/api/stats HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n";
memcpy(conn->read_buf.data + conn->read_buf.tail, second_request, strlen(second_request));
conn->read_buf.tail += strlen(second_request);
TEST_ASSERT(buffer_available_read(&conn->read_buf) > 0, "Second request is in buffer");
if (buffer_available_read(&conn->read_buf) > 0 && conn->state == CLIENT_STATE_READING_HEADERS) {
char *data_start = conn->read_buf.data + conn->read_buf.head;
size_t data_len = buffer_available_read(&conn->read_buf);
char *headers_end = memmem(data_start, data_len, "\r\n\r\n", 4);
if (headers_end) {
size_t headers_len = (headers_end - data_start) + 4;
int parse_result = http_parse_request(data_start, headers_len, &conn->request);
TEST_ASSERT_EQ(1, parse_result, "Second request parsed successfully");
int is_internal = (strncmp(conn->request.uri, "/rproxy/dashboard", 17) == 0 ||
strncmp(conn->request.uri, "/rproxy/api/stats", 17) == 0);
TEST_ASSERT_EQ(1, is_internal, "Second request detected as internal route");
TEST_ASSERT_STR_EQ("/rproxy/api/stats", conn->request.uri, "Second request URI correct");
}
}
if (conn->type != CONN_TYPE_UNUSED) {
buffer_free(&conn->read_buf);
buffer_free(&conn->write_buf);
}
close(sockfd[0]);
close(sockfd[1]);
close(epoll_fd);
epoll_fd = old_epoll;
TEST_SUITE_END();
}
void run_connection_tests(void) { void run_connection_tests(void) {
test_connection_init_all(); test_connection_init_all();
test_connection_set_non_blocking(); test_connection_set_non_blocking();
@ -2198,4 +2284,5 @@ void run_connection_tests(void) {
test_connection_handle_write_complete_close(); test_connection_handle_write_complete_close();
test_connection_handle_error_state(); test_connection_handle_error_state();
test_connection_cleanup_active_conn(); test_connection_cleanup_active_conn();
test_connection_keep_alive_internal_route_second_request();
} }