Skip to content

Commit 1505f09

Browse files
Refactor HttpMessage into generalized templated types
This adds generalized IncomingHttpMessage and OutgoingHttpMessage templates that support different types of streams (via a std::variant) and can both be used for either requests or responses. The tacked on metadata from the old HttpRequest and server connection from the old HttpServerConnection have been moved to HttpApi(Request|Response) classes that derive from the above generalized message types.
1 parent a0f603f commit 1505f09

File tree

6 files changed

+235
-104
lines changed

6 files changed

+235
-104
lines changed

lib/base/tlsstream.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <atomic>
1414
#include <memory>
1515
#include <utility>
16+
#include <variant>
1617
#include <boost/asio/buffered_stream.hpp>
1718
#include <boost/asio/io_context.hpp>
1819
#include <boost/asio/ip/tcp.hpp>
@@ -122,9 +123,9 @@ class AsioTlsStream : public boost::asio::buffered_stream<UnbufferedAsioTlsStrea
122123
}
123124
};
124125

125-
typedef boost::asio::buffered_stream<boost::asio::ip::tcp::socket> AsioTcpStream;
126-
typedef std::pair<Shared<AsioTlsStream>::Ptr, Shared<AsioTcpStream>::Ptr> OptionalTlsStream;
127-
126+
using AsioTcpStream = boost::asio::buffered_stream<boost::asio::ip::tcp::socket>;
127+
using OptionalTlsStream = std::pair<Shared<AsioTlsStream>::Ptr, Shared<AsioTcpStream>::Ptr>;
128+
using AsioTlsOrTcpStream = std::variant<Shared<AsioTlsStream>::Ptr, Shared<AsioTcpStream>::Ptr>;
128129
}
129130

130131
#endif /* TLSSTREAM_H */

lib/remote/httpmessage.cpp

Lines changed: 110 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
22

33
#include "remote/httpmessage.hpp"
4-
#include "base/io-engine.hpp"
54
#include "base/json.hpp"
65
#include "remote/httputility.hpp"
76
#include "remote/url.hpp"
@@ -27,10 +26,15 @@ constexpr std::size_t l_FlushThreshold = 128UL * 1024UL;
2726
*
2827
* @ingroup base
2928
*/
29+
template<typename Message>
3030
class HttpResponseJsonWriter : public AsyncJsonWriter
3131
{
3232
public:
33-
explicit HttpResponseJsonWriter(HttpApiResponse& msg) : m_Message{msg}
33+
HttpResponseJsonWriter(const HttpResponseJsonWriter&) = delete;
34+
HttpResponseJsonWriter(HttpResponseJsonWriter&&) = delete;
35+
HttpResponseJsonWriter& operator=(const HttpResponseJsonWriter&) = delete;
36+
HttpResponseJsonWriter& operator=(HttpResponseJsonWriter&&) = delete;
37+
explicit HttpResponseJsonWriter(Message& msg) : m_Message{msg}
3438
{
3539
m_Message.body().Start();
3640
#if BOOST_VERSION >= 107000
@@ -59,23 +63,37 @@ class HttpResponseJsonWriter : public AsyncJsonWriter
5963
}
6064

6165
private:
62-
HttpApiResponse& m_Message;
66+
Message& m_Message;
6367
};
6468

65-
HttpApiRequest::HttpApiRequest(Shared<AsioTlsStream>::Ptr stream) : m_Stream(std::move(stream))
69+
template<bool isRequest, typename Body, typename StreamVariant>
70+
IncomingHttpMessage<isRequest, Body, StreamVariant>::IncomingHttpMessage(StreamVariant stream)
71+
: m_Stream(std::move(stream))
6672
{
6773
}
6874

69-
void HttpApiRequest::ParseHeader(boost::beast::flat_buffer& buf, boost::asio::yield_context yc)
75+
template<bool isRequest, typename Body, typename StreamVariant>
76+
void IncomingHttpMessage<isRequest, Body, StreamVariant>::ParseHeader(
77+
boost::beast::flat_buffer& buf,
78+
boost::asio::yield_context yc
79+
)
7080
{
71-
boost::beast::http::async_read_header(*m_Stream, buf, m_Parser, yc);
72-
base() = m_Parser.get().base();
81+
std::visit([&](auto& stream) { boost::beast::http::async_read_header(*stream, buf, m_Parser, yc); }, m_Stream);
82+
Base::base() = m_Parser.get().base();
7383
}
7484

