blob: 906c326207eac3ea28bf5423bc43dbfac5b2a3b8 [file] [log] [blame]
V-Sanjana88ada3b2023-04-13 15:18:31 +05301#pragma once
Ed Tanous41fe81c2024-09-02 15:08:41 -07002#include "boost_formatters.hpp"
Ed Tanousb2896142024-01-31 15:25:47 -08003#include "http_body.hpp"
V-Sanjana88ada3b2023-04-13 15:18:31 +05304#include "http_request.hpp"
5#include "http_response.hpp"
6
V-Sanjana88ada3b2023-04-13 15:18:31 +05307#include <boost/asio/buffer.hpp>
8#include <boost/asio/steady_timer.hpp>
9#include <boost/beast/core/multi_buffer.hpp>
V-Sanjana88ada3b2023-04-13 15:18:31 +053010#include <boost/beast/websocket.hpp>
11
12#include <array>
Ed Tanous8f79c5b2024-01-30 15:56:37 -080013#include <cstddef>
V-Sanjana88ada3b2023-04-13 15:18:31 +053014#include <functional>
Ed Tanous8f79c5b2024-01-30 15:56:37 -080015#include <optional>
V-Sanjana88ada3b2023-04-13 15:18:31 +053016
17namespace crow
18{
19
20namespace sse_socket
21{
Ed Tanous93cf0ac2024-03-28 00:35:13 -070022struct Connection : public std::enable_shared_from_this<Connection>
V-Sanjana88ada3b2023-04-13 15:18:31 +053023{
24 public:
Ed Tanous6fde95f2023-06-01 07:33:34 -070025 Connection() = default;
V-Sanjana88ada3b2023-04-13 15:18:31 +053026
27 Connection(const Connection&) = delete;
28 Connection(Connection&&) = delete;
29 Connection& operator=(const Connection&) = delete;
30 Connection& operator=(const Connection&&) = delete;
31 virtual ~Connection() = default;
32
33 virtual boost::asio::io_context& getIoContext() = 0;
V-Sanjana88ada3b2023-04-13 15:18:31 +053034 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous6d799e12024-09-11 14:33:37 -070035 virtual void sendSseEvent(std::string_view id, std::string_view msg) = 0;
V-Sanjana88ada3b2023-04-13 15:18:31 +053036};
37
38template <typename Adaptor>
39class ConnectionImpl : public Connection
40{
41 public:
Ed Tanousf80a87f2024-06-16 12:10:33 -070042 ConnectionImpl(
43 Adaptor&& adaptorIn,
44 std::function<void(Connection&, const Request&)> openHandlerIn,
45 std::function<void(Connection&)> closeHandlerIn) :
Ed Tanous6fde95f2023-06-01 07:33:34 -070046 adaptor(std::move(adaptorIn)),
Ed Tanous93cf0ac2024-03-28 00:35:13 -070047 timer(static_cast<boost::asio::io_context&>(
48 adaptor.get_executor().context())),
49 openHandler(std::move(openHandlerIn)),
V-Sanjana88ada3b2023-04-13 15:18:31 +053050 closeHandler(std::move(closeHandlerIn))
Ed Tanous8f79c5b2024-01-30 15:56:37 -080051
V-Sanjana88ada3b2023-04-13 15:18:31 +053052 {
Ed Tanous62598e32023-07-17 17:06:25 -070053 BMCWEB_LOG_DEBUG("SseConnectionImpl: SSE constructor {}", logPtr(this));
V-Sanjana88ada3b2023-04-13 15:18:31 +053054 }
55
56 ConnectionImpl(const ConnectionImpl&) = delete;
57 ConnectionImpl(const ConnectionImpl&&) = delete;
58 ConnectionImpl& operator=(const ConnectionImpl&) = delete;
59 ConnectionImpl& operator=(const ConnectionImpl&&) = delete;
60
61 ~ConnectionImpl() override
62 {
Ed Tanous62598e32023-07-17 17:06:25 -070063 BMCWEB_LOG_DEBUG("SSE ConnectionImpl: SSE destructor {}", logPtr(this));
V-Sanjana88ada3b2023-04-13 15:18:31 +053064 }
65
66 boost::asio::io_context& getIoContext() override
67 {
68 return static_cast<boost::asio::io_context&>(
69 adaptor.get_executor().context());
70 }
71
Ed Tanousf80a87f2024-06-16 12:10:33 -070072 void start(const Request& req)
V-Sanjana88ada3b2023-04-13 15:18:31 +053073 {
Ed Tanous6fde95f2023-06-01 07:33:34 -070074 if (!openHandler)
V-Sanjana88ada3b2023-04-13 15:18:31 +053075 {
Ed Tanous62598e32023-07-17 17:06:25 -070076 BMCWEB_LOG_CRITICAL("No open handler???");
Ed Tanous6fde95f2023-06-01 07:33:34 -070077 return;
V-Sanjana88ada3b2023-04-13 15:18:31 +053078 }
Ed Tanousf80a87f2024-06-16 12:10:33 -070079 openHandler(*this, req);
Ed Tanous8f79c5b2024-01-30 15:56:37 -080080 sendSSEHeader();
V-Sanjana88ada3b2023-04-13 15:18:31 +053081 }
82
83 void close(const std::string_view msg) override
84 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -080085 BMCWEB_LOG_DEBUG("Closing connection with reason {}", msg);
V-Sanjana88ada3b2023-04-13 15:18:31 +053086 // send notification to handler for cleanup
87 if (closeHandler)
88 {
Ed Tanous6fde95f2023-06-01 07:33:34 -070089 closeHandler(*this);
V-Sanjana88ada3b2023-04-13 15:18:31 +053090 }
Ed Tanous62598e32023-07-17 17:06:25 -070091 BMCWEB_LOG_DEBUG("Closing SSE connection {} - {}", logPtr(this), msg);
Ed Tanous6fde95f2023-06-01 07:33:34 -070092 boost::beast::get_lowest_layer(adaptor).close();
V-Sanjana88ada3b2023-04-13 15:18:31 +053093 }
94
Ed Tanous6fde95f2023-06-01 07:33:34 -070095 void sendSSEHeader()
V-Sanjana88ada3b2023-04-13 15:18:31 +053096 {
Ed Tanous62598e32023-07-17 17:06:25 -070097 BMCWEB_LOG_DEBUG("Starting SSE connection");
Ed Tanous8f79c5b2024-01-30 15:56:37 -080098
Ed Tanous6fde95f2023-06-01 07:33:34 -070099 res.set(boost::beast::http::field::content_type, "text/event-stream");
Ed Tanous8ece0e42024-01-02 13:16:50 -0800100 boost::beast::http::response_serializer<BodyType>& serial =
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800101 serializer.emplace(res);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530102
103 boost::beast::http::async_write_header(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800104 adaptor, serial,
V-Sanjana88ada3b2023-04-13 15:18:31 +0530105 std::bind_front(&ConnectionImpl::sendSSEHeaderCallback, this,
106 shared_from_this()));
107 }
108
109 void sendSSEHeaderCallback(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700110 const boost::system::error_code& ec,
111 size_t /*bytesSent*/)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530112 {
Ed Tanous6fde95f2023-06-01 07:33:34 -0700113 serializer.reset();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530114 if (ec)
115 {
Ed Tanous62598e32023-07-17 17:06:25 -0700116 BMCWEB_LOG_ERROR("Error sending header{}", ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530117 close("async_write_header failed");
118 return;
119 }
Ed Tanous62598e32023-07-17 17:06:25 -0700120 BMCWEB_LOG_DEBUG("SSE header sent - Connection established");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530121
V-Sanjana88ada3b2023-04-13 15:18:31 +0530122 // SSE stream header sent, So let us setup monitor.
123 // Any read data on this stream will be error in case of SSE.
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800124 adaptor.async_read_some(boost::asio::buffer(buffer),
125 std::bind_front(&ConnectionImpl::afterReadError,
126 this, shared_from_this()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530127 }
128
Ed Tanous6fde95f2023-06-01 07:33:34 -0700129 void afterReadError(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800130 const boost::system::error_code& ec, size_t bytesRead)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530131 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800132 BMCWEB_LOG_DEBUG("Read {}", bytesRead);
Ed Tanous6fde95f2023-06-01 07:33:34 -0700133 if (ec == boost::asio::error::operation_aborted)
134 {
135 return;
136 }
V-Sanjana88ada3b2023-04-13 15:18:31 +0530137 if (ec)
138 {
Ed Tanous62598e32023-07-17 17:06:25 -0700139 BMCWEB_LOG_ERROR("Read error: {}", ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530140 }
141
Ed Tanous6fde95f2023-06-01 07:33:34 -0700142 close("Close SSE connection");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530143 }
144
145 void doWrite()
146 {
V-Sanjana88ada3b2023-04-13 15:18:31 +0530147 if (doingWrite)
148 {
149 return;
150 }
151 if (inputBuffer.size() == 0)
152 {
Ed Tanous62598e32023-07-17 17:06:25 -0700153 BMCWEB_LOG_DEBUG("inputBuffer is empty... Bailing out");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530154 return;
155 }
Ed Tanous6fde95f2023-06-01 07:33:34 -0700156 startTimeout();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530157 doingWrite = true;
158
159 adaptor.async_write_some(
160 inputBuffer.data(),
161 std::bind_front(&ConnectionImpl::doWriteCallback, this,
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800162 shared_from_this()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530163 }
164
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800165 void doWriteCallback(const std::shared_ptr<Connection>& /*self*/,
V-Sanjana88ada3b2023-04-13 15:18:31 +0530166 const boost::beast::error_code& ec,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700167 size_t bytesTransferred)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530168 {
Ed Tanous6fde95f2023-06-01 07:33:34 -0700169 timer.cancel();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530170 doingWrite = false;
171 inputBuffer.consume(bytesTransferred);
172
173 if (ec == boost::asio::error::eof)
174 {
Ed Tanous62598e32023-07-17 17:06:25 -0700175 BMCWEB_LOG_ERROR("async_write_some() SSE stream closed");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530176 close("SSE stream closed");
177 return;
178 }
179
180 if (ec)
181 {
Ed Tanous62598e32023-07-17 17:06:25 -0700182 BMCWEB_LOG_ERROR("async_write_some() failed: {}", ec.message());
V-Sanjana88ada3b2023-04-13 15:18:31 +0530183 close("async_write_some failed");
184 return;
185 }
Ed Tanous62598e32023-07-17 17:06:25 -0700186 BMCWEB_LOG_DEBUG("async_write_some() bytes transferred: {}",
187 bytesTransferred);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530188
189 doWrite();
190 }
191
Ed Tanous6d799e12024-09-11 14:33:37 -0700192 void sendSseEvent(std::string_view id, std::string_view msg) override
V-Sanjana88ada3b2023-04-13 15:18:31 +0530193 {
194 if (msg.empty())
195 {
Ed Tanous62598e32023-07-17 17:06:25 -0700196 BMCWEB_LOG_DEBUG("Empty data, bailing out.");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530197 return;
198 }
199
Ed Tanous6fde95f2023-06-01 07:33:34 -0700200 dataFormat(id, msg);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530201
202 doWrite();
203 }
204
Ed Tanous6fde95f2023-06-01 07:33:34 -0700205 void dataFormat(std::string_view id, std::string_view msg)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530206 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800207 constexpr size_t bufferLimit = 10485760U; // 10MB
208 if (id.size() + msg.size() + inputBuffer.size() >= bufferLimit)
209 {
210 BMCWEB_LOG_ERROR("SSE Buffer overflow while waiting for client");
211 close("Buffer overflow");
212 return;
213 }
V-Sanjana88ada3b2023-04-13 15:18:31 +0530214 std::string rawData;
215 if (!id.empty())
216 {
217 rawData += "id: ";
Ed Tanous6fde95f2023-06-01 07:33:34 -0700218 rawData.append(id);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530219 rawData += "\n";
220 }
221
222 rawData += "data: ";
223 for (char character : msg)
224 {
225 rawData += character;
226 if (character == '\n')
227 {
228 rawData += "data: ";
229 }
230 }
231 rawData += "\n\n";
232
Ed Tanous44106f32024-04-06 13:48:50 -0700233 size_t copied = boost::asio::buffer_copy(
234 inputBuffer.prepare(rawData.size()), boost::asio::buffer(rawData));
235 inputBuffer.commit(copied);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530236 }
237
Ed Tanous6fde95f2023-06-01 07:33:34 -0700238 void startTimeout()
V-Sanjana88ada3b2023-04-13 15:18:31 +0530239 {
V-Sanjana88ada3b2023-04-13 15:18:31 +0530240 std::weak_ptr<Connection> weakSelf = weak_from_this();
241 timer.expires_after(std::chrono::seconds(30));
242 timer.async_wait(std::bind_front(&ConnectionImpl::onTimeoutCallback,
243 this, weak_from_this()));
244 }
245
246 void onTimeoutCallback(const std::weak_ptr<Connection>& weakSelf,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700247 const boost::system::error_code& ec)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530248 {
249 std::shared_ptr<Connection> self = weakSelf.lock();
250 if (!self)
251 {
Ed Tanous62598e32023-07-17 17:06:25 -0700252 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
253 logPtr(self.get()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530254 return;
255 }
256
257 if (ec == boost::asio::error::operation_aborted)
258 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800259 BMCWEB_LOG_DEBUG("Timer operation aborted");
Ed Tanous8ece0e42024-01-02 13:16:50 -0800260 // Canceled wait means the path succeeded.
V-Sanjana88ada3b2023-04-13 15:18:31 +0530261 return;
262 }
263 if (ec)
264 {
Ed Tanous62598e32023-07-17 17:06:25 -0700265 BMCWEB_LOG_CRITICAL("{} timer failed {}", logPtr(self.get()), ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530266 }
267
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800268 BMCWEB_LOG_WARNING("{} Connection timed out, closing",
Ed Tanous62598e32023-07-17 17:06:25 -0700269 logPtr(self.get()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530270
271 self->close("closing connection");
272 }
273
274 private:
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800275 std::array<char, 1> buffer{};
V-Sanjana88ada3b2023-04-13 15:18:31 +0530276 boost::beast::multi_buffer inputBuffer;
277
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800278 Adaptor adaptor;
279
Ed Tanousb2896142024-01-31 15:25:47 -0800280 using BodyType = bmcweb::HttpBody;
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800281 boost::beast::http::response<BodyType> res;
282 std::optional<boost::beast::http::response_serializer<BodyType>> serializer;
Ed Tanous6fde95f2023-06-01 07:33:34 -0700283 boost::asio::steady_timer timer;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530284 bool doingWrite = false;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530285
Ed Tanousf80a87f2024-06-16 12:10:33 -0700286 std::function<void(Connection&, const Request&)> openHandler;
Ed Tanous6fde95f2023-06-01 07:33:34 -0700287 std::function<void(Connection&)> closeHandler;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530288};
289} // namespace sse_socket
290} // namespace crow