xref: /openbmc/bmcweb/http/http_response.hpp (revision 27b0cf90f6cba207837f5c263a45c6ea5651975b)
104e438cbSEd Tanous #pragma once
204e438cbSEd Tanous #include "logging.hpp"
33ccb3adbSEd Tanous #include "utils/hex_utils.hpp"
404e438cbSEd Tanous 
5*27b0cf90SEd Tanous #include <boost/beast/http/file_body.hpp>
604e438cbSEd Tanous #include <boost/beast/http/message.hpp>
7*27b0cf90SEd Tanous #include <boost/beast/http/message_generator.hpp>
8d4b6c660SEd Tanous #include <boost/beast/http/string_body.hpp>
9*27b0cf90SEd Tanous #include <boost/variant2/variant.hpp>
10faf100f9SEd Tanous #include <nlohmann/json.hpp>
1104e438cbSEd Tanous 
128a9a25c8SEd Tanous #include <optional>
1304e438cbSEd Tanous #include <string>
148a9a25c8SEd Tanous #include <string_view>
1504e438cbSEd Tanous 
1604e438cbSEd Tanous namespace crow
1704e438cbSEd Tanous {
1804e438cbSEd Tanous 
1904e438cbSEd Tanous template <typename Adaptor, typename Handler>
2004e438cbSEd Tanous class Connection;
2104e438cbSEd Tanous 
22*27b0cf90SEd Tanous namespace http = boost::beast::http;
23*27b0cf90SEd Tanous 
2404e438cbSEd Tanous struct Response
2504e438cbSEd Tanous {
2604e438cbSEd Tanous     template <typename Adaptor, typename Handler>
2704e438cbSEd Tanous     friend class crow::Connection;
2804e438cbSEd Tanous 
29*27b0cf90SEd Tanous     using string_response = http::response<http::string_body>;
30*27b0cf90SEd Tanous     using file_response = http::response<http::file_body>;
31*27b0cf90SEd Tanous 
32*27b0cf90SEd Tanous     // Use boost variant2 because it doesn't have valueless by exception
33*27b0cf90SEd Tanous     boost::variant2::variant<string_response, file_response> response;
3404e438cbSEd Tanous 
3504e438cbSEd Tanous     nlohmann::json jsonValue;
36*27b0cf90SEd Tanous     using fields_type = http::header<false, http::fields>;
37*27b0cf90SEd Tanous     fields_type& fields()
38*27b0cf90SEd Tanous     {
39*27b0cf90SEd Tanous         return boost::variant2::visit(
40*27b0cf90SEd Tanous             [](auto&& r) -> fields_type& { return r.base(); }, response);
41*27b0cf90SEd Tanous     }
42*27b0cf90SEd Tanous 
43*27b0cf90SEd Tanous     const fields_type& fields() const
44*27b0cf90SEd Tanous     {
45*27b0cf90SEd Tanous         return boost::variant2::visit(
46*27b0cf90SEd Tanous             [](auto&& r) -> const fields_type& { return r.base(); }, response);
47*27b0cf90SEd Tanous     }
4804e438cbSEd Tanous 
4926ccae32SEd Tanous     void addHeader(std::string_view key, std::string_view value)
5004e438cbSEd Tanous     {
51*27b0cf90SEd Tanous         fields().insert(key, value);
5204e438cbSEd Tanous     }
5304e438cbSEd Tanous 
54*27b0cf90SEd Tanous     void addHeader(http::field key, std::string_view value)
5504e438cbSEd Tanous     {
56*27b0cf90SEd Tanous         fields().insert(key, value);
57994fd86aSEd Tanous     }
58994fd86aSEd Tanous 
59*27b0cf90SEd Tanous     void clearHeader(http::field key)
60994fd86aSEd Tanous     {
61*27b0cf90SEd Tanous         fields().erase(key);
6204e438cbSEd Tanous     }
6304e438cbSEd Tanous 
64*27b0cf90SEd Tanous     Response() : response(string_response()) {}
6513548d85SEd Tanous     Response(Response&& res) noexcept :
66*27b0cf90SEd Tanous         response(std::move(res.response)), jsonValue(std::move(res.jsonValue)),
67*27b0cf90SEd Tanous         completed(res.completed)
6813548d85SEd Tanous     {
6913548d85SEd Tanous         // See note in operator= move handler for why this is needed.
7013548d85SEd Tanous         if (!res.completed)
7113548d85SEd Tanous         {
7213548d85SEd Tanous             completeRequestHandler = std::move(res.completeRequestHandler);
7313548d85SEd Tanous             res.completeRequestHandler = nullptr;
7413548d85SEd Tanous         }
7513548d85SEd Tanous         isAliveHelper = res.isAliveHelper;
7613548d85SEd Tanous         res.isAliveHelper = nullptr;
7713548d85SEd Tanous     }
7813548d85SEd Tanous 
79ecd6a3a2SEd Tanous     ~Response() = default;
80ecd6a3a2SEd Tanous 
81ecd6a3a2SEd Tanous     Response(const Response&) = delete;
8204e438cbSEd Tanous     Response& operator=(const Response& r) = delete;
8304e438cbSEd Tanous 
8404e438cbSEd Tanous     Response& operator=(Response&& r) noexcept
8504e438cbSEd Tanous     {
8662598e31SEd Tanous         BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
8762598e31SEd Tanous                          logPtr(this), logPtr(&r));
8872374eb7SNan Zhou         if (this == &r)
8972374eb7SNan Zhou         {
9072374eb7SNan Zhou             return *this;
9172374eb7SNan Zhou         }
92*27b0cf90SEd Tanous         response = std::move(r.response);
9304e438cbSEd Tanous         jsonValue = std::move(r.jsonValue);
9413548d85SEd Tanous 
9513548d85SEd Tanous         // Only need to move completion handler if not already completed
9613548d85SEd Tanous         // Note, there are cases where we might move out of a Response object
9713548d85SEd Tanous         // while in a completion handler for that response object.  This check
9813548d85SEd Tanous         // is intended to prevent destructing the functor we are currently
9913548d85SEd Tanous         // executing from in that case.
10013548d85SEd Tanous         if (!r.completed)
10113548d85SEd Tanous         {
10272374eb7SNan Zhou             completeRequestHandler = std::move(r.completeRequestHandler);
10372374eb7SNan Zhou             r.completeRequestHandler = nullptr;
10413548d85SEd Tanous         }
10513548d85SEd Tanous         else
10613548d85SEd Tanous         {
10713548d85SEd Tanous             completeRequestHandler = nullptr;
10813548d85SEd Tanous         }
10913548d85SEd Tanous         completed = r.completed;
11013548d85SEd Tanous         isAliveHelper = std::move(r.isAliveHelper);
11172374eb7SNan Zhou         r.isAliveHelper = nullptr;
11204e438cbSEd Tanous         return *this;
11304e438cbSEd Tanous     }
11404e438cbSEd Tanous 
1153590bd1dSNan Zhou     void result(unsigned v)
1163590bd1dSNan Zhou     {
117*27b0cf90SEd Tanous         fields().result(v);
1183590bd1dSNan Zhou     }
1193590bd1dSNan Zhou 
120*27b0cf90SEd Tanous     void result(http::status v)
12104e438cbSEd Tanous     {
122*27b0cf90SEd Tanous         fields().result(v);
12304e438cbSEd Tanous     }
12404e438cbSEd Tanous 
125*27b0cf90SEd Tanous     void copyBody(const Response& res)
12604e438cbSEd Tanous     {
127*27b0cf90SEd Tanous         const string_response* s =
128*27b0cf90SEd Tanous             boost::variant2::get_if<string_response>(&(res.response));
129*27b0cf90SEd Tanous         if (s == nullptr)
130*27b0cf90SEd Tanous         {
131*27b0cf90SEd Tanous             BMCWEB_LOG_ERROR("Unable to copy a file");
132*27b0cf90SEd Tanous             return;
133*27b0cf90SEd Tanous         }
134*27b0cf90SEd Tanous         string_response* myString =
135*27b0cf90SEd Tanous             boost::variant2::get_if<string_response>(&response);
136*27b0cf90SEd Tanous         if (myString == nullptr)
137*27b0cf90SEd Tanous         {
138*27b0cf90SEd Tanous             myString = &response.emplace<string_response>();
139*27b0cf90SEd Tanous         }
140*27b0cf90SEd Tanous         myString->body() = s->body();
141*27b0cf90SEd Tanous     }
142*27b0cf90SEd Tanous 
143*27b0cf90SEd Tanous     http::status result() const
144*27b0cf90SEd Tanous     {
145*27b0cf90SEd Tanous         return fields().result();
14604e438cbSEd Tanous     }
14704e438cbSEd Tanous 
148039a47e3SCarson Labrado     unsigned resultInt() const
14904e438cbSEd Tanous     {
150*27b0cf90SEd Tanous         return fields().result_int();
15104e438cbSEd Tanous     }
15204e438cbSEd Tanous 
153bb60f4deSEd Tanous     std::string_view reason() const
15404e438cbSEd Tanous     {
155*27b0cf90SEd Tanous         return fields().reason();
15604e438cbSEd Tanous     }
15704e438cbSEd Tanous 
15804e438cbSEd Tanous     bool isCompleted() const noexcept
15904e438cbSEd Tanous     {
16004e438cbSEd Tanous         return completed;
16104e438cbSEd Tanous     }
16204e438cbSEd Tanous 
163*27b0cf90SEd Tanous     const std::string* body()
16404e438cbSEd Tanous     {
165*27b0cf90SEd Tanous         string_response* body =
166*27b0cf90SEd Tanous             boost::variant2::get_if<string_response>(&response);
167*27b0cf90SEd Tanous         if (body == nullptr)
168*27b0cf90SEd Tanous         {
169*27b0cf90SEd Tanous             return nullptr;
170*27b0cf90SEd Tanous         }
171*27b0cf90SEd Tanous         return &body->body();
17204e438cbSEd Tanous     }
17304e438cbSEd Tanous 
17446a81465SCarson Labrado     std::string_view getHeaderValue(std::string_view key) const
17546a81465SCarson Labrado     {
176*27b0cf90SEd Tanous         return fields()[key];
17746a81465SCarson Labrado     }
17846a81465SCarson Labrado 
17904e438cbSEd Tanous     void keepAlive(bool k)
18004e438cbSEd Tanous     {
181*27b0cf90SEd Tanous         return boost::variant2::visit([k](auto&& r) { r.keep_alive(k); },
182*27b0cf90SEd Tanous                                       response);
18304e438cbSEd Tanous     }
18404e438cbSEd Tanous 
185bb60f4deSEd Tanous     bool keepAlive() const
18604e438cbSEd Tanous     {
187*27b0cf90SEd Tanous         return boost::variant2::visit([](auto&& r) { return r.keep_alive(); },
188*27b0cf90SEd Tanous                                       response);
189*27b0cf90SEd Tanous     }
190*27b0cf90SEd Tanous 
191*27b0cf90SEd Tanous     uint64_t getContentLength(boost::optional<uint64_t> pSize)
192*27b0cf90SEd Tanous     {
193*27b0cf90SEd Tanous         // This code is a throw-free equivalent to
194*27b0cf90SEd Tanous         // beast::http::message::prepare_payload
195*27b0cf90SEd Tanous         using http::status;
196*27b0cf90SEd Tanous         using http::status_class;
197*27b0cf90SEd Tanous         using http::to_status_class;
198*27b0cf90SEd Tanous         if (!pSize)
199*27b0cf90SEd Tanous         {
200*27b0cf90SEd Tanous             return 0;
201*27b0cf90SEd Tanous         }
202*27b0cf90SEd Tanous         bool is1XXReturn = to_status_class(result()) ==
203*27b0cf90SEd Tanous                            status_class::informational;
204*27b0cf90SEd Tanous         if (*pSize > 0 && (is1XXReturn || result() == status::no_content ||
205*27b0cf90SEd Tanous                            result() == status::not_modified))
206*27b0cf90SEd Tanous         {
207*27b0cf90SEd Tanous             BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
208*27b0cf90SEd Tanous                                 "no-content or not_modified, which aren't "
209*27b0cf90SEd Tanous                                 "allowed to have a body",
210*27b0cf90SEd Tanous                                 logPtr(this));
211*27b0cf90SEd Tanous             return 0;
212*27b0cf90SEd Tanous         }
213*27b0cf90SEd Tanous         return *pSize;
214*27b0cf90SEd Tanous     }
215*27b0cf90SEd Tanous 
216*27b0cf90SEd Tanous     uint64_t size()
217*27b0cf90SEd Tanous     {
218*27b0cf90SEd Tanous         return boost::variant2::visit(
219*27b0cf90SEd Tanous             [](auto&& res) -> uint64_t { return res.body().size(); }, response);
22004e438cbSEd Tanous     }
22104e438cbSEd Tanous 
22204e438cbSEd Tanous     void preparePayload()
22304e438cbSEd Tanous     {
224*27b0cf90SEd Tanous         boost::variant2::visit(
225*27b0cf90SEd Tanous             [this](auto&& r) {
226*27b0cf90SEd Tanous             r.content_length(getContentLength(r.payload_size()));
227*27b0cf90SEd Tanous         },
228*27b0cf90SEd Tanous             response);
22904e438cbSEd Tanous     }
23004e438cbSEd Tanous 
23104e438cbSEd Tanous     void clear()
23204e438cbSEd Tanous     {
23362598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
234*27b0cf90SEd Tanous         response.emplace<string_response>();
235a6695a84SEd Tanous         jsonValue = nullptr;
23604e438cbSEd Tanous         completed = false;
237291d709dSEd Tanous         expectedHash = std::nullopt;
23804e438cbSEd Tanous     }
23904e438cbSEd Tanous 
2402d6cb56bSEd Tanous     std::string computeEtag() const
24104e438cbSEd Tanous     {
24289f18008SEd Tanous         // Only set etag if this request succeeded
243*27b0cf90SEd Tanous         if (result() != http::status::ok)
24489f18008SEd Tanous         {
2452d6cb56bSEd Tanous             return "";
24689f18008SEd Tanous         }
2472d6cb56bSEd Tanous         // and the json response isn't empty
2482d6cb56bSEd Tanous         if (jsonValue.empty())
2492d6cb56bSEd Tanous         {
2502d6cb56bSEd Tanous             return "";
2512d6cb56bSEd Tanous         }
2522d6cb56bSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
2532d6cb56bSEd Tanous         return "\"" + intToHexString(hashval, 8) + "\"";
2542d6cb56bSEd Tanous     }
2552d6cb56bSEd Tanous 
256*27b0cf90SEd Tanous     void write(std::string&& bodyPart)
257*27b0cf90SEd Tanous     {
258*27b0cf90SEd Tanous         string_response* str =
259*27b0cf90SEd Tanous             boost::variant2::get_if<string_response>(&response);
260*27b0cf90SEd Tanous         if (str != nullptr)
261*27b0cf90SEd Tanous         {
262*27b0cf90SEd Tanous             str->body() += bodyPart;
263*27b0cf90SEd Tanous             return;
264*27b0cf90SEd Tanous         }
265*27b0cf90SEd Tanous         response.emplace<string_response>(result(), 11, std::move(bodyPart));
266*27b0cf90SEd Tanous     }
267*27b0cf90SEd Tanous 
2682d6cb56bSEd Tanous     void end()
2692d6cb56bSEd Tanous     {
2702d6cb56bSEd Tanous         std::string etag = computeEtag();
2712d6cb56bSEd Tanous         if (!etag.empty())
2722d6cb56bSEd Tanous         {
273*27b0cf90SEd Tanous             addHeader(http::field::etag, etag);
27489f18008SEd Tanous         }
27504e438cbSEd Tanous         if (completed)
27604e438cbSEd Tanous         {
27762598e31SEd Tanous             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
27804e438cbSEd Tanous             return;
27904e438cbSEd Tanous         }
28004e438cbSEd Tanous         completed = true;
28162598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
28204e438cbSEd Tanous         if (completeRequestHandler)
28304e438cbSEd Tanous         {
28462598e31SEd Tanous             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
28572374eb7SNan Zhou             completeRequestHandler(*this);
28604e438cbSEd Tanous         }
28704e438cbSEd Tanous     }
28804e438cbSEd Tanous 
289bb60f4deSEd Tanous     bool isAlive() const
29004e438cbSEd Tanous     {
29104e438cbSEd Tanous         return isAliveHelper && isAliveHelper();
29204e438cbSEd Tanous     }
29304e438cbSEd Tanous 
29472374eb7SNan Zhou     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
2954147b8acSJohn Edward Broadbent     {
29662598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
29772374eb7SNan Zhou         completeRequestHandler = std::move(handler);
29813548d85SEd Tanous 
29913548d85SEd Tanous         // Now that we have a new completion handler attached, we're no longer
30013548d85SEd Tanous         // complete
30113548d85SEd Tanous         completed = false;
30272374eb7SNan Zhou     }
30372374eb7SNan Zhou 
30472374eb7SNan Zhou     std::function<void(Response&)> releaseCompleteRequestHandler()
30572374eb7SNan Zhou     {
30662598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
30762598e31SEd Tanous                          static_cast<bool>(completeRequestHandler));
30872374eb7SNan Zhou         std::function<void(Response&)> ret = completeRequestHandler;
30972374eb7SNan Zhou         completeRequestHandler = nullptr;
31013548d85SEd Tanous         completed = true;
31172374eb7SNan Zhou         return ret;
31272374eb7SNan Zhou     }
31372374eb7SNan Zhou 
31472374eb7SNan Zhou     void setIsAliveHelper(std::function<bool()>&& handler)
31572374eb7SNan Zhou     {
31672374eb7SNan Zhou         isAliveHelper = std::move(handler);
31772374eb7SNan Zhou     }
31872374eb7SNan Zhou 
31972374eb7SNan Zhou     std::function<bool()> releaseIsAliveHelper()
32072374eb7SNan Zhou     {
32172374eb7SNan Zhou         std::function<bool()> ret = std::move(isAliveHelper);
32272374eb7SNan Zhou         isAliveHelper = nullptr;
32372374eb7SNan Zhou         return ret;
3244147b8acSJohn Edward Broadbent     }
3254147b8acSJohn Edward Broadbent 
326291d709dSEd Tanous     void setHashAndHandleNotModified()
327291d709dSEd Tanous     {
328291d709dSEd Tanous         // Can only hash if we have content that's valid
329*27b0cf90SEd Tanous         if (jsonValue.empty() || result() != http::status::ok)
330291d709dSEd Tanous         {
331291d709dSEd Tanous             return;
332291d709dSEd Tanous         }
333291d709dSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
334291d709dSEd Tanous         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
335*27b0cf90SEd Tanous         addHeader(http::field::etag, hexVal);
336291d709dSEd Tanous         if (expectedHash && hexVal == *expectedHash)
337291d709dSEd Tanous         {
338a6695a84SEd Tanous             jsonValue = nullptr;
339*27b0cf90SEd Tanous             result(http::status::not_modified);
340291d709dSEd Tanous         }
341291d709dSEd Tanous     }
342291d709dSEd Tanous 
343291d709dSEd Tanous     void setExpectedHash(std::string_view hash)
344291d709dSEd Tanous     {
345291d709dSEd Tanous         expectedHash = hash;
346291d709dSEd Tanous     }
347291d709dSEd Tanous 
348*27b0cf90SEd Tanous     using message_generator = http::message_generator;
349*27b0cf90SEd Tanous     message_generator generator()
350*27b0cf90SEd Tanous     {
351*27b0cf90SEd Tanous         return boost::variant2::visit(
352*27b0cf90SEd Tanous             [](auto& r) -> message_generator { return std::move(r); },
353*27b0cf90SEd Tanous             response);
354*27b0cf90SEd Tanous     }
355*27b0cf90SEd Tanous 
356*27b0cf90SEd Tanous     bool openFile(const std::filesystem::path& path)
357*27b0cf90SEd Tanous     {
358*27b0cf90SEd Tanous         http::file_body::value_type file;
359*27b0cf90SEd Tanous         boost::beast::error_code ec;
360*27b0cf90SEd Tanous         file.open(path.c_str(), boost::beast::file_mode::read, ec);
361*27b0cf90SEd Tanous         if (ec)
362*27b0cf90SEd Tanous         {
363*27b0cf90SEd Tanous             return false;
364*27b0cf90SEd Tanous         }
365*27b0cf90SEd Tanous         // store the headers on stack temporarily so we can reconstruct the new
366*27b0cf90SEd Tanous         // base with the old headers copied in.
367*27b0cf90SEd Tanous         http::header<false> headTemp = std::move(fields());
368*27b0cf90SEd Tanous         file_response& fileResponse =
369*27b0cf90SEd Tanous             response.emplace<file_response>(std::move(headTemp));
370*27b0cf90SEd Tanous         fileResponse.body() = std::move(file);
371*27b0cf90SEd Tanous         return true;
372*27b0cf90SEd Tanous     }
373*27b0cf90SEd Tanous 
37404e438cbSEd Tanous   private:
375291d709dSEd Tanous     std::optional<std::string> expectedHash;
37672374eb7SNan Zhou     bool completed = false;
37772374eb7SNan Zhou     std::function<void(Response&)> completeRequestHandler;
37804e438cbSEd Tanous     std::function<bool()> isAliveHelper;
37904e438cbSEd Tanous };
38004e438cbSEd Tanous } // namespace crow
381