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>
3030class HttpResponseJsonWriter : public AsyncJsonWriter
3131{
3232public:
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
6165private:
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
8199ApiUser::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
152195void 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