1#include <xrpl/net/HTTPClient.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/instrumentation.h>
7#include <boost/asio/awaitable.hpp>
8#include <boost/asio/co_spawn.hpp>
9#include <boost/asio/detached.hpp>
10#include <boost/asio/io_context.hpp>
11#include <boost/asio/ip/tcp.hpp>
12#include <boost/asio/socket_base.hpp>
13#include <boost/asio/use_awaitable.hpp>
14#include <boost/asio/use_future.hpp>
15#include <boost/beast/core.hpp>
16#include <boost/beast/http.hpp>
18#include <gtest/gtest.h>
19#include <helpers/TestSink.h>
36 boost::asio::io_context ioc_;
37 boost::asio::ip::tcp::acceptor acceptor_;
38 boost::asio::ip::tcp::endpoint endpoint_;
40 bool finished_{
false};
41 unsigned short port_{0};
44 std::map<std::string, std::string> customHeaders_;
45 std::string responseBody_;
46 unsigned int statusCode_{200};
51 TestHTTPServer() : acceptor_(ioc_), j_(TestSink::instance())
54 endpoint_ = {boost::asio::ip::tcp::v4(), 0};
55 acceptor_.open(endpoint_.protocol());
56 acceptor_.set_option(boost::asio::socket_base::reuse_address(
true));
57 acceptor_.bind(endpoint_);
61 port_ = acceptor_.local_endpoint().port();
64 boost::asio::co_spawn(ioc_,
accept(), boost::asio::detached);
67 TestHTTPServer(TestHTTPServer&&) =
delete;
69 operator=(TestHTTPServer&&) =
delete;
73 XRPL_ASSERT(finished(),
"xrpl::TestHTTPServer::~TestHTTPServer : accept future ready");
76 boost::asio::io_context&
82 [[nodiscard]]
unsigned short
89 setHeader(std::string
const& name, std::string
const& value)
91 customHeaders_[name] = value;
95 setResponseBody(std::string
const& body)
101 setStatusCode(
unsigned int code)
120 boost::asio::awaitable<void>
127 auto socket =
co_await acceptor_.async_accept(boost::asio::use_awaitable);
133 co_await handleConnection(std::move(socket));
135 catch (std::exception
const& e)
138 JLOG(j_.debug()) <<
"Error: " << e.
what();
146 boost::asio::awaitable<void>
147 handleConnection(boost::asio::ip::tcp::socket socket)
151 boost::beast::flat_buffer buffer;
152 boost::beast::http::request<boost::beast::http::string_body> req;
155 co_await boost::beast::http::async_read(
156 socket, buffer, req, boost::asio::use_awaitable);
159 boost::beast::http::response<boost::beast::http::string_body> res;
160 res.version(req.version());
161 res.result(statusCode_);
162 res.set(boost::beast::http::field::server,
"TestServer");
165 res.body() = responseBody_;
166 res.prepare_payload();
171 for (
auto const& [name, value] : customHeaders_)
173 res.set(name, value);
177 co_await boost::beast::http::async_write(socket, res, boost::asio::use_awaitable);
180 boost::system::error_code shutdownEc;
183 socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send, shutdownEc);
185 catch (std::exception
const& e)
188 JLOG(j_.debug()) <<
"Connection error: " << e.
what();
210 "" ,
"" ,
false ,
j_ );
225 TestHTTPServer& server,
230 boost::system::error_code& resultError)
240 [&](boost::system::error_code
const& ec,
int status,
std::string const& data) ->
bool {
242 resultStatus = status;
251 while (server.ioc().run_one() != 0)
283 for (
auto const& headerName : headerCases)
285 TestHTTPServer server;
287 server.setResponseBody(testBody);
290 bool completed{
false};
293 boost::system::error_code resultError;
295 bool const testCompleted =
296 runHTTPTest(server,
"/test", completed, resultStatus, resultData, resultError);
298 EXPECT_TRUE(testCompleted);
299 EXPECT_FALSE(resultError);
300 EXPECT_EQ(resultStatus, 200);
301 EXPECT_EQ(resultData, testBody);
307 TestHTTPServer server;
309 server.setResponseBody(testBody);
310 server.setHeader(
"Content-Type",
"text/plain");
312 bool completed{
false};
315 boost::system::error_code resultError;
317 bool const testCompleted =
318 runHTTPTest(server,
"/basic", completed, resultStatus, resultData, resultError);
320 EXPECT_TRUE(testCompleted);
321 EXPECT_FALSE(resultError);
322 EXPECT_EQ(resultStatus, 200);
323 EXPECT_EQ(resultData, testBody);
328 TestHTTPServer server;
329 server.setResponseBody(
"");
330 server.setHeader(
"Content-Length",
"0");
332 bool completed{
false};
335 boost::system::error_code resultError;
337 bool const testCompleted =
338 runHTTPTest(server,
"/empty", completed, resultStatus, resultData, resultError);
340 EXPECT_TRUE(testCompleted);
341 EXPECT_FALSE(resultError);
342 EXPECT_EQ(resultStatus, 200);
343 EXPECT_TRUE(resultData.
empty());
350 for (
auto status : statusCodes)
352 TestHTTPServer server;
353 server.setStatusCode(status);
356 bool completed{
false};
359 boost::system::error_code resultError;
361 bool const testCompleted =
362 runHTTPTest(server,
"/status", completed, resultStatus, resultData, resultError);
364 EXPECT_TRUE(testCompleted);
365 EXPECT_FALSE(resultError);
366 EXPECT_EQ(resultStatus,
static_cast<int>(status));
bool runHTTPTest(TestHTTPServer &server, std::string const &path, bool &completed, int &resultStatus, std::string &resultData, boost::system::error_code &resultError)
A generic endpoint for log messages.
static void initializeSSLContext(std::string const &sslVerifyDir, std::string const &sslVerifyFile, bool sslVerify, beast::Journal j)
static void cleanupSSLContext()
Destroys the global SSL context created by initializeSSLContext().
static void get(bool bSSL, boost::asio::io_context &ioContext, std::deque< std::string > deqSites, unsigned short const port, std::string const &strPath, std::size_t responseMax, std::chrono::seconds timeout, std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> complete, beast::Journal const &j)
static TestSink & instance()
json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.