75-
void HttpApiRequest::ParseBody(boost::beast::flat_buffer& buf, boost::asio::yield_context yc)
85+
template<bool isRequest, typename Body, typename StreamVariant>
86+
void IncomingHttpMessage<isRequest, Body, StreamVariant>::ParseBody(
87+
boost::beast::flat_buffer& buf,
88+
boost::asio::yield_context yc
89+
)
90+
{
91+
std::visit([&](auto& stream) { boost::beast::http::async_read(*stream, buf, m_Parser, yc); }, m_Stream);
92+
Base::body() = std::move(m_Parser.release().body());
93+
}
94+
95+
HttpApiRequest::HttpApiRequest(Shared<AsioTlsStream>::Ptr stream) : IncomingHttpMessage(std::move(stream))
7696
{
77-
boost::beast::http::async_read(*m_Stream, buf, m_Parser, yc);
78-
body() = std::move(m_Parser.release().body());
7997
}
8098

8199
ApiUser::Ptr HttpApiRequest::User() const
@@ -111,49 +129,72 @@ void HttpApiRequest::DecodeParams()
111129
m_Params = HttpUtility::FetchRequestParameters(m_Url, body());
112130
}
113131

114-
HttpApiResponse::HttpApiResponse(Shared<AsioTlsStream>::Ptr stream, HttpServerConnection::Ptr server)
115-
: m_Server(std::move(server)), m_Stream(std::move(stream))
132+
template<bool isRequest, typename Body, typename StreamVariant>
133+
OutgoingHttpMessage<isRequest, Body, StreamVariant>::OutgoingHttpMessage(StreamVariant stream)
134+
: m_Stream(std::move(stream))
116135
{
117136
}
118137

119-
void HttpApiResponse::Clear()
138+
template<bool isRequest, typename Body, typename StreamVariant>
139+
void OutgoingHttpMessage<isRequest, Body, StreamVariant>::Clear()
120140
{
121141
ASSERT(!m_SerializationStarted);
122-
boost::beast::http::response<body_type>::operator=({});
142+
Base::operator=({});
123143
}
124144

