HTTPServer: read requests from connected clients

`SocketHandlerConnected()` adapted from CConnman

Testing this requires adding a new feature to the SocketTestingSetup,
inserting a "request" payload into the mock client that connects
to us.

This commit also moves IOErrorIsPermanent() from sock.cpp to sock.h
so it can be called from the socket handler in httpserver.cpp

Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
This commit is contained in:
Matthew Zipkin
2024-10-31 13:34:19 -04:00
parent 3c5226ab96
commit 80e1cfe5a2
5 changed files with 215 additions and 29 deletions

View File

@@ -1072,6 +1072,65 @@ void HTTPServer::NewSockAccepted(std::unique_ptr<Sock>&& sock, const CService& a
addr.ToStringAddrPort(), id);
}
void HTTPServer::SocketHandlerConnected(const IOReadiness& io_readiness) const
{
for (const auto& [sock, events] : io_readiness.events_per_sock) {
if (m_interrupt_net) {
return;
}
auto it{io_readiness.httpclients_per_sock.find(sock)};
if (it == io_readiness.httpclients_per_sock.end()) {
continue;
}
const std::shared_ptr<HTTPRemoteClient>& client{it->second};
bool send_ready = events.occurred & Sock::SEND;
bool recv_ready = events.occurred & Sock::RECV;
bool err_ready = events.occurred & Sock::ERR;
if (send_ready) {
// TODO: send data
}
if (recv_ready || err_ready) {
std::byte buf[0x10000]; // typical socket buffer is 8K-64K
const ssize_t nrecv{WITH_LOCK(
client->m_sock_mutex,
return client->m_sock->Recv(buf, sizeof(buf), MSG_DONTWAIT);)};
if (nrecv < 0) {
const int err = WSAGetLastError();
if (IOErrorIsPermanent(err)) {
LogDebug(
BCLog::HTTP,
"Permanent read error from %s (id=%llu): %s",
client->m_origin,
client->m_id,
NetworkErrorString(err));
// TODO: Disconnect
}
} else if (nrecv == 0) {
LogDebug(
BCLog::HTTP,
"Received EOF from %s (id=%llu)",
client->m_origin,
client->m_id);
// TODO: Disconnect
} else {
// Copy data from socket buffer to client receive buffer
client->m_recv_buffer.insert(
client->m_recv_buffer.end(),
buf,
buf + nrecv);
// Process as much received data as we can
MaybeDispatchRequestsFromClient(client);
}
}
}
}
void HTTPServer::SocketHandlerListening(const Sock::EventsPerSock& events_per_sock)
{
for (const auto& sock : m_listen) {
@@ -1131,8 +1190,66 @@ void HTTPServer::ThreadSocketHandler()
m_interrupt_net.sleep_for(SELECT_TIMEOUT);
}
// Service (send/receive) each of the already connected sockets.
SocketHandlerConnected(io_readiness);
// Accept new connections from listening sockets.
SocketHandlerListening(io_readiness.events_per_sock);
}
}
void HTTPServer::MaybeDispatchRequestsFromClient(const std::shared_ptr<HTTPRemoteClient>& client) const
{
// Try reading (potentially multiple) HTTP requests from the buffer
while (!client->m_recv_buffer.empty()) {
// Create a new request object and try to fill it with data from the receive buffer
auto req = std::make_unique<HTTPRequest>(client);
try {
// Stop reading if we need more data from the client to parse a complete request
if (!client->ReadRequest(*req)) break;
} catch (const std::runtime_error& e) {
LogDebug(
BCLog::HTTP,
"Error reading HTTP request from client %s (id=%llu): %s",
client->m_origin,
client->m_id,
e.what());
// We failed to read a complete request from the buffer
// TODO: respond with HTTP_BAD_REQUEST and disconnect
return;
}
// We read a complete request from the buffer into the queue
LogDebug(
BCLog::HTTP,
"Received a %s request for %s from %s (id=%llu)",
req->m_method,
req->m_target,
client->m_origin,
client->m_id);
// handle request
m_request_dispatcher(std::move(req));
}
}
bool HTTPRemoteClient::ReadRequest(HTTPRequest& req)
{
LineReader reader(m_recv_buffer, MAX_HEADERS_SIZE);
if (!req.LoadControlData(reader)) return false;
if (!req.LoadHeaders(reader)) return false;
if (!req.LoadBody(reader)) return false;
// Remove the bytes read out of the buffer.
// If one of the above calls throws an error, the caller must
// catch it and disconnect the client.
m_recv_buffer.erase(
m_recv_buffer.begin(),
m_recv_buffer.begin() + reader.Consumed());
return true;
}
} // namespace http_bitcoin