feat: enable keep-alive for internal routes by resetting state and handling pending reads
test: add test for keep-alive second request on internal route
This commit is contained in:
parent
967cf09beb
commit
6c91398c1c
@ -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.
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user