blob: 89baed26d1cbeb5081c156fbfedcfaad8f264929 [file] [log] [blame]
Abhilash Rajub5f288d2023-11-08 22:32:44 -06001#pragma once
2
Ed Tanousf51d8632024-05-16 09:14:01 -07003#include "duplicatable_file_handle.hpp"
Ed Tanous52e31622024-01-23 16:31:11 -08004#include "logging.hpp"
Abhilash Rajub5f288d2023-11-08 22:32:44 -06005#include "utility.hpp"
6
Ed Tanous88c7c422024-04-06 08:52:40 -07007#include <fcntl.h>
Ed Tanous52e31622024-01-23 16:31:11 -08008#include <unistd.h>
9
10#include <boost/beast/core/buffers_range.hpp>
Abhilash Rajub5f288d2023-11-08 22:32:44 -060011#include <boost/beast/core/file_posix.hpp>
12#include <boost/beast/http/message.hpp>
13#include <boost/system/error_code.hpp>
14
Potin Lai608fb7b2024-08-09 16:05:06 +080015#include <cstdint>
16#include <optional>
Ed Tanous52e31622024-01-23 16:31:11 -080017#include <string_view>
18
Abhilash Rajub5f288d2023-11-08 22:32:44 -060019namespace bmcweb
20{
Ed Tanousb2896142024-01-31 15:25:47 -080021struct HttpBody
Abhilash Rajub5f288d2023-11-08 22:32:44 -060022{
Ed Tanous05016962024-03-19 11:53:11 -070023 // Body concept requires specific naming of classes
24 // NOLINTBEGIN(readability-identifier-naming)
Abhilash Rajub5f288d2023-11-08 22:32:44 -060025 class writer;
Ed Tanous52e31622024-01-23 16:31:11 -080026 class reader;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060027 class value_type;
Ed Tanous05016962024-03-19 11:53:11 -070028 // NOLINTEND(readability-identifier-naming)
Potin Lai608fb7b2024-08-09 16:05:06 +080029
30 static std::uint64_t size(const value_type& body);
Abhilash Rajub5f288d2023-11-08 22:32:44 -060031};
32
33enum class EncodingType
34{
35 Raw,
36 Base64,
37};
38
Ed Tanousb2896142024-01-31 15:25:47 -080039class HttpBody::value_type
Abhilash Rajub5f288d2023-11-08 22:32:44 -060040{
Ed Tanousf51d8632024-05-16 09:14:01 -070041 DuplicatableFileHandle fileHandle;
Ed Tanous52e31622024-01-23 16:31:11 -080042 std::optional<size_t> fileSize;
43 std::string strBody;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060044
45 public:
Ed Tanousf51d8632024-05-16 09:14:01 -070046 value_type() = default;
47 explicit value_type(std::string_view s) : strBody(s) {}
48 explicit value_type(EncodingType e) : encodingType(e) {}
Abhilash Rajub5f288d2023-11-08 22:32:44 -060049 EncodingType encodingType = EncodingType::Raw;
50
Ed Tanousf51d8632024-05-16 09:14:01 -070051 const boost::beast::file_posix& file() const
Ed Tanous52e31622024-01-23 16:31:11 -080052 {
Ed Tanousf51d8632024-05-16 09:14:01 -070053 return fileHandle.fileHandle;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060054 }
55
Ed Tanous52e31622024-01-23 16:31:11 -080056 std::string& str()
Abhilash Rajub5f288d2023-11-08 22:32:44 -060057 {
Ed Tanous52e31622024-01-23 16:31:11 -080058 return strBody;
59 }
60
61 const std::string& str() const
62 {
63 return strBody;
64 }
65
66 std::optional<size_t> payloadSize() const
67 {
Ed Tanousf51d8632024-05-16 09:14:01 -070068 if (!fileHandle.fileHandle.is_open())
Ed Tanous52e31622024-01-23 16:31:11 -080069 {
70 return strBody.size();
71 }
72 if (fileSize)
73 {
74 if (encodingType == EncodingType::Base64)
75 {
76 return crow::utility::Base64Encoder::encodedSize(*fileSize);
77 }
78 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -060079 return fileSize;
80 }
81
Ed Tanous52e31622024-01-23 16:31:11 -080082 void clear()
83 {
84 strBody.clear();
85 strBody.shrink_to_fit();
Ed Tanousf51d8632024-05-16 09:14:01 -070086 fileHandle.fileHandle = boost::beast::file_posix();
Ed Tanous52e31622024-01-23 16:31:11 -080087 fileSize = std::nullopt;
Ed Tanous06fc9be2024-03-27 19:58:11 -070088 encodingType = EncodingType::Raw;
Ed Tanous52e31622024-01-23 16:31:11 -080089 }
90
Abhilash Rajub5f288d2023-11-08 22:32:44 -060091 void open(const char* path, boost::beast::file_mode mode,
92 boost::system::error_code& ec)
93 {
Ed Tanousf51d8632024-05-16 09:14:01 -070094 fileHandle.fileHandle.open(path, mode, ec);
Ed Tanous52e31622024-01-23 16:31:11 -080095 if (ec)
96 {
97 return;
98 }
99 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700100 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800101 if (!ec2)
102 {
103 BMCWEB_LOG_INFO("File size was {} bytes", size);
104 fileSize = static_cast<size_t>(size);
105 }
106 else
107 {
108 BMCWEB_LOG_WARNING("Failed to read file size on {}", path);
109 }
Ed Tanous88c7c422024-04-06 08:52:40 -0700110
Ed Tanousf51d8632024-05-16 09:14:01 -0700111 int fadvise = posix_fadvise(fileHandle.fileHandle.native_handle(), 0, 0,
Ed Tanous88c7c422024-04-06 08:52:40 -0700112 POSIX_FADV_SEQUENTIAL);
113 if (fadvise != 0)
114 {
115 BMCWEB_LOG_WARNING("Fasvise returned {} ignoring", fadvise);
116 }
Ed Tanous52e31622024-01-23 16:31:11 -0800117 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600118 }
119
120 void setFd(int fd, boost::system::error_code& ec)
121 {
Ed Tanousf51d8632024-05-16 09:14:01 -0700122 fileHandle.fileHandle.native_handle(fd);
Ed Tanous52e31622024-01-23 16:31:11 -0800123
124 boost::system::error_code ec2;
Ed Tanousf51d8632024-05-16 09:14:01 -0700125 uint64_t size = fileHandle.fileHandle.size(ec2);
Ed Tanous52e31622024-01-23 16:31:11 -0800126 if (!ec2)
127 {
128 if (size != 0 && size < std::numeric_limits<size_t>::max())
129 {
130 fileSize = static_cast<size_t>(size);
131 }
132 }
133 ec = {};
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600134 }
135};
136
Ed Tanousb2896142024-01-31 15:25:47 -0800137class HttpBody::writer
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600138{
139 public:
140 using const_buffers_type = boost::asio::const_buffer;
141
142 private:
143 std::string buf;
144 crow::utility::Base64Encoder encoder;
145
146 value_type& body;
Ed Tanous52e31622024-01-23 16:31:11 -0800147 size_t sent = 0;
Ed Tanouse428b442024-03-29 10:49:27 -0700148 // 64KB This number is arbitrary, and selected to try to optimize for larger
149 // files and fewer loops over per-connection reduction in memory usage.
150 // Nginx uses 16-32KB here, so we're in the range of what other webservers
151 // do.
152 constexpr static size_t readBufSize = 1024UL * 64UL;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600153 std::array<char, readBufSize> fileReadBuf{};
154
155 public:
156 template <bool IsRequest, class Fields>
157 writer(boost::beast::http::header<IsRequest, Fields>& /*header*/,
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400158 value_type& bodyIn) : body(bodyIn)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600159 {}
160
161 static void init(boost::beast::error_code& ec)
162 {
163 ec = {};
164 }
165
166 boost::optional<std::pair<const_buffers_type, bool>>
167 get(boost::beast::error_code& ec)
168 {
Ed Tanous52e31622024-01-23 16:31:11 -0800169 return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
170 }
171
172 boost::optional<std::pair<const_buffers_type, bool>>
173 getWithMaxSize(boost::beast::error_code& ec, size_t maxSize)
174 {
175 std::pair<const_buffers_type, bool> ret;
176 if (!body.file().is_open())
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600177 {
Ed Tanous52e31622024-01-23 16:31:11 -0800178 size_t remain = body.str().size() - sent;
179 size_t toReturn = std::min(maxSize, remain);
180 ret.first = const_buffers_type(&body.str()[sent], toReturn);
181
182 sent += toReturn;
183 ret.second = sent < body.str().size();
184 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
185 ret.second);
186 return ret;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600187 }
Ed Tanous52e31622024-01-23 16:31:11 -0800188 size_t readReq = std::min(fileReadBuf.size(), maxSize);
Ed Tanous0242baf2024-05-16 19:52:47 -0700189 BMCWEB_LOG_INFO("Reading {}", readReq);
190 boost::system::error_code readEc;
191 size_t read = body.file().read(fileReadBuf.data(), readReq, readEc);
192 if (readEc)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600193 {
Ed Tanous0242baf2024-05-16 19:52:47 -0700194 if (readEc != boost::system::errc::operation_would_block &&
195 readEc != boost::system::errc::resource_unavailable_try_again)
196 {
197 BMCWEB_LOG_CRITICAL("Failed to read from file {}",
198 readEc.message());
199 ec = readEc;
200 return boost::none;
201 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600202 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600203
204 std::string_view chunkView(fileReadBuf.data(), read);
Ed Tanous52e31622024-01-23 16:31:11 -0800205 BMCWEB_LOG_INFO("Read {} bytes from file", read);
206 // If the number of bytes read equals the amount requested, we haven't
207 // reached EOF yet
208 ret.second = read == readReq;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600209 if (body.encodingType == EncodingType::Base64)
210 {
211 buf.clear();
212 buf.reserve(
213 crow::utility::Base64Encoder::encodedSize(chunkView.size()));
214 encoder.encode(chunkView, buf);
215 if (!ret.second)
216 {
217 encoder.finalize(buf);
218 }
219 ret.first = const_buffers_type(buf.data(), buf.size());
220 }
221 else
222 {
223 ret.first = const_buffers_type(chunkView.data(), chunkView.size());
224 }
225 return ret;
226 }
227};
Ed Tanous52e31622024-01-23 16:31:11 -0800228
Ed Tanousb2896142024-01-31 15:25:47 -0800229class HttpBody::reader
Ed Tanous52e31622024-01-23 16:31:11 -0800230{
231 value_type& value;
232
233 public:
234 template <bool IsRequest, class Fields>
235 reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/,
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400236 value_type& body) : value(body)
Ed Tanous52e31622024-01-23 16:31:11 -0800237 {}
238
239 void init(const boost::optional<std::uint64_t>& contentLength,
240 boost::beast::error_code& ec)
241 {
242 if (contentLength)
243 {
244 if (!value.file().is_open())
245 {
246 value.str().reserve(static_cast<size_t>(*contentLength));
247 }
248 }
249 ec = {};
250 }
251
252 template <class ConstBufferSequence>
253 std::size_t put(const ConstBufferSequence& buffers,
254 boost::system::error_code& ec)
255 {
256 size_t extra = boost::beast::buffer_bytes(buffers);
257 for (const auto b : boost::beast::buffers_range_ref(buffers))
258 {
259 const char* ptr = static_cast<const char*>(b.data());
260 value.str() += std::string_view(ptr, b.size());
261 }
262 ec = {};
263 return extra;
264 }
265
266 static void finish(boost::system::error_code& ec)
267 {
268 ec = {};
269 }
270};
271
Potin Lai608fb7b2024-08-09 16:05:06 +0800272inline std::uint64_t HttpBody::size(const value_type& body)
273{
274 std::optional<size_t> payloadSize = body.payloadSize();
275 return payloadSize.value_or(0U);
276}
277
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600278} // namespace bmcweb