diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 45824b7a2cf..a1b3e7a2f1e 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -808,4 +808,14 @@ std::string HTTPHeaders::Stringify() const return out; } + +std::string HTTPResponse::StringifyHeaders() const +{ + return strprintf("HTTP/%d.%d %d %s\r\n%s", + m_version.major, + m_version.minor, + m_status, + HTTPStatusReasonString(m_status), + m_headers.Stringify()); +} } // namespace http_bitcoin diff --git a/src/httpserver.h b/src/httpserver.h index a888979fa7d..245abb750c9 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -234,6 +235,29 @@ private: */ std::vector> m_headers; }; + +struct HTTPVersion { + /** + * Default HTTP protocol version 1.1 is used by error responses + * when a request is unreadable. + */ + /// @{ + uint8_t major{1}; + uint8_t minor{1}; + /// @} +}; + + +class HTTPResponse +{ +public: + HTTPVersion m_version; + + HTTPStatusCode m_status{HTTP_INTERNAL_SERVER_ERROR}; + HTTPHeaders m_headers; + + std::string StringifyHeaders() const; +}; } // namespace http_bitcoin #endif // BITCOIN_HTTPSERVER_H diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 40e685d5868..0ba17ae0218 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -20,6 +20,27 @@ enum HTTPStatusCode : int HTTP_SERVICE_UNAVAILABLE = 503, }; +//! Mapping of HTTP status codes to short string explanation. +//! Copied from libevent http.c success_phrases[] and client_error_phrases[] +inline std::string_view HTTPStatusReasonString(HTTPStatusCode code) +{ + switch (code) { + case HTTP_OK: return "OK"; + case HTTP_NO_CONTENT: return "No Content"; + case HTTP_BAD_REQUEST: return "Bad Request"; + case HTTP_UNAUTHORIZED: return "Unauthorized"; + case HTTP_FORBIDDEN: return "Forbidden"; + case HTTP_NOT_FOUND: return "Not Found"; + case HTTP_BAD_METHOD: return "Method Not Allowed"; + case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error"; + case HTTP_SERVICE_UNAVAILABLE: return "Service Unavailable"; + } + + // Reason phrases are optional and may be replaced by local variants. + // https://httpwg.org/specs/rfc9110.html#rfc.section.15.1 + return ""; +} + //! Bitcoin RPC error codes enum RPCErrorCode { diff --git a/src/test/httpserver_tests.cpp b/src/test/httpserver_tests.cpp index d2d03f8aa7b..baf0ae40ec7 100644 --- a/src/test/httpserver_tests.cpp +++ b/src/test/httpserver_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -10,6 +11,7 @@ #include using http_bitcoin::HTTPHeaders; +using http_bitcoin::HTTPResponse; using http_bitcoin::MAX_HEADERS_SIZE; BOOST_FIXTURE_TEST_SUITE(httpserver_tests, BasicTestingSetup) @@ -144,4 +146,23 @@ BOOST_AUTO_TEST_CASE(http_headers_tests) BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value"); } } + +BOOST_AUTO_TEST_CASE(http_response_tests) +{ + // Typical HTTP 1.1 response headers + HTTPHeaders headers{}; + headers.Write("Content-Length", "41"); + + // Response points to headers which already exist because some of them + // are set before we even know what the response will be. + HTTPResponse res; + res.m_version = {.major = 1, .minor = 1}; + res.m_status = HTTP_OK; + res.m_headers = std::move(headers); + BOOST_CHECK_EQUAL( + res.StringifyHeaders(), + "HTTP/1.1 200 OK\r\n" + "Content-Length: 41\r\n" + "\r\n"); +} BOOST_AUTO_TEST_SUITE_END()