blob: 40158992e452aae894803de1fe708f2d5d2875b0 [file] [log] [blame]
Ed Tanous7045c8d2017-04-03 10:04:37 -07001#pragma once
Abhilash Rajub5f288d2023-11-08 22:32:44 -06002#include "http_file_body.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07003#include "logging.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08004#include "utils/hex_utils.hpp"
Ed Tanous1abe55e2018-09-05 08:30:59 -07005
Ed Tanousd43cd0c2020-09-30 20:46:53 -07006#include <boost/beast/http/message.hpp>
Ed Tanous27b0cf92023-08-07 12:02:40 -07007#include <boost/beast/http/message_generator.hpp>
Ed Tanousd4b6c662021-03-10 13:29:30 -08008#include <boost/beast/http/string_body.hpp>
Ed Tanous27b0cf92023-08-07 12:02:40 -07009#include <boost/variant2/variant.hpp>
Ed Tanousfaf100f2023-05-25 10:03:14 -070010#include <nlohmann/json.hpp>
Ed Tanous7045c8d2017-04-03 10:04:37 -070011
Ed Tanous8a9a25c2021-05-11 14:50:58 -070012#include <optional>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050013#include <string>
Ed Tanous8a9a25c2021-05-11 14:50:58 -070014#include <string_view>
Abhilash Raju8e3f7032023-07-17 08:53:11 -050015#include <utility>
Ed Tanous1abe55e2018-09-05 08:30:59 -070016namespace crow
17{
Ed Tanouse0d918b2018-03-27 17:41:04 -070018
Ed Tanous52cc1122020-07-18 13:51:21 -070019template <typename Adaptor, typename Handler>
Ed Tanous7045c8d2017-04-03 10:04:37 -070020class Connection;
Borawski.Lukasz9d8fd302018-01-05 14:56:09 +010021
Ed Tanous27b0cf92023-08-07 12:02:40 -070022namespace http = boost::beast::http;
23
Ed Tanous1abe55e2018-09-05 08:30:59 -070024struct Response
25{
Ed Tanous52cc1122020-07-18 13:51:21 -070026 template <typename Adaptor, typename Handler>
Ed Tanous1abe55e2018-09-05 08:30:59 -070027 friend class crow::Connection;
Ed Tanous7045c8d2017-04-03 10:04:37 -070028
Ed Tanous27b0cf92023-08-07 12:02:40 -070029 using string_response = http::response<http::string_body>;
Abhilash Rajub5f288d2023-11-08 22:32:44 -060030 using file_response = http::response<bmcweb::FileBody>;
Ed Tanous27b0cf92023-08-07 12:02:40 -070031
32 // Use boost variant2 because it doesn't have valueless by exception
33 boost::variant2::variant<string_response, file_response> response;
Ed Tanouse0d918b2018-03-27 17:41:04 -070034
Ed Tanous1abe55e2018-09-05 08:30:59 -070035 nlohmann::json jsonValue;
Ed Tanous27b0cf92023-08-07 12:02:40 -070036 using fields_type = http::header<false, http::fields>;
37 fields_type& fields()
38 {
39 return boost::variant2::visit(
40 [](auto&& r) -> fields_type& { return r.base(); }, response);
41 }
42
43 const fields_type& fields() const
44 {
45 return boost::variant2::visit(
46 [](auto&& r) -> const fields_type& { return r.base(); }, response);
47 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070048
Ed Tanous26ccae32023-02-16 10:28:44 -080049 void addHeader(std::string_view key, std::string_view value)
Ed Tanous1abe55e2018-09-05 08:30:59 -070050 {
Ed Tanous27b0cf92023-08-07 12:02:40 -070051 fields().insert(key, value);
Ed Tanous7045c8d2017-04-03 10:04:37 -070052 }
Ed Tanous1abe55e2018-09-05 08:30:59 -070053
Ed Tanous27b0cf92023-08-07 12:02:40 -070054 void addHeader(http::field key, std::string_view value)
Ed Tanous1abe55e2018-09-05 08:30:59 -070055 {
Ed Tanous27b0cf92023-08-07 12:02:40 -070056 fields().insert(key, value);
Ed Tanous994fd862023-06-06 13:37:03 -070057 }
58
Ed Tanous27b0cf92023-08-07 12:02:40 -070059 void clearHeader(http::field key)
Ed Tanous994fd862023-06-06 13:37:03 -070060 {
Ed Tanous27b0cf92023-08-07 12:02:40 -070061 fields().erase(key);
Ed Tanous2cd4cc12018-07-25 10:51:19 -070062 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070063
Ed Tanous27b0cf92023-08-07 12:02:40 -070064 Response() : response(string_response()) {}
Ed Tanous13548d82022-07-22 09:50:44 -070065 Response(Response&& res) noexcept :
Ed Tanous27b0cf92023-08-07 12:02:40 -070066 response(std::move(res.response)), jsonValue(std::move(res.jsonValue)),
67 completed(res.completed)
Ed Tanous13548d82022-07-22 09:50:44 -070068 {
Ed Tanous13548d82022-07-22 09:50:44 -070069 // See note in operator= move handler for why this is needed.
70 if (!res.completed)
71 {
72 completeRequestHandler = std::move(res.completeRequestHandler);
73 res.completeRequestHandler = nullptr;
74 }
75 isAliveHelper = res.isAliveHelper;
76 res.isAliveHelper = nullptr;
77 }
78
Ed Tanousecd6a3a2022-01-07 09:18:40 -080079 ~Response() = default;
80
81 Response(const Response&) = delete;
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 Response& operator=(const Response& r) = delete;
83
84 Response& operator=(Response&& r) noexcept
85 {
Ed Tanous62598e32023-07-17 17:06:25 -070086 BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
87 logPtr(this), logPtr(&r));
Nan Zhou72374eb2022-01-27 17:06:51 -080088 if (this == &r)
89 {
90 return *this;
91 }
Ed Tanous27b0cf92023-08-07 12:02:40 -070092 response = std::move(r.response);
Ed Tanous1abe55e2018-09-05 08:30:59 -070093 jsonValue = std::move(r.jsonValue);
Ed Tanous13548d82022-07-22 09:50:44 -070094
95 // Only need to move completion handler if not already completed
96 // Note, there are cases where we might move out of a Response object
97 // while in a completion handler for that response object. This check
98 // is intended to prevent destructing the functor we are currently
99 // executing from in that case.
100 if (!r.completed)
101 {
102 completeRequestHandler = std::move(r.completeRequestHandler);
103 r.completeRequestHandler = nullptr;
104 }
105 else
106 {
107 completeRequestHandler = nullptr;
108 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700109 completed = r.completed;
Nan Zhou72374eb2022-01-27 17:06:51 -0800110 isAliveHelper = std::move(r.isAliveHelper);
Nan Zhou72374eb2022-01-27 17:06:51 -0800111 r.isAliveHelper = nullptr;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700112 return *this;
113 }
114
Nan Zhou3590bd12022-08-12 18:05:09 +0000115 void result(unsigned v)
116 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700117 fields().result(v);
Nan Zhou3590bd12022-08-12 18:05:09 +0000118 }
119
Ed Tanous27b0cf92023-08-07 12:02:40 -0700120 void result(http::status v)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700121 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700122 fields().result(v);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700123 }
124
Ed Tanous27b0cf92023-08-07 12:02:40 -0700125 void copyBody(const Response& res)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700126 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700127 const string_response* s =
128 boost::variant2::get_if<string_response>(&(res.response));
129 if (s == nullptr)
130 {
131 BMCWEB_LOG_ERROR("Unable to copy a file");
132 return;
133 }
134 string_response* myString =
135 boost::variant2::get_if<string_response>(&response);
136 if (myString == nullptr)
137 {
138 myString = &response.emplace<string_response>();
139 }
140 myString->body() = s->body();
141 }
142
143 http::status result() const
144 {
145 return fields().result();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700146 }
147
Carson Labrado039a47e2022-04-05 16:03:20 +0000148 unsigned resultInt() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700149 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700150 return fields().result_int();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700151 }
152
Ed Tanousbb60f4d2022-06-27 10:39:09 -0700153 std::string_view reason() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700154 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700155 return fields().reason();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700156 }
157
158 bool isCompleted() const noexcept
159 {
160 return completed;
161 }
162
Ed Tanous27b0cf92023-08-07 12:02:40 -0700163 const std::string* body()
Ed Tanous1abe55e2018-09-05 08:30:59 -0700164 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700165 string_response* body =
166 boost::variant2::get_if<string_response>(&response);
167 if (body == nullptr)
168 {
169 return nullptr;
170 }
171 return &body->body();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700172 }
173
Carson Labrado46a81462022-04-27 21:11:37 +0000174 std::string_view getHeaderValue(std::string_view key) const
175 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700176 return fields()[key];
Carson Labrado46a81462022-04-27 21:11:37 +0000177 }
178
Ed Tanous1abe55e2018-09-05 08:30:59 -0700179 void keepAlive(bool k)
180 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700181 return boost::variant2::visit([k](auto&& r) { r.keep_alive(k); },
182 response);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700183 }
184
Ed Tanousbb60f4d2022-06-27 10:39:09 -0700185 bool keepAlive() const
Ed Tanousceac6f72018-12-02 11:58:47 -0800186 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700187 return boost::variant2::visit([](auto&& r) { return r.keep_alive(); },
188 response);
189 }
190
191 uint64_t getContentLength(boost::optional<uint64_t> pSize)
192 {
193 // This code is a throw-free equivalent to
194 // beast::http::message::prepare_payload
195 using http::status;
196 using http::status_class;
197 using http::to_status_class;
198 if (!pSize)
199 {
200 return 0;
201 }
202 bool is1XXReturn = to_status_class(result()) ==
203 status_class::informational;
204 if (*pSize > 0 && (is1XXReturn || result() == status::no_content ||
205 result() == status::not_modified))
206 {
207 BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
208 "no-content or not_modified, which aren't "
209 "allowed to have a body",
210 logPtr(this));
211 return 0;
212 }
213 return *pSize;
214 }
215
216 uint64_t size()
217 {
218 return boost::variant2::visit(
219 [](auto&& res) -> uint64_t { return res.body().size(); }, response);
Ed Tanousceac6f72018-12-02 11:58:47 -0800220 }
221
Ed Tanous1abe55e2018-09-05 08:30:59 -0700222 void preparePayload()
223 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700224 boost::variant2::visit(
225 [this](auto&& r) {
226 r.content_length(getContentLength(r.payload_size()));
227 },
228 response);
Ed Tanous23a21a12020-07-25 04:45:05 +0000229 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700230
231 void clear()
232 {
Ed Tanous62598e32023-07-17 17:06:25 -0700233 BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
Ed Tanous27b0cf92023-08-07 12:02:40 -0700234 response.emplace<string_response>();
Ed Tanousa6695a82023-05-16 11:39:18 -0700235 jsonValue = nullptr;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700236 completed = false;
Ed Tanous291d7092022-04-13 12:34:57 -0700237 expectedHash = std::nullopt;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238 }
239
Ed Tanous2d6cb562022-07-07 20:44:54 -0700240 std::string computeEtag() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700241 {
Ed Tanous89f18002022-03-24 18:38:24 -0700242 // Only set etag if this request succeeded
Ed Tanous27b0cf92023-08-07 12:02:40 -0700243 if (result() != http::status::ok)
Ed Tanous89f18002022-03-24 18:38:24 -0700244 {
Ed Tanous2d6cb562022-07-07 20:44:54 -0700245 return "";
246 }
247 // and the json response isn't empty
248 if (jsonValue.empty())
249 {
250 return "";
251 }
252 size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
253 return "\"" + intToHexString(hashval, 8) + "\"";
254 }
255
Ed Tanous27b0cf92023-08-07 12:02:40 -0700256 void write(std::string&& bodyPart)
257 {
258 string_response* str =
259 boost::variant2::get_if<string_response>(&response);
260 if (str != nullptr)
261 {
262 str->body() += bodyPart;
263 return;
264 }
Abhilash Raju8e3f7032023-07-17 08:53:11 -0500265 http::header<false> headTemp = std::move(fields());
266 string_response& stringResponse =
267 response.emplace<string_response>(std::move(headTemp));
268 stringResponse.body() = std::move(bodyPart);
Ed Tanous27b0cf92023-08-07 12:02:40 -0700269 }
270
Ed Tanous2d6cb562022-07-07 20:44:54 -0700271 void end()
272 {
273 std::string etag = computeEtag();
274 if (!etag.empty())
275 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700276 addHeader(http::field::etag, etag);
Ed Tanous89f18002022-03-24 18:38:24 -0700277 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700278 if (completed)
279 {
Ed Tanous62598e32023-07-17 17:06:25 -0700280 BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700281 return;
282 }
283 completed = true;
Ed Tanous62598e32023-07-17 17:06:25 -0700284 BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700285 if (completeRequestHandler)
286 {
Ed Tanous62598e32023-07-17 17:06:25 -0700287 BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
Nan Zhou72374eb2022-01-27 17:06:51 -0800288 completeRequestHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700289 }
290 }
291
Ed Tanousbb60f4d2022-06-27 10:39:09 -0700292 bool isAlive() const
Ed Tanous1abe55e2018-09-05 08:30:59 -0700293 {
294 return isAliveHelper && isAliveHelper();
295 }
296
Nan Zhou72374eb2022-01-27 17:06:51 -0800297 void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
John Edward Broadbent4147b8a2021-07-19 16:52:24 -0700298 {
Ed Tanous62598e32023-07-17 17:06:25 -0700299 BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
Nan Zhou72374eb2022-01-27 17:06:51 -0800300 completeRequestHandler = std::move(handler);
Ed Tanous13548d82022-07-22 09:50:44 -0700301
302 // Now that we have a new completion handler attached, we're no longer
303 // complete
304 completed = false;
Nan Zhou72374eb2022-01-27 17:06:51 -0800305 }
306
307 std::function<void(Response&)> releaseCompleteRequestHandler()
308 {
Ed Tanous62598e32023-07-17 17:06:25 -0700309 BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
310 static_cast<bool>(completeRequestHandler));
Nan Zhou72374eb2022-01-27 17:06:51 -0800311 std::function<void(Response&)> ret = completeRequestHandler;
312 completeRequestHandler = nullptr;
Ed Tanous13548d82022-07-22 09:50:44 -0700313 completed = true;
Nan Zhou72374eb2022-01-27 17:06:51 -0800314 return ret;
315 }
316
317 void setIsAliveHelper(std::function<bool()>&& handler)
318 {
319 isAliveHelper = std::move(handler);
320 }
321
322 std::function<bool()> releaseIsAliveHelper()
323 {
324 std::function<bool()> ret = std::move(isAliveHelper);
325 isAliveHelper = nullptr;
326 return ret;
John Edward Broadbent4147b8a2021-07-19 16:52:24 -0700327 }
328
Ed Tanous291d7092022-04-13 12:34:57 -0700329 void setHashAndHandleNotModified()
330 {
331 // Can only hash if we have content that's valid
Ed Tanous27b0cf92023-08-07 12:02:40 -0700332 if (jsonValue.empty() || result() != http::status::ok)
Ed Tanous291d7092022-04-13 12:34:57 -0700333 {
334 return;
335 }
336 size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
337 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
Ed Tanous27b0cf92023-08-07 12:02:40 -0700338 addHeader(http::field::etag, hexVal);
Ed Tanous291d7092022-04-13 12:34:57 -0700339 if (expectedHash && hexVal == *expectedHash)
340 {
Ed Tanousa6695a82023-05-16 11:39:18 -0700341 jsonValue = nullptr;
Ed Tanous27b0cf92023-08-07 12:02:40 -0700342 result(http::status::not_modified);
Ed Tanous291d7092022-04-13 12:34:57 -0700343 }
344 }
345
346 void setExpectedHash(std::string_view hash)
347 {
348 expectedHash = hash;
349 }
350
Ed Tanous27b0cf92023-08-07 12:02:40 -0700351 using message_generator = http::message_generator;
352 message_generator generator()
353 {
354 return boost::variant2::visit(
355 [](auto& r) -> message_generator { return std::move(r); },
356 response);
357 }
358
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600359 bool openFile(const std::filesystem::path& path,
360 bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700361 {
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600362 file_response::body_type::value_type body(enc);
Ed Tanous27b0cf92023-08-07 12:02:40 -0700363 boost::beast::error_code ec;
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600364 body.open(path.c_str(), boost::beast::file_mode::read, ec);
Ed Tanous27b0cf92023-08-07 12:02:40 -0700365 if (ec)
366 {
367 return false;
368 }
Abhilash Rajub5f288d2023-11-08 22:32:44 -0600369 updateFileBody(std::move(body));
370 return true;
371 }
372
373 bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
374 {
375 file_response::body_type::value_type body(enc);
376 boost::beast::error_code ec;
377 body.setFd(fd, ec);
378 if (ec)
379 {
380 BMCWEB_LOG_ERROR("Failed to set fd");
381 return false;
382 }
383 updateFileBody(std::move(body));
384 return true;
385 }
386
387 private:
388 void updateFileBody(file_response::body_type::value_type file)
389 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700390 // store the headers on stack temporarily so we can reconstruct the new
391 // base with the old headers copied in.
392 http::header<false> headTemp = std::move(fields());
393 file_response& fileResponse =
394 response.emplace<file_response>(std::move(headTemp));
395 fileResponse.body() = std::move(file);
Ed Tanous27b0cf92023-08-07 12:02:40 -0700396 }
397
Ed Tanous291d7092022-04-13 12:34:57 -0700398 std::optional<std::string> expectedHash;
Nan Zhou72374eb2022-01-27 17:06:51 -0800399 bool completed = false;
400 std::function<void(Response&)> completeRequestHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700401 std::function<bool()> isAliveHelper;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700402};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700403} // namespace crow