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

@@ -281,6 +281,8 @@ public:
std::string StringifyHeaders() const;
};
class HTTPRemoteClient;
class HTTPRequest
{
public:
@@ -290,6 +292,13 @@ public:
HTTPHeaders m_headers;
std::string m_body;
//! Pointer to the client that made the request so we know who to respond to.
std::shared_ptr<HTTPRemoteClient> m_client;
explicit HTTPRequest(std::shared_ptr<HTTPRemoteClient> client) : m_client{std::move(client)} {}
//! Construct with a null client for unit tests
explicit HTTPRequest() : m_client{} {}
/**
* Methods that attempt to parse HTTP request fields line-by-line
* from a receive buffer.
@@ -305,8 +314,6 @@ public:
/// @}
};
class HTTPRemoteClient;
class HTTPServer
{
public:
@@ -315,6 +322,8 @@ public:
*/
using Id = uint64_t;
explicit HTTPServer(std::function<void(std::unique_ptr<HTTPRequest>&&)> func) : m_request_dispatcher{std::move(func)} {}
virtual ~HTTPServer()
{
Assume(!m_thread_socket_handler.joinable()); // Missing call to JoinSocketsThreads()
@@ -344,13 +353,6 @@ public:
*/
size_t GetConnectionsCount() const { return m_connected_size.load(std::memory_order_acquire); }
/**
* This is a temporary method used to get a pointer to an HTTPRemoteClient
* so its connection can be inspected in a unit test.
* It will be removed in a future commit.
*/
const HTTPRemoteClient& GetFirstConnection() { return *m_connected.front(); }
/**
* Start the necessary threads for sockets IO.
*/
@@ -436,6 +438,11 @@ private:
*/
std::thread m_thread_socket_handler;
/*
* What to do with HTTP requests once received, validated and parsed
*/
std::function<void(std::unique_ptr<HTTPRequest>&&)> m_request_dispatcher;
/**
* Accept a connection.
* @param[in] listen_sock Socket on which to accept the connection.
@@ -457,6 +464,12 @@ private:
*/
void NewSockAccepted(std::unique_ptr<Sock>&& sock, const CService& addr);
/**
* Do the read/write for connected sockets that are ready for IO.
* @param[in] io_readiness Which sockets are ready and their corresponding HTTPRemoteClients.
*/
void SocketHandlerConnected(const IOReadiness& io_readiness) const;
/**
* Accept incoming connections, one from each read-ready listening socket.
* @param[in] events_per_sock Sockets that are ready for IO.
@@ -475,6 +488,15 @@ private:
* This is the main I/O loop of the server.
*/
void ThreadSocketHandler();
/**
* Try to read HTTPRequests from a client's receive buffer.
* Complete requests are dispatched, incomplete requests are
* left in the buffer to wait for more data. Some read errors
* will mark this client for disconnection.
* @param[in] client The HTTPRemoteClient to read requests from
*/
void MaybeDispatchRequestsFromClient(const std::shared_ptr<HTTPRemoteClient>& client) const;
};
class HTTPRemoteClient
@@ -489,6 +511,13 @@ public:
//! IP:port of connected client, cached for logging purposes
const std::string m_origin;
/**
* In lieu of an intermediate transport class like p2p uses,
* we copy data from the socket buffer to the client object
* and attempt to read HTTP requests from here.
*/
std::vector<std::byte> m_recv_buffer{};
/**
* Mutex that serializes the Send() and Recv() calls on `m_sock`. Reading
* from the client occurs in the I/O thread but writing back to a client
@@ -511,6 +540,13 @@ public:
// Disable copies (should only be used as shared pointers)
HTTPRemoteClient(const HTTPRemoteClient&) = delete;
HTTPRemoteClient& operator=(const HTTPRemoteClient&) = delete;
/**
* Try to read an HTTP request from the receive buffer.
* @param[in] req A HTTPRequest to read into
* @returns true upon reading a complete request, otherwise false (may throw).
*/
bool ReadRequest(HTTPRequest& req);
};
} // namespace http_bitcoin