Add test that shows parsing issue
It's meant to show the pipelining issue. I guess we'll solve the newly-discovered parsing issue first.
This commit is contained in:
@@ -128,7 +128,7 @@ struct Connection : MessageSender {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
void append_message(std::span<std::string_view> data_parts, Arena arena,
|
void append_message(std::span<std::string_view> data_parts, Arena arena,
|
||||||
bool close_after_send = false) override;
|
bool close_after_send) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get a WeakRef to this connection for async operations.
|
* @brief Get a WeakRef to this connection for async operations.
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ void HttpHandler::handle_get_metrics(Connection &conn,
|
|||||||
for (auto sv : metrics_span) {
|
for (auto sv : metrics_span) {
|
||||||
*out++ = sv;
|
*out++ = sv;
|
||||||
}
|
}
|
||||||
conn.append_message(result, std::move(state.arena));
|
conn.append_message(result, std::move(state.arena), state.connection_close);
|
||||||
state.reset();
|
state.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,107 @@
|
|||||||
#include "server.hpp"
|
#include "server.hpp"
|
||||||
|
|
||||||
#include <doctest/doctest.h>
|
#include <doctest/doctest.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
TEST_CASE("HTTP pipelined responses out of order") {
|
||||||
|
weaseldb::Config config;
|
||||||
|
HttpHandler handler(config);
|
||||||
|
auto server = Server::create(config, handler, {});
|
||||||
|
int fd = server->create_local_connection();
|
||||||
|
|
||||||
|
auto runThread = std::thread{[&]() { server->run(); }};
|
||||||
|
|
||||||
|
// Send /ok and /metrics in one write call
|
||||||
|
std::string pipelined_requests = "GET /ok HTTP/1.1\r\n"
|
||||||
|
"Host: localhost\r\n"
|
||||||
|
"Connection: keep-alive\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"GET /metrics HTTP/1.1\r\n"
|
||||||
|
"Host: localhost\r\n"
|
||||||
|
"Connection: keep-alive\r\n"
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
int w = write(fd, pipelined_requests.c_str(), pipelined_requests.size());
|
||||||
|
REQUIRE(w == static_cast<int>(pipelined_requests.size()));
|
||||||
|
|
||||||
|
// Set socket to non-blocking
|
||||||
|
int flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
|
// Read all responses with non-blocking I/O and poll
|
||||||
|
char buf[8192];
|
||||||
|
int total_read = 0;
|
||||||
|
|
||||||
|
bool found_ok = false;
|
||||||
|
bool found_http_response = false;
|
||||||
|
|
||||||
|
std::string ok_response_header = "Content-Length: 2";
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// Use poll to wait for data availability
|
||||||
|
struct pollfd pfd = {fd, POLLIN, 0};
|
||||||
|
int poll_result = poll(&pfd, 1, -1); // Block indefinitely
|
||||||
|
|
||||||
|
if (poll_result > 0 && (pfd.revents & POLLIN)) {
|
||||||
|
int r = read(fd, buf + total_read, sizeof(buf) - total_read - 1);
|
||||||
|
if (r > 0) {
|
||||||
|
printf("%.*s", r, buf + total_read);
|
||||||
|
total_read += r;
|
||||||
|
|
||||||
|
// Check if we have what we need after each read
|
||||||
|
buf[total_read] = '\0';
|
||||||
|
std::string current_data(buf, total_read);
|
||||||
|
|
||||||
|
found_http_response =
|
||||||
|
current_data.find("HTTP/1.1") != std::string::npos;
|
||||||
|
found_ok = current_data.find(ok_response_header) != std::string::npos;
|
||||||
|
|
||||||
|
// If we have both HTTP response and ok_response_header, we can proceed
|
||||||
|
// with the test
|
||||||
|
if (found_http_response && found_ok) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (r == 0) {
|
||||||
|
REQUIRE(false);
|
||||||
|
break; // EOF
|
||||||
|
} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||||
|
REQUIRE(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[total_read] = '\0';
|
||||||
|
std::string response_data(buf, total_read);
|
||||||
|
|
||||||
|
// Ensure we found both HTTP response and ok_response_header
|
||||||
|
REQUIRE(found_http_response);
|
||||||
|
REQUIRE(found_ok);
|
||||||
|
|
||||||
|
// Find first occurrence of ok_response_header in response body
|
||||||
|
std::size_t ok_pos = response_data.find(ok_response_header);
|
||||||
|
REQUIRE(ok_pos != std::string::npos);
|
||||||
|
|
||||||
|
// Count HTTP response status lines before the ok_response_header
|
||||||
|
std::string before_ok = response_data.substr(0, ok_pos);
|
||||||
|
int http_response_count = 0;
|
||||||
|
std::size_t pos = 0;
|
||||||
|
while ((pos = before_ok.find("HTTP/1.1", pos)) != std::string::npos) {
|
||||||
|
http_response_count++;
|
||||||
|
pos += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert there's exactly one HTTP response line before ok_response_header
|
||||||
|
// This would fail if /metrics response comes before /ok response
|
||||||
|
CHECK(http_response_count == 1);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
server->shutdown();
|
||||||
|
runThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("HTTP pipelined POST requests race condition") {
|
TEST_CASE("HTTP pipelined POST requests race condition") {
|
||||||
weaseldb::Config config;
|
weaseldb::Config config;
|
||||||
|
|||||||
Reference in New Issue
Block a user