From acbcf299a033a85698cb67c8000c509b24bf02af Mon Sep 17 00:00:00 2001
From: Yonas Habteab
Date: Thu, 24 Jul 2025 09:20:31 +0200
Subject: [PATCH] Avoid copying buffers around when serializing HTTP response
---
lib/remote/configfileshandler.cpp | 2 +-
lib/remote/eventshandler.cpp | 2 +-
lib/remote/httphandler.cpp | 2 +-
lib/remote/httpmessage.cpp | 153 +++++++++++-----
lib/remote/httpmessage.hpp | 265 ++++++---------------------
lib/remote/httpserverconnection.cpp | 14 +-
lib/remote/infohandler.cpp | 13 +-
lib/remote/mallocinfohandler.cpp | 2 +-
test/CMakeLists.txt | 2 -
test/remote-httpmessage.cpp | 51 +-----
test/remote-httpserverconnection.cpp | 6 +-
11 files changed, 186 insertions(+), 326 deletions(-)
diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp
index 401b205e2..2a17b46e7 100644
--- a/lib/remote/configfileshandler.cpp
+++ b/lib/remote/configfileshandler.cpp
@@ -82,7 +82,7 @@ bool ConfigFilesHandler::HandleRequest(
response.result(http::status::ok);
response.set(http::field::content_type, "application/octet-stream");
- response.body() << fp.rdbuf();
+ response << fp.rdbuf();
} catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 500, "Could not read file.",
DiagnosticInformation(ex));
diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp
index b4132677f..21ddf6d22 100644
--- a/lib/remote/eventshandler.cpp
+++ b/lib/remote/eventshandler.cpp
@@ -114,7 +114,7 @@ bool EventsHandler::HandleRequest(
if (event) {
encoder.Encode(event);
- response.body() << '\n';
+ response << '\n';
response.Flush(yc);
}
}
diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp
index 64cd15bf6..3656c7899 100644
--- a/lib/remote/httphandler.cpp
+++ b/lib/remote/httphandler.cpp
@@ -114,7 +114,7 @@ void HttpHandler::ProcessRequest(
* in the middle of a streaming response. We can't send any error response, so the
* only thing we can do is propagate it up.
*/
- if (response.HasSerializationStarted()) {
+ if (response.IsHeaderDone()) {
throw;
}
diff --git a/lib/remote/httpmessage.cpp b/lib/remote/httpmessage.cpp
index b3f1e32f5..79f872bd2 100644
--- a/lib/remote/httpmessage.cpp
+++ b/lib/remote/httpmessage.cpp
@@ -12,36 +12,25 @@
using namespace icinga;
/**
- * Adapter class for Boost Beast HTTP messages body to be used with the @c JsonEncoder.
+ * Adapter class for writing JSON data to an HTTP response.
*
- * This class implements the @c nlohmann::detail::output_adapter_protocol<> interface and provides
- * a way to write JSON data directly into the body of a Boost Beast HTTP message. The adapter is designed
- * to work with Boost Beast HTTP messages that conform to the Beast HTTP message interface and must provide
- * a body type that has a publicly accessible `reader` type that satisfies the Beast BodyReader [^1] requirements.
+ * This class implements the @c AsyncJsonWriter interface and writes JSON data directly to the @c HttpResponse
+ * object. It uses the @c HttpResponse's output stream operator to fill the response body with JSON data.
+ * It is designed to be used with the @c JsonEncoder class to indirectly fill the response body with JSON
+ * data and then forward it to the underlying stream using the @c Flush() method whenever it needs to do so.
*
* @ingroup base
- *
- * [^1]: https://www.boost.org/doc/libs/1_85_0/libs/beast/doc/html/beast/concepts/BodyReader.html
*/
class HttpResponseJsonWriter : public AsyncJsonWriter
{
public:
- explicit HttpResponseJsonWriter(HttpResponse& msg) : m_Reader(msg.base(), msg.body()), m_Message{msg}
+ explicit HttpResponseJsonWriter(HttpResponse& msg) : m_Message{msg}
{
- boost::system::error_code ec;
- // This never returns an actual error, except when overflowing the max
- // buffer size, which we don't do here.
- m_Reader.init(m_MinPendingBufferSize, ec);
- ASSERT(!ec);
}
~HttpResponseJsonWriter() override
{
- boost::system::error_code ec;
- // Same here as in the constructor, all the standard Beast HTTP message reader implementations
- // never return an error here, it's just there to satisfy the interface requirements.
- m_Reader.finish(ec);
- ASSERT(!ec);
+ m_Message.Finish();
}
void write_character(char c) override
@@ -51,30 +40,27 @@ public:
void write_characters(const char* s, std::size_t length) override
{
- boost::system::error_code ec;
- boost::asio::const_buffer buf{s, length};
- while (buf.size()) {
- std::size_t w = m_Reader.put(buf, ec);
- ASSERT(!ec);
- buf += w;
- }
- m_PendingBufferSize += length;
+ m_Message << std::string_view(s, length);
}
void MayFlush(boost::asio::yield_context& yield) override
{
- if (m_PendingBufferSize >= m_MinPendingBufferSize) {
+ if (m_Message.Size() >= m_MinPendingBufferSize) {
m_Message.Flush(yield);
- m_PendingBufferSize = 0;
}
}
private:
- HttpResponse::body_type::reader m_Reader;
HttpResponse& m_Message;
- // The size of the pending buffer to avoid unnecessary writes
- std::size_t m_PendingBufferSize{0};
- // Minimum size of the pending buffer before we flush the data to the underlying stream
+ /**
+ * Minimum size of the pending buffer before we flush the data to the underlying stream.
+ *
+ * This magic number represents 4x the default size of the internal buffer used by @c AsioTlsStream,
+ * which is 1024 bytes. This ensures that we don't flush too often and allows for efficient streaming
+ * of the response body. The value is chosen to balance between performance and memory usage, as flushing
+ * too often can lead to excessive I/O operations and increased latency, while flushing too infrequently
+ * can lead to high memory usage.
+ */
static constexpr std::size_t m_MinPendingBufferSize = 4096;
};
@@ -131,37 +117,108 @@ HttpResponse::HttpResponse(Shared::Ptr stream)
{
}
+/**
+ * Get the size of the current response body buffer.
+ *
+ * @return size of the response buffer
+ */
+std::size_t HttpResponse::Size() const
+{
+ return m_Buffer.size();
+}
+
+/**
+ * Checks whether the HTTP response headers have been written to the stream.
+ *
+ * @return true if the HTTP response headers have been written to the stream, false otherwise.
+ */
+bool HttpResponse::IsHeaderDone() const
+{
+ return m_HeaderDone;
+}
+
+/**
+ * Flush the response to the underlying stream.
+ *
+ * This function flushes the response body and headers to the underlying stream in the following two ways:
+ * - If the response is chunked, it writes the body in chunks using the @c boost::beast::http::make_chunk() function.
+ * The size of the current @c m_Buffer represents the chunk size with each call to this function. If there is no
+ * data in the buffer, and the response isn't finished yet, it's a no-op and will silently return. Otherwise, if
+ * the response is finished, it writes the last chunk with a size of zero to indicate the end of the response.
+ *
+ * - If the response is not chunked, it writes the entire buffer at once using the @c boost::asio::async_write()
+ * function. The content length is set to the size of the current @c m_Buffer. Calling this function more than
+ * once will immediately terminate the process in debug builds, as this should never happen and indicates a
+ * programming error.
+ *
+ * The headers are written in the same way in both cases, using the @c boost::beast::http::response_serializer<> class.
+ *
+ * @param yc The yield context for asynchronous operations
+ */
void HttpResponse::Flush(boost::asio::yield_context yc)
{
if (!chunked()) {
- ASSERT(!m_Serializer.is_header_done());
- prepare_payload();
+ ASSERT(!m_HeaderDone);
+ content_length(Size());
}
- boost::system::error_code ec;
- boost::beast::http::async_write(*m_Stream, m_Serializer, yc[ec]);
- if (ec && ec != boost::beast::http::error::need_buffer) {
- if (yc.ec_) {
- *yc.ec_ = ec;
- return;
- } else {
- BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
+ if (!m_HeaderDone) {
+ m_HeaderDone = true;
+ boost::beast::http::response_serializer s(*this);
+ boost::beast::http::async_write_header(*m_Stream, s, yc);
+ }
+
+ if (chunked()) {
+ bool flush = false;
+ // In case this is our last chunk (i.e., the response is finished), we need to write any
+ // remaining data in the buffer before we terminate the response with a zero-sized last chunk.
+ if (Size() > 0) {
+ boost::system::error_code ec;
+ boost::asio::async_write(*m_Stream, boost::beast::http::make_chunk(m_Buffer.data()), yc[ec]);
+ if (ec) {
+ if (yc.ec_) {
+ *yc.ec_ = std::move(ec);
+ return;
+ }
+ BOOST_THROW_EXCEPTION(boost::system::system_error(ec, "Failed to write chunked response"));
+ }
+
+ // The above write operation is guaranteed to either write the entire buffer or fail, and if it's the
+ // latter, we shouldn't reach this point, so we can safely trim the entire buffer input sequence here.
+ m_Buffer.consume(Size());
+ flush = true;
}
+
+ if (m_Finished) {
+ // If the response is finished, we need to write the last chunk with a size of zero.
+ boost::asio::async_write(*m_Stream, boost::beast::http::make_chunk_last(), yc);
+ } else if (!flush) {
+ // If we haven't written any data, we don't need to trigger a flush operation as well.
+ return;
+ }
+ } else {
+ // Non-chunked response, write the entire buffer at once.
+ async_write(*m_Stream, m_Buffer, yc);
+ m_Buffer.consume(Size()); // Won't be reused, but we want to free the memory ASAP.
}
m_Stream->async_flush(yc);
-
- ASSERT(chunked() || m_Serializer.is_done());
}
+/**
+ * Start streaming the response body in chunks.
+ *
+ * This function sets the response to use chunked encoding and prepares it for streaming.
+ * It must be called before any data is written to the response body, otherwise it will trigger an assertion failure.
+ *
+ * @note This function should only be called if the response body is expected to be streamed in chunks.
+ */
void HttpResponse::StartStreaming()
{
- ASSERT(body().Size() == 0 && !m_Serializer.is_header_done());
- body().Start();
+ ASSERT(Size() == 0 && !m_HeaderDone);
chunked(true);
}
JsonEncoder HttpResponse::GetJsonEncoder(bool pretty)
{
- auto adapter = std::make_shared(*this);
- return JsonEncoder{adapter, pretty};
+ return JsonEncoder{std::make_shared(*this), pretty};
}
diff --git a/lib/remote/httpmessage.hpp b/lib/remote/httpmessage.hpp
index bc67301c1..71606e2bd 100644
--- a/lib/remote/httpmessage.hpp
+++ b/lib/remote/httpmessage.hpp
@@ -9,198 +9,11 @@
#include "remote/url.hpp"
#include "remote/apiuser.hpp"
#include
-#include
+#include
namespace icinga
{
-/**
- * A custom body_type for a @c boost::beast::http::message
- *
- * It combines the memory management of @c boost::beast::http::dynamic_body,
- * which uses a multi_buffer, with the ability to continue serialization when
- * new data arrives of the @c boost::beast::http::buffer_body.
- *
- * @tparam DynamicBuffer A buffer conforming to the boost::beast interface of the same name
- *
- * @ingroup remote
- */
-template
-struct SerializableBody
-{
- class reader;
- class writer;
-
- class value_type
- {
- public:
- template
- value_type& operator<<(T && right)
- {
- /* Preferably, we would return an ostream object here instead. However
- * there seems to be a bug in boost::beast where if the ostream, or rather its
- * streambuf object is moved into the return value, the chunked encoding gets
- * mangled, leading to the client disconnecting.
- *
- * A workaround would have been to construct the boost::beast::detail::ostream_helper
- * with the last parameter set to false, indicating that the streambuf object is not
- * movable, but that is an implementation detail we'd rather not use directly in our
- * code.
- *
- * This version has a certain overhead of the ostream being constructed on every call
- * to the operator, which leads to an individual append for each time, whereas if the
- * object could be kept until the entire chain of output operators is finished, only
- * a single call to prepare()/commit() would have been needed.
- *
- * However, since this operator is mostly used for small error messages and the big
- * responses are handled via a reader instance, this shouldn't be too much of a
- * problem.
- */
- boost::beast::ostream(m_Buffer) << std::forward(right);
- return *this;
- }
-
- std::size_t Size() const { return m_Buffer.size(); }
-
- void Finish() { m_More = false; }
- void Start() { m_More = true; }
-
- friend class reader;
- friend class writer;
-
- private:
- /* This defaults to false so the body does not require any special handling
- * for simple messages and can still be written with http::async_write().
- */
- bool m_More = false;
- DynamicBuffer m_Buffer;
- };
-
- static std::uint64_t size(const value_type& body) { return body.Size(); }
-
- /**
- * Implement the boost::beast BodyReader interface for this body type
- *
- * This is used by the @c boost::beast::http::parser (which we don't use for responses)
- * or in the @c BeastHttpMessageAdapter for our own @c JsonEncoder to write JSON directly
- * into the body of a HTTP response message.
- *
- * The reader automatically sets the body's `more` flag on the call to its init() function
- * and resets it when its finish() function is called. Regarding the usage in the
- * @c JsonEncoder above, this means that the message is automatically marked as complete
- * when the encoder object is destroyed.
- */
- class reader
- {
- public:
- template
- explicit reader(boost::beast::http::header& h, value_type& b) : m_Body(b)
- {
- }
-
- void init(const boost::optional& n, boost::beast::error_code& ec)
- {
- ec = {};
- m_Body.Start();
-#if BOOST_VERSION >= 107000
- if (n) {
- m_Body.m_Buffer.reserve(*n);
- }
-#endif /* BOOST_VERSION */
- }
-
- template
- std::size_t put(const ConstBufferSequence& buffers, boost::beast::error_code& ec)
- {
- auto size = boost::asio::buffer_size(buffers);
- if (size > m_Body.m_Buffer.max_size() - m_Body.Size()) {
- ec = boost::beast::http::error::buffer_overflow;
- return 0;
- }
-
- auto const wBuf = m_Body.m_Buffer.prepare(size);
- boost::asio::buffer_copy(wBuf, buffers);
- m_Body.m_Buffer.commit(size);
- return size;
- }
-
- void finish(boost::beast::error_code& ec)
- {
- ec = {};
- m_Body.Finish();
- }
-
- private:
- value_type& m_Body;
- };
-
- /**
- * Implement the boost::beast BodyWriter interface for this body type
- *
- * This is used (for example) by the @c boost::beast::http::serializer to write out the
- * message over the TLS stream. The logic is similar to the writer of the
- * @c boost::beast::http::buffer_body.
- *
- * On the every call, it will free up the buffer range that has previously been written,
- * then return a buffer containing data the has become available in the meantime. Otherwise,
- * if there is more data expected in the future, for example because a corresponding reader
- * has not yet finished filling the body, a `need_buffer` error is returned, to inform the
- * serializer to abort writing for now, which in turn leads to the outer call to
- * `http::async_write` to call their completion handlers with a `need_buffer` error, to
- * notify that more data is required for another call to `http::async_write`.
- */
- class writer
- {
- public:
- using const_buffers_type = typename decltype(value_type::m_Buffer)::const_buffers_type;
-
-#if BOOST_VERSION > 106600
- template
- explicit writer(const boost::beast::http::header& h, value_type& b)
- : m_Body(b)
- {
- }
-#else
- /**
- * This constructor is needed specifically for boost-1.66, which was the first version
- * the beast library was introduced and is still used on older (supported) distros.
- */
- template
- explicit writer(const boost::beast::http::message& msg)
- : m_Body(const_cast(msg.body()))
- {
- }
-#endif
- void init(boost::beast::error_code& ec) { ec = {}; }
-
- boost::optional> get(boost::beast::error_code& ec)
- {
- using namespace boost::beast::http;
-
- if (m_SizeWritten > 0) {
- m_Body.m_Buffer.consume(std::exchange(m_SizeWritten, 0));
- }
-
- if (m_Body.m_Buffer.size()) {
- ec = {};
- m_SizeWritten = m_Body.m_Buffer.size();
- return {{m_Body.m_Buffer.data(), m_Body.m_More}};
- }
-
- if (m_Body.m_More) {
- ec = {make_error_code(error::need_buffer)};
- } else {
- ec = {};
- }
- return boost::none;
- }
-
- private:
- value_type& m_Body;
- std::size_t m_SizeWritten = 0;
- };
-};
-
/**
* A wrapper class for a boost::beast HTTP request
*
@@ -254,40 +67,74 @@ private:
};
/**
- * A wrapper class for a boost::beast HTTP response
+ * A wrapper class for a boost::beast HTTP response.
+ *
+ * This is a convenience class to handle HTTP responses in a more streaming-like way. This class is derived
+ * from @c boost::beast::http::response and as its name implies, it doesn't
+ * and won't contain a body. Instead, it uses its own internal buffer to efficiently manage the response body
+ * data, which can be filled using the output stream operator (`<<`) or the @c JsonEncoder class. This allows
+ * for a more flexible and efficient way to handle HTTP responses, without having to copy Asio/Beast buffers
+ * around. It uses a @c boost::asio::streambuf internally to manage the response body data, which is suitable
+ * for the @c JsonEncoder class to write JSON data directly into it and then forward it as-is to Asio's
+ * @c boost::asio::async_write() function.
+ *
+ * Furthermore, it supports chunked encoding as well as fixed-size responses. If chunked encoding is used, the
+ * @c m_Buffer is written in chunks created by the @c boost::beast::http::make_chunk() function, which doesn't
+ * copy the buffer data, but instead creates a view similar to @c std::string_view but for buffers and allows
+ * for efficient streaming of the response body data. Once you've finished writing to the response, you must
+ * call the @c Finish() followed by a call to @c Flush() to indicate that there will be no more data to stream
+ * and to write the final chunk with a size of zero to indicate the end of the response. If you don't call @c Finish(),
+ * the response will not be flushed and the client will not receive the final chunk, which may lead to a timeout
+ * or other issues on the client side.
*
* @ingroup remote
*/
-class HttpResponse: public boost::beast::http::response>
+class HttpResponse : public boost::beast::http::response
{
public:
explicit HttpResponse(Shared::Ptr stream);
- /**
- * Writes as much of the response as is currently available.
- *
- * Uses chunk-encoding if the content_length has not been set by the time this is called
- * for the first time.
- *
- * The caller needs to ensure that the header is finished before calling this for the
- * first time as changes to the header afterwards will not have any effect.
- *
- * @param yc The yield_context for this operation
- */
+ std::size_t Size() const;
+ bool IsHeaderDone() const;
+
void Flush(boost::asio::yield_context yc);
- bool HasSerializationStarted () { return m_Serializer.is_header_done(); }
-
- /**
- * Enables chunked encoding.
- */
void StartStreaming();
+ void Finish()
+ {
+ m_Finished = true;
+ }
+
JsonEncoder GetJsonEncoder(bool pretty = false);
-
+
+ /**
+ * Write data to the response body.
+ *
+ * This function appends the given data to the response body using the internal output stream.
+ * This is the only available way to fill the response body with data and must not be called
+ * after the response has been finished (i.e., after calling @c Finish()).
+ *
+ * @tparam T The type of the data to write. It can be any type that supports the output stream operator (`<<`).
+ *
+ * @param data The data to write to the response body
+ *
+ * @return Reference to this HttpResponse object for chaining operations.
+ */
+ template
+ HttpResponse& operator<<(T&& data)
+ {
+ ASSERT(!m_Finished); // Never allow writing to an already finished response.
+ m_Ostream << std::forward(data);
+ return *this;
+ }
+
private:
- using Serializer = boost::beast::http::response_serializer;
- Serializer m_Serializer{*this};
+ bool m_HeaderDone = false; // Indicates if the response headers have been written to the stream.
+ bool m_Finished = false; // Indicates if the response is finished and no more data will be written.
+
+ boost::asio::streambuf m_Buffer; // The buffer used to store the response body.
+ std::ostream m_Ostream{&m_Buffer}; // The output stream used to write data to the response body.
Shared::Ptr m_Stream;
};
diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp
index 0345ab5f5..230b54c9d 100644
--- a/lib/remote/httpserverconnection.cpp
+++ b/lib/remote/httpserverconnection.cpp
@@ -148,7 +148,7 @@ bool EnsureValidHeaders(
HttpUtility::SendJsonError(response, nullptr, 400, "Bad Request: " + errorMsg);
} else {
response.set(http::field::content_type, "text/html");
- response.body() << "Bad Request
" << errorMsg << "
\r\n";
+ response << "Bad Request
" << errorMsg << "
\r\n";
}
response.set(http::field::connection, "close");
@@ -207,7 +207,7 @@ bool HandleAccessControl(
response.result(http::status::ok);
response.set(http::field::access_control_allow_methods, "GET, POST, PUT, DELETE");
response.set(http::field::access_control_allow_headers, "Authorization, Content-Type, X-HTTP-Method-Override");
- response.body() << "Preflight OK";
+ response << "Preflight OK";
response.set(http::field::connection, "close");
response.Flush(yc);
@@ -233,7 +233,7 @@ bool EnsureAcceptHeader(
if (request.method() != http::verb::get && request[http::field::accept] != "application/json") {
response.result(http::status::bad_request);
response.set(http::field::content_type, "text/html");
- response.body() << "Accept header is missing or not set to 'application/json'.
\r\n";
+ response << "Accept header is missing or not set to 'application/json'.
\r\n";
response.set(http::field::connection, "close");
response.Flush(yc);
@@ -265,7 +265,7 @@ bool EnsureAuthenticatedUser(
HttpUtility::SendJsonError(response, nullptr, 401, "Unauthorized. Please check your user credentials.");
} else {
response.set(http::field::content_type, "text/html");
- response.body() << "Unauthorized. Please check your user credentials.
\r\n";
+ response << "Unauthorized. Please check your user credentials.
\r\n";
}
response.Flush(yc);
@@ -346,7 +346,7 @@ bool EnsureValidBody(
HttpUtility::SendJsonError(response, nullptr, 400, "Bad Request: " + ec.message());
} else {
response.set(http::field::content_type, "text/html");
- response.body() << "Bad Request
" << ec.message() << "
\r\n";
+ response << "Bad Request
" << ec.message() << "
\r\n";
}
response.set(http::field::connection, "close");
@@ -379,7 +379,7 @@ bool ProcessRequest(
/* Since we don't know the state the stream is in, we can't send an error response and
* have to just cause a disconnect here.
*/
- if (response.HasSerializationStarted()) {
+ if (response.IsHeaderDone()) {
throw;
}
@@ -388,7 +388,7 @@ bool ProcessRequest(
return true;
}
- response.body().Finish();
+ response.Finish();
response.Flush(yc);
return true;
diff --git a/lib/remote/infohandler.cpp b/lib/remote/infohandler.cpp
index afc62f50b..94c044508 100644
--- a/lib/remote/infohandler.cpp
+++ b/lib/remote/infohandler.cpp
@@ -76,23 +76,22 @@ bool InfoHandler::HandleRequest(
} else {
response.set(http::field::content_type, "text/html");
- auto & body = response.body();
- body << "Icinga 2Hello from Icinga 2 (Version: "
+ response << "Icinga 2Hello from Icinga 2 (Version: "
<< Application::GetAppVersion() << ")!
"
<< "
You are authenticated as " << user->GetName() << ". ";
if (!permInfo.empty()) {
- body << "Your user has the following permissions:
";
+ response << "Your user has the following permissions: ";
for (const String& perm : permInfo) {
- body << "- " << perm << "
";
+ response << "- " << perm << "
";
}
- body << "
";
+ response << "
";
} else
- body << "Your user does not have any permissions.";
+ response << "Your user does not have any permissions.";
- body << R"(More information about API requests is available in the documentation.
)";
+ response << R"(More information about API requests is available in the documentation.
)";
}
return true;
diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp
index 4ca37d555..1c32461ff 100644
--- a/lib/remote/mallocinfohandler.cpp
+++ b/lib/remote/mallocinfohandler.cpp
@@ -86,7 +86,7 @@ bool MallocInfoHandler::HandleRequest(
response.result(200);
response.set(http::field::content_type, "application/xml");
- response.body() << std::string_view(buf, bufSize);
+ response << std::string_view(buf, bufSize);
#endif /* HAVE_MALLOC_INFO */
return true;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index fb94a4384..435e0a2ee 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -279,7 +279,6 @@ add_boost_test(base
remote_httpmessage/request_parse
remote_httpmessage/request_params
remote_httpmessage/response_flush_nothrow
- remote_httpmessage/response_body_reader
remote_httpmessage/response_write_empty
remote_httpmessage/response_write_fixed
remote_httpmessage/response_write_chunked
@@ -311,7 +310,6 @@ if(BUILD_TESTING)
base-remote_httpmessage/request_parse
base-remote_httpmessage/request_params
base-remote_httpmessage/response_flush_nothrow
- base-remote_httpmessage/response_body_reader
base-remote_httpmessage/response_write_empty
base-remote_httpmessage/response_write_fixed
base-remote_httpmessage/response_write_chunked
diff --git a/test/remote-httpmessage.cpp b/test/remote-httpmessage.cpp
index c9375c80e..84b0fef1b 100644
--- a/test/remote-httpmessage.cpp
+++ b/test/remote-httpmessage.cpp
@@ -64,47 +64,6 @@ BOOST_AUTO_TEST_CASE(request_params)
BOOST_REQUIRE(!HttpUtility::GetLastParameter(params, "test4"));
}
-BOOST_AUTO_TEST_CASE(response_body_reader)
-{
- auto & io = IoEngine::Get();
- io.SpawnCoroutine(io.GetIoContext(), [&, this](boost::asio::yield_context yc) {
- HttpResponse response(server);
- response.result(http::status::ok);
- response.StartStreaming();
-
- SerializableBody::reader rd{response.base(), response.body()};
- boost::system::error_code ec;
- rd.init(3, ec);
- BOOST_REQUIRE(!ec);
-
- boost::asio::const_buffer seq1("test1", 5);
- rd.put(seq1, ec);
- BOOST_REQUIRE(!ec);
- BOOST_REQUIRE_NO_THROW(response.Flush(yc));
-
- boost::asio::const_buffer seq2("test2", 5);
- rd.put(seq2, ec);
- BOOST_REQUIRE(!ec);
- BOOST_REQUIRE_NO_THROW(response.Flush(yc));
-
- rd.finish(ec);
- BOOST_REQUIRE(!ec);
- BOOST_REQUIRE_NO_THROW(response.Flush(yc));
-
- BOOST_REQUIRE(Shutdown(server, yc));
- });
-
- http::response_parser parser;
- flat_buffer buf;
- http::read(*client, buf, parser);
-
- BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok);
- BOOST_REQUIRE_EQUAL(parser.get().chunked(), true);
- BOOST_REQUIRE_EQUAL(parser.get().body(), "test1test2");
-
- BOOST_REQUIRE(Shutdown(client));
-}
-
BOOST_AUTO_TEST_CASE(response_flush_nothrow)
{
auto& io = IoEngine::Get();
@@ -157,7 +116,7 @@ BOOST_AUTO_TEST_CASE(response_write_fixed)
io.SpawnCoroutine(io.GetIoContext(), [&, this](boost::asio::yield_context yc) {
HttpResponse response(server);
response.result(http::status::ok);
- response.body() << "test";
+ response << "test";
BOOST_REQUIRE_NO_THROW(response.Flush(yc));
@@ -183,14 +142,14 @@ BOOST_AUTO_TEST_CASE(response_write_chunked)
response.result(http::status::ok);
response.StartStreaming();
- response.body() << "test" << 1;
+ response << "test" << 1;
BOOST_REQUIRE_NO_THROW(response.Flush(yc));
- response.body() << "test" << 2;
+ response << "test" << 2;
BOOST_REQUIRE_NO_THROW(response.Flush(yc));
- response.body() << "test" << 3;
- response.body().Finish();
+ response << "test" << 3;
+ response.Finish();
BOOST_REQUIRE_NO_THROW(response.Flush(yc));
BOOST_REQUIRE(Shutdown(server, yc));
diff --git a/test/remote-httpserverconnection.cpp b/test/remote-httpserverconnection.cpp
index 2ad1563f8..9a5597cb9 100644
--- a/test/remote-httpserverconnection.cpp
+++ b/test/remote-httpserverconnection.cpp
@@ -84,7 +84,7 @@ class UnitTestHandler final: public HttpHandler
response.StartStreaming();
response.Flush(yc);
for (;;) {
- response.body() << "test";
+ response << "test";
response.Flush(yc);
}
@@ -93,12 +93,12 @@ class UnitTestHandler final: public HttpHandler
if (HttpUtility::GetLastParameter(request.Params(), "throw")) {
response.StartStreaming();
- response.body() << "test";
+ response << "test";
response.Flush(yc);
throw std::runtime_error{"The front fell off"};
}
- response.body() << "test";
+ response << "test";
return true;
}
};