The functional test never lets the client drain, so neither the release of the held request nor this Complete state ends up covered, and nor does the order between the held one and the one queued behind it.
The DummyClient already in httpserver_tests.cpp closes all three. It passes on the head, and without the new return nullptr it fails on the two checks that assert the request was held. Want it?
BOOST_AUTO_TEST_CASE(http_send_buffer_throttle_tests)
{
// A socket that accepts no outbound bytes while m_blocked is set, standing in
// for a client that has stopped draining its end of the connection.
class StalledSock : public ZeroSock
{
public:
ssize_t Send(const void*, size_t len, int) const override
{
return m_blocked ? 0 : static_cast<ssize_t>(len);
}
mutable bool m_blocked{true};
};
class DummyClient : public HTTPRemoteClient
{
public:
explicit DummyClient(std::unique_ptr<Sock> sock)
: HTTPRemoteClient{/*id=*/0, /*addr=*/CService(), /*socket=*/std::move(sock)} {}
void receive(std::string_view s) { MutateRecvBuffer().append(s); }
};
const std::string wire1{"GET /first HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"};
const std::string wire2{"GET /second HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"};
const std::string wire3{"GET /third HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"};
auto sock{std::make_unique<StalledSock>()};
StalledSock& stalled{*sock};
std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>(std::move(sock))};
// The first request is dispatched normally.
client->receive(wire1);
auto first{HTTPRemoteClient::TryReadRequest(client)};
BOOST_REQUIRE(first);
BOOST_CHECK_EQUAL(first->GetURI(), "/first");
BOOST_CHECK(!client->GetRequest());
// Its reply is larger than the throttle and the client is not reading it,
// so it stays in m_send_buffer.
first->WriteReply(HTTP_OK, std::string(MAX_BODY_SIZE + 1, 'x'));
BOOST_CHECK(client->ReadyToSend());
// The next two arrive while the throttle holds. The second parses to
// Complete and is held back instead of dispatched, and the third is left
// untouched in the receive buffer behind it.
client->receive(wire2);
client->receive(wire3);
BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
BOOST_REQUIRE(client->GetRequest());
BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Complete);
BOOST_CHECK_EQUAL(client->GetRequest()->GetURI(), "/second");
BOOST_CHECK_EQUAL(client->GetRecvBuffer(), wire3);
// Draining the send buffer releases the held request, and the one queued
// behind it follows in the order it arrived.
stalled.m_blocked = false;
BOOST_CHECK(client->MaybeSendBytesFromBuffer());
BOOST_CHECK(!client->ReadyToSend());
auto second{HTTPRemoteClient::TryReadRequest(client)};
BOOST_REQUIRE(second);
BOOST_CHECK_EQUAL(second->GetURI(), "/second");
second->WriteReply(HTTP_OK, "");
auto third{HTTPRemoteClient::TryReadRequest(client)};
BOOST_REQUIRE(third);
BOOST_CHECK_EQUAL(third->GetURI(), "/third");
BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 0);
}