blob: a8bccdb45077e605bd93aeb47d83e59e57a21c2b [file] [log] [blame]
V-Sanjana88ada3b2023-04-13 15:18:31 +05301#pragma once
Ed Tanousb2896142024-01-31 15:25:47 -08002#include "http_body.hpp"
V-Sanjana88ada3b2023-04-13 15:18:31 +05303#include "http_request.hpp"
4#include "http_response.hpp"
5
V-Sanjana88ada3b2023-04-13 15:18:31 +05306#include <boost/asio/buffer.hpp>
7#include <boost/asio/steady_timer.hpp>
8#include <boost/beast/core/multi_buffer.hpp>
V-Sanjana88ada3b2023-04-13 15:18:31 +05309#include <boost/beast/websocket.hpp>
10
11#include <array>
Ed Tanous8f79c5b2024-01-30 15:56:37 -080012#include <cstddef>
V-Sanjana88ada3b2023-04-13 15:18:31 +053013#include <functional>
Ed Tanous8f79c5b2024-01-30 15:56:37 -080014#include <optional>
V-Sanjana88ada3b2023-04-13 15:18:31 +053015
16namespace crow
17{
18
19namespace sse_socket
20{
V-Sanjana88ada3b2023-04-13 15:18:31 +053021struct Connection : std::enable_shared_from_this<Connection>
22{
23 public:
Ed Tanous6fde95f2023-06-01 07:33:34 -070024 Connection() = default;
V-Sanjana88ada3b2023-04-13 15:18:31 +053025
26 Connection(const Connection&) = delete;
27 Connection(Connection&&) = delete;
28 Connection& operator=(const Connection&) = delete;
29 Connection& operator=(const Connection&&) = delete;
30 virtual ~Connection() = default;
31
32 virtual boost::asio::io_context& getIoContext() = 0;
V-Sanjana88ada3b2023-04-13 15:18:31 +053033 virtual void close(std::string_view msg = "quit") = 0;
34 virtual void sendEvent(std::string_view id, std::string_view msg) = 0;
V-Sanjana88ada3b2023-04-13 15:18:31 +053035};
36
37template <typename Adaptor>
38class ConnectionImpl : public Connection
39{
40 public:
Ed Tanous8f79c5b2024-01-30 15:56:37 -080041 ConnectionImpl(boost::asio::io_context& ioIn, Adaptor&& adaptorIn,
Ed Tanous6fde95f2023-06-01 07:33:34 -070042 std::function<void(Connection&)> openHandlerIn,
43 std::function<void(Connection&)> closeHandlerIn) :
44 adaptor(std::move(adaptorIn)),
Ed Tanous8f79c5b2024-01-30 15:56:37 -080045 ioc(ioIn), timer(ioc), openHandler(std::move(openHandlerIn)),
V-Sanjana88ada3b2023-04-13 15:18:31 +053046 closeHandler(std::move(closeHandlerIn))
Ed Tanous8f79c5b2024-01-30 15:56:37 -080047
V-Sanjana88ada3b2023-04-13 15:18:31 +053048 {
Ed Tanous62598e32023-07-17 17:06:25 -070049 BMCWEB_LOG_DEBUG("SseConnectionImpl: SSE constructor {}", logPtr(this));
V-Sanjana88ada3b2023-04-13 15:18:31 +053050 }
51
52 ConnectionImpl(const ConnectionImpl&) = delete;
53 ConnectionImpl(const ConnectionImpl&&) = delete;
54 ConnectionImpl& operator=(const ConnectionImpl&) = delete;
55 ConnectionImpl& operator=(const ConnectionImpl&&) = delete;
56
57 ~ConnectionImpl() override
58 {
Ed Tanous62598e32023-07-17 17:06:25 -070059 BMCWEB_LOG_DEBUG("SSE ConnectionImpl: SSE destructor {}", logPtr(this));
V-Sanjana88ada3b2023-04-13 15:18:31 +053060 }
61
62 boost::asio::io_context& getIoContext() override
63 {
64 return static_cast<boost::asio::io_context&>(
65 adaptor.get_executor().context());
66 }
67
68 void start()
69 {
Ed Tanous6fde95f2023-06-01 07:33:34 -070070 if (!openHandler)
V-Sanjana88ada3b2023-04-13 15:18:31 +053071 {
Ed Tanous62598e32023-07-17 17:06:25 -070072 BMCWEB_LOG_CRITICAL("No open handler???");
Ed Tanous6fde95f2023-06-01 07:33:34 -070073 return;
V-Sanjana88ada3b2023-04-13 15:18:31 +053074 }
Ed Tanous6fde95f2023-06-01 07:33:34 -070075 openHandler(*this);
Ed Tanous8f79c5b2024-01-30 15:56:37 -080076 sendSSEHeader();
V-Sanjana88ada3b2023-04-13 15:18:31 +053077 }
78
79 void close(const std::string_view msg) override
80 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -080081 BMCWEB_LOG_DEBUG("Closing connection with reason {}", msg);
V-Sanjana88ada3b2023-04-13 15:18:31 +053082 // send notification to handler for cleanup
83 if (closeHandler)
84 {
Ed Tanous6fde95f2023-06-01 07:33:34 -070085 closeHandler(*this);
V-Sanjana88ada3b2023-04-13 15:18:31 +053086 }
Ed Tanous62598e32023-07-17 17:06:25 -070087 BMCWEB_LOG_DEBUG("Closing SSE connection {} - {}", logPtr(this), msg);
Ed Tanous6fde95f2023-06-01 07:33:34 -070088 boost::beast::get_lowest_layer(adaptor).close();
V-Sanjana88ada3b2023-04-13 15:18:31 +053089 }
90
Ed Tanous6fde95f2023-06-01 07:33:34 -070091 void sendSSEHeader()
V-Sanjana88ada3b2023-04-13 15:18:31 +053092 {
Ed Tanous62598e32023-07-17 17:06:25 -070093 BMCWEB_LOG_DEBUG("Starting SSE connection");
Ed Tanous8f79c5b2024-01-30 15:56:37 -080094
Ed Tanous6fde95f2023-06-01 07:33:34 -070095 res.set(boost::beast::http::field::content_type, "text/event-stream");
Ed Tanous8ece0e42024-01-02 13:16:50 -080096 boost::beast::http::response_serializer<BodyType>& serial =
Ed Tanous8f79c5b2024-01-30 15:56:37 -080097 serializer.emplace(res);
V-Sanjana88ada3b2023-04-13 15:18:31 +053098
99 boost::beast::http::async_write_header(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800100 adaptor, serial,
V-Sanjana88ada3b2023-04-13 15:18:31 +0530101 std::bind_front(&ConnectionImpl::sendSSEHeaderCallback, this,
102 shared_from_this()));
103 }
104
105 void sendSSEHeaderCallback(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700106 const boost::system::error_code& ec,
107 size_t /*bytesSent*/)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530108 {
Ed Tanous6fde95f2023-06-01 07:33:34 -0700109 serializer.reset();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530110 if (ec)
111 {
Ed Tanous62598e32023-07-17 17:06:25 -0700112 BMCWEB_LOG_ERROR("Error sending header{}", ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530113 close("async_write_header failed");
114 return;
115 }
Ed Tanous62598e32023-07-17 17:06:25 -0700116 BMCWEB_LOG_DEBUG("SSE header sent - Connection established");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530117
V-Sanjana88ada3b2023-04-13 15:18:31 +0530118 // SSE stream header sent, So let us setup monitor.
119 // Any read data on this stream will be error in case of SSE.
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800120 adaptor.async_read_some(boost::asio::buffer(buffer),
121 std::bind_front(&ConnectionImpl::afterReadError,
122 this, shared_from_this()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530123 }
124
Ed Tanous6fde95f2023-06-01 07:33:34 -0700125 void afterReadError(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800126 const boost::system::error_code& ec, size_t bytesRead)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530127 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800128 BMCWEB_LOG_DEBUG("Read {}", bytesRead);
Ed Tanous6fde95f2023-06-01 07:33:34 -0700129 if (ec == boost::asio::error::operation_aborted)
130 {
131 return;
132 }
V-Sanjana88ada3b2023-04-13 15:18:31 +0530133 if (ec)
134 {
Ed Tanous62598e32023-07-17 17:06:25 -0700135 BMCWEB_LOG_ERROR("Read error: {}", ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530136 }
137
Ed Tanous6fde95f2023-06-01 07:33:34 -0700138 close("Close SSE connection");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530139 }
140
141 void doWrite()
142 {
V-Sanjana88ada3b2023-04-13 15:18:31 +0530143 if (doingWrite)
144 {
145 return;
146 }
147 if (inputBuffer.size() == 0)
148 {
Ed Tanous62598e32023-07-17 17:06:25 -0700149 BMCWEB_LOG_DEBUG("inputBuffer is empty... Bailing out");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530150 return;
151 }
Ed Tanous6fde95f2023-06-01 07:33:34 -0700152 startTimeout();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530153 doingWrite = true;
154
155 adaptor.async_write_some(
156 inputBuffer.data(),
157 std::bind_front(&ConnectionImpl::doWriteCallback, this,
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800158 shared_from_this()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530159 }
160
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800161 void doWriteCallback(const std::shared_ptr<Connection>& /*self*/,
V-Sanjana88ada3b2023-04-13 15:18:31 +0530162 const boost::beast::error_code& ec,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700163 size_t bytesTransferred)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530164 {
Ed Tanous6fde95f2023-06-01 07:33:34 -0700165 timer.cancel();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530166 doingWrite = false;
167 inputBuffer.consume(bytesTransferred);
168
169 if (ec == boost::asio::error::eof)
170 {
Ed Tanous62598e32023-07-17 17:06:25 -0700171 BMCWEB_LOG_ERROR("async_write_some() SSE stream closed");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530172 close("SSE stream closed");
173 return;
174 }
175
176 if (ec)
177 {
Ed Tanous62598e32023-07-17 17:06:25 -0700178 BMCWEB_LOG_ERROR("async_write_some() failed: {}", ec.message());
V-Sanjana88ada3b2023-04-13 15:18:31 +0530179 close("async_write_some failed");
180 return;
181 }
Ed Tanous62598e32023-07-17 17:06:25 -0700182 BMCWEB_LOG_DEBUG("async_write_some() bytes transferred: {}",
183 bytesTransferred);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530184
185 doWrite();
186 }
187
V-Sanjana88ada3b2023-04-13 15:18:31 +0530188 void sendEvent(std::string_view id, std::string_view msg) override
189 {
190 if (msg.empty())
191 {
Ed Tanous62598e32023-07-17 17:06:25 -0700192 BMCWEB_LOG_DEBUG("Empty data, bailing out.");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530193 return;
194 }
195
Ed Tanous6fde95f2023-06-01 07:33:34 -0700196 dataFormat(id, msg);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530197
198 doWrite();
199 }
200
Ed Tanous6fde95f2023-06-01 07:33:34 -0700201 void dataFormat(std::string_view id, std::string_view msg)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530202 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800203 constexpr size_t bufferLimit = 10485760U; // 10MB
204 if (id.size() + msg.size() + inputBuffer.size() >= bufferLimit)
205 {
206 BMCWEB_LOG_ERROR("SSE Buffer overflow while waiting for client");
207 close("Buffer overflow");
208 return;
209 }
V-Sanjana88ada3b2023-04-13 15:18:31 +0530210 std::string rawData;
211 if (!id.empty())
212 {
213 rawData += "id: ";
Ed Tanous6fde95f2023-06-01 07:33:34 -0700214 rawData.append(id);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530215 rawData += "\n";
216 }
217
218 rawData += "data: ";
219 for (char character : msg)
220 {
221 rawData += character;
222 if (character == '\n')
223 {
224 rawData += "data: ";
225 }
226 }
227 rawData += "\n\n";
228
229 boost::asio::buffer_copy(inputBuffer.prepare(rawData.size()),
230 boost::asio::buffer(rawData));
231 inputBuffer.commit(rawData.size());
232 }
233
Ed Tanous6fde95f2023-06-01 07:33:34 -0700234 void startTimeout()
V-Sanjana88ada3b2023-04-13 15:18:31 +0530235 {
V-Sanjana88ada3b2023-04-13 15:18:31 +0530236 std::weak_ptr<Connection> weakSelf = weak_from_this();
237 timer.expires_after(std::chrono::seconds(30));
238 timer.async_wait(std::bind_front(&ConnectionImpl::onTimeoutCallback,
239 this, weak_from_this()));
240 }
241
242 void onTimeoutCallback(const std::weak_ptr<Connection>& weakSelf,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700243 const boost::system::error_code& ec)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530244 {
245 std::shared_ptr<Connection> self = weakSelf.lock();
246 if (!self)
247 {
Ed Tanous62598e32023-07-17 17:06:25 -0700248 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
249 logPtr(self.get()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530250 return;
251 }
252
253 if (ec == boost::asio::error::operation_aborted)
254 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800255 BMCWEB_LOG_DEBUG("Timer operation aborted");
Ed Tanous8ece0e42024-01-02 13:16:50 -0800256 // Canceled wait means the path succeeded.
V-Sanjana88ada3b2023-04-13 15:18:31 +0530257 return;
258 }
259 if (ec)
260 {
Ed Tanous62598e32023-07-17 17:06:25 -0700261 BMCWEB_LOG_CRITICAL("{} timer failed {}", logPtr(self.get()), ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530262 }
263
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800264 BMCWEB_LOG_WARNING("{} Connection timed out, closing",
Ed Tanous62598e32023-07-17 17:06:25 -0700265 logPtr(self.get()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530266
267 self->close("closing connection");
268 }
269
270 private:
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800271 std::array<char, 1> buffer{};
V-Sanjana88ada3b2023-04-13 15:18:31 +0530272 boost::beast::multi_buffer inputBuffer;
273
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800274 Adaptor adaptor;
275
Ed Tanousb2896142024-01-31 15:25:47 -0800276 using BodyType = bmcweb::HttpBody;
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800277 boost::beast::http::response<BodyType> res;
278 std::optional<boost::beast::http::response_serializer<BodyType>> serializer;
279 boost::asio::io_context& ioc;
Ed Tanous6fde95f2023-06-01 07:33:34 -0700280 boost::asio::steady_timer timer;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530281 bool doingWrite = false;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530282
Ed Tanous6fde95f2023-06-01 07:33:34 -0700283 std::function<void(Connection&)> openHandler;
284 std::function<void(Connection&)> closeHandler;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530285};
286} // namespace sse_socket
287} // namespace crow