blob: 673058e7a6723fc62bfa539441877b62d04b3cf0 [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*/,
158 value_type& bodyIn) :
Ed Tanous52e31622024-01-23 16:31:11 -0800159 body(bodyIn)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600160 {}
161
162 static void init(boost::beast::error_code& ec)
163 {
164 ec = {};
165 }
166
167 boost::optional<std::pair<const_buffers_type, bool>>
168 get(boost::beast::error_code& ec)
169 {
Ed Tanous52e31622024-01-23 16:31:11 -0800170 return getWithMaxSize(ec, std::numeric_limits<size_t>::max());
171 }
172
173 boost::optional<std::pair<const_buffers_type, bool>>
174 getWithMaxSize(boost::beast::error_code& ec, size_t maxSize)
175 {
176 std::pair<const_buffers_type, bool> ret;
177 if (!body.file().is_open())
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600178 {
Ed Tanous52e31622024-01-23 16:31:11 -0800179 size_t remain = body.str().size() - sent;
180 size_t toReturn = std::min(maxSize, remain);
181 ret.first = const_buffers_type(&body.str()[sent], toReturn);
182
183 sent += toReturn;
184 ret.second = sent < body.str().size();
185 BMCWEB_LOG_INFO("Returning {} bytes more={}", ret.first.size(),
186 ret.second);
187 return ret;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600188 }
Ed Tanous52e31622024-01-23 16:31:11 -0800189 size_t readReq = std::min(fileReadBuf.size(), maxSize);
Ed Tanous0242baf2024-05-16 19:52:47 -0700190 BMCWEB_LOG_INFO("Reading {}", readReq);
191 boost::system::error_code readEc;
192 size_t read = body.file().read(fileReadBuf.data(), readReq, readEc);
193 if (readEc)
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600194 {
Ed Tanous0242baf2024-05-16 19:52:47 -0700195 if (readEc != boost::system::errc::operation_would_block &&
196 readEc != boost::system::errc::resource_unavailable_try_again)
197 {
198 BMCWEB_LOG_CRITICAL("Failed to read from file {}",
199 readEc.message());
200 ec = readEc;
201 return boost::none;
202 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600203 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600204
205 std::string_view chunkView(fileReadBuf.data(), read);
Ed Tanous52e31622024-01-23 16:31:11 -0800206 BMCWEB_LOG_INFO("Read {} bytes from file", read);
207 // If the number of bytes read equals the amount requested, we haven't
208 // reached EOF yet
209 ret.second = read == readReq;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600210 if (body.encodingType == EncodingType::Base64)
211 {
212 buf.clear();
213 buf.reserve(
214 crow::utility::Base64Encoder::encodedSize(chunkView.size()));
215 encoder.encode(chunkView, buf);
216 if (!ret.second)
217 {
218 encoder.finalize(buf);
219 }
220 ret.first = const_buffers_type(buf.data(), buf.size());
221 }
222 else
223 {
224 ret.first = const_buffers_type(chunkView.data(), chunkView.size());
225 }
226 return ret;
227 }
228};
Ed Tanous52e31622024-01-23 16:31:11 -0800229
Ed Tanousb2896142024-01-31 15:25:47 -0800230class HttpBody::reader
Ed Tanous52e31622024-01-23 16:31:11 -0800231{
232 value_type& value;
233
234 public:
235 template <bool IsRequest, class Fields>
236 reader(boost::beast::http::header<IsRequest, Fields>& /*headers*/,
237 value_type& body) :
238 value(body)
239 {}
240
241 void init(const boost::optional<std::uint64_t>& contentLength,
242 boost::beast::error_code& ec)
243 {
244 if (contentLength)
245 {
246 if (!value.file().is_open())
247 {
248 value.str().reserve(static_cast<size_t>(*contentLength));
249 }
250 }
251 ec = {};
252 }
253
254 template <class ConstBufferSequence>
255 std::size_t put(const ConstBufferSequence& buffers,
256 boost::system::error_code& ec)
257 {
258 size_t extra = boost::beast::buffer_bytes(buffers);
259 for (const auto b : boost::beast::buffers_range_ref(buffers))
260 {
261 const char* ptr = static_cast<const char*>(b.data());
262 value.str() += std::string_view(ptr, b.size());
263 }
264 ec = {};
265 return extra;
266 }
267
268 static void finish(boost::system::error_code& ec)
269 {
270 ec = {};
271 }
272};
273
Potin Lai608fb7b2024-08-09 16:05:06 +0800274inline std::uint64_t HttpBody::size(const value_type& body)
275{
276 std::optional<size_t> payloadSize = body.payloadSize();
277 return payloadSize.value_or(0U);
278}
279
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600280} // namespace bmcweb