mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-29 00:24:23 +02:00
Avoid copying buffers around when serializing HTTP response
This commit is contained in:
parent
d587bca3bf
commit
acbcf299a0
@ -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));
|
||||
|
@ -114,7 +114,7 @@ bool EventsHandler::HandleRequest(
|
||||
|
||||
if (event) {
|
||||
encoder.Encode(event);
|
||||
response.body() << '\n';
|
||||
response << '\n';
|
||||
response.Flush(yc);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<AsioTlsStream>::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<body_type> 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<HttpResponseJsonWriter>(*this);
|
||||
return JsonEncoder{adapter, pretty};
|
||||
return JsonEncoder{std::make_shared<HttpResponseJsonWriter>(*this), pretty};
|
||||
}
|
||||
|
@ -9,198 +9,11 @@
|
||||
#include "remote/url.hpp"
|
||||
#include "remote/apiuser.hpp"
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/version.hpp>
|
||||
#include <boost/asio/streambuf.hpp>
|
||||
|
||||
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<class DynamicBuffer>
|
||||
struct SerializableBody
|
||||
{
|
||||
class reader;
|
||||
class writer;
|
||||
|
||||
class value_type
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
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<T>(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 <bool isRequest, class Fields>
|
||||
explicit reader(boost::beast::http::header<isRequest, Fields>& h, value_type& b) : m_Body(b)
|
||||
{
|
||||
}
|
||||
|
||||
void init(const boost::optional<std::uint64_t>& 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 <class ConstBufferSequence>
|
||||
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 <bool isRequest, class Fields>
|
||||
explicit writer(const boost::beast::http::header<isRequest, Fields>& 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 <bool isRequest, class Fields>
|
||||
explicit writer(const boost::beast::http::message<isRequest, SerializableBody, Fields>& msg)
|
||||
: m_Body(const_cast<value_type &>(msg.body()))
|
||||
{
|
||||
}
|
||||
#endif
|
||||
void init(boost::beast::error_code& ec) { ec = {}; }
|
||||
|
||||
boost::optional<std::pair<const_buffers_type, bool>> 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<boost::beast::http::empty_body> 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<SerializableBody<boost::beast::multi_buffer>>
|
||||
class HttpResponse : public boost::beast::http::response<boost::beast::http::empty_body>
|
||||
{
|
||||
public:
|
||||
explicit HttpResponse(Shared<AsioTlsStream>::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<typename T>
|
||||
HttpResponse& operator<<(T&& data)
|
||||
{
|
||||
ASSERT(!m_Finished); // Never allow writing to an already finished response.
|
||||
m_Ostream << std::forward<T>(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
using Serializer = boost::beast::http::response_serializer<HttpResponse::body_type>;
|
||||
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<AsioTlsStream>::Ptr m_Stream;
|
||||
};
|
||||
|
@ -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() << "<h1>Bad Request</h1><p><pre>" << errorMsg << "</pre></p>\r\n";
|
||||
response << "<h1>Bad Request</h1><p><pre>" << errorMsg << "</pre></p>\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() << "<h1>Accept header is missing or not set to 'application/json'.</h1>\r\n";
|
||||
response << "<h1>Accept header is missing or not set to 'application/json'.</h1>\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() << "<h1>Unauthorized. Please check your user credentials.</h1>\r\n";
|
||||
response << "<h1>Unauthorized. Please check your user credentials.</h1>\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() << "<h1>Bad Request</h1><p><pre>" << ec.message() << "</pre></p>\r\n";
|
||||
response << "<h1>Bad Request</h1><p><pre>" << ec.message() << "</pre></p>\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;
|
||||
|
@ -76,23 +76,22 @@ bool InfoHandler::HandleRequest(
|
||||
} else {
|
||||
response.set(http::field::content_type, "text/html");
|
||||
|
||||
auto & body = response.body();
|
||||
body << "<html><head><title>Icinga 2</title></head><h1>Hello from Icinga 2 (Version: "
|
||||
response << "<html><head><title>Icinga 2</title></head><h1>Hello from Icinga 2 (Version: "
|
||||
<< Application::GetAppVersion() << ")!</h1>"
|
||||
<< "<p>You are authenticated as <b>" << user->GetName() << "</b>. ";
|
||||
|
||||
if (!permInfo.empty()) {
|
||||
body << "Your user has the following permissions:</p> <ul>";
|
||||
response << "Your user has the following permissions:</p> <ul>";
|
||||
|
||||
for (const String& perm : permInfo) {
|
||||
body << "<li>" << perm << "</li>";
|
||||
response << "<li>" << perm << "</li>";
|
||||
}
|
||||
|
||||
body << "</ul>";
|
||||
response << "</ul>";
|
||||
} else
|
||||
body << "Your user does not have any permissions.</p>";
|
||||
response << "Your user does not have any permissions.</p>";
|
||||
|
||||
body << R"(<p>More information about API requests is available in the <a href="https://icinga.com/docs/icinga2/latest/" target="_blank">documentation</a>.</p></html>)";
|
||||
response << R"(<p>More information about API requests is available in the <a href="https://icinga.com/docs/icinga2/latest/" target="_blank">documentation</a>.</p></html>)";
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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<boost::beast::multi_buffer>::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<http::string_body> 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));
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user