xref: /openbmc/bmcweb/http/http_response.hpp (revision b5f288d294e17719f30e32acc40e07681baf04b9)
104e438cbSEd Tanous #pragma once
2*b5f288d2SAbhilash Raju #include "http_file_body.hpp"
304e438cbSEd Tanous #include "logging.hpp"
43ccb3adbSEd Tanous #include "utils/hex_utils.hpp"
504e438cbSEd Tanous 
604e438cbSEd Tanous #include <boost/beast/http/message.hpp>
727b0cf90SEd Tanous #include <boost/beast/http/message_generator.hpp>
8d4b6c660SEd Tanous #include <boost/beast/http/string_body.hpp>
927b0cf90SEd 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>
158e3f7032SAbhilash Raju #include <utility>
1604e438cbSEd Tanous namespace crow
1704e438cbSEd Tanous {
1804e438cbSEd Tanous 
1904e438cbSEd Tanous template <typename Adaptor, typename Handler>
2004e438cbSEd Tanous class Connection;
2104e438cbSEd Tanous 
2227b0cf90SEd Tanous namespace http = boost::beast::http;
2327b0cf90SEd Tanous 
2404e438cbSEd Tanous struct Response
2504e438cbSEd Tanous {
2604e438cbSEd Tanous     template <typename Adaptor, typename Handler>
2704e438cbSEd Tanous     friend class crow::Connection;
2804e438cbSEd Tanous 
2927b0cf90SEd Tanous     using string_response = http::response<http::string_body>;
30*b5f288d2SAbhilash Raju     using file_response = http::response<bmcweb::FileBody>;
3127b0cf90SEd Tanous 
3227b0cf90SEd Tanous     // Use boost variant2 because it doesn't have valueless by exception
3327b0cf90SEd Tanous     boost::variant2::variant<string_response, file_response> response;
3404e438cbSEd Tanous 
3504e438cbSEd Tanous     nlohmann::json jsonValue;
3627b0cf90SEd Tanous     using fields_type = http::header<false, http::fields>;
3727b0cf90SEd Tanous     fields_type& fields()
3827b0cf90SEd Tanous     {
3927b0cf90SEd Tanous         return boost::variant2::visit(
4027b0cf90SEd Tanous             [](auto&& r) -> fields_type& { return r.base(); }, response);
4127b0cf90SEd Tanous     }
4227b0cf90SEd Tanous 
4327b0cf90SEd Tanous     const fields_type& fields() const
4427b0cf90SEd Tanous     {
4527b0cf90SEd Tanous         return boost::variant2::visit(
4627b0cf90SEd Tanous             [](auto&& r) -> const fields_type& { return r.base(); }, response);
4727b0cf90SEd Tanous     }
4804e438cbSEd Tanous 
4926ccae32SEd Tanous     void addHeader(std::string_view key, std::string_view value)
5004e438cbSEd Tanous     {
5127b0cf90SEd Tanous         fields().insert(key, value);
5204e438cbSEd Tanous     }
5304e438cbSEd Tanous 
5427b0cf90SEd Tanous     void addHeader(http::field key, std::string_view value)
5504e438cbSEd Tanous     {
5627b0cf90SEd Tanous         fields().insert(key, value);
57994fd86aSEd Tanous     }
58994fd86aSEd Tanous 
5927b0cf90SEd Tanous     void clearHeader(http::field key)
60994fd86aSEd Tanous     {
6127b0cf90SEd Tanous         fields().erase(key);
6204e438cbSEd Tanous     }
6304e438cbSEd Tanous 
6427b0cf90SEd Tanous     Response() : response(string_response()) {}
6513548d85SEd Tanous     Response(Response&& res) noexcept :
6627b0cf90SEd Tanous         response(std::move(res.response)), jsonValue(std::move(res.jsonValue)),
6727b0cf90SEd 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         }
9227b0cf90SEd 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     {
11727b0cf90SEd Tanous         fields().result(v);
1183590bd1dSNan Zhou     }
1193590bd1dSNan Zhou 
12027b0cf90SEd Tanous     void result(http::status v)
12104e438cbSEd Tanous     {
12227b0cf90SEd Tanous         fields().result(v);
12304e438cbSEd Tanous     }
12404e438cbSEd Tanous 
12527b0cf90SEd Tanous     void copyBody(const Response& res)
12604e438cbSEd Tanous     {
12727b0cf90SEd Tanous         const string_response* s =
12827b0cf90SEd Tanous             boost::variant2::get_if<string_response>(&(res.response));
12927b0cf90SEd Tanous         if (s == nullptr)
13027b0cf90SEd Tanous         {
13127b0cf90SEd Tanous             BMCWEB_LOG_ERROR("Unable to copy a file");
13227b0cf90SEd Tanous             return;
13327b0cf90SEd Tanous         }
13427b0cf90SEd Tanous         string_response* myString =
13527b0cf90SEd Tanous             boost::variant2::get_if<string_response>(&response);
13627b0cf90SEd Tanous         if (myString == nullptr)
13727b0cf90SEd Tanous         {
13827b0cf90SEd Tanous             myString = &response.emplace<string_response>();
13927b0cf90SEd Tanous         }
14027b0cf90SEd Tanous         myString->body() = s->body();
14127b0cf90SEd Tanous     }
14227b0cf90SEd Tanous 
14327b0cf90SEd Tanous     http::status result() const
14427b0cf90SEd Tanous     {
14527b0cf90SEd Tanous         return fields().result();
14604e438cbSEd Tanous     }
14704e438cbSEd Tanous 
148039a47e3SCarson Labrado     unsigned resultInt() const
14904e438cbSEd Tanous     {
15027b0cf90SEd Tanous         return fields().result_int();
15104e438cbSEd Tanous     }
15204e438cbSEd Tanous 
153bb60f4deSEd Tanous     std::string_view reason() const
15404e438cbSEd Tanous     {
15527b0cf90SEd Tanous         return fields().reason();
15604e438cbSEd Tanous     }
15704e438cbSEd Tanous 
15804e438cbSEd Tanous     bool isCompleted() const noexcept
15904e438cbSEd Tanous     {
16004e438cbSEd Tanous         return completed;
16104e438cbSEd Tanous     }
16204e438cbSEd Tanous 
16327b0cf90SEd Tanous     const std::string* body()
16404e438cbSEd Tanous     {
16527b0cf90SEd Tanous         string_response* body =
16627b0cf90SEd Tanous             boost::variant2::get_if<string_response>(&response);
16727b0cf90SEd Tanous         if (body == nullptr)
16827b0cf90SEd Tanous         {
16927b0cf90SEd Tanous             return nullptr;
17027b0cf90SEd Tanous         }
17127b0cf90SEd Tanous         return &body->body();
17204e438cbSEd Tanous     }
17304e438cbSEd Tanous 
17446a81465SCarson Labrado     std::string_view getHeaderValue(std::string_view key) const
17546a81465SCarson Labrado     {
17627b0cf90SEd Tanous         return fields()[key];
17746a81465SCarson Labrado     }
17846a81465SCarson Labrado 
17904e438cbSEd Tanous     void keepAlive(bool k)
18004e438cbSEd Tanous     {
18127b0cf90SEd Tanous         return boost::variant2::visit([k](auto&& r) { r.keep_alive(k); },
18227b0cf90SEd Tanous                                       response);
18304e438cbSEd Tanous     }
18404e438cbSEd Tanous 
185bb60f4deSEd Tanous     bool keepAlive() const
18604e438cbSEd Tanous     {
18727b0cf90SEd Tanous         return boost::variant2::visit([](auto&& r) { return r.keep_alive(); },
18827b0cf90SEd Tanous                                       response);
18927b0cf90SEd Tanous     }
19027b0cf90SEd Tanous 
19127b0cf90SEd Tanous     uint64_t getContentLength(boost::optional<uint64_t> pSize)
19227b0cf90SEd Tanous     {
19327b0cf90SEd Tanous         // This code is a throw-free equivalent to
19427b0cf90SEd Tanous         // beast::http::message::prepare_payload
19527b0cf90SEd Tanous         using http::status;
19627b0cf90SEd Tanous         using http::status_class;
19727b0cf90SEd Tanous         using http::to_status_class;
19827b0cf90SEd Tanous         if (!pSize)
19927b0cf90SEd Tanous         {
20027b0cf90SEd Tanous             return 0;
20127b0cf90SEd Tanous         }
20227b0cf90SEd Tanous         bool is1XXReturn = to_status_class(result()) ==
20327b0cf90SEd Tanous                            status_class::informational;
20427b0cf90SEd Tanous         if (*pSize > 0 && (is1XXReturn || result() == status::no_content ||
20527b0cf90SEd Tanous                            result() == status::not_modified))
20627b0cf90SEd Tanous         {
20727b0cf90SEd Tanous             BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
20827b0cf90SEd Tanous                                 "no-content or not_modified, which aren't "
20927b0cf90SEd Tanous                                 "allowed to have a body",
21027b0cf90SEd Tanous                                 logPtr(this));
21127b0cf90SEd Tanous             return 0;
21227b0cf90SEd Tanous         }
21327b0cf90SEd Tanous         return *pSize;
21427b0cf90SEd Tanous     }
21527b0cf90SEd Tanous 
21627b0cf90SEd Tanous     uint64_t size()
21727b0cf90SEd Tanous     {
21827b0cf90SEd Tanous         return boost::variant2::visit(
21927b0cf90SEd Tanous             [](auto&& res) -> uint64_t { return res.body().size(); }, response);
22004e438cbSEd Tanous     }
22104e438cbSEd Tanous 
22204e438cbSEd Tanous     void preparePayload()
22304e438cbSEd Tanous     {
22427b0cf90SEd Tanous         boost::variant2::visit(
22527b0cf90SEd Tanous             [this](auto&& r) {
22627b0cf90SEd Tanous             r.content_length(getContentLength(r.payload_size()));
22727b0cf90SEd Tanous         },
22827b0cf90SEd Tanous             response);
22904e438cbSEd Tanous     }
23004e438cbSEd Tanous 
23104e438cbSEd Tanous     void clear()
23204e438cbSEd Tanous     {
23362598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
23427b0cf90SEd 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
24327b0cf90SEd 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 
25627b0cf90SEd Tanous     void write(std::string&& bodyPart)
25727b0cf90SEd Tanous     {
25827b0cf90SEd Tanous         string_response* str =
25927b0cf90SEd Tanous             boost::variant2::get_if<string_response>(&response);
26027b0cf90SEd Tanous         if (str != nullptr)
26127b0cf90SEd Tanous         {
26227b0cf90SEd Tanous             str->body() += bodyPart;
26327b0cf90SEd Tanous             return;
26427b0cf90SEd Tanous         }
2658e3f7032SAbhilash Raju         http::header<false> headTemp = std::move(fields());
2668e3f7032SAbhilash Raju         string_response& stringResponse =
2678e3f7032SAbhilash Raju             response.emplace<string_response>(std::move(headTemp));
2688e3f7032SAbhilash Raju         stringResponse.body() = std::move(bodyPart);
26927b0cf90SEd Tanous     }
27027b0cf90SEd Tanous 
2712d6cb56bSEd Tanous     void end()
2722d6cb56bSEd Tanous     {
2732d6cb56bSEd Tanous         std::string etag = computeEtag();
2742d6cb56bSEd Tanous         if (!etag.empty())
2752d6cb56bSEd Tanous         {
27627b0cf90SEd Tanous             addHeader(http::field::etag, etag);
27789f18008SEd Tanous         }
27804e438cbSEd Tanous         if (completed)
27904e438cbSEd Tanous         {
28062598e31SEd Tanous             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
28104e438cbSEd Tanous             return;
28204e438cbSEd Tanous         }
28304e438cbSEd Tanous         completed = true;
28462598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
28504e438cbSEd Tanous         if (completeRequestHandler)
28604e438cbSEd Tanous         {
28762598e31SEd Tanous             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
28872374eb7SNan Zhou             completeRequestHandler(*this);
28904e438cbSEd Tanous         }
29004e438cbSEd Tanous     }
29104e438cbSEd Tanous 
292bb60f4deSEd Tanous     bool isAlive() const
29304e438cbSEd Tanous     {
29404e438cbSEd Tanous         return isAliveHelper && isAliveHelper();
29504e438cbSEd Tanous     }
29604e438cbSEd Tanous 
29772374eb7SNan Zhou     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
2984147b8acSJohn Edward Broadbent     {
29962598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
30072374eb7SNan Zhou         completeRequestHandler = std::move(handler);
30113548d85SEd Tanous 
30213548d85SEd Tanous         // Now that we have a new completion handler attached, we're no longer
30313548d85SEd Tanous         // complete
30413548d85SEd Tanous         completed = false;
30572374eb7SNan Zhou     }
30672374eb7SNan Zhou 
30772374eb7SNan Zhou     std::function<void(Response&)> releaseCompleteRequestHandler()
30872374eb7SNan Zhou     {
30962598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
31062598e31SEd Tanous                          static_cast<bool>(completeRequestHandler));
31172374eb7SNan Zhou         std::function<void(Response&)> ret = completeRequestHandler;
31272374eb7SNan Zhou         completeRequestHandler = nullptr;
31313548d85SEd Tanous         completed = true;
31472374eb7SNan Zhou         return ret;
31572374eb7SNan Zhou     }
31672374eb7SNan Zhou 
31772374eb7SNan Zhou     void setIsAliveHelper(std::function<bool()>&& handler)
31872374eb7SNan Zhou     {
31972374eb7SNan Zhou         isAliveHelper = std::move(handler);
32072374eb7SNan Zhou     }
32172374eb7SNan Zhou 
32272374eb7SNan Zhou     std::function<bool()> releaseIsAliveHelper()
32372374eb7SNan Zhou     {
32472374eb7SNan Zhou         std::function<bool()> ret = std::move(isAliveHelper);
32572374eb7SNan Zhou         isAliveHelper = nullptr;
32672374eb7SNan Zhou         return ret;
3274147b8acSJohn Edward Broadbent     }
3284147b8acSJohn Edward Broadbent 
329291d709dSEd Tanous     void setHashAndHandleNotModified()
330291d709dSEd Tanous     {
331291d709dSEd Tanous         // Can only hash if we have content that's valid
33227b0cf90SEd Tanous         if (jsonValue.empty() || result() != http::status::ok)
333291d709dSEd Tanous         {
334291d709dSEd Tanous             return;
335291d709dSEd Tanous         }
336291d709dSEd Tanous         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
337291d709dSEd Tanous         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
33827b0cf90SEd Tanous         addHeader(http::field::etag, hexVal);
339291d709dSEd Tanous         if (expectedHash && hexVal == *expectedHash)
340291d709dSEd Tanous         {
341a6695a84SEd Tanous             jsonValue = nullptr;
34227b0cf90SEd Tanous             result(http::status::not_modified);
343291d709dSEd Tanous         }
344291d709dSEd Tanous     }
345291d709dSEd Tanous 
346291d709dSEd Tanous     void setExpectedHash(std::string_view hash)
347291d709dSEd Tanous     {
348291d709dSEd Tanous         expectedHash = hash;
349291d709dSEd Tanous     }
350291d709dSEd Tanous 
35127b0cf90SEd Tanous     using message_generator = http::message_generator;
35227b0cf90SEd Tanous     message_generator generator()
35327b0cf90SEd Tanous     {
35427b0cf90SEd Tanous         return boost::variant2::visit(
35527b0cf90SEd Tanous             [](auto& r) -> message_generator { return std::move(r); },
35627b0cf90SEd Tanous             response);
35727b0cf90SEd Tanous     }
35827b0cf90SEd Tanous 
359*b5f288d2SAbhilash Raju     bool openFile(const std::filesystem::path& path,
360*b5f288d2SAbhilash Raju                   bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
36127b0cf90SEd Tanous     {
362*b5f288d2SAbhilash Raju         file_response::body_type::value_type body(enc);
36327b0cf90SEd Tanous         boost::beast::error_code ec;
364*b5f288d2SAbhilash Raju         body.open(path.c_str(), boost::beast::file_mode::read, ec);
36527b0cf90SEd Tanous         if (ec)
36627b0cf90SEd Tanous         {
36727b0cf90SEd Tanous             return false;
36827b0cf90SEd Tanous         }
369*b5f288d2SAbhilash Raju         updateFileBody(std::move(body));
370*b5f288d2SAbhilash Raju         return true;
371*b5f288d2SAbhilash Raju     }
372*b5f288d2SAbhilash Raju 
373*b5f288d2SAbhilash Raju     bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
374*b5f288d2SAbhilash Raju     {
375*b5f288d2SAbhilash Raju         file_response::body_type::value_type body(enc);
376*b5f288d2SAbhilash Raju         boost::beast::error_code ec;
377*b5f288d2SAbhilash Raju         body.setFd(fd, ec);
378*b5f288d2SAbhilash Raju         if (ec)
379*b5f288d2SAbhilash Raju         {
380*b5f288d2SAbhilash Raju             BMCWEB_LOG_ERROR("Failed to set fd");
381*b5f288d2SAbhilash Raju             return false;
382*b5f288d2SAbhilash Raju         }
383*b5f288d2SAbhilash Raju         updateFileBody(std::move(body));
384*b5f288d2SAbhilash Raju         return true;
385*b5f288d2SAbhilash Raju     }
386*b5f288d2SAbhilash Raju 
387*b5f288d2SAbhilash Raju   private:
388*b5f288d2SAbhilash Raju     void updateFileBody(file_response::body_type::value_type file)
389*b5f288d2SAbhilash Raju     {
39027b0cf90SEd Tanous         // store the headers on stack temporarily so we can reconstruct the new
39127b0cf90SEd Tanous         // base with the old headers copied in.
39227b0cf90SEd Tanous         http::header<false> headTemp = std::move(fields());
39327b0cf90SEd Tanous         file_response& fileResponse =
39427b0cf90SEd Tanous             response.emplace<file_response>(std::move(headTemp));
39527b0cf90SEd Tanous         fileResponse.body() = std::move(file);
39627b0cf90SEd Tanous     }
39727b0cf90SEd Tanous 
398291d709dSEd Tanous     std::optional<std::string> expectedHash;
39972374eb7SNan Zhou     bool completed = false;
40072374eb7SNan Zhou     std::function<void(Response&)> completeRequestHandler;
40104e438cbSEd Tanous     std::function<bool()> isAliveHelper;
40204e438cbSEd Tanous };
40304e438cbSEd Tanous } // namespace crow
404