blob: a0105f1b08180bc072afe17d93c85dc86fe9e966 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
V-Sanjana88ada3b2023-04-13 15:18:31 +05303#pragma once
Ed Tanous41fe81c2024-09-02 15:08:41 -07004#include "boost_formatters.hpp"
Ed Tanousb2896142024-01-31 15:25:47 -08005#include "http_body.hpp"
V-Sanjana88ada3b2023-04-13 15:18:31 +05306#include "http_request.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -08007#include "logging.hpp"
V-Sanjana88ada3b2023-04-13 15:18:31 +05308
V-Sanjana88ada3b2023-04-13 15:18:31 +05309#include <boost/asio/buffer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080010#include <boost/asio/error.hpp>
11#include <boost/asio/io_context.hpp>
V-Sanjana88ada3b2023-04-13 15:18:31 +053012#include <boost/asio/steady_timer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080013#include <boost/beast/core/error.hpp>
V-Sanjana88ada3b2023-04-13 15:18:31 +053014#include <boost/beast/core/multi_buffer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080015#include <boost/beast/http/field.hpp>
16#include <boost/beast/http/serializer.hpp>
17#include <boost/beast/http/write.hpp>
V-Sanjana88ada3b2023-04-13 15:18:31 +053018
19#include <array>
Ed Tanousd7857202025-01-28 15:32:26 -080020#include <chrono>
Ed Tanous8f79c5b2024-01-30 15:56:37 -080021#include <cstddef>
V-Sanjana88ada3b2023-04-13 15:18:31 +053022#include <functional>
Ed Tanousd7857202025-01-28 15:32:26 -080023#include <memory>
Ed Tanous8f79c5b2024-01-30 15:56:37 -080024#include <optional>
Ed Tanousd7857202025-01-28 15:32:26 -080025#include <string>
26#include <string_view>
27#include <utility>
V-Sanjana88ada3b2023-04-13 15:18:31 +053028
29namespace crow
30{
31
32namespace sse_socket
33{
Ed Tanous93cf0ac2024-03-28 00:35:13 -070034struct Connection : public std::enable_shared_from_this<Connection>
V-Sanjana88ada3b2023-04-13 15:18:31 +053035{
36 public:
Ed Tanous6fde95f2023-06-01 07:33:34 -070037 Connection() = default;
V-Sanjana88ada3b2023-04-13 15:18:31 +053038
39 Connection(const Connection&) = delete;
40 Connection(Connection&&) = delete;
41 Connection& operator=(const Connection&) = delete;
42 Connection& operator=(const Connection&&) = delete;
43 virtual ~Connection() = default;
44
45 virtual boost::asio::io_context& getIoContext() = 0;
V-Sanjana88ada3b2023-04-13 15:18:31 +053046 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous6d799e12024-09-11 14:33:37 -070047 virtual void sendSseEvent(std::string_view id, std::string_view msg) = 0;
V-Sanjana88ada3b2023-04-13 15:18:31 +053048};
49
50template <typename Adaptor>
51class ConnectionImpl : public Connection
52{
53 public:
Ed Tanousf80a87f2024-06-16 12:10:33 -070054 ConnectionImpl(
55 Adaptor&& adaptorIn,
56 std::function<void(Connection&, const Request&)> openHandlerIn,
57 std::function<void(Connection&)> closeHandlerIn) :
Ed Tanous6fde95f2023-06-01 07:33:34 -070058 adaptor(std::move(adaptorIn)),
Ed Tanous93cf0ac2024-03-28 00:35:13 -070059 timer(static_cast<boost::asio::io_context&>(
60 adaptor.get_executor().context())),
61 openHandler(std::move(openHandlerIn)),
V-Sanjana88ada3b2023-04-13 15:18:31 +053062 closeHandler(std::move(closeHandlerIn))
Ed Tanous8f79c5b2024-01-30 15:56:37 -080063
V-Sanjana88ada3b2023-04-13 15:18:31 +053064 {
Ed Tanous62598e32023-07-17 17:06:25 -070065 BMCWEB_LOG_DEBUG("SseConnectionImpl: SSE constructor {}", logPtr(this));
V-Sanjana88ada3b2023-04-13 15:18:31 +053066 }
67
68 ConnectionImpl(const ConnectionImpl&) = delete;
69 ConnectionImpl(const ConnectionImpl&&) = delete;
70 ConnectionImpl& operator=(const ConnectionImpl&) = delete;
71 ConnectionImpl& operator=(const ConnectionImpl&&) = delete;
72
73 ~ConnectionImpl() override
74 {
Ed Tanous62598e32023-07-17 17:06:25 -070075 BMCWEB_LOG_DEBUG("SSE ConnectionImpl: SSE destructor {}", logPtr(this));
V-Sanjana88ada3b2023-04-13 15:18:31 +053076 }
77
78 boost::asio::io_context& getIoContext() override
79 {
80 return static_cast<boost::asio::io_context&>(
81 adaptor.get_executor().context());
82 }
83
Ed Tanousf80a87f2024-06-16 12:10:33 -070084 void start(const Request& req)
V-Sanjana88ada3b2023-04-13 15:18:31 +053085 {
Ed Tanous464924c2024-12-20 14:49:45 -080086 BMCWEB_LOG_DEBUG("Starting SSE connection");
87
88 res.set(boost::beast::http::field::content_type, "text/event-stream");
89 boost::beast::http::response_serializer<BodyType>& serial =
90 serializer.emplace(res);
91
92 boost::beast::http::async_write_header(
93 adaptor, serial,
94 std::bind_front(&ConnectionImpl::sendSSEHeaderCallback, this,
95 shared_from_this(), req));
V-Sanjana88ada3b2023-04-13 15:18:31 +053096 }
97
98 void close(const std::string_view msg) override
99 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800100 BMCWEB_LOG_DEBUG("Closing connection with reason {}", msg);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530101 // send notification to handler for cleanup
102 if (closeHandler)
103 {
Ed Tanous6fde95f2023-06-01 07:33:34 -0700104 closeHandler(*this);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530105 }
Ed Tanous62598e32023-07-17 17:06:25 -0700106 BMCWEB_LOG_DEBUG("Closing SSE connection {} - {}", logPtr(this), msg);
Ed Tanous6fde95f2023-06-01 07:33:34 -0700107 boost::beast::get_lowest_layer(adaptor).close();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530108 }
109
V-Sanjana88ada3b2023-04-13 15:18:31 +0530110 void sendSSEHeaderCallback(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous464924c2024-12-20 14:49:45 -0800111 const Request& req,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700112 const boost::system::error_code& ec,
113 size_t /*bytesSent*/)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530114 {
Ed Tanous6fde95f2023-06-01 07:33:34 -0700115 serializer.reset();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530116 if (ec)
117 {
Ed Tanous62598e32023-07-17 17:06:25 -0700118 BMCWEB_LOG_ERROR("Error sending header{}", ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530119 close("async_write_header failed");
120 return;
121 }
Ed Tanous62598e32023-07-17 17:06:25 -0700122 BMCWEB_LOG_DEBUG("SSE header sent - Connection established");
Ed Tanous464924c2024-12-20 14:49:45 -0800123 if (!openHandler)
124 {
125 BMCWEB_LOG_CRITICAL("No open handler???");
126 return;
127 }
128 openHandler(*this, req);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530129
V-Sanjana88ada3b2023-04-13 15:18:31 +0530130 // SSE stream header sent, So let us setup monitor.
131 // Any read data on this stream will be error in case of SSE.
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800132 adaptor.async_read_some(boost::asio::buffer(buffer),
133 std::bind_front(&ConnectionImpl::afterReadError,
134 this, shared_from_this()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530135 }
136
Ed Tanous6fde95f2023-06-01 07:33:34 -0700137 void afterReadError(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800138 const boost::system::error_code& ec, size_t bytesRead)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530139 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800140 BMCWEB_LOG_DEBUG("Read {}", bytesRead);
Ed Tanous6fde95f2023-06-01 07:33:34 -0700141 if (ec == boost::asio::error::operation_aborted)
142 {
143 return;
144 }
V-Sanjana88ada3b2023-04-13 15:18:31 +0530145 if (ec)
146 {
Ed Tanous62598e32023-07-17 17:06:25 -0700147 BMCWEB_LOG_ERROR("Read error: {}", ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530148 }
149
Ed Tanous6fde95f2023-06-01 07:33:34 -0700150 close("Close SSE connection");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530151 }
152
153 void doWrite()
154 {
V-Sanjana88ada3b2023-04-13 15:18:31 +0530155 if (doingWrite)
156 {
157 return;
158 }
159 if (inputBuffer.size() == 0)
160 {
Ed Tanous62598e32023-07-17 17:06:25 -0700161 BMCWEB_LOG_DEBUG("inputBuffer is empty... Bailing out");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530162 return;
163 }
Ed Tanous6fde95f2023-06-01 07:33:34 -0700164 startTimeout();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530165 doingWrite = true;
166
167 adaptor.async_write_some(
168 inputBuffer.data(),
169 std::bind_front(&ConnectionImpl::doWriteCallback, this,
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800170 shared_from_this()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530171 }
172
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800173 void doWriteCallback(const std::shared_ptr<Connection>& /*self*/,
V-Sanjana88ada3b2023-04-13 15:18:31 +0530174 const boost::beast::error_code& ec,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700175 size_t bytesTransferred)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530176 {
Ed Tanous6fde95f2023-06-01 07:33:34 -0700177 timer.cancel();
V-Sanjana88ada3b2023-04-13 15:18:31 +0530178 doingWrite = false;
179 inputBuffer.consume(bytesTransferred);
180
181 if (ec == boost::asio::error::eof)
182 {
Ed Tanous62598e32023-07-17 17:06:25 -0700183 BMCWEB_LOG_ERROR("async_write_some() SSE stream closed");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530184 close("SSE stream closed");
185 return;
186 }
187
188 if (ec)
189 {
Ed Tanous62598e32023-07-17 17:06:25 -0700190 BMCWEB_LOG_ERROR("async_write_some() failed: {}", ec.message());
V-Sanjana88ada3b2023-04-13 15:18:31 +0530191 close("async_write_some failed");
192 return;
193 }
Ed Tanous62598e32023-07-17 17:06:25 -0700194 BMCWEB_LOG_DEBUG("async_write_some() bytes transferred: {}",
195 bytesTransferred);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530196
197 doWrite();
198 }
199
Ed Tanous6d799e12024-09-11 14:33:37 -0700200 void sendSseEvent(std::string_view id, std::string_view msg) override
V-Sanjana88ada3b2023-04-13 15:18:31 +0530201 {
202 if (msg.empty())
203 {
Ed Tanous62598e32023-07-17 17:06:25 -0700204 BMCWEB_LOG_DEBUG("Empty data, bailing out.");
V-Sanjana88ada3b2023-04-13 15:18:31 +0530205 return;
206 }
207
Ed Tanous6fde95f2023-06-01 07:33:34 -0700208 dataFormat(id, msg);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530209
210 doWrite();
211 }
212
Ed Tanous6fde95f2023-06-01 07:33:34 -0700213 void dataFormat(std::string_view id, std::string_view msg)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530214 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800215 constexpr size_t bufferLimit = 10485760U; // 10MB
216 if (id.size() + msg.size() + inputBuffer.size() >= bufferLimit)
217 {
218 BMCWEB_LOG_ERROR("SSE Buffer overflow while waiting for client");
219 close("Buffer overflow");
220 return;
221 }
V-Sanjana88ada3b2023-04-13 15:18:31 +0530222 std::string rawData;
223 if (!id.empty())
224 {
225 rawData += "id: ";
Ed Tanous6fde95f2023-06-01 07:33:34 -0700226 rawData.append(id);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530227 rawData += "\n";
228 }
229
230 rawData += "data: ";
231 for (char character : msg)
232 {
233 rawData += character;
234 if (character == '\n')
235 {
236 rawData += "data: ";
237 }
238 }
239 rawData += "\n\n";
240
Ed Tanous44106f32024-04-06 13:48:50 -0700241 size_t copied = boost::asio::buffer_copy(
242 inputBuffer.prepare(rawData.size()), boost::asio::buffer(rawData));
243 inputBuffer.commit(copied);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530244 }
245
Ed Tanous6fde95f2023-06-01 07:33:34 -0700246 void startTimeout()
V-Sanjana88ada3b2023-04-13 15:18:31 +0530247 {
V-Sanjana88ada3b2023-04-13 15:18:31 +0530248 std::weak_ptr<Connection> weakSelf = weak_from_this();
249 timer.expires_after(std::chrono::seconds(30));
250 timer.async_wait(std::bind_front(&ConnectionImpl::onTimeoutCallback,
251 this, weak_from_this()));
252 }
253
254 void onTimeoutCallback(const std::weak_ptr<Connection>& weakSelf,
Ed Tanous6fde95f2023-06-01 07:33:34 -0700255 const boost::system::error_code& ec)
V-Sanjana88ada3b2023-04-13 15:18:31 +0530256 {
257 std::shared_ptr<Connection> self = weakSelf.lock();
258 if (!self)
259 {
Ed Tanous62598e32023-07-17 17:06:25 -0700260 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
261 logPtr(self.get()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530262 return;
263 }
264
265 if (ec == boost::asio::error::operation_aborted)
266 {
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800267 BMCWEB_LOG_DEBUG("Timer operation aborted");
Ed Tanous8ece0e42024-01-02 13:16:50 -0800268 // Canceled wait means the path succeeded.
V-Sanjana88ada3b2023-04-13 15:18:31 +0530269 return;
270 }
271 if (ec)
272 {
Ed Tanous62598e32023-07-17 17:06:25 -0700273 BMCWEB_LOG_CRITICAL("{} timer failed {}", logPtr(self.get()), ec);
V-Sanjana88ada3b2023-04-13 15:18:31 +0530274 }
275
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800276 BMCWEB_LOG_WARNING("{} Connection timed out, closing",
Ed Tanous62598e32023-07-17 17:06:25 -0700277 logPtr(self.get()));
V-Sanjana88ada3b2023-04-13 15:18:31 +0530278
279 self->close("closing connection");
280 }
281
282 private:
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800283 std::array<char, 1> buffer{};
V-Sanjana88ada3b2023-04-13 15:18:31 +0530284 boost::beast::multi_buffer inputBuffer;
285
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800286 Adaptor adaptor;
287
Ed Tanousb2896142024-01-31 15:25:47 -0800288 using BodyType = bmcweb::HttpBody;
Ed Tanous8f79c5b2024-01-30 15:56:37 -0800289 boost::beast::http::response<BodyType> res;
290 std::optional<boost::beast::http::response_serializer<BodyType>> serializer;
Ed Tanous6fde95f2023-06-01 07:33:34 -0700291 boost::asio::steady_timer timer;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530292 bool doingWrite = false;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530293
Ed Tanousf80a87f2024-06-16 12:10:33 -0700294 std::function<void(Connection&, const Request&)> openHandler;
Ed Tanous6fde95f2023-06-01 07:33:34 -0700295 std::function<void(Connection&)> closeHandler;
V-Sanjana88ada3b2023-04-13 15:18:31 +0530296};
297} // namespace sse_socket
298} // namespace crow