feat: enable keep-alive for internal routes by resetting connection state and handling pending reads

Reset multiple connection flags (content_type_checked, is_textual_content, response_headers_parsed, original_content_length, content_length_delta, patch_blocked) when reusing connection for internal route keep-alive. Add immediate handling of any buffered read data after state reset to prevent stale data blocking subsequent requests. Add comprehensive test verifying second request processing on internal dashboard route via Unix socket pair.
This commit is contained in:
2025-12-29 01:22:20 +00:00
parent 45005f904b
commit 0eac08394c
3 changed files with 107 additions and 0 deletions
+87
View File
@@ -4,6 +4,7 @@
#include "../src/buffer.h"
#include "../src/monitor.h"
#include "../src/config.h"
#include "../src/http.h"
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
@@ -2136,6 +2137,91 @@ void test_connection_cleanup_active_conn(void) {
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) {
test_connection_init_all();
test_connection_set_non_blocking();
@@ -2198,4 +2284,5 @@ void run_connection_tests(void) {
test_connection_handle_write_complete_close();
test_connection_handle_error_state();
test_connection_cleanup_active_conn();
test_connection_keep_alive_internal_route_second_request();
}