125-
void HttpApiResponse::Flush(boost::asio::yield_context yc)
145+
template<bool isRequest, typename Body, typename StreamVariant>
146+
void OutgoingHttpMessage<isRequest, Body, StreamVariant>::Flush(boost::asio::yield_context yc, bool finish)
126147
{
127-
if (!chunked() && !has_content_length()) {
148+
if (!Base::chunked() && !Base::has_content_length()) {
128149
ASSERT(!m_SerializationStarted);
129-
prepare_payload();
150+
Base::prepare_payload();
130151
}
131152

132-
m_SerializationStarted = true;
133-
134-
if (!m_Serializer.is_header_done()) {
135-
boost::beast::http::write_header(*m_Stream, m_Serializer);
136-
}
153+
std::visit(
154+
[&](auto& stream) {
155+
m_SerializationStarted = true;
156+
157+
if (!m_Serializer.is_header_done()) {
158+
boost::beast::http::write_header(*stream, m_Serializer);
159+
}
160+
161+
if (finish) {
162+
Base::body().Finish();
163+
}
164+
165+
boost::system::error_code ec;
166+
boost::beast::http::async_write(*stream, m_Serializer, yc[ec]);
167+
if (ec && ec != boost::beast::http::error::need_buffer) {
168+
if (yc.ec_) {
169+
*yc.ec_ = ec;
170+
return;
171+
}
172+
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
173+
}
174+
stream->async_flush(yc);
175+
176+
ASSERT(m_Serializer.is_done() || !Base::body().Finished());
177+
},
178+
m_Stream
179+
);
180+
}
137181

138-
boost::system::error_code ec;
139-
boost::beast::http::async_write(*m_Stream, m_Serializer, yc[ec]);
140-
if (ec && ec != boost::beast::http::error::need_buffer) {
141-
if (yc.ec_) {
142-
*yc.ec_ = ec;
143-
return;
144-
}
145-
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
146-
}
147-
m_Stream->async_flush(yc);
182+
template<bool isRequest, typename Body, typename StreamVariant>
183+
void OutgoingHttpMessage<isRequest, Body, StreamVariant>::StartStreaming()
184+
{
185+
ASSERT(Base::body().Size() == 0 && !m_SerializationStarted);
186+
Base::body().Start();
187+
Base::chunked(true);
188+
}
148189

149-
ASSERT(m_Serializer.is_done() || !body().Finished());
190+
HttpApiResponse::HttpApiResponse(Shared<AsioTlsStream>::Ptr stream, HttpServerConnection::Ptr server)
191+
: OutgoingHttpMessage(std::move(stream)), m_Server(std::move(server))
192+
{
150193
}
151194

152195
void HttpApiResponse::StartStreaming(bool checkForDisconnect)
153196
{
154-
ASSERT(body().Size() == 0 && !m_SerializationStarted);
155-
body().Start();
156-
chunked(true);
197+
OutgoingHttpMessage::StartStreaming();
157198

158199
if (checkForDisconnect) {
159200
ASSERT(m_Server);
@@ -167,30 +208,56 @@ bool HttpApiResponse::IsClientDisconnected() const
167208
return m_Server->Disconnected();
168209
}
169210

170-
void HttpApiResponse::SendFile(const String& path, const boost::asio::yield_context& yc)
211+
template<bool isRequest, typename Body, typename StreamVariant>
212+
void OutgoingHttpMessage<isRequest, Body, StreamVariant>::SendFile(
213+
const String& path,
214+
const boost::asio::yield_context& yc
215+
)
171216
{
172217
std::ifstream fp(path.CStr(), std::ifstream::in | std::ifstream::binary | std::ifstream::ate);
173218
fp.exceptions(std::ifstream::badbit | std::ifstream::eofbit);
174219

175220
std::uint64_t remaining = fp.tellg();
176221
fp.seekg(0);
177222

178-
content_length(remaining);
179-
body().Start();
223+
Base::content_length(remaining);
224+
Base::body().Start();
180225

181226
while (remaining) {
182227
auto maxTransfer = std::min(remaining, static_cast<std::uint64_t>(l_FlushThreshold));
183228

184-
auto buf = *body().Buffer().prepare(maxTransfer).begin();
229+
using BodyBuffer = std::decay_t<decltype(std::declval<typename Body::value_type>().Buffer())>;
230+
using BufferOrSequence = typename BodyBuffer::mutable_buffers_type;
231+
232+
boost::asio::mutable_buffer buf;
233+
234+
if constexpr (!std::is_same_v<BufferOrSequence, boost::asio::mutable_buffer>) {
235+
buf = *Base::body().Buffer().prepare(maxTransfer).begin();
236+
} else {
237+
buf = Base::body().Buffer().prepare(maxTransfer);
238+
}
185239
fp.read(static_cast<char*>(buf.data()), buf.size());
186-
body().Buffer().commit(buf.size());
240+
Base::body().Buffer().commit(buf.size());
187241

188242
remaining -= buf.size();
189243
Flush(yc);
190244
}
191245
}
192246

193-
JsonEncoder HttpApiResponse::GetJsonEncoder(bool pretty)
247+
template<bool isRequest, typename Body, typename StreamVariant>
248+
JsonEncoder OutgoingHttpMessage<isRequest, Body, StreamVariant>::GetJsonEncoder(bool pretty)
194249
{
195-
return JsonEncoder{std::make_shared<HttpResponseJsonWriter>(*this), pretty};
250+
return JsonEncoder{
251+
std::make_shared<HttpResponseJsonWriter<OutgoingHttpMessage<isRequest, Body, StreamVariant>>>(*this), pretty
252+
};
196253
}
254+
255+
// More general instantiations
256+
template class icinga::OutgoingHttpMessage<true, SerializableFlatBufferBody, AsioTlsOrTcpStream>;
257+
template class icinga::OutgoingHttpMessage<false, SerializableFlatBufferBody, AsioTlsOrTcpStream>;
258+
template class icinga::IncomingHttpMessage<true, boost::beast::http::string_body, AsioTlsOrTcpStream>;
259+
template class icinga::IncomingHttpMessage<false, boost::beast::http::string_body, AsioTlsOrTcpStream>;
260+
261+
// Instantiations specifically for HttpApi(Request|Response)
262+
template class icinga::IncomingHttpMessage<true, boost::beast::http::string_body, std::variant<Shared<AsioTlsStream>::Ptr>>;
263+
template class icinga::OutgoingHttpMessage<false, SerializableMultiBufferBody, std::variant<Shared<AsioTlsStream>::Ptr>>;

0 commit comments

Comments
 (0)